Rename the project from the top bar; export from a dialog
The title is a button that becomes a field, saved on Enter or blur. Export's target and scale move out of the bar into a dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0f9ce826ef
commit
c139e4d747
3 changed files with 85 additions and 47 deletions
|
|
@ -42,6 +42,7 @@
|
||||||
[arthur.flow.take :as take]
|
[arthur.flow.take :as take]
|
||||||
[arthur.fx.http :as http]
|
[arthur.fx.http :as http]
|
||||||
[arthur.synth :as synth]
|
[arthur.synth :as synth]
|
||||||
|
[clojure.string :as str]
|
||||||
[re-frame.core :as rf]))
|
[re-frame.core :as rf]))
|
||||||
|
|
||||||
(defn- analysis-payload [analysis]
|
(defn- analysis-payload [analysis]
|
||||||
|
|
@ -577,9 +578,16 @@
|
||||||
::save! {:id (when-not (false? can-edit?) pid)
|
::save! {:id (when-not (false? can-edit?) pid)
|
||||||
:base (get-in db [:project :seq])
|
:base (get-in db [:project :seq])
|
||||||
:cid cid
|
:cid cid
|
||||||
:label (or (:label clip) (name id))
|
:label (or (not-empty (get-in db [:project :name]))
|
||||||
|
(:label clip) (name id))
|
||||||
:clip clip}}))))
|
:clip clip}}))))
|
||||||
|
|
||||||
|
(rf/reg-event-fx
|
||||||
|
::rename
|
||||||
|
(fn [{:keys [db]} [_ value]]
|
||||||
|
{:db (assoc-in db [:project :name] (not-empty (str/trim value)))
|
||||||
|
:dispatch [::save {:auto? true :force? true}]}))
|
||||||
|
|
||||||
(rf/reg-event-fx
|
(rf/reg-event-fx
|
||||||
::project-setting
|
::project-setting
|
||||||
(fn [{:keys [db]} [_ key value]]
|
(fn [{:keys [db]} [_ key value]]
|
||||||
|
|
|
||||||
|
|
@ -13,55 +13,68 @@
|
||||||
[arthur.ui.share :as share]
|
[arthur.ui.share :as share]
|
||||||
[arthur.ui.snapshots :as snapshots]
|
[arthur.ui.snapshots :as snapshots]
|
||||||
[arthur.ui.undo :as undo]
|
[arthur.ui.undo :as undo]
|
||||||
[re-frame.core :as rf]))
|
[re-frame.core :as rf]
|
||||||
|
[reagent.core :as r]))
|
||||||
|
|
||||||
(defn- exporter []
|
(defn- exporter [open?]
|
||||||
(let [{:keys [target zoom busy? done total]} @(rf/subscribe [::export/state])
|
(let [{:keys [target zoom busy? done total]} @(rf/subscribe [::export/state])
|
||||||
targets @(rf/subscribe [::export/targets])]
|
targets @(rf/subscribe [::export/targets])]
|
||||||
[:<>
|
[:<>
|
||||||
;; ONE SELECT over everything exportable: each symbol, and each instance
|
[:button {:disabled busy? :on-click #(reset! open? true)} "export…"]
|
||||||
;; in the open one. They are one list because they
|
(when @open?
|
||||||
;; are one kind of request — render this, alone.
|
[:<> [:div.export-scrim {:on-click #(when-not busy? (reset! open? false))}]
|
||||||
;;
|
[:section.export-dialog {:role "dialog" :aria-modal true :aria-label "Export"}
|
||||||
;; The option VALUE goes through `export/target-value`, never `name`: a
|
[:h2 "Export"]
|
||||||
;; symbol id is `:sym/face-8625` and an instance is a uuid, and writing
|
[:label.export-field "what to render"
|
||||||
;; either through `name` loses what identifies it.
|
[:select {:value (export/target-value target) :disabled busy?
|
||||||
[:select {:value (export/target-value target)
|
|
||||||
:disabled busy?
|
|
||||||
:title "what to render"
|
|
||||||
:on-change #(rf/dispatch [::export/set-target
|
:on-change #(rf/dispatch [::export/set-target
|
||||||
(export/target-id (.. % -target -value))])}
|
(export/target-id (.. % -target -value))])}
|
||||||
(doall
|
(doall (for [{:keys [label] :as t} targets]
|
||||||
(for [{:keys [label] :as target} targets]
|
^{:key (export/target-value t)}
|
||||||
^{:key (export/target-value target)}
|
[:option {:value (export/target-value t)}
|
||||||
[:option {:value (export/target-value target)}
|
(str (when (:isolate t) "· ") label)]))]]
|
||||||
(str (when (:isolate target) "· ") label)]))]
|
[:label.export-field "scale"
|
||||||
[:select {:value zoom :disabled busy?
|
[:select {:value zoom :disabled busy?
|
||||||
:title "integer zoom"
|
|
||||||
:on-change #(rf/dispatch [::export/set-zoom
|
:on-change #(rf/dispatch [::export/set-zoom
|
||||||
(js/parseInt (.. % -target -value) 10)])}
|
(js/parseInt (.. % -target -value) 10)])}
|
||||||
(doall (for [z export/zooms] ^{:key z} [:option {:value z} (str z "x")]))]
|
(doall (for [z export/zooms] ^{:key z} [:option {:value z} (str z "×")]))]]
|
||||||
[:button {:disabled busy? :on-click #(rf/dispatch [::export/start])
|
[:p.dim "PNG sequence and audio in a ZIP archive"]
|
||||||
:title "a lossless PNG sequence and the mixed audio, in one zip"}
|
[:div.row
|
||||||
(if busy? (str "rendering " done "/" total) "export")]]))
|
[:button {:disabled busy? :on-click #(rf/dispatch [::export/start])}
|
||||||
|
(if busy? (str "rendering " done "/" total) "start export")]
|
||||||
|
[:button {:disabled busy? :on-click #(reset! open? false)} "close"]]]])]))
|
||||||
|
|
||||||
(defn view []
|
(defn view []
|
||||||
(let [{project-name :name :keys [busy? seq] project-status :status}
|
(r/with-let [renaming? (r/atom false)
|
||||||
@(rf/subscribe [::playback/project])
|
draft (r/atom "")
|
||||||
|
export-open? (r/atom false)]
|
||||||
|
(let [{project-name :name :keys [busy? seq status]} @(rf/subscribe [::playback/project])
|
||||||
{footage-status :status} @(rf/subscribe [::playback/footage])
|
{footage-status :status} @(rf/subscribe [::playback/footage])
|
||||||
{export-status :status} @(rf/subscribe [::export/state])]
|
{export-status :status} @(rf/subscribe [::export/state])
|
||||||
|
title (or project-name "untitled")
|
||||||
|
commit! (fn []
|
||||||
|
(rf/dispatch [::project/rename @draft])
|
||||||
|
(reset! renaming? false))]
|
||||||
[:header.top
|
[:header.top
|
||||||
[:a.brand {:href "/" :title "your projects"
|
[:a.brand {:href "/" :title "your projects"
|
||||||
:on-click (fn [e] (.preventDefault e) (collab/navigate! "/"))} "arthur"]
|
:on-click (fn [e] (.preventDefault e) (collab/navigate! "/"))} "arthur"]
|
||||||
[:span.status
|
|
||||||
(str (or project-name "untitled") (when seq (str " r" seq))
|
|
||||||
;; One line, and the most recent thing to have happened wins it. A
|
|
||||||
;; strip with three status slots is three empty boxes most of the time.
|
|
||||||
(when-let [said (or export-status project-status footage-status)]
|
|
||||||
(str " · " said)))]
|
|
||||||
[exporter]
|
|
||||||
[:button {:disabled busy? :on-click #(rf/dispatch [::collab/create])} "new"]
|
[:button {:disabled busy? :on-click #(rf/dispatch [::collab/create])} "new"]
|
||||||
[openmenu/view]
|
[openmenu/view]
|
||||||
[undo/view]
|
[undo/view]
|
||||||
[snapshots/view]
|
[snapshots/view]
|
||||||
[share/view]]))
|
(if @renaming?
|
||||||
|
[:input.project-name {:auto-focus true :value @draft
|
||||||
|
:on-change #(reset! draft (.. % -target -value))
|
||||||
|
:on-blur (fn [_] (commit!))
|
||||||
|
:on-key-down (fn [e]
|
||||||
|
(case (.-key e)
|
||||||
|
"Enter" (do (.preventDefault e) (commit!))
|
||||||
|
"Escape" (reset! renaming? false)
|
||||||
|
nil))}]
|
||||||
|
[:button.project-title {:disabled busy? :title "click to rename project"
|
||||||
|
:on-click (fn [] (reset! draft title)
|
||||||
|
(reset! renaming? true))}
|
||||||
|
title (when seq (str " r" seq))])
|
||||||
|
[:span.status (or export-status status footage-status)]
|
||||||
|
[exporter export-open?]
|
||||||
|
[share/view]])))
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,19 @@ input[type="range"] { width: 100%; accent-color: var(--sel); }
|
||||||
color: var(--dim);
|
color: var(--dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-title { max-width: 22vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.project-name { width: 180px; }
|
||||||
|
.export-scrim { position: fixed; inset: 0; z-index: 50; background: rgba(0, 0, 0, .45); }
|
||||||
|
.export-dialog {
|
||||||
|
position: fixed; z-index: 51; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||||
|
width: min(360px, calc(100vw - 32px)); padding: 14px;
|
||||||
|
background: var(--pane); border: 1px solid var(--line); border-radius: 3px;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, .45);
|
||||||
|
}
|
||||||
|
.export-dialog h2 { margin: 0 0 12px; font: inherit; font-weight: 600; }
|
||||||
|
.export-field { display: grid; gap: 4px; margin: 8px 0; color: var(--dim); }
|
||||||
|
.export-field select { color: var(--fg); }
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
the open menu */
|
the open menu */
|
||||||
|
|
||||||
|
|
@ -520,6 +533,10 @@ button.share-button:hover, button.share-button.on { filter: brightness(1.1); }
|
||||||
letter-spacing: .07em;
|
letter-spacing: .07em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inspector-form { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: 7px; }
|
||||||
|
.inspector-field { display: grid; gap: 2px; color: var(--dim); }
|
||||||
|
.inspector-field input { width: 100%; color: var(--fg); }
|
||||||
|
|
||||||
/* Label / value, once, for every read-only fact in the pane. */
|
/* Label / value, once, for every read-only fact in the pane. */
|
||||||
.facts { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; }
|
.facts { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; }
|
||||||
.facts dt { color: var(--dim); }
|
.facts dt { color: var(--dim); }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue