Bring in symbols from other projects; new and open send the clock home

Dropping a symbol out of the pool's all-assets folder fetches that project's
clip and the blocks it names, copies the symbol and everything it places in
through clip/adopt — renamed where ids collide — and places it where it was
dropped. It comes in as drawing: its tracking stays with the analysis that
measured it. New and open now seek the audio clock to 0 with the readout,
where play used to pick up wherever the last document's audio had got to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Olive Vaughn 2026-09-29 13:51:39 -04:00
parent 490460bf45
commit 7bb80d315d
2 changed files with 62 additions and 1 deletions

View file

@ -23,6 +23,8 @@
fx, which is the only thing in this namespace that is not pure." fx, which is the only thing in this namespace that is not pure."
(:require [arthur.db :as db] (:require [arthur.db :as db]
[arthur.domain.clip :as clip] [arthur.domain.clip :as clip]
[arthur.domain.leaf :as leaf]
[arthur.events.edit :as edit]
[arthur.audio.mix :as mix] [arthur.audio.mix :as mix]
[arthur.demo.stage :as stage] [arthur.demo.stage :as stage]
[arthur.domain.feature :as feature] [arthur.domain.feature :as feature]
@ -118,6 +120,58 @@
(-> (mix/mix! built (clip/opens-on built) (:audio entry) (:store entry)) (-> (mix/mix! built (clip/opens-on built) (:audio entry) (:store entry))
(.then (fn [audio] (assoc entry :audio audio))))))))))) (.then (fn [audio] (assoc entry :audio audio)))))))))))
(defn- saved-clip!
"Promise of clip `cid` of saved project `pid`, as `{:clip :store}`: its
document and the blocks it names, and nothing played or mixed."
[pid cid]
(-> (http/GET (str "/api/projects/" pid))
(.then (fn [^js doc]
(let [^js c (or (first (filter #(= cid (.-cid ^js %)) (array-seq (.-clips doc))))
(throw (ex-info "that project no longer has that clip" {:cid cid})))]
(-> (js/Promise.all (into-array (map #(http/GET (str "/api/blocks/" %))
(array-seq (.-blocks c)))))
(.then (fn [blocks]
(project/load cid #js {:leaves (.-leaves c) :blocks blocks})))))))))
(rf/reg-fx
::import!
(fn [{:keys [project cid] :as request}]
(-> (saved-clip! project cid)
(.then #(rf/dispatch [::imported request %]))
(.catch (fn [error]
(js/console.error error)
(rf/dispatch [::failed (or (ex-message error) (str error))]))))))
(rf/reg-event-fx
::import
;; A symbol out of another saved project, dropped at `frame` of the open symbol
;; and, from the stage, with its middle on `point`.
(fn [{:keys [db]} [_ carried frame point]]
{:db (update db :project merge {:status (str "fetching " (:label carried) "…")})
::import! (assoc (select-keys carried [:project :cid :symbol :label])
:host (get-in db [:ui :open]) :frame frame :point point)}))
(rf/reg-event-fx
::imported
;; Copied, not linked: the symbol and everything it places come in under ids of
;; their own, with the blocks they name, and editing them here does not touch
;; the project they came from. They come in as DRAWINGS — their tracking stays
;; with the analysis that measured them — so they play but do not re-tune.
(fn [{:keys [db]} [_ {:keys [symbol host frame point label]} other]]
(let [sid (leaf/unsegment symbol)
uuid (random-uuid)
{:keys [clip ids]} (clip/adopt (:clip (store/entry (:clip/current db)))
(:clip other) [sid] {})
db (edit/edit-entry
db
#(let [st (merge (:store %) (:store other))]
(assoc % :store st
:clip (clip/place-symbol clip st host (ids sid) frame uuid point))))]
{:db (-> db
(assoc-in [:ui :selection] [:node host uuid [uuid]])
(update :project merge {:status (str "brought in " label)}))
:dispatch [::pb/refresh-clock]})))
(rf/reg-fx (rf/reg-fx
::save! ::save!
(fn [{:keys [id cid label clip]}] (fn [{:keys [id cid label clip]}]
@ -424,6 +478,9 @@
(pb/show id) (pb/show id)
(assoc :project {:id nil :cid (:cid entry) :name nil :seq nil (assoc :project {:id nil :cid (:cid entry) :name nil :seq nil
:busy? false :status "new document"})) :busy? false :status "new document"}))
;; The readout goes home with the document; so must the clock, or play
;; picks up wherever the last document's audio had got to.
::pb/seek! [(:fps entry) (clip/frames (:clip entry) (clip/opens-on (:clip entry))) 0]
::pb/pause! nil}))) ::pb/pause! nil})))
(rf/reg-event-fx (rf/reg-event-fx
@ -499,7 +556,9 @@
:cid (:cid (store/entry clip-id)) :cid (:cid (store/entry clip-id))
:busy? false :busy? false
:status (str "opened " name " r" seq)})) :status (str "opened " name " r" seq)}))
::pb/pause! nil})) ::pb/pause! nil
::pb/seek! (let [{c :clip fps :fps} (store/entry clip-id)]
[fps (clip/frames c (clip/opens-on c)) 0])}))
(rf/reg-event-db (rf/reg-event-db
::failed ::failed

View file

@ -12,6 +12,7 @@
(:require [arthur.domain.clip :as clip] (:require [arthur.domain.clip :as clip]
[arthur.domain.palette :as pal] [arthur.domain.palette :as pal]
[arthur.events.footage :as footage] [arthur.events.footage :as footage]
[arthur.events.project :as project]
[arthur.events.ui :as ui] [arthur.events.ui :as ui]
[arthur.footage.store :as store] [arthur.footage.store :as store]
[re-frame.core :as rf])) [re-frame.core :as rf]))
@ -100,5 +101,6 @@
;; Video is asked about before anything happens: which frames, and what ;; Video is asked about before anything happens: which frames, and what
;; the symbol they become is called. ;; the symbol they become is called.
:footage (rf/dispatch [::footage/ask-convert c frame point]) :footage (rf/dispatch [::footage/ask-convert c frame point])
:import (rf/dispatch [::project/import c frame point])
nil)) nil))
(done!)) (done!))