claud erefactor checkpoint
This commit is contained in:
parent
06dd2b597c
commit
7ac8f27b57
3 changed files with 155 additions and 45 deletions
|
|
@ -34,3 +34,9 @@ decided: a clip/subclip-ref mark never crosses a clip boundary, so the "range wh
|
||||||
this brings us to playing. since right now there is only one source video file, we need to be able to seek to arbitrary frames. each context (mark-group) keeps its own local playhead, used when it's the top of the timeline stack. when we hit play, in the example above of annotation X, we find the clip under the playhead, compute the source frame, seek there, and start playing. one correction though: the local playhead has to be the master clock, not the video. you can't derive local position from currentTime -- once an annotation repeats or reorders clips, one source frame maps to several local frames, it's not invertible. so the local playhead advances on its own (wall-clock x fps while playing), and every frame we compute expected = group->media(local) and seek the video there only if round(currentTime*fps) != expected. within a clip, expected tracks the video's natural playback so no seek fires; at a mark boundary it jumps once and we seek. and right -- no recursion at play time: we resolve the current context once into flat ordered spans, and group->media is just the flat lookup the renderer already does.
|
this brings us to playing. since right now there is only one source video file, we need to be able to seek to arbitrary frames. each context (mark-group) keeps its own local playhead, used when it's the top of the timeline stack. when we hit play, in the example above of annotation X, we find the clip under the playhead, compute the source frame, seek there, and start playing. one correction though: the local playhead has to be the master clock, not the video. you can't derive local position from currentTime -- once an annotation repeats or reorders clips, one source frame maps to several local frames, it's not invertible. so the local playhead advances on its own (wall-clock x fps while playing), and every frame we compute expected = group->media(local) and seek the video there only if round(currentTime*fps) != expected. within a clip, expected tracks the video's natural playback so no seek fires; at a mark boundary it jumps once and we seek. and right -- no recursion at play time: we resolve the current context once into flat ordered spans, and group->media is just the flat lookup the renderer already does.
|
||||||
# how do we determine which tracks are included when we zoom into each annotation? for now it should just be if a clip is within the ranges of the mark-group, its track is included in the annotation.
|
# how do we determine which tracks are included when we zoom into each annotation? for now it should just be if a clip is within the ranges of the mark-group, its track is included in the annotation.
|
||||||
# automatically scroll to bottom-most track in mark group range when we hit the start mark? but what if it's massively spread out. maybe not then. scrolling should be an option turned on. thats ok. make it explicit.
|
# automatically scroll to bottom-most track in mark group range when we hit the start mark? but what if it's massively spread out. maybe not then. scrolling should be an option turned on. thats ok. make it explicit.
|
||||||
|
|
||||||
|
|
||||||
|
* update!
|
||||||
|
- ok so the idea is this. you hit the new annotation button. it does not auto-select a mark for you. you can either click the clip, click the frame button, or drag a range. after you select a range, you are automatically in drawing mode. your drawings are connected to the mark, not the annotation. a mark should only ever appear in one annotation. instead of creating a new annotation mark group by default, this mode also allows you to either create new or associate with an existing annotation. associate with existing gives you our dropdown with only other annotations available. when you pick one, you effectively go into "edit" mode on that annotation with the new marks suddenly added. so this is basically our "transclusion": we can have annotations with marks embedded in other timelines. this is great for if we have subdivided our analysis into "chapters" but want to annotate shared concepts across them while keeping the main annotation pane clear. it's organized. so the big thing is that you don't create the annotation, you create the mark(s) first, then either create or assoc the annotation.
|
||||||
|
- another crucial thing: if we click and drag and it spans multiple clips, the range we have in our create/add annotation UI in the annotation pane should only show the start and end points relative to the clips at start and end. so the way this will work is we will create a mark group that's not an annotation as our "proxy" marks so that they don't appear in the ui that spans the full range and contains the full sequential clips, and then the mark group on the annotation that contains that mark group will just use that mark group as start and end as if it had been clicked. so, for example: there's clips A, B, C and D contiguous. user drags region from clip A to clip D. in the UI, we should see that our mark starts at clip A frame 0 and ends at clip D last frame, so we need to "pass through" the synthetic unnamed non-annotation mark group to the underlying clips, the synthetic unnamed non-annotation mark group is a proxy.. so that proxy mark group has marks that go from clip A start-clip A end, clip B start - clip B end, clip C start to clip C end, and clip D start to clip D end. makes sense? so what do we do if the user wants to drag adjust endpoint in UI? let's say there's another clip before clip A called clip 0. we move start point BACK to clip 0 frame 50. well, our main mark group just shows the range as we would expect: clip 0 frame 50 TO clip D last frame. but the proxy mark group? it has a new mark range with new mark id at the beginning, but the other mark ids are stable. and the same is true of rolling the end point forward: new mark id, new range, rest are stsable. what if we roll the endpoints inward? same principle but we kill off mark ranges instead of adding new ones. should be clean. so this means we needs we need to change how draft/edit marks look/act in the lane. when in draft/edit mode, clicking on the mark brings up drawing mode for that mark (there can still be a button next to the mark in the edit pane). you can drag the whole mark left to right. and you can also grab handles on the edges of the annotation left to right. and since we consider whatever last created or last touched mark to be the "active" one for associating drawings to, we need to have that visually represented in the timeline, and in the pane where the draft marks or edit annotation is. and these need to share the same look for the mark range,s its only the stuff above it that iwll change. make sense?
|
||||||
|
- note: when we talk about rolling the whole clip around, we know that the mark ids are going to change if we highlight one clip, unhighlight, then return back. this means that if we annotated a range defined w/r/t that annotation, the underlying gids are broken forever, even if they're rolled back. so that they exist still, right? since the underlying clips will never change, i wonder if we could just give each clip a stable identifier and define our root-most ranges in terms of those stable identifiers? or is that worse? idk
|
||||||
|
|
|
||||||
|
|
@ -390,9 +390,18 @@
|
||||||
:else t)
|
:else t)
|
||||||
note {:type :script-note :name nm :color "#c2864e"
|
note {:type :script-note :name nm :color "#c2864e"
|
||||||
:regions [(merge {:id rid :kind :text :content ""} region)]}
|
:regions [(merge {:id rid :kind :text :content ""} region)]}
|
||||||
|
target (get-in db [:view :note-target])
|
||||||
db (-> db (assoc-in [:scene :groups gid] note)
|
db (-> db (assoc-in [:scene :groups gid] note)
|
||||||
(assoc-in [:view :active-note] gid)
|
(assoc-in [:view :active-note] gid)
|
||||||
(assoc :save-error nil))]
|
(assoc :save-error nil))
|
||||||
|
;; created for a mark → bind it and hop back to the annotation
|
||||||
|
db (if target
|
||||||
|
(-> db (update-in [:scene :groups (:gid target) :marks]
|
||||||
|
(fn [ms] (mapv (fn [m] (if (= (:mark-id target) (:id m))
|
||||||
|
(update m :notes add-in gid) m)) ms)))
|
||||||
|
(assoc-in [:view :note-target] nil)
|
||||||
|
(assoc-in [:view :pane] :annotations))
|
||||||
|
db)]
|
||||||
(merge {:db db} (persist-note-fx db gid)))))
|
(merge {:db db} (persist-note-fx db gid)))))
|
||||||
|
|
||||||
;; commentary edits update the note locally (on-change); persist on blur so we
|
;; commentary edits update the note locally (on-change); persist on blur so we
|
||||||
|
|
@ -433,6 +442,21 @@
|
||||||
(fn [db [_ gid mark-id note-gid]]
|
(fn [db [_ gid mark-id note-gid]]
|
||||||
(update-mark db gid mark-id #(update % :notes rm-in note-gid))))
|
(update-mark db gid mark-id #(update % :notes rm-in note-gid))))
|
||||||
|
|
||||||
|
;; click a mark row to make it the active one (drawings/edits target it)
|
||||||
|
(rf/reg-event-db ::set-active-mark
|
||||||
|
(fn [db [_ mark-id]] (assoc-in db [:view :active-mark] mark-id)))
|
||||||
|
|
||||||
|
;; "add a new script note for this mark": remember the target mark + hop to the
|
||||||
|
;; Script pane. When a note is created there (highlight-into-new-note) it binds to
|
||||||
|
;; the target and returns to the annotation; ::cancel-note-target backs out.
|
||||||
|
(rf/reg-event-db ::new-note-for-mark
|
||||||
|
(fn [db [_ gid mark-id]]
|
||||||
|
(-> db (assoc-in [:view :note-target] {:gid gid :mark-id mark-id})
|
||||||
|
(assoc-in [:view :pane] :script))))
|
||||||
|
(rf/reg-event-db ::cancel-note-target
|
||||||
|
(fn [db _] (-> db (assoc-in [:view :note-target] nil)
|
||||||
|
(assoc-in [:view :pane] :annotations))))
|
||||||
|
|
||||||
;; --- drawings -------------------------------------------------------------
|
;; --- drawings -------------------------------------------------------------
|
||||||
;; A drawing is a first-class entity (:type :drawing) — a bag of normalized
|
;; A drawing is a first-class entity (:type :drawing) — a bag of normalized
|
||||||
;; strokes + a seed for its wiggle boil — bound to a mark via mark :drawings
|
;; strokes + a seed for its wiggle boil — bound to a mark via mark :drawings
|
||||||
|
|
@ -704,6 +728,18 @@
|
||||||
(into (subvec marks 0 i) (subvec marks (inc i))))
|
(into (subvec marks 0 i) (subvec marks (inc i))))
|
||||||
(assoc-in [:view :pt] {:seg (:ref keep) :f (:at keep) :mark mark :i i})))))
|
(assoc-in [:view :pt] {:seg (:ref keep) :f (:at keep) :mark mark :i i})))))
|
||||||
|
|
||||||
|
;; clear ONE endpoint of a proxy mark to re-pick it: remember the OPPOSITE
|
||||||
|
;; endpoint's ctx-local position (kept fixed) so the next clip-click only rerolls
|
||||||
|
;; the cleared side — the kept end never turns into the start (the old swap bug).
|
||||||
|
(rf/reg-event-db
|
||||||
|
::unset-proxy-endpoint
|
||||||
|
(fn [db [_ gid mark-id pid which]]
|
||||||
|
(let [scene (:scene db)
|
||||||
|
ctx (:parent (get-in scene [:groups gid]))
|
||||||
|
segs (scene/content-segments scene ctx)
|
||||||
|
[lo hi] (scene/mark-extent scene ctx gid mark-id segs)]
|
||||||
|
(assoc-in db [:view :pt] {:proxy pid :which which :keep (if (= which :start) hi lo)}))))
|
||||||
|
|
||||||
;; complete a selection on the active draft: wrap the run [lo hi) in a proxy (a
|
;; complete a selection on the active draft: wrap the run [lo hi) in a proxy (a
|
||||||
;; synthetic clip), give the annotation one mark referencing it, make it active,
|
;; synthetic clip), give the annotation one mark referencing it, make it active,
|
||||||
;; seek to its start, and drop into drawing mode ("select a range → you're drawing").
|
;; seek to its start, and drop into drawing mode ("select a range → you're drawing").
|
||||||
|
|
@ -737,7 +773,21 @@
|
||||||
[gid g] (some (fn [[gid g]] (when (:draft g) [gid g])) (:groups scene))
|
[gid g] (some (fn [[gid g]] (when (:draft g) [gid g])) (:groups scene))
|
||||||
segs (scene/content-segments scene (:parent g))
|
segs (scene/content-segments scene (:parent g))
|
||||||
pt (get-in db [:view :pt])]
|
pt (get-in db [:view :pt])]
|
||||||
(if (map? pt)
|
(cond
|
||||||
|
;; re-picking one endpoint of a proxy: roll only that side, keep the other
|
||||||
|
(:proxy pt)
|
||||||
|
(let [proxy (get-in scene [:groups (:proxy pt)])
|
||||||
|
new-local (if (= (:which pt) :start)
|
||||||
|
(scene/seg-local segs seg-id (or frame 0))
|
||||||
|
(scene/seg-local segs seg-id (or frame (scene/seg-length segs seg-id))))
|
||||||
|
keep (:keep pt)
|
||||||
|
lo (js/Math.round (min new-local keep))
|
||||||
|
hi (js/Math.round (max new-local keep))]
|
||||||
|
{:db (-> db (assoc-in [:scene :groups (:proxy pt)]
|
||||||
|
(scene/roll-proxy scene (:parent g) proxy lo (max (inc lo) hi)))
|
||||||
|
(assoc-in [:view :pt] :new))})
|
||||||
|
|
||||||
|
(map? pt)
|
||||||
(let [a (scene/seg-local segs (:seg pt) (:f pt))
|
(let [a (scene/seg-local segs (:seg pt) (:f pt))
|
||||||
b (scene/seg-local segs seg-id (or frame (scene/seg-length segs seg-id)))
|
b (scene/seg-local segs seg-id (or frame (scene/seg-length segs seg-id)))
|
||||||
lo (min a b) hi (max a b)
|
lo (min a b) hi (max a b)
|
||||||
|
|
@ -758,6 +808,8 @@
|
||||||
(assoc-in [:view :pt] :new))})
|
(assoc-in [:view :pt] :new))})
|
||||||
;; new selection (two-click): same completion as a timeline drag.
|
;; new selection (two-click): same completion as a timeline drag.
|
||||||
(select-range-fx db gid g lo hi)))
|
(select-range-fx db gid g lo hi)))
|
||||||
|
|
||||||
|
:else
|
||||||
{:db (assoc-in db [:view :pt] {:seg seg-id :f (or frame 0)})}))))
|
{:db (assoc-in db [:view :pt] {:seg seg-id :f (or frame 0)})}))))
|
||||||
|
|
||||||
;; remove mark `i` from `gid`; if it referenced a proxy, drop the now-orphaned
|
;; remove mark `i` from `gid`; if it referenced a proxy, drop the now-orphaned
|
||||||
|
|
@ -796,6 +848,16 @@
|
||||||
(assoc-in db [:scene :groups pid] (scene/roll-proxy scene ctx proxy la* lb*))
|
(assoc-in db [:scene :groups pid] (scene/roll-proxy scene ctx proxy la* lb*))
|
||||||
db))))
|
db))))
|
||||||
|
|
||||||
|
;; numeric endpoint edit from the pane: set a proxy's boundary internal mark's
|
||||||
|
;; :at directly (the collapsed row's start = first mark's start, end = last mark's
|
||||||
|
;; end). In-place within one clip — no boundary crossing (that's the lane handles).
|
||||||
|
(rf/reg-event-db
|
||||||
|
::set-proxy-frame
|
||||||
|
(fn [db [_ pid which frame]]
|
||||||
|
(let [marks (get-in db [:scene :groups pid :marks])
|
||||||
|
idx (if (= which :start) 0 (dec (count marks)))]
|
||||||
|
(assoc-in db [:scene :groups pid :marks idx which :at] frame))))
|
||||||
|
|
||||||
;; transclusion: instead of creating a new annotation, append the draft's marks
|
;; transclusion: instead of creating a new annotation, append the draft's marks
|
||||||
;; to an EXISTING one and open it in edit mode ("the new marks suddenly added").
|
;; to an EXISTING one and open it in edit mode ("the new marks suddenly added").
|
||||||
;; We persist the attachment now (the marks + their proxies) and discard the draft
|
;; We persist the attachment now (the marks + their proxies) and discard the draft
|
||||||
|
|
|
||||||
|
|
@ -493,9 +493,10 @@
|
||||||
|
|
||||||
(defn- region-select!
|
(defn- region-select!
|
||||||
"On the timeline while authoring: DRAG to select a region [lo hi) → one
|
"On the timeline while authoring: DRAG to select a region [lo hi) → one
|
||||||
selection (a proxy); a plain CLICK (no drag) just moves the playhead, so you
|
selection (a proxy). A plain CLICK (no drag) calls `on-click` — clicking a clip
|
||||||
can still scrub in marking mode. `content` is the coord ref."
|
picks it (draft-click-seg), clicking empty timeline just moves the playhead —
|
||||||
[content fps zoom ev]
|
so both still work in marking mode. `content` is the coord ref."
|
||||||
|
[content fps zoom on-click ev]
|
||||||
(.preventDefault ev)
|
(.preventDefault ev)
|
||||||
(let [rect (.getBoundingClientRect content)
|
(let [rect (.getBoundingClientRect content)
|
||||||
sx (.-clientX ev)
|
sx (.-clientX ev)
|
||||||
|
|
@ -513,7 +514,7 @@
|
||||||
(reset! region-sel nil)
|
(reset! region-sel nil)
|
||||||
(if (and was (> (- hi lo) 0.5))
|
(if (and was (> (- hi lo) 0.5))
|
||||||
(rf/dispatch [::events/draft-select-range (js/Math.round lo) (js/Math.round hi)])
|
(rf/dispatch [::events/draft-select-range (js/Math.round lo) (js/Math.round hi)])
|
||||||
(goto! a))))] ; click → move the playhead
|
(on-click a))))] ; click (no drag)
|
||||||
(.addEventListener js/document "mousemove" mv)
|
(.addEventListener js/document "mousemove" mv)
|
||||||
(.addEventListener js/document "mouseup" up)))
|
(.addEventListener js/document "mouseup" up)))
|
||||||
|
|
||||||
|
|
@ -755,7 +756,8 @@
|
||||||
[:div.content.track-content {:ref (fn [n] (reset! content n))
|
[:div.content.track-content {:ref (fn [n] (reset! content n))
|
||||||
;; while authoring, drag empty space to select a region
|
;; while authoring, drag empty space to select a region
|
||||||
:on-mouse-down (if authoring?
|
:on-mouse-down (if authoring?
|
||||||
#(region-select! @content fps zoom %)
|
;; empty timeline: drag = region, click = move playhead
|
||||||
|
#(region-select! @content fps zoom goto! %)
|
||||||
#(scrub! @content fps zoom %))
|
#(scrub! @content fps zoom %))
|
||||||
:style {:width width :height tracks-h}}
|
:style {:width width :height tracks-h}}
|
||||||
(for [[i t] (map-indexed vector tracks)]
|
(for [[i t] (map-indexed vector tracks)]
|
||||||
|
|
@ -772,7 +774,10 @@
|
||||||
(if linking
|
(if linking
|
||||||
(commit-link! (scene/seg-point scene seg 0)
|
(commit-link! (scene/seg-point scene seg 0)
|
||||||
(clip-label scene segs (:mark seg)))
|
(clip-label scene segs (:mark seg)))
|
||||||
(region-select! @content fps zoom e))))
|
;; drag = region; click = pick this clip (two-click flow)
|
||||||
|
(region-select! @content fps zoom
|
||||||
|
(fn [_] (rf/dispatch [::events/draft-click-seg (:mark seg)]))
|
||||||
|
e))))
|
||||||
:style {:left (px c fps zoom) :width (max 1 (px (- d c) fps zoom))
|
:style {:left (px c fps zoom) :width (max 1 (px (- d c) fps zoom))
|
||||||
:top (* (get track-y track 0) row-h) :height (- row-h 2)
|
:top (* (get track-y track 0) row-h) :height (- row-h 2)
|
||||||
:line-height (str (- row-h 2) "px") :position "absolute"}}
|
:line-height (str (- row-h 2) "px") :position "absolute"}}
|
||||||
|
|
@ -937,7 +942,9 @@
|
||||||
idx (r/atom 0)
|
idx (r/atom 0)
|
||||||
off (r/atom (or (:offset value) 0))
|
off (r/atom (or (:offset value) 0))
|
||||||
picked (r/atom value)
|
picked (r/atom value)
|
||||||
open? (r/atom true)]
|
;; start CLOSED; only opens on focus (an auto-focused picker opens
|
||||||
|
;; itself via :on-focus). Was defaulting open even when unfocused.
|
||||||
|
open? (r/atom (boolean auto-focus?))]
|
||||||
(let [cands (point-candidates scene ctx @text timelines?)
|
(let [cands (point-candidates scene ctx @text timelines?)
|
||||||
i (min @idx (max 0 (dec (count cands))))
|
i (min @idx (max 0 (dec (count cands))))
|
||||||
cur (or (:candidate @picked) (get cands i))
|
cur (or (:candidate @picked) (get cands i))
|
||||||
|
|
@ -966,7 +973,12 @@
|
||||||
(reset! open? true)
|
(reset! open? true)
|
||||||
(reset! off 0)
|
(reset! off 0)
|
||||||
(preview! (get cands j) 0))]
|
(preview! (get cands j) 0))]
|
||||||
[:div.pt-input {:class class}
|
[:div.pt-input {:class class
|
||||||
|
;; close the dropdown once focus leaves the picker entirely
|
||||||
|
;; (but not when it just moves between the text/frame inputs)
|
||||||
|
:on-blur (fn [e]
|
||||||
|
(when-not (.contains (.-currentTarget e) (.-relatedTarget e))
|
||||||
|
(reset! open? false)))}
|
||||||
[:input.pt-text
|
[:input.pt-text
|
||||||
{:auto-focus auto-focus?
|
{:auto-focus auto-focus?
|
||||||
:placeholder (or placeholder "clip / annotation / frame...")
|
:placeholder (or placeholder "clip / annotation / frame...")
|
||||||
|
|
@ -1274,6 +1286,23 @@
|
||||||
[:button.pt-chip-x {:type "button" :title "Re-pick this end"
|
[:button.pt-chip-x {:type "button" :title "Re-pick this end"
|
||||||
:on-click #(rf/dispatch [::events/unset-endpoint (:gid d) i k])} "✕"]]))
|
:on-click #(rf/dispatch [::events/unset-endpoint (:gid d) i k])} "✕"]]))
|
||||||
|
|
||||||
|
(defn- proxy-frame-chip
|
||||||
|
"Editable endpoint for a proxy (synthetic-clip) mark: clip name + frame input
|
||||||
|
that edits the proxy's OWN boundary internal mark's :at in place (`which` =
|
||||||
|
:start on its first mark, :end on its last). Crossing a clip boundary is the
|
||||||
|
lane handles' job; this is the whole-frame numeric nudge within a clip. The ✕
|
||||||
|
clears just this end to re-pick it (the other end stays put)."
|
||||||
|
[scene segs gid mark-id pid which {:keys [seg f]}]
|
||||||
|
(let [len (scene/seg-length segs seg)]
|
||||||
|
[:div.pt-chip
|
||||||
|
[:span.pt-chip-name (clip-label scene segs seg)]
|
||||||
|
[:input.pt-frame {:type "number" :min 0 :max len :value f
|
||||||
|
:on-change #(rf/dispatch [::events/set-proxy-frame pid which
|
||||||
|
(to-frame (.. % -target -value) len)])}]
|
||||||
|
[:span.pt-dur (str "/" len)]
|
||||||
|
[:button.pt-chip-x {:type "button" :title "Re-pick this end"
|
||||||
|
:on-click #(rf/dispatch [::events/unset-proxy-endpoint gid mark-id pid which])} "✕"]]))
|
||||||
|
|
||||||
;; --- script-note bindings (annotation form) ------------------------------
|
;; --- script-note bindings (annotation form) ------------------------------
|
||||||
;; A note is bound by dragging its chip from the source list onto a drop target
|
;; A note is bound by dragging its chip from the source list onto a drop target
|
||||||
;; (a mark row, or the annotation-level target). The pointer lives on the referrer
|
;; (a mark row, or the annotation-level target). The pointer lives on the referrer
|
||||||
|
|
@ -1347,10 +1376,11 @@
|
||||||
gid (:gid d)
|
gid (:gid d)
|
||||||
new? (= :new (:draft d))
|
new? (= :new (:draft d))
|
||||||
root? (nil? (:parent d))
|
root? (nil? (:parent d))
|
||||||
;; a fresh draft starts in the "choosing" stage: just marks + a title
|
;; ONE shared form for new + edit. In draft/new we HIDE the lower fields
|
||||||
;; autocomplete (pick an existing annotation → associate; type a new
|
;; (content, notes, save) until a title is picked — pick a new title to
|
||||||
;; title → create). Everything else appears once you've committed.
|
;; "create new", or an existing annotation to "edit existing". The title
|
||||||
choosing? (and new? (not root?) (= :choosing @(rf/subscribe [::subs/draft-stage]))) ; the root timeline: content only
|
;; sits at the top (above the marks), same place the name does in edit.
|
||||||
|
choosing? (and new? (not root?) (= :choosing @(rf/subscribe [::subs/draft-stage])))
|
||||||
put (fn [g] (rf/dispatch [::events/put-group gid (dissoc g :gid)]))
|
put (fn [g] (rf/dispatch [::events/put-group gid (dissoc g :gid)]))
|
||||||
notes @(rf/subscribe [::subs/notes])
|
notes @(rf/subscribe [::subs/notes])
|
||||||
nmap (into {} (map (juxt :id identity)) notes)
|
nmap (into {} (map (juxt :id identity)) notes)
|
||||||
|
|
@ -1403,6 +1433,24 @@
|
||||||
[:div.form-hint "Click a clip or the frame-readout to link it, or pick above."])])
|
[:div.form-hint "Click a clip or the frame-readout to link it, or pick above."])])
|
||||||
(when-not root?
|
(when-not root?
|
||||||
[:<>
|
[:<>
|
||||||
|
;; the title IS the create/associate control: type a new title to make a
|
||||||
|
;; fresh annotation with these marks, or pick an existing annotation to
|
||||||
|
;; add them to it (transclusion). Above the marks + autofocused so you can
|
||||||
|
;; name it first thing.
|
||||||
|
(when choosing?
|
||||||
|
(let [targets @(rf/subscribe [::subs/associate-targets])
|
||||||
|
by-name (into {} (map (juxt :name :gid)) targets)
|
||||||
|
ctx-of (into {} (map (juxt :name :in)) targets)]
|
||||||
|
[:div.form-associate
|
||||||
|
[:div.form-marks-label "Title"]
|
||||||
|
[autocomplete {:items (mapv :name targets) :allow-new? true :auto-focus? true
|
||||||
|
:placeholder "new title, or pick an annotation to add to…"
|
||||||
|
:item-suffix (fn [n] (when-let [c (ctx-of n)]
|
||||||
|
[:span.cand-group (str " · " c)]))
|
||||||
|
:on-choose (fn [choice]
|
||||||
|
(if-let [g (by-name choice)]
|
||||||
|
(rf/dispatch [::events/associate-marks gid g])
|
||||||
|
(rf/dispatch [::events/create-named gid choice])))}]]))
|
||||||
[:div.form-marks-label "Marks"]
|
[:div.form-marks-label "Marks"]
|
||||||
(doall
|
(doall
|
||||||
(for [[i row] (map-indexed vector rows)
|
(for [[i row] (map-indexed vector rows)
|
||||||
|
|
@ -1424,16 +1472,30 @@
|
||||||
(reset! mark-drag {:src i}))} "⠿"]
|
(reset! mark-drag {:src i}))} "⠿"]
|
||||||
(when (contains? broken mark-id)
|
(when (contains? broken mark-id)
|
||||||
[:span.ann-warn {:title "This mark's clip/reference no longer resolves"} "△ "])
|
[:span.ann-warn {:title "This mark's clip/reference no longer resolves"} "△ "])
|
||||||
;; a proxy collapses its cross-clip run to one read-only span
|
;; a proxy collapses its cross-clip run to first-clip start →
|
||||||
;; (first-clip start → last-clip end); endpoint editing is via the
|
;; last-clip end; each endpoint edits the proxy's own boundary
|
||||||
;; lane handles (a later chunk). A plain clip mark stays editable.
|
;; mark's frame (crossing a clip boundary is the lane handles).
|
||||||
(if (:proxy row)
|
;; A plain clip mark edits its own :at directly.
|
||||||
[:<>
|
(if-let [pid (:proxy row)]
|
||||||
[:span.pt-chip.proxy [:span.pt-chip-name
|
;; while re-picking one end (after its ✕) that end becomes a live
|
||||||
(str (clip-label scene segs (:seg (:s row))) " @" (:f (:s row)) "f")]]
|
;; clip picker in the row; the other end stays a normal chip.
|
||||||
[:span.mark-arrow "→"]
|
(let [pick (when (and (map? pt) (= (:proxy pt) pid)) (:which pt))]
|
||||||
[:span.pt-chip.proxy [:span.pt-chip-name
|
[:<>
|
||||||
(str (clip-label scene segs (:seg (:e row))) " @" (:f (:e row)) "f")]]]
|
(if (= pick :start)
|
||||||
|
[point-picker {:scene scene :ctx ctx :class "active" :auto-focus? true
|
||||||
|
:placeholder "click a clip for start…"
|
||||||
|
:on-cancel #(rf/dispatch [::events/draft-focus :new])
|
||||||
|
:on-pick #(when-let [p (local->draft-point segs (:local %))]
|
||||||
|
(rf/dispatch [::events/draft-click-seg (:seg p) (:f p)]))}]
|
||||||
|
[proxy-frame-chip scene segs gid mark-id pid :start (:s row)])
|
||||||
|
[:span.mark-arrow "→"]
|
||||||
|
(if (= pick :end)
|
||||||
|
[point-picker {:scene scene :ctx ctx :class "active" :auto-focus? true
|
||||||
|
:placeholder "click a clip for end…"
|
||||||
|
:on-cancel #(rf/dispatch [::events/draft-focus :new])
|
||||||
|
:on-pick #(when-let [p (local->draft-end-point segs (:local %))]
|
||||||
|
(rf/dispatch [::events/draft-click-seg (:seg p) (:f p)]))}]
|
||||||
|
[proxy-frame-chip scene segs gid mark-id pid :end (:e row)])])
|
||||||
[:<>
|
[:<>
|
||||||
[frame-chip scene segs put d i :start (:s row)]
|
[frame-chip scene segs put d i :start (:s row)]
|
||||||
[:span.mark-arrow "→"]
|
[:span.mark-arrow "→"]
|
||||||
|
|
@ -1452,7 +1514,7 @@
|
||||||
[note-drop nil (:notes mark) nmap live
|
[note-drop nil (:notes mark) nmap live
|
||||||
#(rf/dispatch [::events/bind-note-mark gid mark-id %])
|
#(rf/dispatch [::events/bind-note-mark gid mark-id %])
|
||||||
#(rf/dispatch [::events/unbind-note-mark gid mark-id %])])]))
|
#(rf/dispatch [::events/unbind-note-mark gid mark-id %])])]))
|
||||||
(when (map? pt)
|
(when (and (map? pt) (not (:proxy pt)))
|
||||||
[:div.mark-row
|
[:div.mark-row
|
||||||
[:div.pt-chip [:span.pt-chip-name (str (clip-label scene segs (:seg pt))
|
[:div.pt-chip [:span.pt-chip-name (str (clip-label scene segs (:seg pt))
|
||||||
" @" (js/Math.round (:f pt)) "f")]]
|
" @" (js/Math.round (:f pt)) "f")]]
|
||||||
|
|
@ -1472,26 +1534,6 @@
|
||||||
:on-pick #(when-let [p (local->draft-point segs (:local %))]
|
:on-pick #(when-let [p (local->draft-point segs (:local %))]
|
||||||
(rf/dispatch [::events/draft-click-seg (:seg p) (:f p)]))}]])
|
(rf/dispatch [::events/draft-click-seg (:seg p) (:f p)]))}]])
|
||||||
[:div.form-hint "Drag across the timeline to select a range (click to move the playhead)."]
|
[:div.form-hint "Drag across the timeline to select a range (click to move the playhead)."]
|
||||||
;; the title IS the create/associate control: type a new title to make a
|
|
||||||
;; fresh annotation with these marks, or pick an existing annotation to
|
|
||||||
;; add them to it (dropping straight into its edit form). Transclusion.
|
|
||||||
(when choosing?
|
|
||||||
(let [targets @(rf/subscribe [::subs/associate-targets])
|
|
||||||
;; match on the plain NAME (round-trips cleanly); the home
|
|
||||||
;; context is a dropdown-only hint via :item-suffix, so it never
|
|
||||||
;; leaks into a newly-created annotation's name.
|
|
||||||
by-name (into {} (map (juxt :name :gid)) targets)
|
|
||||||
ctx-of (into {} (map (juxt :name :in)) targets)]
|
|
||||||
[:div.form-associate
|
|
||||||
[:div.form-marks-label "Title"]
|
|
||||||
[autocomplete {:items (mapv :name targets) :allow-new? true
|
|
||||||
:placeholder "new title, or pick an annotation to add to…"
|
|
||||||
:item-suffix (fn [n] (when-let [c (ctx-of n)]
|
|
||||||
[:span.cand-group (str " · " c)]))
|
|
||||||
:on-choose (fn [choice]
|
|
||||||
(if-let [g (by-name choice)]
|
|
||||||
(rf/dispatch [::events/associate-marks gid g])
|
|
||||||
(rf/dispatch [::events/create-named gid choice])))}]]))
|
|
||||||
(when-not choosing?
|
(when-not choosing?
|
||||||
[:<>
|
[:<>
|
||||||
[:div.form-marks-label "Script notes"]
|
[:div.form-marks-label "Script notes"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue