From 7bb80d315d1df831e10f75d647aff64003823cda Mon Sep 17 00:00:00 2001 From: Olive Vaughn Date: Tue, 29 Sep 2026 13:51:39 -0400 Subject: [PATCH] Bring in symbols from other projects; new and open send the clock home MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/src/arthur/events/project.cljs | 61 ++++++++++++++++++++++++- frontend/src/arthur/ui/drag.cljs | 2 + 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/frontend/src/arthur/events/project.cljs b/frontend/src/arthur/events/project.cljs index 68f6fe0..8429215 100644 --- a/frontend/src/arthur/events/project.cljs +++ b/frontend/src/arthur/events/project.cljs @@ -23,6 +23,8 @@ fx, which is the only thing in this namespace that is not pure." (:require [arthur.db :as db] [arthur.domain.clip :as clip] + [arthur.domain.leaf :as leaf] + [arthur.events.edit :as edit] [arthur.audio.mix :as mix] [arthur.demo.stage :as stage] [arthur.domain.feature :as feature] @@ -118,6 +120,58 @@ (-> (mix/mix! built (clip/opens-on built) (:audio entry) (:store entry)) (.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 ::save! (fn [{:keys [id cid label clip]}] @@ -424,6 +478,9 @@ (pb/show id) (assoc :project {:id nil :cid (:cid entry) :name nil :seq nil :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}))) (rf/reg-event-fx @@ -499,7 +556,9 @@ :cid (:cid (store/entry clip-id)) :busy? false :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 ::failed diff --git a/frontend/src/arthur/ui/drag.cljs b/frontend/src/arthur/ui/drag.cljs index 955587e..130f93c 100644 --- a/frontend/src/arthur/ui/drag.cljs +++ b/frontend/src/arthur/ui/drag.cljs @@ -12,6 +12,7 @@ (:require [arthur.domain.clip :as clip] [arthur.domain.palette :as pal] [arthur.events.footage :as footage] + [arthur.events.project :as project] [arthur.events.ui :as ui] [arthur.footage.store :as store] [re-frame.core :as rf])) @@ -100,5 +101,6 @@ ;; Video is asked about before anything happens: which frames, and what ;; the symbol they become is called. :footage (rf/dispatch [::footage/ask-convert c frame point]) + :import (rf/dispatch [::project/import c frame point]) nil)) (done!))