@lovelace_lol/polymer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +226 -0
  2. package/dist/index.d.ts +2603 -0
  3. package/dist/index.js +1031 -0
  4. package/dist/test.cjs +1519 -0
  5. package/dist/worker/agency-worker.js +972 -0
  6. package/dist/worker/manifest.edn +1 -0
  7. package/package.json +57 -0
  8. package/shadow-cljs.edn +173 -0
  9. package/src/polymer/animation/agency.cljs +84 -0
  10. package/src/polymer/animation/domain.cljs +110 -0
  11. package/src/polymer/animation/planner.cljs +88 -0
  12. package/src/polymer/animation/runtime.cljs +137 -0
  13. package/src/polymer/animation/scheduler.cljs +178 -0
  14. package/src/polymer/animation/service.cljs +981 -0
  15. package/src/polymer/animation/state.cljs +112 -0
  16. package/src/polymer/blink/agency.cljs +131 -0
  17. package/src/polymer/blink/planner.cljs +115 -0
  18. package/src/polymer/blink/scheduler.cljs +90 -0
  19. package/src/polymer/blink/snippet.cljs +50 -0
  20. package/src/polymer/blink/state.cljs +102 -0
  21. package/src/polymer/camera_context/agency.cljs +132 -0
  22. package/src/polymer/camera_context/domain.cljs +186 -0
  23. package/src/polymer/camera_context/planner.cljs +50 -0
  24. package/src/polymer/camera_context/scheduler.cljs +113 -0
  25. package/src/polymer/camera_context/state.cljs +121 -0
  26. package/src/polymer/character.cljs +471 -0
  27. package/src/polymer/conversation/agency.cljs +83 -0
  28. package/src/polymer/conversation/domain.cljs +35 -0
  29. package/src/polymer/conversation/effector.cljs +130 -0
  30. package/src/polymer/conversation/planner.cljs +178 -0
  31. package/src/polymer/conversation/scheduler.cljs +202 -0
  32. package/src/polymer/conversation/service.cljs +329 -0
  33. package/src/polymer/conversation/state.cljs +186 -0
  34. package/src/polymer/core.cljs +172 -0
  35. package/src/polymer/eye_head/agency.cljs +75 -0
  36. package/src/polymer/eye_head/domain.cljs +133 -0
  37. package/src/polymer/eye_head/goap.cljs +106 -0
  38. package/src/polymer/eye_head/scheduler.cljs +176 -0
  39. package/src/polymer/eye_head/service.cljs +259 -0
  40. package/src/polymer/eye_head/snippet.cljs +92 -0
  41. package/src/polymer/eye_head/state.cljs +167 -0
  42. package/src/polymer/gaze/agency.cljs +85 -0
  43. package/src/polymer/gaze/domain.cljs +211 -0
  44. package/src/polymer/gaze/goap.cljs +208 -0
  45. package/src/polymer/gaze/scheduler.cljs +184 -0
  46. package/src/polymer/gaze/state.cljs +202 -0
  47. package/src/polymer/gesture/agency.cljs +185 -0
  48. package/src/polymer/gesture/goap.cljs +461 -0
  49. package/src/polymer/gesture/scheduler.cljs +122 -0
  50. package/src/polymer/gesture/snippet.cljs +198 -0
  51. package/src/polymer/gesture/state.cljs +273 -0
  52. package/src/polymer/lipsync/agency.cljs +593 -0
  53. package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
  54. package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
  55. package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
  56. package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
  57. package/src/polymer/lipsync/goap.cljs +132 -0
  58. package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
  59. package/src/polymer/lipsync/scheduler.cljs +302 -0
  60. package/src/polymer/lipsync/state.cljs +202 -0
  61. package/src/polymer/nlp/phonemes.cljs +164 -0
  62. package/src/polymer/prosodic/agency.cljs +196 -0
  63. package/src/polymer/prosodic/goap.cljs +122 -0
  64. package/src/polymer/prosodic/scheduler.cljs +99 -0
  65. package/src/polymer/prosodic/snippet.cljs +106 -0
  66. package/src/polymer/prosodic/state.cljs +139 -0
  67. package/src/polymer/runtime/embody.cljs +117 -0
  68. package/src/polymer/runtime/embody_wasm.cljs +177 -0
  69. package/src/polymer/runtime/rust_embody_host.cljs +645 -0
  70. package/src/polymer/society.cljs +239 -0
  71. package/src/polymer/stream.cljs +130 -0
  72. package/src/polymer/transcription/agency.cljs +69 -0
  73. package/src/polymer/transcription/domain.cljs +68 -0
  74. package/src/polymer/transcription/effector.cljs +611 -0
  75. package/src/polymer/transcription/planner.cljs +203 -0
  76. package/src/polymer/transcription/scheduler.cljs +225 -0
  77. package/src/polymer/transcription/service.cljs +209 -0
  78. package/src/polymer/transcription/state.cljs +239 -0
  79. package/src/polymer/tts/agency.cljs +503 -0
  80. package/src/polymer/tts/azure.cljs +290 -0
  81. package/src/polymer/tts/planner.cljs +178 -0
  82. package/src/polymer/tts/runtime.cljs +624 -0
  83. package/src/polymer/tts/scheduler.cljs +162 -0
  84. package/src/polymer/tts/state.cljs +191 -0
  85. package/src/polymer/worker.cljs +70 -0
@@ -0,0 +1,981 @@
1
+ (ns polymer.animation.service
2
+ (:require [clojure.string :as str]
3
+ [polymer.animation.agency :as animation-agency]))
4
+
5
+ ;; This namespace is the JavaScript compatibility API for LoomLarge's existing
6
+ ;; animation manager calls. It does not reimplement Latticework's runtime path:
7
+ ;; snippet playback is normalized here as data and then dispatched into
8
+ ;; Polymer's CLJS Animation agency, whose scheduler owns the Embody side effect.
9
+
10
+ (def snippet-categories
11
+ #{"emotionAnimationsList"
12
+ "speakingAnimationsList"
13
+ "visemeAnimationsList"
14
+ "eyeHeadTrackingAnimationsList"})
15
+
16
+ (def bundled-snippet-manifest-key "bundledAnimationSnippetsManifest")
17
+ (def bundled-snippet-version-key "bundledAnimationSnippetsVersion")
18
+
19
+ (defn now-ms []
20
+ (let [performance (aget js/globalThis "performance")]
21
+ (if (and performance (fn? (aget performance "now")))
22
+ (.now performance)
23
+ (.now js/Date))))
24
+
25
+ (defn finite-number? [value]
26
+ (and (number? value) (js/isFinite value)))
27
+
28
+ (defn number-or [value fallback]
29
+ (if (finite-number? value) value fallback))
30
+
31
+ (defn string-or [value fallback]
32
+ (if (and (string? value) (not (str/blank? value))) value fallback))
33
+
34
+ (defn key->string [value]
35
+ (cond
36
+ (keyword? value) (name value)
37
+ (string? value) value
38
+ (nil? value) ""
39
+ :else (str value)))
40
+
41
+ (defn value->clj [value]
42
+ (cond
43
+ (nil? value) nil
44
+ (map? value) value
45
+ :else (js->clj value :keywordize-keys true)))
46
+
47
+ (defn js-call [target method-name & args]
48
+ (when target
49
+ (when-let [method (aget target method-name)]
50
+ (when (fn? method)
51
+ (.apply method target (to-array args))))))
52
+
53
+ (defn subscription [unsubscribe]
54
+ #js {:unsubscribe unsubscribe})
55
+
56
+ (defn subscribe! [listeners listener]
57
+ (swap! listeners conj listener)
58
+ (subscription (fn [] (swap! listeners disj listener))))
59
+
60
+ (defn notify-listener! [listener payload]
61
+ (cond
62
+ (fn? listener)
63
+ (listener payload)
64
+
65
+ (and listener (fn? (aget listener "next")))
66
+ (.call (aget listener "next") listener payload)
67
+
68
+ :else
69
+ (.warn js/console "[Polymer Animation] stream listener has no callback" listener)))
70
+
71
+ (defn emit-to! [listeners payload]
72
+ (let [js-payload (clj->js payload)]
73
+ (doseq [listener @listeners]
74
+ (try
75
+ (notify-listener! listener js-payload)
76
+ (catch :default error
77
+ (.error js/console "[Polymer Animation] stream listener failed" error))))))
78
+
79
+ (defn create-port [listeners]
80
+ (let [port (atom nil)]
81
+ (reset! port
82
+ #js {:subscribe (fn [listener] (subscribe! listeners listener))
83
+ :pipe (fn [& operators]
84
+ ;; LoomLarge still uses RxJS operators such as
85
+ ;; filter(...). Those operators expect a source with
86
+ ;; subscribe(...) and return a derived observable. This
87
+ ;; small pipe keeps Polymer's stream as plain data
88
+ ;; while preserving the old observable API contract.
89
+ (reduce (fn [source operator]
90
+ (operator source))
91
+ @port
92
+ operators))})
93
+ @port))
94
+
95
+ (defn event-timestamp []
96
+ (now-ms))
97
+
98
+ (defn emit-event! [event-listeners event]
99
+ (emit-to! event-listeners (assoc event :timestamp (event-timestamp))))
100
+
101
+ (defn normalize-point [point]
102
+ (let [source (or (value->clj point) {})
103
+ time (number-or (:time source) (number-or (:t source) 0))
104
+ intensity (number-or (:intensity source) (number-or (:v source) 0))]
105
+ {:time time
106
+ :intensity intensity
107
+ :inherit (boolean (:inherit source))}))
108
+
109
+ (defn normalize-curve-points [points]
110
+ (->> (or points [])
111
+ (map normalize-point)
112
+ (sort-by :time)
113
+ vec))
114
+
115
+ (defn normalize-curves [curves]
116
+ (let [source (or (value->clj curves) {})]
117
+ (reduce-kv
118
+ (fn [out k points]
119
+ (assoc out (key->string k) (normalize-curve-points points)))
120
+ {}
121
+ source)))
122
+
123
+ (defn add-authoring-point [out point key-name]
124
+ (let [source (or (value->clj point) {})
125
+ curve-key (key->string (or (get source key-name) (:id source) (:key source)))]
126
+ (if (str/blank? curve-key)
127
+ out
128
+ (update out curve-key (fnil conj []) (normalize-point source)))))
129
+
130
+ (defn authoring-curves [snippet]
131
+ (let [mapped (-> {}
132
+ (#(reduce (fn [out point] (add-authoring-point out point :id))
133
+ %
134
+ (or (:au snippet) [])))
135
+ (#(reduce (fn [out point] (add-authoring-point out point :key))
136
+ %
137
+ (or (:viseme snippet) []))))]
138
+ (reduce-kv
139
+ (fn [out key points]
140
+ (assoc out key (normalize-curve-points points)))
141
+ {}
142
+ mapped)))
143
+
144
+ (defn curve-duration [curves]
145
+ (reduce
146
+ (fn [max-time points]
147
+ (if-let [last-point (last points)]
148
+ (max max-time (number-or (:time last-point) 0))
149
+ max-time))
150
+ 0
151
+ (vals (or curves {}))))
152
+
153
+ (defn channel-duration [channels]
154
+ (reduce
155
+ (fn [max-time channel]
156
+ (reduce
157
+ (fn [inner-max keyframe]
158
+ (max inner-max (number-or (:time keyframe) 0)))
159
+ max-time
160
+ (or (:keyframes channel) [])))
161
+ 0
162
+ (or channels [])))
163
+
164
+ (defn normalize-channels [channels]
165
+ (when (seq channels)
166
+ (->> channels
167
+ (map value->clj)
168
+ (mapv (fn [channel]
169
+ (update channel :keyframes normalize-curve-points))))))
170
+
171
+ (defn normalize-loop-mode [snippet]
172
+ (let [mode (:mixerLoopMode snippet)]
173
+ (if (#{"repeat" "once" "pingpong"} mode)
174
+ mode
175
+ (if (:loop snippet) "repeat" "once"))))
176
+
177
+ (defn normalize-snippet [snippet]
178
+ (let [source (or (value->clj snippet) {})
179
+ now (now-ms)
180
+ curves (if (:curves source)
181
+ (normalize-curves (:curves source))
182
+ (authoring-curves source))
183
+ channels (normalize-channels (:channels source))
184
+ duration (max (number-or (:duration source) 0)
185
+ (number-or (:maxTime source) 0)
186
+ (curve-duration curves)
187
+ (channel-duration channels))
188
+ loop-mode (normalize-loop-mode source)
189
+ name (string-or (:name source) (str "polymer:animation:" now))]
190
+ (cond->
191
+ (assoc source
192
+ :name name
193
+ :curves curves
194
+ :isPlaying (boolean (:isPlaying source))
195
+ :loop (not= loop-mode "once")
196
+ :loopIteration (number-or (:loopIteration source) 0)
197
+ :loopDirection (if (= (:loopDirection source) -1) -1 (if (:mixerReverse source) -1 1))
198
+ :lastLoopTime (number-or (:lastLoopTime source) 0)
199
+ :snippetPlaybackRate (number-or (:snippetPlaybackRate source) 1)
200
+ :snippetIntensityScale (number-or (:snippetIntensityScale source) 1)
201
+ :snippetBlendMode (if (= (:snippetBlendMode source) "additive") "additive" "replace")
202
+ :snippetJawScale (number-or (:snippetJawScale source) 1)
203
+ :snippetBalance (max -1 (min 1 (number-or (:snippetBalance source) 0)))
204
+ :snippetBalanceMap (or (:snippetBalanceMap source) {})
205
+ :snippetCategory (string-or (:snippetCategory source) "default")
206
+ :snippetPriority (number-or (:snippetPriority source) 0)
207
+ :snippetEasing (string-or (:snippetEasing source) "linear")
208
+ :mixerLoopMode loop-mode
209
+ :mixerRepeatCount (when (finite-number? (:mixerRepeatCount source))
210
+ (:mixerRepeatCount source))
211
+ :mixerReverse (boolean (:mixerReverse source))
212
+ :currentTime (number-or (:currentTime source) 0)
213
+ :startWallTime (number-or (:startWallTime source) now)
214
+ :duration duration
215
+ :maxTime duration
216
+ :cursor (or (:cursor source) {}))
217
+ channels (assoc :channels channels)
218
+ (contains? source :autoVisemeJaw) (assoc :autoVisemeJaw (boolean (:autoVisemeJaw source))))))
219
+
220
+ (defn snippet-ui-state [snippet]
221
+ (let [loop-mode (normalize-loop-mode snippet)]
222
+ {:name (:name snippet)
223
+ :isPlaying (boolean (:isPlaying snippet))
224
+ :loop (not= loop-mode "once")
225
+ :loopMode loop-mode
226
+ :reverse (boolean (:mixerReverse snippet))
227
+ :repeatCount (:mixerRepeatCount snippet)
228
+ :loopIteration (:loopIteration snippet)
229
+ :loopDirection (:loopDirection snippet)
230
+ :currentTime (number-or (:currentTime snippet) 0)
231
+ :duration (number-or (:duration snippet) 0)
232
+ :playbackRate (number-or (:snippetPlaybackRate snippet) 1)
233
+ :intensityScale (number-or (:snippetIntensityScale snippet) 1)
234
+ :blendMode (if (= (:snippetBlendMode snippet) "additive") "additive" "replace")
235
+ :balance (number-or (:snippetBalance snippet) 0)
236
+ :category (string-or (:snippetCategory snippet) "default")
237
+ :easing (string-or (:snippetEasing snippet) "linear")}))
238
+
239
+ (defn clone-snippet [snippet]
240
+ (assoc snippet
241
+ :curves (reduce-kv (fn [out key points] (assoc out key (mapv identity points))) {} (:curves snippet))
242
+ :snippetBalanceMap (merge {} (:snippetBalanceMap snippet))
243
+ :cursor (merge {} (:cursor snippet))))
244
+
245
+ (defn normalize-baked-clip [clip]
246
+ (let [source (or (value->clj clip) {})]
247
+ {:name (string-or (:name source) "")
248
+ :duration (number-or (:duration source) 0)
249
+ :channels (vec (or (:channels source) []))}))
250
+
251
+ (defn baked-ui-state [clip state]
252
+ (let [clip (normalize-baked-clip clip)
253
+ state (or state {})
254
+ current-time (number-or (:currentTime state) (number-or (:time state) 0))
255
+ playback-rate (number-or (:playbackRate state) (number-or (:speed state) 1))
256
+ intensity-scale (number-or (:intensityScale state) (number-or (:weight state) 1))
257
+ loop-mode (if (#{"repeat" "once" "pingpong"} (:loopMode state))
258
+ (:loopMode state)
259
+ (if (:loop state) "repeat" "once"))
260
+ blend-mode (if (= (:blendMode state) "additive") "additive" "replace")]
261
+ {:name (:name clip)
262
+ :source "baked"
263
+ :time current-time
264
+ :currentTime current-time
265
+ :duration (number-or (:duration state) (:duration clip))
266
+ :speed playback-rate
267
+ :playbackRate playback-rate
268
+ :weight (number-or (:weight state) intensity-scale)
269
+ :intensityScale intensity-scale
270
+ :isPlaying (boolean (:isPlaying state))
271
+ :isPaused (boolean (:isPaused state))
272
+ :loop (if (contains? state :loop) (boolean (:loop state)) (not= loop-mode "once"))
273
+ :loopMode loop-mode
274
+ :reverse (boolean (:reverse state))
275
+ :repeatCount (:repeatCount state)
276
+ :requestedBlendMode (if (= (:requestedBlendMode state) "additive") "additive" blend-mode)
277
+ :blendMode blend-mode
278
+ :balance (max -1 (min 1 (number-or (:balance state) 0)))
279
+ :category "baked"
280
+ :easing (string-or (:easing state) "linear")
281
+ :channels (vec (or (:channels state) (:channels clip) []))}))
282
+
283
+ (defn create-event-system []
284
+ (let [event-listeners (atom #{})
285
+ snippet-list-listeners (atom #{})
286
+ global-state-listeners (atom #{})
287
+ baked-clip-listeners (atom #{})
288
+ playing-baked-listeners (atom #{})
289
+ baked-animation-listeners (atom {})
290
+ snippet-accessor (atom (fn [] []))
291
+ global-state (atom "stopped")
292
+ baked-clips (atom [])
293
+ baked-states (atom {})]
294
+ (letfn [(raw-snippets [] (@snippet-accessor))
295
+ (raw-snippet [name] (first (filter #(= (:name %) name) (raw-snippets))))
296
+ (ui-snippets [] (mapv snippet-ui-state (raw-snippets)))
297
+ (emit-snippet-list! [] (emit-to! snippet-list-listeners (mapv :name (ui-snippets))))
298
+ (get-baked-state [clip-name] (get @baked-states clip-name))
299
+ (emit-baked-state! [clip-name]
300
+ (when-let [listeners (get @baked-animation-listeners clip-name)]
301
+ (emit-to! (atom listeners) (get-baked-state clip-name))))
302
+ (playing-baked []
303
+ (->> (vals @baked-states)
304
+ (filter #(or (:isPlaying %) (:isPaused %)))
305
+ vec))
306
+ (emit-playing-baked! [] (emit-to! playing-baked-listeners (playing-baked)))
307
+ (merge-baked! [clip-name patch]
308
+ (let [clip (or (first (filter #(= (:name %) clip-name) @baked-clips))
309
+ {:name clip-name :duration 0 :channels []})
310
+ current (get-baked-state clip-name)
311
+ next-state (baked-ui-state clip (merge current (or patch {})))]
312
+ (swap! baked-states assoc clip-name next-state)
313
+ next-state))
314
+ (emit-baked-param-change! [clip-name params]
315
+ (emit-event! event-listeners
316
+ {:type "BAKED_ANIMATION_PARAMS_CHANGED"
317
+ :clipName clip-name
318
+ :params params})
319
+ (emit-baked-state! clip-name)
320
+ (emit-playing-baked!))]
321
+ {:events (create-port event-listeners)
322
+ :snippet-list-port (create-port snippet-list-listeners)
323
+ :global-state-port (create-port global-state-listeners)
324
+ :baked-clip-port (create-port baked-clip-listeners)
325
+ :playing-baked-port (create-port playing-baked-listeners)
326
+ :baked-animation-state-port
327
+ (fn [clip-name]
328
+ #js {:subscribe
329
+ (fn [listener]
330
+ (swap! baked-animation-listeners update clip-name (fnil conj #{}) listener)
331
+ (subscription
332
+ (fn []
333
+ (swap! baked-animation-listeners update clip-name disj listener))))})
334
+ :set-baked-state! (fn [clip-name state]
335
+ (let [next-state (baked-ui-state
336
+ (or (first (filter #(= (:name %) clip-name) @baked-clips))
337
+ {:name clip-name :duration 0 :channels []})
338
+ (value->clj state))]
339
+ (swap! baked-states assoc clip-name next-state)
340
+ (emit-baked-state! clip-name)
341
+ (emit-playing-baked!)
342
+ next-state))
343
+ :get-baked-state get-baked-state
344
+ :emitter
345
+ #js {:events (create-port event-listeners)
346
+ :setSnippetAccessor (fn [get-snippets]
347
+ (reset! snippet-accessor
348
+ (fn []
349
+ (mapv normalize-snippet (or (get-snippets) [])))))
350
+ :getSnippets (fn [] (clj->js (ui-snippets)))
351
+ :getRawSnippets (fn [] (clj->js (mapv clone-snippet (raw-snippets))))
352
+ :getRawSnippet (fn [name] (when-let [snippet (raw-snippet name)] (clj->js (clone-snippet snippet))))
353
+ :getSnippet (fn [name] (when-let [snippet (raw-snippet name)] (clj->js (snippet-ui-state snippet))))
354
+ :getGlobalState (fn [] @global-state)
355
+ :emitSnippetAdded (fn [snippet-name]
356
+ (emit-event! event-listeners {:type "SNIPPET_ADDED" :snippetName snippet-name})
357
+ (emit-snippet-list!))
358
+ :emitSnippetRemoved (fn [snippet-name]
359
+ (emit-event! event-listeners {:type "SNIPPET_REMOVED" :snippetName snippet-name})
360
+ (emit-snippet-list!))
361
+ :emitPlayStateChanged (fn [snippet-name playing?]
362
+ (emit-event! event-listeners
363
+ {:type "SNIPPET_PLAY_STATE_CHANGED"
364
+ :snippetName snippet-name
365
+ :isPlaying (boolean playing?)}))
366
+ :emitSnippetUpdated (fn [snippet-name]
367
+ (emit-event! event-listeners {:type "SNIPPET_UPDATED" :snippetName snippet-name}))
368
+ :emitSnippetLooped (fn [data]
369
+ (emit-event! event-listeners
370
+ (merge {:type "SNIPPET_LOOPED"} (or (value->clj data) {}))))
371
+ :emitSnippetCompleted (fn [snippet-name]
372
+ (emit-event! event-listeners {:type "SNIPPET_COMPLETED" :snippetName snippet-name})
373
+ (emit-event! event-listeners
374
+ {:type "SNIPPET_PLAY_STATE_CHANGED"
375
+ :snippetName snippet-name
376
+ :isPlaying false}))
377
+ :emitKeyframeCompleted (fn [data]
378
+ (emit-event! event-listeners
379
+ (merge {:type "KEYFRAME_COMPLETED"} (or (value->clj data) {}))))
380
+ :emitGlobalPlaybackChanged (fn [state]
381
+ (reset! global-state state)
382
+ (emit-event! event-listeners
383
+ {:type "GLOBAL_PLAYBACK_CHANGED" :state state})
384
+ (emit-to! global-state-listeners state))
385
+ :emitSnippetSeeked (fn [snippet-name time]
386
+ (emit-event! event-listeners
387
+ {:type "SNIPPET_SEEKED"
388
+ :snippetName snippet-name
389
+ :time time}))
390
+ :emitParamsChanged (fn [snippet-name params]
391
+ (emit-event! event-listeners
392
+ {:type "SNIPPET_PARAMS_CHANGED"
393
+ :snippetName snippet-name
394
+ :params (or (value->clj params) {})}))
395
+ :emitBakedClipsLoaded (fn [clips]
396
+ (let [next-clips (mapv normalize-baked-clip (or (value->clj clips) []))
397
+ next-states (reduce
398
+ (fn [out clip]
399
+ (assoc out (:name clip)
400
+ (baked-ui-state clip (get @baked-states (:name clip)))))
401
+ {}
402
+ next-clips)]
403
+ (reset! baked-clips next-clips)
404
+ (reset! baked-states next-states)
405
+ (emit-event! event-listeners
406
+ {:type "BAKED_CLIPS_LOADED" :clips next-clips})
407
+ (emit-to! baked-clip-listeners next-clips)
408
+ (emit-playing-baked!)
409
+ (doseq [clip next-clips]
410
+ (emit-baked-state! (:name clip)))))
411
+ :emitBakedAnimationStarted (fn [clip-name state]
412
+ (let [next-state (merge-baked! clip-name
413
+ (assoc (or (value->clj state) {})
414
+ :isPlaying true
415
+ :isPaused false))]
416
+ (emit-event! event-listeners
417
+ {:type "BAKED_ANIMATION_STARTED"
418
+ :clipName clip-name
419
+ :state next-state})
420
+ (emit-baked-state! clip-name)
421
+ (emit-playing-baked!)))
422
+ :emitBakedAnimationStopped (fn [clip-name]
423
+ (merge-baked! clip-name {:isPlaying false :isPaused false :time 0 :currentTime 0})
424
+ (emit-event! event-listeners
425
+ {:type "BAKED_ANIMATION_STOPPED" :clipName clip-name})
426
+ (emit-baked-state! clip-name)
427
+ (emit-playing-baked!))
428
+ :emitBakedAnimationPaused (fn [clip-name]
429
+ (merge-baked! clip-name {:isPlaying false :isPaused true})
430
+ (emit-event! event-listeners
431
+ {:type "BAKED_ANIMATION_PAUSED" :clipName clip-name})
432
+ (emit-baked-state! clip-name)
433
+ (emit-playing-baked!))
434
+ :emitBakedAnimationResumed (fn [clip-name]
435
+ (merge-baked! clip-name {:isPlaying true :isPaused false})
436
+ (emit-event! event-listeners
437
+ {:type "BAKED_ANIMATION_RESUMED" :clipName clip-name})
438
+ (emit-baked-state! clip-name)
439
+ (emit-playing-baked!))
440
+ :emitBakedAnimationCompleted (fn [clip-name]
441
+ (let [current (merge-baked! clip-name {})
442
+ terminal-time (if (:reverse current) 0 (:duration current))]
443
+ (merge-baked! clip-name {:isPlaying false
444
+ :isPaused false
445
+ :time terminal-time
446
+ :currentTime terminal-time})
447
+ (emit-event! event-listeners
448
+ {:type "BAKED_ANIMATION_COMPLETED" :clipName clip-name})
449
+ (emit-baked-state! clip-name)
450
+ (emit-playing-baked!)))
451
+ :emitBakedAnimationProgress (fn [clip-name time duration]
452
+ (merge-baked! clip-name {:time time :currentTime time :duration duration})
453
+ (emit-event! event-listeners
454
+ {:type "BAKED_ANIMATION_PROGRESS"
455
+ :clipName clip-name
456
+ :time time
457
+ :duration duration})
458
+ (emit-baked-state! clip-name))
459
+ :emitBakedAnimationParamsChanged emit-baked-param-change!
460
+ :getBakedClips (fn [] (clj->js @baked-clips))
461
+ :getPlayingBakedAnimations (fn [] (clj->js (playing-baked)))
462
+ :getBakedAnimationState (fn [clip-name] (when-let [state (get-baked-state clip-name)] (clj->js state)))
463
+ :updateBakedAnimationState (fn [clip-name state]
464
+ (merge-baked! clip-name (or (value->clj state) {}))
465
+ (emit-baked-state! clip-name)
466
+ (emit-playing-baked!))}})))
467
+
468
+ (defonce animation-event-system (create-event-system))
469
+
470
+ (def animationEventEmitter (:emitter animation-event-system))
471
+ (def snippetList$ (:snippet-list-port animation-event-system))
472
+ (def globalPlaybackState$ (:global-state-port animation-event-system))
473
+ (def bakedClipList$ (:baked-clip-port animation-event-system))
474
+ (def playingBakedAnimations$ (:playing-baked-port animation-event-system))
475
+ (def bakedAnimationProgress$ (:events animation-event-system))
476
+
477
+ (defn bakedAnimationState$ [clip-name]
478
+ ((:baked-animation-state-port animation-event-system) clip-name))
479
+
480
+ (defn snippet-state-port [event-emitter snippet-name]
481
+ #js {:subscribe
482
+ (fn [listener]
483
+ (let [subscription (js-call (aget event-emitter "events")
484
+ "subscribe"
485
+ (fn [event]
486
+ (let [event-map (value->clj event)
487
+ type (:type event-map)]
488
+ (when (or (#{"SNIPPET_ADDED" "SNIPPET_REMOVED"} type)
489
+ (= (:snippetName event-map) snippet-name))
490
+ (listener (js-call event-emitter "getSnippet" snippet-name))))))]
491
+ subscription))})
492
+
493
+ (defn snippet-time-port [event-emitter snippet-name]
494
+ #js {:subscribe
495
+ (fn [listener]
496
+ (js-call (aget event-emitter "events")
497
+ "subscribe"
498
+ (fn [event]
499
+ (let [event-map (value->clj event)]
500
+ (when (and (= (:type event-map) "SNIPPET_SEEKED")
501
+ (= (:snippetName event-map) snippet-name))
502
+ (listener (:time event-map)))))))})
503
+
504
+ (defn snippetState$ [snippet-name]
505
+ (snippet-state-port animationEventEmitter snippet-name))
506
+
507
+ (defn snippetTime$ [snippet-name]
508
+ (snippet-time-port animationEventEmitter snippet-name))
509
+
510
+ (defn upsert-snippet [snippets snippet playing?]
511
+ (let [next-snippet (assoc snippet :isPlaying (boolean playing?))]
512
+ (conj (vec (remove #(= (:name %) (:name next-snippet)) snippets)) next-snippet)))
513
+
514
+ (defn patch-snippet [snippets name patch]
515
+ (mapv (fn [snippet]
516
+ (if (= (:name snippet) name)
517
+ (merge snippet patch)
518
+ snippet))
519
+ snippets))
520
+
521
+ (defn find-snippet [snippets name]
522
+ (first (filter #(= (:name %) name) snippets)))
523
+
524
+ (defn agency-schedule! [agency snippet options]
525
+ (js-call agency "scheduleSnippet" (clj->js snippet) (clj->js options)))
526
+
527
+ (defn agency-remove! [agency name]
528
+ (js-call agency "removeSnippet" name))
529
+
530
+ (defn agency-update! [agency name params]
531
+ (js-call agency "updateSnippet" name (clj->js params)))
532
+
533
+ (defn agency-seek! [agency name time-sec]
534
+ (js-call agency "seekSnippet" name time-sec))
535
+
536
+ (defn playback-options [snippet options]
537
+ (merge
538
+ {:autoPlay true
539
+ :sourceAgency (or (:sourceAgency options) "animationService")
540
+ :priority (:snippetPriority snippet)}
541
+ (or options {})))
542
+
543
+ (defn event-system-set-baked-state! [event-system clip-name state]
544
+ ((:set-baked-state! event-system) clip-name state))
545
+
546
+ (defn event-system-get-baked-state [event-system clip-name]
547
+ ((:get-baked-state event-system) clip-name))
548
+
549
+ (defn event-system-merge-baked-state! [event-system clip-name patch]
550
+ (event-system-set-baked-state!
551
+ event-system
552
+ clip-name
553
+ (merge (or (event-system-get-baked-state event-system clip-name) {}) patch)))
554
+
555
+ (defn call-method [target method-name & args]
556
+ (apply js-call target method-name args))
557
+
558
+ (defn baked-play-options [state]
559
+ (clj->js {:loop (:loop state)
560
+ :loopMode (:loopMode state)
561
+ :repeatCount (:repeatCount state)
562
+ :reverse (:reverse state)
563
+ :playbackRate (:playbackRate state)
564
+ :speed (:playbackRate state)
565
+ :weight (:intensityScale state)
566
+ :intensity (:intensityScale state)
567
+ :blendMode (:requestedBlendMode state)
568
+ :balance (:balance state)
569
+ :easing (:easing state)}))
570
+
571
+ (defn createAnimationService [engine]
572
+ (let [event-system (create-event-system)
573
+ event-emitter (:emitter event-system)
574
+ animation (animation-agency/create-animation-agency #js {:engine engine})
575
+ snippets (atom [])
576
+ baked-engine (atom engine)
577
+ disposed? (atom false)
578
+ playing? (atom false)
579
+ transition-listeners (atom #{})]
580
+ ;; Compatibility state belongs to this service instance. A character must
581
+ ;; never replace another character's snippet accessor or publish into its
582
+ ;; event ports merely because both services came from the same JS module.
583
+ (js-call event-emitter "setSnippetAccessor" (fn [] @snippets))
584
+ (letfn [(notify-transition! []
585
+ (let [snapshot #js {:context #js {:animations (clj->js (mapv clone-snippet @snippets))}}]
586
+ (doseq [listener @transition-listeners]
587
+ (listener snapshot))))
588
+ (load-snippet! [data playing-state options]
589
+ (let [snippet (merge (normalize-snippet data)
590
+ (select-keys options [:snippetPriority]))
591
+ name (:name snippet)]
592
+ (swap! snippets upsert-snippet snippet playing-state)
593
+ (js-call event-emitter "emitSnippetAdded" name)
594
+ (when playing-state
595
+ (agency-schedule! animation snippet (playback-options snippet options))
596
+ (js-call event-emitter "emitPlayStateChanged" name true))
597
+ (notify-transition!)
598
+ name))
599
+ (set-param! [name patch event-params restart?]
600
+ (when-let [snippet (find-snippet @snippets name)]
601
+ (let [next-snippet (merge snippet patch)]
602
+ (swap! snippets patch-snippet name patch)
603
+ (js-call event-emitter "emitParamsChanged" name (clj->js event-params))
604
+ (agency-update! animation name event-params)
605
+ (when (and restart? (:isPlaying next-snippet))
606
+ (agency-remove! animation name)
607
+ (agency-schedule! animation next-snippet (playback-options next-snippet {})))
608
+ (notify-transition!))))
609
+ (set-playing! [name next-playing]
610
+ (when-let [snippet (find-snippet @snippets name)]
611
+ (swap! snippets patch-snippet name {:isPlaying (boolean next-playing)})
612
+ (if next-playing
613
+ (agency-schedule! animation (assoc snippet :isPlaying true) (playback-options snippet {}))
614
+ (agency-remove! animation name))
615
+ (js-call event-emitter "emitPlayStateChanged" name (boolean next-playing))
616
+ (notify-transition!)
617
+ true))
618
+ (play-all! []
619
+ (reset! playing? true)
620
+ (js-call event-emitter "emitGlobalPlaybackChanged" "playing")
621
+ (doseq [snippet @snippets]
622
+ (when (:isPlaying snippet)
623
+ (agency-schedule! animation snippet (playback-options snippet {}))))
624
+ (notify-transition!))
625
+ (pause-all! []
626
+ (reset! playing? false)
627
+ (doseq [snippet @snippets]
628
+ (when (:isPlaying snippet)
629
+ (agency-remove! animation (:name snippet))))
630
+ (js-call event-emitter "emitGlobalPlaybackChanged" "paused")
631
+ (notify-transition!))
632
+ (stop-all! []
633
+ (reset! playing? false)
634
+ (doseq [snippet @snippets]
635
+ (agency-remove! animation (:name snippet)))
636
+ (swap! snippets (fn [entries] (mapv #(assoc % :isPlaying false :currentTime 0) entries)))
637
+ (js-call event-emitter "emitGlobalPlaybackChanged" "stopped")
638
+ (notify-transition!))
639
+ (set-baked-engine! [next-engine]
640
+ (reset! baked-engine next-engine)
641
+ (let [clips (mapv normalize-baked-clip (or (call-method next-engine "getAnimationClips") []))]
642
+ (js-call event-emitter "emitBakedClipsLoaded" (clj->js clips))))
643
+ (current-baked-state [clip-name]
644
+ (or (event-system-get-baked-state event-system clip-name)
645
+ (baked-ui-state {:name clip-name :duration 0 :channels []} nil)))
646
+ (play-baked! [clip-name options]
647
+ (let [base (current-baked-state clip-name)
648
+ option-map (or (value->clj options) {})
649
+ next-state (merge base
650
+ {:isPlaying true
651
+ :isPaused false}
652
+ (select-keys option-map
653
+ [:loop :loopMode :repeatCount :reverse
654
+ :blendMode :requestedBlendMode :balance :easing])
655
+ (when (or (:playbackRate option-map) (:speed option-map))
656
+ {:playbackRate (number-or (:playbackRate option-map) (:speed option-map))
657
+ :speed (number-or (:playbackRate option-map) (:speed option-map))})
658
+ (when (or (:weight option-map) (:intensity option-map))
659
+ {:intensityScale (number-or (:weight option-map) (:intensity option-map))
660
+ :weight (number-or (:weight option-map) (:intensity option-map))}))
661
+ handle (call-method @baked-engine "playAnimation" clip-name (baked-play-options next-state))]
662
+ (js-call event-emitter "emitBakedAnimationStarted" clip-name (clj->js next-state))
663
+ handle))]
664
+ (set-baked-engine! engine)
665
+ (let [api #js {:loadFromJSON
666
+ (fn [data] (load-snippet! data false {}))
667
+ :updateSnippet
668
+ (fn [data]
669
+ (let [snippet (normalize-snippet data)
670
+ name (:name snippet)
671
+ existing (find-snippet @snippets name)
672
+ next-playing (boolean (:isPlaying (or existing snippet)))]
673
+ (swap! snippets upsert-snippet snippet next-playing)
674
+ (js-call event-emitter "emitSnippetUpdated" name)
675
+ (when next-playing
676
+ (agency-remove! animation name)
677
+ (agency-schedule! animation (assoc snippet :isPlaying true) (playback-options snippet {})))
678
+ (notify-transition!)
679
+ name))
680
+ :schedule
681
+ (fn
682
+ ([data] (load-snippet! data true {}))
683
+ ([data opts]
684
+ (let [options (or (value->clj opts) {})
685
+ priority (number-or (:priority options) (:snippetPriority (or (value->clj data) {})))
686
+ data-with-priority (merge (or (value->clj data) {}) {:snippetPriority priority})]
687
+ (load-snippet! data-with-priority true options))))
688
+ :remove
689
+ (fn [name]
690
+ (agency-remove! animation name)
691
+ (swap! snippets #(vec (remove (fn [snippet] (= (:name snippet) name)) %)))
692
+ (js-call event-emitter "emitSnippetRemoved" name)
693
+ (notify-transition!))
694
+ :play play-all!
695
+ :pause pause-all!
696
+ :stop stop-all!
697
+ :enable (fn [name on?] (set-playing! name (not (false? on?))))
698
+ :seek (fn [name offset-sec]
699
+ (let [time (max 0 (number-or offset-sec 0))]
700
+ (swap! snippets patch-snippet name {:currentTime time})
701
+ (agency-seek! animation name time)
702
+ (js-call event-emitter "emitSnippetSeeked" name time)
703
+ (notify-transition!)
704
+ true))
705
+ :getState (fn [] #js {:context #js {:animations (clj->js (mapv clone-snippet @snippets))}})
706
+ :getScheduleSnapshot
707
+ (fn []
708
+ (clj->js
709
+ (mapv (fn [snippet]
710
+ {:name (:name snippet)
711
+ :enabled (:isPlaying snippet)
712
+ :startsAt 0
713
+ :offset 0
714
+ :localTime (:currentTime snippet)
715
+ :duration (:duration snippet)
716
+ :loop (:loop snippet)
717
+ :priority (:snippetPriority snippet)
718
+ :playbackRate (:snippetPlaybackRate snippet)
719
+ :intensityScale (:snippetIntensityScale snippet)})
720
+ @snippets)))
721
+ :getCurrentValue (fn [au-id] (number-or (call-method engine "getAU" au-id) 0))
722
+ :isPlaying (fn [] @playing?)
723
+ :loadFromLocal
724
+ (fn
725
+ ([key] nil)
726
+ ([key cat] nil)
727
+ ([key cat prio] nil))
728
+ :setSnippetPlaybackRate (fn [name rate]
729
+ (let [next-rate (max 0.0001 (number-or rate 1))]
730
+ (set-param! name
731
+ {:snippetPlaybackRate next-rate}
732
+ {:rate next-rate :playbackRate next-rate}
733
+ false)))
734
+ :setSnippetIntensityScale (fn [name scale]
735
+ (let [next-scale (max 0 (number-or scale 1))]
736
+ (set-param! name
737
+ {:snippetIntensityScale next-scale}
738
+ {:weight next-scale :intensityScale next-scale}
739
+ false)))
740
+ :setSnippetBlendMode (fn [name mode]
741
+ (let [next-mode (if (= mode "additive") "additive" "replace")]
742
+ (set-param! name
743
+ {:snippetBlendMode next-mode}
744
+ {:blendMode next-mode}
745
+ false)))
746
+ :setSnippetBalance (fn [name balance]
747
+ (let [next-balance (max -1 (min 1 (number-or balance 0)))]
748
+ (set-param! name
749
+ {:snippetBalance next-balance}
750
+ {:balance next-balance}
751
+ true)))
752
+ :setSnippetEasing (fn [name easing]
753
+ (set-param! name {:snippetEasing easing} {:easing easing} false))
754
+ :setSnippetPriority (fn [name priority]
755
+ (swap! snippets patch-snippet name {:snippetPriority (number-or priority 0)})
756
+ (notify-transition!))
757
+ :setSnippetLoopMode (fn [name mode]
758
+ (let [next-mode (if (#{"repeat" "once" "pingpong"} mode) mode "once")]
759
+ (set-param! name
760
+ {:mixerLoopMode next-mode :loop (not= next-mode "once")}
761
+ {:loopMode next-mode :mixerLoopMode next-mode :loop (not= next-mode "once")}
762
+ false)))
763
+ :setSnippetRepeatCount (fn [name repeat-count]
764
+ (let [next-repeat (when (and (finite-number? repeat-count) (>= repeat-count 0))
765
+ (js/Math.floor repeat-count))]
766
+ (set-param! name
767
+ {:mixerRepeatCount next-repeat}
768
+ {:repeatCount next-repeat}
769
+ false)))
770
+ :setSnippetReverse (fn [name reverse?]
771
+ (set-param! name
772
+ {:mixerReverse (boolean reverse?)}
773
+ {:reverse (boolean reverse?)}
774
+ false))
775
+ :setSnippetPlaying set-playing!
776
+ :setSnippetTime (fn [name time-sec]
777
+ (let [time (max 0 (number-or time-sec 0))]
778
+ (swap! snippets patch-snippet name {:currentTime time})
779
+ (agency-seek! animation name time)
780
+ (js-call event-emitter "emitSnippetSeeked" name time)
781
+ (notify-transition!)))
782
+ :setSnippetLoopState (fn [name iteration local-time]
783
+ (swap! snippets patch-snippet name
784
+ (cond-> {:loopIteration (max 0 (number-or iteration 0))}
785
+ (finite-number? local-time) (assoc :lastLoopTime local-time)))
786
+ (notify-transition!))
787
+ :pauseSnippet (fn [name] (set-playing! name false))
788
+ :resumeSnippet (fn [name] (set-playing! name true))
789
+ :restartSnippet (fn [name]
790
+ (when-let [snippet (find-snippet @snippets name)]
791
+ (agency-remove! animation name)
792
+ (agency-schedule! animation (assoc snippet :isPlaying true :currentTime 0)
793
+ (playback-options snippet {}))
794
+ (swap! snippets patch-snippet name {:isPlaying true :currentTime 0})
795
+ (js-call event-emitter "emitPlayStateChanged" name true)
796
+ true))
797
+ :stopSnippet (fn [name] (set-playing! name false))
798
+ :onTransition (fn [listener]
799
+ (swap! transition-listeners conj listener)
800
+ (fn [] (swap! transition-listeners disj listener)))
801
+ :dispose (fn []
802
+ (when-not @disposed?
803
+ (reset! disposed? true)
804
+ (js-call animation "dispose")
805
+ (reset! snippets [])
806
+ (js-call event-emitter "setSnippetAccessor" (fn [] []))
807
+ (js-call event-emitter "emitBakedClipsLoaded" #js [])))
808
+ :debug (fn [] (.log js/console "[Polymer AnimationService]" (clj->js @snippets)))
809
+ :eventEmitter event-emitter
810
+ :events (aget event-emitter "events")
811
+ :snippetList$ (:snippet-list-port event-system)
812
+ :globalPlaybackState$ (:global-state-port event-system)
813
+ :bakedClipList$ (:baked-clip-port event-system)
814
+ :playingBakedAnimations$ (:playing-baked-port event-system)
815
+ :bakedAnimationProgress$ (:events event-system)
816
+ :snippetState$ (fn [snippet-name] (snippet-state-port event-emitter snippet-name))
817
+ :snippetTime$ (fn [snippet-name] (snippet-time-port event-emitter snippet-name))
818
+ :bakedAnimationState$ (:baked-animation-state-port event-system)
819
+ :setBakedAnimationEngine set-baked-engine!
820
+ :playBakedAnimation
821
+ (fn
822
+ ([clip-name] (play-baked! clip-name nil))
823
+ ([clip-name options] (play-baked! clip-name options)))
824
+ :stopBakedAnimation (fn [clip-name]
825
+ (call-method @baked-engine "stopAnimation" clip-name)
826
+ (js-call event-emitter "emitBakedAnimationStopped" clip-name))
827
+ :pauseBakedAnimation (fn [clip-name]
828
+ (call-method @baked-engine "pauseAnimation" clip-name)
829
+ (js-call event-emitter "emitBakedAnimationPaused" clip-name))
830
+ :resumeBakedAnimation (fn [clip-name]
831
+ (call-method @baked-engine "resumeAnimation" clip-name)
832
+ (js-call event-emitter "emitBakedAnimationResumed" clip-name))
833
+ :setBakedAnimationSpeed (fn [clip-name speed]
834
+ (let [next-speed (max 0.1 (number-or speed 1))]
835
+ (call-method @baked-engine "setAnimationSpeed" clip-name next-speed)
836
+ (event-system-merge-baked-state! event-system clip-name {:speed next-speed :playbackRate next-speed})
837
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
838
+ (clj->js {:speed next-speed
839
+ :playbackRate next-speed}))))
840
+ :setBakedAnimationWeight (fn [clip-name weight]
841
+ (let [next-weight (max 0 (number-or weight 1))]
842
+ (call-method @baked-engine "setAnimationIntensity" clip-name next-weight)
843
+ (event-system-merge-baked-state! event-system clip-name {:weight next-weight :intensityScale next-weight})
844
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
845
+ (clj->js {:weight next-weight
846
+ :intensityScale next-weight}))))
847
+ :setBakedAnimationLoop (fn [clip-name loop?]
848
+ (let [mode (if loop? "repeat" "once")]
849
+ (call-method @baked-engine "setAnimationLoopMode" clip-name mode)
850
+ (event-system-merge-baked-state! event-system clip-name {:loop (boolean loop?) :loopMode mode})
851
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
852
+ (clj->js {:loop (boolean loop?)
853
+ :loopMode mode}))))
854
+ :setBakedAnimationLoopMode (fn [clip-name mode]
855
+ (let [next-mode (if (#{"repeat" "once" "pingpong"} mode) mode "once")]
856
+ (call-method @baked-engine "setAnimationLoopMode" clip-name next-mode)
857
+ (event-system-merge-baked-state! event-system clip-name {:loop (not= next-mode "once") :loopMode next-mode})
858
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
859
+ (clj->js {:loop (not= next-mode "once")
860
+ :loopMode next-mode}))))
861
+ :setBakedAnimationRepeatCount (fn [clip-name repeat-count]
862
+ (let [next-repeat (when (and (finite-number? repeat-count) (>= repeat-count 0))
863
+ (js/Math.floor repeat-count))]
864
+ (call-method @baked-engine "setAnimationRepeatCount" clip-name next-repeat)
865
+ (event-system-merge-baked-state! event-system clip-name {:repeatCount next-repeat})
866
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
867
+ (clj->js {:repeatCount next-repeat}))))
868
+ :setBakedAnimationReverse (fn [clip-name reverse?]
869
+ (call-method @baked-engine "setAnimationReverse" clip-name (boolean reverse?))
870
+ (event-system-merge-baked-state! event-system clip-name {:reverse (boolean reverse?)})
871
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
872
+ (clj->js {:reverse (boolean reverse?)})))
873
+ :setBakedAnimationBlendMode (fn [clip-name mode]
874
+ (let [next-mode (if (= mode "additive") "additive" "replace")]
875
+ (call-method @baked-engine "setAnimationBlendMode" clip-name next-mode)
876
+ (event-system-merge-baked-state! event-system clip-name {:blendMode next-mode :requestedBlendMode next-mode})
877
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
878
+ (clj->js {:blendMode next-mode}))))
879
+ :setBakedAnimationBalance (fn [clip-name balance]
880
+ (let [next-balance (max -1 (min 1 (number-or balance 0)))]
881
+ (event-system-merge-baked-state! event-system clip-name {:balance next-balance})
882
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
883
+ (clj->js {:balance next-balance}))))
884
+ :setBakedAnimationEasing (fn [clip-name easing]
885
+ (event-system-merge-baked-state! event-system clip-name {:easing easing})
886
+ (js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
887
+ (clj->js {:easing easing})))
888
+ :seekBakedAnimation (fn [clip-name time-sec]
889
+ (let [time (max 0 (number-or time-sec 0))
890
+ duration (:duration (current-baked-state clip-name))]
891
+ (or (call-method @baked-engine "seekAnimation" clip-name time)
892
+ (call-method @baked-engine "setAnimationTime" clip-name time)
893
+ (call-method @baked-engine "setTime" clip-name time))
894
+ (event-system-merge-baked-state! event-system clip-name {:time time :currentTime time})
895
+ (js-call event-emitter "emitBakedAnimationProgress" clip-name time duration)
896
+ true))
897
+ :canSeekBakedAnimation (fn []
898
+ (boolean (or (aget @baked-engine "seekAnimation")
899
+ (aget @baked-engine "setAnimationTime")
900
+ (aget @baked-engine "setTime"))))
901
+ :stopAllBakedAnimations (fn []
902
+ (let [active (js->clj (js-call event-emitter "getPlayingBakedAnimations")
903
+ :keywordize-keys true)]
904
+ (call-method @baked-engine "stopAllAnimations")
905
+ (doseq [animation active]
906
+ (js-call event-emitter "emitBakedAnimationStopped" (:name animation)))))
907
+ :getBakedClips (fn [] (js-call event-emitter "getBakedClips"))
908
+ :getPlayingBakedAnimations (fn [] (js-call event-emitter "getPlayingBakedAnimations"))}]
909
+ (js/Object.defineProperty api "playing" #js {:get (fn [] @playing?)})
910
+ api))))
911
+
912
+ (defn get-storage [storage]
913
+ (or storage
914
+ (try
915
+ (aget js/globalThis "localStorage")
916
+ (catch :default _ nil))))
917
+
918
+ (defn parse-json [raw fallback]
919
+ (if (and raw (string? raw))
920
+ (try
921
+ (js->clj (.parse js/JSON raw) :keywordize-keys true)
922
+ (catch :default _ fallback))
923
+ fallback))
924
+
925
+ (defn parse-string-list [raw]
926
+ (let [parsed (parse-json raw [])]
927
+ (if (sequential? parsed)
928
+ (vec (filter string? parsed))
929
+ [])))
930
+
931
+ (defn getBundledSnippetNames [list-key]
932
+ ;; Polymer no longer ships Latticework's TS snippet glob catalog. User-authored
933
+ ;; snippets remain available through localStorage; built-ins can be ported as
934
+ ;; Polymer data in a separate focused PR.
935
+ #js [])
936
+
937
+ (defn getStoredSnippetNames
938
+ ([list-key] (getStoredSnippetNames list-key nil))
939
+ ([list-key storage]
940
+ (if-not (snippet-categories list-key)
941
+ #js []
942
+ (let [resolved-storage (get-storage storage)]
943
+ (if-not resolved-storage
944
+ #js []
945
+ (clj->js (parse-string-list (.getItem resolved-storage list-key))))))))
946
+
947
+ (defn getAvailableSnippetNames
948
+ ([list-key] (getAvailableSnippetNames list-key nil))
949
+ ([list-key storage]
950
+ (getStoredSnippetNames list-key storage)))
951
+
952
+ (defn resolveSnippetEntry
953
+ ([list-key name] (resolveSnippetEntry list-key name nil))
954
+ ([list-key name storage]
955
+ (js/Promise.
956
+ (fn [resolve _reject]
957
+ (let [resolved-storage (get-storage storage)
958
+ storage-key (str list-key "/" name)
959
+ raw (when resolved-storage (.getItem resolved-storage storage-key))
960
+ parsed (parse-json raw nil)]
961
+ (resolve
962
+ (when (map? parsed)
963
+ (clj->js {:name (string-or (:name parsed) name)
964
+ :data parsed
965
+ :source "localStorage"
966
+ :storageKey storage-key}))))))))
967
+
968
+ (defn preloadAllSnippets [] nil)
969
+
970
+ (defn clearPreloadedSnippets
971
+ ([] (clearPreloadedSnippets nil))
972
+ ([storage]
973
+ (let [resolved-storage (get-storage storage)
974
+ manifest (when resolved-storage
975
+ (parse-json (.getItem resolved-storage bundled-snippet-manifest-key) {}))]
976
+ (when resolved-storage
977
+ (doseq [[list-key names] manifest
978
+ name names]
979
+ (.removeItem resolved-storage (str (key->string list-key) "/" name)))
980
+ (.removeItem resolved-storage bundled-snippet-manifest-key)
981
+ (.removeItem resolved-storage bundled-snippet-version-key)))))