mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
1d68e50782
commit_hash:8863bfcf57ccb5a8b9ea4986010f4770821772db
480 lines
15 KiB
Swift
480 lines
15 KiB
Swift
import LayoutKitInterface
|
|
import VGSL
|
|
|
|
protocol DivPatchCallbacks {
|
|
var elementChanged: (String) -> Void { get set }
|
|
}
|
|
|
|
struct Callbacks: DivPatchCallbacks {
|
|
var elementChanged: (String) -> Void
|
|
}
|
|
|
|
extension Callbacks {
|
|
static var empty: Self {
|
|
.init(elementChanged: { _ in })
|
|
}
|
|
}
|
|
|
|
extension DivData {
|
|
public func applyPatch(_ patch: DivPatch) -> DivData {
|
|
applyPatch(patch, callbacks: Callbacks.empty)
|
|
}
|
|
|
|
public func applyPatchWithActions(
|
|
_ patch: DivPatch,
|
|
context: DivBlockModelingContext
|
|
) -> DivData {
|
|
var changedElementIds: [String] = []
|
|
|
|
let callbacks = Callbacks(
|
|
elementChanged: { id in
|
|
changedElementIds.append(id)
|
|
}
|
|
)
|
|
|
|
let patchedData = applyPatch(patch, callbacks: callbacks)
|
|
|
|
let allChangesApplied = changedElementIds.count == patch.changes.count
|
|
let isTransactional = patch.resolveMode(context.expressionResolver) == .transactional
|
|
let isPatchApplied = !isTransactional || allChangesApplied
|
|
|
|
if isPatchApplied {
|
|
changedElementIds.forEach { id in
|
|
context.triggersStorage?.reset(elementId: id)
|
|
}
|
|
}
|
|
let actions = isPatchApplied ? patch.onAppliedActions : patch.onFailedActions
|
|
actions?.forEach { action in
|
|
context.actionHandler?.handle(
|
|
action,
|
|
path: UIElementPath(context.cardId.rawValue),
|
|
source: .callback,
|
|
sender: nil
|
|
)
|
|
}
|
|
|
|
return isPatchApplied ? patchedData : self
|
|
}
|
|
|
|
func applyPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> DivData {
|
|
let states = states.map {
|
|
State(div: $0.div.applySingleItemPatch(patch, callbacks: callbacks), stateId: $0.stateId)
|
|
}
|
|
return DivData(
|
|
functions: functions,
|
|
logId: logId,
|
|
states: states,
|
|
timers: timers,
|
|
transitionAnimationSelector: transitionAnimationSelector,
|
|
variableTriggers: variableTriggers,
|
|
variables: variables
|
|
)
|
|
}
|
|
}
|
|
|
|
extension Div {
|
|
fileprivate func applySingleItemPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> Div {
|
|
if let id, let change = patch.getChange(id: id) {
|
|
guard let items = change.items, items.count == 1 else {
|
|
DivKitLogger.error("Patch contains multiple items, but single item is expected: \(id)")
|
|
return self
|
|
}
|
|
callbacks.elementChanged(id)
|
|
return items[0]
|
|
}
|
|
return applyPatchToChildren(patch, callbacks: callbacks)
|
|
}
|
|
|
|
fileprivate func applyOptionalItemPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> Div? {
|
|
if let id, let change = patch.getChange(id: id) {
|
|
guard let items = change.items else {
|
|
callbacks.elementChanged(id)
|
|
return nil
|
|
}
|
|
if items.count == 1 {
|
|
callbacks.elementChanged(id)
|
|
return items[0]
|
|
}
|
|
DivKitLogger.error("Patch contains multiple items, but single item is expected: \(id)")
|
|
return self
|
|
}
|
|
return applyPatchToChildren(patch, callbacks: callbacks)
|
|
}
|
|
|
|
fileprivate func applyMultipleItemsPatch(
|
|
_ patch: DivPatch,
|
|
callbacks: DivPatchCallbacks
|
|
) -> [Div] {
|
|
if let id, let change = patch.getChange(id: id) {
|
|
callbacks.elementChanged(id)
|
|
return change.items ?? []
|
|
}
|
|
return [applyPatchToChildren(patch, callbacks: callbacks)]
|
|
}
|
|
|
|
private func applyPatchToChildren(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> Div {
|
|
switch self {
|
|
case .divCustom,
|
|
.divGifImage,
|
|
.divInput,
|
|
.divImage,
|
|
.divIndicator,
|
|
.divSelect,
|
|
.divSeparator,
|
|
.divSlider,
|
|
.divSwitch,
|
|
.divVideo,
|
|
.divText:
|
|
// no children
|
|
self
|
|
case let .divContainer(value):
|
|
.divContainer(value.applyPatch(patch, callbacks: callbacks))
|
|
case let .divGallery(value):
|
|
.divGallery(value.applyPatch(patch, callbacks: callbacks))
|
|
case let .divGrid(value):
|
|
.divGrid(value.applyPatch(patch, callbacks: callbacks))
|
|
case let .divPager(value):
|
|
.divPager(value.applyPatch(patch, callbacks: callbacks))
|
|
case let .divState(value):
|
|
.divState(value.applyPatch(patch, callbacks: callbacks))
|
|
case let .divTabs(value):
|
|
.divTabs(value.applyPatch(patch, callbacks: callbacks))
|
|
}
|
|
}
|
|
}
|
|
|
|
extension DivContainer {
|
|
fileprivate func applyPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> DivContainer {
|
|
let patchedItems = nonNilItems
|
|
.flatMap { $0.applyMultipleItemsPatch(patch, callbacks: callbacks) }
|
|
return DivContainer(
|
|
accessibility: accessibility,
|
|
action: action,
|
|
actionAnimation: actionAnimation,
|
|
actions: actions,
|
|
alignmentHorizontal: alignmentHorizontal,
|
|
alignmentVertical: alignmentVertical,
|
|
alpha: alpha,
|
|
animators: animators,
|
|
aspect: aspect,
|
|
background: background,
|
|
border: border,
|
|
clipToBounds: clipToBounds,
|
|
columnSpan: columnSpan,
|
|
contentAlignmentHorizontal: contentAlignmentHorizontal,
|
|
contentAlignmentVertical: contentAlignmentVertical,
|
|
disappearActions: disappearActions,
|
|
doubletapActions: doubletapActions,
|
|
extensions: extensions,
|
|
focus: focus,
|
|
functions: functions,
|
|
height: height,
|
|
hoverEndActions: hoverEndActions,
|
|
hoverStartActions: hoverStartActions,
|
|
id: id,
|
|
itemBuilder: itemBuilder,
|
|
items: patchedItems,
|
|
layoutMode: layoutMode,
|
|
layoutProvider: layoutProvider,
|
|
lineSeparator: lineSeparator,
|
|
longtapActions: longtapActions,
|
|
margins: margins,
|
|
orientation: orientation,
|
|
paddings: paddings,
|
|
pressEndActions: pressEndActions,
|
|
pressStartActions: pressStartActions,
|
|
reuseId: reuseId,
|
|
rowSpan: rowSpan,
|
|
selectedActions: selectedActions,
|
|
separator: separator,
|
|
tooltips: tooltips,
|
|
transform: transform,
|
|
transitionChange: transitionChange,
|
|
transitionIn: transitionIn,
|
|
transitionOut: transitionOut,
|
|
transitionTriggers: transitionTriggers,
|
|
variableTriggers: variableTriggers,
|
|
variables: variables,
|
|
visibility: visibility,
|
|
visibilityAction: visibilityAction,
|
|
visibilityActions: visibilityActions,
|
|
width: width
|
|
)
|
|
}
|
|
}
|
|
|
|
extension DivGallery {
|
|
fileprivate func applyPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> DivGallery {
|
|
let patchedItems = nonNilItems
|
|
.flatMap { $0.applyMultipleItemsPatch(patch, callbacks: callbacks) }
|
|
return DivGallery(
|
|
accessibility: accessibility,
|
|
alignmentHorizontal: alignmentHorizontal,
|
|
alignmentVertical: alignmentVertical,
|
|
alpha: alpha,
|
|
animators: animators,
|
|
background: background,
|
|
border: border,
|
|
columnCount: columnCount,
|
|
columnSpan: columnSpan,
|
|
crossContentAlignment: crossContentAlignment,
|
|
crossSpacing: crossSpacing,
|
|
defaultItem: defaultItem,
|
|
disappearActions: disappearActions,
|
|
extensions: extensions,
|
|
focus: focus,
|
|
functions: functions,
|
|
height: height,
|
|
id: id,
|
|
itemBuilder: itemBuilder,
|
|
itemSpacing: itemSpacing,
|
|
items: patchedItems,
|
|
layoutProvider: layoutProvider,
|
|
margins: margins,
|
|
orientation: orientation,
|
|
paddings: paddings,
|
|
restrictParentScroll: restrictParentScroll,
|
|
reuseId: reuseId,
|
|
rowSpan: rowSpan,
|
|
scrollMode: scrollMode,
|
|
scrollbar: scrollbar,
|
|
selectedActions: selectedActions,
|
|
tooltips: tooltips,
|
|
transform: transform,
|
|
transitionChange: transitionChange,
|
|
transitionIn: transitionIn,
|
|
transitionOut: transitionOut,
|
|
transitionTriggers: transitionTriggers,
|
|
variableTriggers: variableTriggers,
|
|
variables: variables,
|
|
visibility: visibility,
|
|
visibilityAction: visibilityAction,
|
|
visibilityActions: visibilityActions,
|
|
width: width
|
|
)
|
|
}
|
|
}
|
|
|
|
extension DivGrid {
|
|
fileprivate func applyPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> DivGrid {
|
|
let patchedItems = nonNilItems
|
|
.flatMap { $0.applyMultipleItemsPatch(patch, callbacks: callbacks) }
|
|
return DivGrid(
|
|
accessibility: accessibility,
|
|
action: action,
|
|
actionAnimation: actionAnimation,
|
|
actions: actions,
|
|
alignmentHorizontal: alignmentHorizontal,
|
|
alignmentVertical: alignmentVertical,
|
|
alpha: alpha,
|
|
animators: animators,
|
|
background: background,
|
|
border: border,
|
|
columnCount: columnCount,
|
|
columnSpan: columnSpan,
|
|
contentAlignmentHorizontal: contentAlignmentHorizontal,
|
|
contentAlignmentVertical: contentAlignmentVertical,
|
|
disappearActions: disappearActions,
|
|
doubletapActions: doubletapActions,
|
|
extensions: extensions,
|
|
focus: focus,
|
|
functions: functions,
|
|
height: height,
|
|
hoverEndActions: hoverEndActions,
|
|
hoverStartActions: hoverStartActions,
|
|
id: id,
|
|
items: patchedItems,
|
|
layoutProvider: layoutProvider,
|
|
longtapActions: longtapActions,
|
|
margins: margins,
|
|
paddings: paddings,
|
|
pressEndActions: pressEndActions,
|
|
pressStartActions: pressStartActions,
|
|
reuseId: reuseId,
|
|
rowSpan: rowSpan,
|
|
selectedActions: selectedActions,
|
|
tooltips: tooltips,
|
|
transform: transform,
|
|
transitionChange: transitionChange,
|
|
transitionIn: transitionIn,
|
|
transitionOut: transitionOut,
|
|
transitionTriggers: transitionTriggers,
|
|
variableTriggers: variableTriggers,
|
|
variables: variables,
|
|
visibility: visibility,
|
|
visibilityAction: visibilityAction,
|
|
visibilityActions: visibilityActions,
|
|
width: width
|
|
)
|
|
}
|
|
}
|
|
|
|
extension DivPager {
|
|
fileprivate func applyPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> DivPager {
|
|
let patchedItems = nonNilItems
|
|
.flatMap { $0.applyMultipleItemsPatch(patch, callbacks: callbacks) }
|
|
return DivPager(
|
|
accessibility: accessibility,
|
|
alignmentHorizontal: alignmentHorizontal,
|
|
alignmentVertical: alignmentVertical,
|
|
alpha: alpha,
|
|
animators: animators,
|
|
background: background,
|
|
border: border,
|
|
columnSpan: columnSpan,
|
|
crossAxisAlignment: crossAxisAlignment,
|
|
defaultItem: defaultItem,
|
|
disappearActions: disappearActions,
|
|
extensions: extensions,
|
|
focus: focus,
|
|
functions: functions,
|
|
height: height,
|
|
id: id,
|
|
infiniteScroll: infiniteScroll,
|
|
itemBuilder: itemBuilder,
|
|
itemSpacing: itemSpacing,
|
|
items: patchedItems,
|
|
layoutMode: layoutMode,
|
|
layoutProvider: layoutProvider,
|
|
margins: margins,
|
|
orientation: orientation,
|
|
paddings: paddings,
|
|
pageTransformation: pageTransformation,
|
|
restrictParentScroll: restrictParentScroll,
|
|
reuseId: reuseId,
|
|
rowSpan: rowSpan,
|
|
scrollAxisAlignment: scrollAxisAlignment,
|
|
selectedActions: selectedActions,
|
|
tooltips: tooltips,
|
|
transform: transform,
|
|
transitionChange: transitionChange,
|
|
transitionIn: transitionIn,
|
|
transitionOut: transitionOut,
|
|
transitionTriggers: transitionTriggers,
|
|
variableTriggers: variableTriggers,
|
|
variables: variables,
|
|
visibility: visibility,
|
|
visibilityAction: visibilityAction,
|
|
visibilityActions: visibilityActions,
|
|
width: width
|
|
)
|
|
}
|
|
}
|
|
|
|
extension DivState {
|
|
fileprivate func applyPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> DivState {
|
|
let patchedStates = states.map {
|
|
DivState.State(
|
|
animationIn: $0.animationIn,
|
|
animationOut: $0.animationOut,
|
|
div: $0.div?.applyOptionalItemPatch(patch, callbacks: callbacks),
|
|
stateId: $0.stateId,
|
|
swipeOutActions: $0.swipeOutActions
|
|
)
|
|
}
|
|
return DivState(
|
|
accessibility: accessibility,
|
|
alignmentHorizontal: alignmentHorizontal,
|
|
alignmentVertical: alignmentVertical,
|
|
alpha: alpha,
|
|
animators: animators,
|
|
background: background,
|
|
border: border,
|
|
clipToBounds: clipToBounds,
|
|
columnSpan: columnSpan,
|
|
defaultStateId: defaultStateId,
|
|
disappearActions: disappearActions,
|
|
divId: divId,
|
|
extensions: extensions,
|
|
focus: focus,
|
|
functions: functions,
|
|
height: height,
|
|
id: id,
|
|
layoutProvider: layoutProvider,
|
|
margins: margins,
|
|
paddings: paddings,
|
|
reuseId: reuseId,
|
|
rowSpan: rowSpan,
|
|
selectedActions: selectedActions,
|
|
stateIdVariable: stateIdVariable,
|
|
states: patchedStates,
|
|
tooltips: tooltips,
|
|
transform: transform,
|
|
transitionAnimationSelector: transitionAnimationSelector,
|
|
transitionChange: transitionChange,
|
|
transitionIn: transitionIn,
|
|
transitionOut: transitionOut,
|
|
transitionTriggers: transitionTriggers,
|
|
variableTriggers: variableTriggers,
|
|
variables: variables,
|
|
visibility: visibility,
|
|
visibilityAction: visibilityAction,
|
|
visibilityActions: visibilityActions,
|
|
width: width
|
|
)
|
|
}
|
|
}
|
|
|
|
extension DivTabs {
|
|
fileprivate func applyPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> DivTabs {
|
|
let patchedItems = items.map {
|
|
DivTabs.Item(
|
|
div: $0.div.applySingleItemPatch(patch, callbacks: callbacks),
|
|
title: $0.title,
|
|
titleClickAction: $0.titleClickAction
|
|
)
|
|
}
|
|
return DivTabs(
|
|
accessibility: accessibility,
|
|
alignmentHorizontal: alignmentHorizontal,
|
|
alignmentVertical: alignmentVertical,
|
|
alpha: alpha,
|
|
animators: animators,
|
|
background: background,
|
|
border: border,
|
|
columnSpan: columnSpan,
|
|
disappearActions: disappearActions,
|
|
dynamicHeight: dynamicHeight,
|
|
extensions: extensions,
|
|
focus: focus,
|
|
functions: functions,
|
|
hasSeparator: hasSeparator,
|
|
height: height,
|
|
id: id,
|
|
items: patchedItems,
|
|
layoutProvider: layoutProvider,
|
|
margins: margins,
|
|
paddings: paddings,
|
|
restrictParentScroll: restrictParentScroll,
|
|
reuseId: reuseId,
|
|
rowSpan: rowSpan,
|
|
selectedActions: selectedActions,
|
|
selectedTab: selectedTab,
|
|
separatorColor: separatorColor,
|
|
separatorPaddings: separatorPaddings,
|
|
switchTabsByContentSwipeEnabled: switchTabsByContentSwipeEnabled,
|
|
tabTitleDelimiter: tabTitleDelimiter,
|
|
tabTitleStyle: tabTitleStyle,
|
|
titlePaddings: titlePaddings,
|
|
tooltips: tooltips,
|
|
transform: transform,
|
|
transitionChange: transitionChange,
|
|
transitionIn: transitionIn,
|
|
transitionOut: transitionOut,
|
|
transitionTriggers: transitionTriggers,
|
|
variableTriggers: variableTriggers,
|
|
variables: variables,
|
|
visibility: visibility,
|
|
visibilityAction: visibilityAction,
|
|
visibilityActions: visibilityActions,
|
|
width: width
|
|
)
|
|
}
|
|
}
|
|
|
|
extension DivPatch {
|
|
fileprivate func getChange(id: String) -> Change? {
|
|
changes.first { $0.id == id }
|
|
}
|
|
}
|