diff --git a/backend/src/app/features/file_snapshots.clj b/backend/src/app/features/file_snapshots.clj index 192030cbf8..e013b90d00 100644 --- a/backend/src/app/features/file_snapshots.clj +++ b/backend/src/app/features/file_snapshots.clj @@ -112,8 +112,9 @@ THEN (c.deleted_at IS NULL OR c.deleted_at >= ?::timestamptz) END")) -(defn- get-snapshot - "Get snapshot with decoded data" +(defn get-snapshot-data + "Get a fully decoded snapshot for read-only preview or restoration. + Returns the snapshot map with decoded :data field." [cfg file-id snapshot-id] (let [now (ct/now)] (->> (db/get-with-sql cfg [sql:get-snapshot file-id snapshot-id now] @@ -326,7 +327,7 @@ (sto/resolve cfg {::db/reuse-conn true}) snapshot - (get-snapshot cfg file-id snapshot-id)] + (get-snapshot-data cfg file-id snapshot-id)] (when-not snapshot (ex/raise :type :not-found diff --git a/backend/src/app/rpc/commands/files_snapshot.clj b/backend/src/app/rpc/commands/files_snapshot.clj index 8325772361..7736b66cd9 100644 --- a/backend/src/app/rpc/commands/files_snapshot.clj +++ b/backend/src/app/rpc/commands/files_snapshot.clj @@ -8,6 +8,7 @@ (:require [app.binfile.common :as bfc] [app.common.exceptions :as ex] + [app.common.features :as-alias cfeat] [app.common.schema :as sm] [app.common.time :as ct] [app.db :as db] @@ -35,6 +36,43 @@ (files/check-read-permissions! conn profile-id file-id) (fsnap/get-visible-snapshots conn file-id)))) +;; --- COMMAND QUERY: get-file-snapshot + +(def ^:private schema:get-file-snapshot + [:map {:title "get-file-snapshot"} + [:file-id ::sm/uuid] + [:id ::sm/uuid] + [:features {:optional true} ::cfeat/features]]) + +(sv/defmethod ::get-file-snapshot + "Retrieve a file bundle with data from a specific snapshot for + read-only preview. Does not modify any database state." + {::doc/added "2.16" + ::sm/params schema:get-file-snapshot + ::sm/result files/schema:file-with-permissions + ::db/transaction true} + [{:keys [::db/conn] :as cfg} {:keys [::rpc/profile-id file-id id] :as params}] + (let [perms (bfc/get-file-permissions conn profile-id file-id)] + (files/check-read-permissions! perms) + (let [snapshot (fsnap/get-snapshot-data cfg file-id id)] + (when-not snapshot + (ex/raise :type :not-found + :code :snapshot-not-found + :hint "unable to find snapshot with the provided id" + :snapshot-id id + :file-id file-id)) + ;; Load current file metadata only (no data decoding) then overlay + ;; the snapshot data so the client receives the same shape as a + ;; normal get-file response but with historical page/object content. + (let [base-file (bfc/get-file cfg file-id :load-data? false)] + (-> base-file + (assoc :data (:data snapshot)) + (assoc :version (:version snapshot)) + (assoc :features (:features snapshot)) + (assoc :revn (:revn snapshot)) + (assoc :vern (rand-int 100000)) + (assoc :permissions perms)))))) + (def ^:private schema:create-file-snapshot [:map [:file-id ::sm/uuid] diff --git a/frontend/src/app/main/data/persistence.cljs b/frontend/src/app/main/data/persistence.cljs index adcc70cbb3..c90c423f96 100644 --- a/frontend/src/app/main/data/persistence.cljs +++ b/frontend/src/app/main/data/persistence.cljs @@ -121,8 +121,10 @@ :features features} permissions (:permissions state)] - ;; Prevent commit changes by a team member without edition permission - (when (:can-edit permissions) + ;; Prevent saving changes when in version preview (read-only) mode + ;; or when the user does not have edition permission. + (when (and (:can-edit permissions) + (not (get-in state [:workspace-global :read-only?]))) (->> (rp/cmd! :update-file params) (rx/mapcat (fn [{:keys [revn lagged] :as response}] (log/debug :hint "changes persisted" :commit-id (dm/str commit-id) :lagged (count lagged)) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 461a611315..1d5acfed38 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -511,7 +511,8 @@ :workspace-persistence :workspace-presence :workspace-tokens - :workspace-undo) + :workspace-undo + :workspace-versions) (update :workspace-global dissoc :read-only?) (assoc-in [:workspace-global :options-mode] :design) (update :files d/update-vals #(dissoc % :data)))) diff --git a/frontend/src/app/main/data/workspace/versions.cljs b/frontend/src/app/main/data/workspace/versions.cljs index 85630cfccb..072f8c634a 100644 --- a/frontend/src/app/main/data/workspace/versions.cljs +++ b/frontend/src/app/main/data/workspace/versions.cljs @@ -8,23 +8,28 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.logging :as log] [app.common.schema :as sm] [app.common.time :as ct] [app.main.data.event :as ev] + [app.main.data.helpers :as dsh] [app.main.data.notifications :as ntf] [app.main.data.persistence :as dwp] [app.main.data.workspace :as dw] [app.main.data.workspace.pages :as dwpg] [app.main.data.workspace.thumbnails :as th] + [app.main.features :as features] [app.main.refs :as refs] [app.main.repo :as rp] + [app.util.i18n :refer [tr]] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) (defonce default-state {:status :loading :data nil - :editing nil}) + :editing nil + :preview-id nil}) (declare fetch-versions) @@ -122,32 +127,6 @@ (rx/take 1) (rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id snapshot-id})))) -(defn restore-version - [id origin] - (assert (uuid? id) "expected valid uuid for `id`") - (ptk/reify ::restore-version - ptk/WatchEvent - (watch [_ state _] - (let [file-id (:current-file-id state) - team-id (:current-team-id state) - event-name (case origin - :version "restore-pin-version" - :snapshot "restore-autosave" - :plugin "restore-version-plugin")] - - (rx/concat - (rx/of ::dwp/force-persist - (dw/remove-layout-flag :document-history)) - - (->> (wait-for-persistence file-id id) - (rx/map #(initialize-version))) - - (if event-name - (rx/of (ev/event {::ev/name event-name - :file-id file-id - :team-id team-id})) - (rx/empty))))))) - (defn delete-version [id] (assert (uuid? id) "expected valid uuid for `id`") @@ -193,6 +172,145 @@ (->> (rp/cmd! :unlock-file-snapshot {:id id}) (rx/map fetch-versions))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; RESTORE VERSION EVENTS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- restore-version + [id] + (assert (uuid? id) "expected valid uuid for `id`") + (ptk/reify ::restore-version + ptk/WatchEvent + (watch [_ state _] + (let [file-id (:current-file-id state)] + (rx/concat + (rx/of ::dwp/force-persist + (dw/remove-layout-flag :document-history)) + + (->> (wait-for-persistence file-id id) + (rx/map #(initialize-version)))))))) + +(defn enter-restore + [id] + (assert (uuid? id) "expected valid uuid for `id`") + (ptk/reify ::enter-restore + ptk/WatchEvent + (watch [_ _ _] + (let [output-s (rx/subject)] + (rx/merge + output-s + (rx/of (ntf/dialog + :content (tr "workspace.versions.restore-warning") + :controls :inline-actions + :cancel {:label (tr "workspace.updates.dismiss") + :callback #(do + (rx/push! output-s (ntf/hide :tag :restore-dialog)) + (rx/end! output-s))} + :accept {:label (tr "labels.restore") + :callback #(do + (rx/push! output-s (restore-version id)) + (rx/end! output-s))} + :tag :restore-dialog))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; PREVIEW VERSION EVENTS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- apply-snapshot + "Swap the file data in app state with the provided snapshot-file + response. Used by the version preview feature to show historical + file content without modifying the database" + [{:keys [id] :as snapshot}] + (ptk/reify ::apply-snapshot-data + ptk/UpdateEvent + (update [_ state] + (update state :files assoc id snapshot)))) + +(defn exit-preview + "Exit from preview mode and reload the live file data" + [] + (ptk/reify ::exit-preview + ptk/UpdateEvent + (update [_ state] + (let [backup (dm/get-in state [:workspace-versions :backup])] + (-> state + (update :workspace-versions dissoc :backup) + (update :workspace-global dissoc :read-only? :preview-id) + (update :files assoc (:id backup) backup)))) + + ptk/WatchEvent + (watch [_ state _] + (let [file-id (:current-file-id state) + page-id (:current-page-id state)] + + (rx/of (dwpg/initialize-page file-id page-id)))))) + +(defn enter-preview + "Load a snapshot into the workspace for read-only preview without + modifying any database state. Sets a read-only flag so no changes + are persisted while previewing and enter on the preview mode" + [id] + (assert (uuid? id) "expected valid uuid for `id`") + + (ptk/reify ::enter-preview + ptk/UpdateEvent + (update [_ state] + (let [file (dsh/lookup-file state)] + (-> state + (update :workspace-versions assoc :backup file) + (update :workspace-global assoc :read-only? true :preview-id id)))) + + ptk/WatchEvent + (watch [_ state _] + (let [file-id (:current-file-id state) + page-id (:current-page-id state) + team-id (:current-team-id state) + features (features/get-enabled-features state team-id) + snapshot (->> (dm/get-in state [:workspace-versions :data]) + (d/seek #(= id (:id %)))) + label (or (:label snapshot) + (tr "workspace.versions.preview.unnamed")) + output-s (rx/subject)] + (rx/merge + output-s + + (rx/of (ntf/dialog + :content (tr "workspace.versions.preview-banner-title" label) + :controls :inline-actions + :cancel {:label (tr "labels.exit") + :callback #(do + (rx/push! output-s (ntf/hide)) + (rx/push! output-s (exit-preview)) + (rx/end! output-s))} + :accept {:label (tr "labels.restore") + :callback #(do + (rx/push! output-s (ntf/hide)) + (rx/push! output-s (restore-version id)) + (rx/end! output-s))} + :tag :preview-dialog)) + + (->> (rp/cmd! :get-file-snapshot + {:file-id file-id + :id id + :features features}) + (rx/mapcat + (fn [snapshot] + (rx/of + ;; Swap the file data in state with snapshot content. + ;; Passing id sets workspace-file-version-id, which + ;; causes the WASM viewport to reload its shape buffer. + (apply-snapshot snapshot) + ;; Re-initialize the page to rebuild its search index + ;; and page-local state with the new snapshot + ;; objects. + (dwpg/initialize-page file-id page-id)))) + + (rx/catch (fn [err] + ;; On error roll back the read-only flag so the + ;; user is not stuck in a broken preview state. + (log/error :hint "failed to load snapshot" :cause err :file-id file-id :snapshot-id id) + (rx/of (exit-preview)))))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PLUGINS SPECIFIC EVENTS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -246,20 +364,18 @@ (ptk/reify ::restore-version-from-plugins ptk/WatchEvent - (watch [_ state _] - (let [team-id (:current-team-id state)] - (rx/concat - (rx/of (ev/event {::ev/name "restore-version-plugin" - :file-id file-id - :team-id team-id}) - ::dwp/force-persist) + (watch [_ _ _] + (rx/concat + (rx/of (ev/event {::ev/name "restore-version" + ::ev/origin "plugins"}) + ::dwp/force-persist) - (->> (wait-for-persistence file-id id) - (rx/map #(initialize-version))) + (->> (wait-for-persistence file-id id) + (rx/map #(initialize-version))) - (->> (rx/of 1) - (rx/tap resolve) - (rx/ignore))))))) + (->> (rx/of 1) + (rx/tap resolve) + (rx/ignore)))))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/versions.cljs b/frontend/src/app/main/ui/workspace/sidebar/versions.cljs index 37edf428cd..0289f7c2f6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/versions.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/versions.cljs @@ -11,7 +11,7 @@ [app.common.time :as ct] [app.common.uuid :as uuid] [app.config :as cfg] - [app.main.data.notifications :as ntf] + [app.main.data.event :as ev] [app.main.data.workspace.versions :as dwv] [app.main.refs :as refs] [app.main.store :as st] @@ -77,20 +77,49 @@ (assoc item :index index))) (reverse))) -(defn- open-restore-version-dialog - [origin id] - (st/emit! (ntf/dialog - :content (tr "workspace.versions.restore-warning") - :controls :inline-actions - :cancel {:label (tr "workspace.updates.dismiss") - :callback #(st/emit! (ntf/hide))} - :accept {:label (tr "labels.restore") - :callback #(st/emit! (dwv/restore-version id origin))} - :tag :restore-dialog))) +(defn- on-name-input-focus + [event] + (dom/select-text! (dom/get-target event))) + +(defn- extract-id-from-event + [event] + (-> event dom/get-current-target (dom/get-data "id") uuid/parse)) + +(defn- on-create-version + [] + (st/emit! (dwv/create-version))) + +(defn- on-edit-version + [id _event] + (st/emit! (dwv/update-versions-state {:editing id}))) + +(defn- on-cancel-version-edition + [_id _event] + (st/emit! (dwv/update-versions-state {:editing nil}))) + +(defn- on-rename-version + [id label] + (st/emit! (dwv/rename-version id label))) + +(defn- on-delete-version + [id] + (st/emit! (dwv/delete-version id))) + +(defn- on-pin-version + [id] + (st/emit! (dwv/pin-version id))) + +(defn- on-lock-version + [id] + (st/emit! (dwv/lock-version id))) + +(defn- on-unlock-version + [id] + (st/emit! (dwv/unlock-version id))) (mf/defc version-entry* {::mf/private true} - [{:keys [entry current-profile on-restore on-delete on-rename on-lock on-unlock on-edit on-cancel-edit is-editing]}] + [{:keys [entry current-profile on-preview on-restore on-delete on-rename on-lock on-unlock on-edit on-cancel-edit is-editing]}] (let [show-menu? (mf/use-state false) profiles (mf/deref refs/profiles) @@ -108,6 +137,13 @@ (fn [event] (on-edit (:id entry) event))) + on-preview + (mf/use-fn + (mf/deps entry on-preview) + (fn [] + (when (fn? on-preview) + (on-preview (:id entry))))) + on-restore (mf/use-fn (mf/deps entry on-restore) @@ -136,11 +172,6 @@ (when on-unlock (on-unlock (:id entry))))) - on-name-input-focus - (mf/use-fn - (fn [event] - (dom/select-text! (dom/get-target event)))) - on-name-input-blur (mf/use-fn (mf/deps entry on-rename on-cancel-edit) @@ -191,6 +222,11 @@ :on-click on-edit} (tr "labels.rename")]) + [:li {:class (stl/css :menu-option) + :role "button" + :on-click on-preview} + (tr "workspace.versions.button.preview")] + [:li {:class (stl/css :menu-option) :role "button" :on-click on-restore} @@ -216,7 +252,7 @@ (tr "labels.delete")])])]])) (mf/defc snapshot-entry* - [{:keys [entry on-pin-snapshot on-restore-snapshot]}] + [{:keys [entry on-pin-snapshot on-restore-snapshot on-preview-snapshot]}] (let [open-menu* (mf/use-state nil) entry-ref (mf/use-ref nil) @@ -225,23 +261,22 @@ (mf/use-fn (mf/deps on-pin-snapshot) (fn [event] - (let [node (dom/get-current-target event) - id (-> node - (dom/get-data "id") - (uuid/parse))] - (when (fn? on-pin-snapshot) - (on-pin-snapshot id event))))) + (when (fn? on-pin-snapshot) + (on-pin-snapshot (extract-id-from-event event) event)))) on-restore-snapshot (mf/use-fn (mf/deps on-restore-snapshot) (fn [event] - (let [node (dom/get-current-target event) - id (-> node - (dom/get-data "id") - (uuid/parse))] - (when (fn? on-restore-snapshot) - (on-restore-snapshot id event))))) + (when (fn? on-restore-snapshot) + (on-restore-snapshot (extract-id-from-event event) event)))) + + on-preview-snapshot + (mf/use-fn + (mf/deps on-preview-snapshot) + (fn [event] + (when (fn? on-preview-snapshot) + (on-preview-snapshot (extract-id-from-event event) event)))) on-open-snapshot-menu (mf/use-fn @@ -266,6 +301,11 @@ :on-close #(reset! open-menu* nil)} [:ul {:class (stl/css :version-options-dropdown) :style {"--offset" (dm/str (:offset @open-menu*) "px")}} + [:li {:class (stl/css :menu-option) + :role "button" + :data-id (dm/str (:snapshot @open-menu*)) + :on-click on-preview-snapshot} + (tr "workspace.versions.button.preview")] [:li {:class (stl/css :menu-option) :role "button" :data-id (dm/str (:snapshot @open-menu*)) @@ -302,66 +342,50 @@ (= (:filter state) (:profile-id %))))) (group-snapshots))) - on-create-version + on-preview-version (mf/use-fn - (fn [] (st/emit! (dwv/create-version)))) + (fn [id] + (st/emit! (dwv/enter-preview id) + (ev/event {::ev/name "preview-version" + ::ev/origin "workspace:sidebar" + :type "pinned-version"})))) - on-edit-version + on-preview-snapshot (mf/use-fn (fn [id _event] - (st/emit! (dwv/update-versions-state {:editing id})))) - - on-cancel-version-edition - (mf/use-fn - (fn [_id _event] - (st/emit! (dwv/update-versions-state {:editing nil})))) - - on-rename-version - (mf/use-fn - (fn [id label] - (st/emit! (dwv/rename-version id label)))) + (st/emit! (dwv/enter-preview id) + (ev/event {::ev/name "preview-version" + ::ev/origin "workspace:sidebar" + :type "autosaved-version"})))) on-restore-version (mf/use-fn (fn [id _event] - (open-restore-version-dialog :version id))) + (st/emit! (dwv/enter-restore id) + (ev/event {::ev/name "restore-version" + ::ev/origin "workspace:sidebar" + :type "pinned-version"})))) on-restore-snapshot (mf/use-fn (fn [id _event] - (open-restore-version-dialog :snapshot id))) - - on-delete-version - (mf/use-fn - (fn [id] - (st/emit! (dwv/delete-version id)))) - - on-pin-version - (mf/use-fn - (fn [id] (st/emit! (dwv/pin-version id)))) - - on-lock-version - (mf/use-fn - (fn [id] - (st/emit! (dwv/lock-version id)))) - - on-unlock-version - (mf/use-fn - (fn [id] - (st/emit! (dwv/unlock-version id)))) + (st/emit! (dwv/enter-restore id) + (ev/event {::ev/name "restore-version" + ::ev/origin "workspace:sidebar" + :type "autosaved-version"})))) on-change-filter (mf/use-fn - (fn [filter] + (fn [filter-value] (cond - (= :all filter) + (= :all filter-value) (st/emit! (dwv/update-versions-state {:filter nil})) - (= :own filter) + (= :own filter-value) (st/emit! (dwv/update-versions-state {:filter (:id profile)})) :else - (st/emit! (dwv/update-versions-state {:filter filter}))))) + (st/emit! (dwv/update-versions-state {:filter filter-value}))))) options (mf/with-memo [users profile] @@ -415,6 +439,7 @@ :on-edit on-edit-version :on-cancel-edit on-cancel-version-edition :on-rename on-rename-version + :on-preview on-preview-version :on-restore on-restore-version :on-delete on-delete-version :on-lock on-lock-version @@ -423,6 +448,7 @@ :snapshot [:> snapshot-entry* {:key (:index entry) :entry entry + :on-preview-snapshot on-preview-snapshot :on-restore-snapshot on-restore-snapshot :on-pin-snapshot on-pin-version}] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a7bb343fb4..5e7e5fd59c 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -97,6 +97,7 @@ {:keys [options-mode tooltip + preview-id show-distances? picking-color?]} wglobal @@ -314,23 +315,28 @@ (hooks/setup-shortcuts path-editing? path-drawing? text-editing? grid-editing?) (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox) - [:div {:class (stl/css :viewport) :style #js {"--zoom" zoom} :data-testid "viewport"} - (when (:can-edit permissions) - (if read-only? - [:> view-only-bar* {}] - [:* - (when-not hide-ui? - [:> top-toolbar* {:layout layout}]) + [:div {:class (stl/css :viewport) :style {"--zoom" zoom} :data-testid "viewport"} + (cond + (some? preview-id) + nil - (when (and ^boolean path-editing? - ^boolean single-select?) - [:> path-edition-bar* {:shape editing-shape - :edit-path-state edit-path-state - :layout layout}]) + (and read-only? (:can-edit permissions)) + [:> view-only-bar* {}] - (when (and ^boolean grid-editing? - ^boolean single-select?) - [:> grid-edition-bar* {:shape editing-shape}])])) + :else + [:* + (when-not hide-ui? + [:> top-toolbar* {:layout layout}]) + + (when (and ^boolean path-editing? + ^boolean single-select?) + [:> path-edition-bar* {:shape editing-shape + :edit-path-state edit-path-state + :layout layout}]) + + (when (and ^boolean grid-editing? + ^boolean single-select?) + [:> grid-edition-bar* {:shape editing-shape}])]) [:div {:class (stl/css :viewport-overlays)} ;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap diff --git a/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs b/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs index b9f4f69cb4..2fca82347f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/top_bar.cljs @@ -20,14 +20,12 @@ ;; branch. (mf/defc view-only-bar* - {::mf/private true} [] - (let [handle-close-view-mode + (let [on-close (mf/use-fn - (fn [] - (st/emit! :interrupt - (dw/set-options-mode :design) - (dwc/set-workspace-read-only false))))] + #(st/emit! :interrupt + (dw/set-options-mode :design) + (dwc/set-workspace-read-only false)))] [:div {:class (stl/css :viewport-actions)} [:div {:class (stl/css :viewport-actions-container)} [:div {:class (stl/css :viewport-actions-title)} @@ -35,7 +33,7 @@ {:tag-name "span" :content (tr "workspace.top-bar.view-only")}]] [:button {:class (stl/css :done-btn) - :on-click handle-close-view-mode} + :on-click on-close} (tr "workspace.top-bar.read-only.done")]]])) (mf/defc path-edition-bar* diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index eb61350ba7..36d9d7e8e6 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -98,6 +98,7 @@ {:keys [options-mode tooltip show-distances? + preview-id picking-color?]} wglobal @@ -456,22 +457,28 @@ (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox) [:div {:class (stl/css :viewport) :style #js {"--zoom" zoom} :data-testid "viewport"} - (when (:can-edit permissions) - (if read-only? - [:> view-only-bar* {}] - [:* - (when-not hide-ui? - [:> top-toolbar* {:layout layout}]) - (when (and ^boolean path-editing? - ^boolean single-select?) - [:> path-edition-bar* {:shape editing-shape - :edit-path-state edit-path-state - :layout layout}]) + (cond + (some? preview-id) + nil - (when (and ^boolean grid-editing? - ^boolean single-select?) - [:> grid-edition-bar* {:shape editing-shape}])])) + (and read-only? (:can-edit permissions)) + [:> view-only-bar* {}] + + :else + [:* + (when-not hide-ui? + [:> top-toolbar* {:layout layout}]) + + (when (and ^boolean path-editing? + ^boolean single-select?) + [:> path-edition-bar* {:shape editing-shape + :edit-path-state edit-path-state + :layout layout}]) + + (when (and ^boolean grid-editing? + ^boolean single-select?) + [:> grid-edition-bar* {:shape editing-shape}])]) [:div {:class (stl/css :viewport-overlays)} (when show-comments? diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 54a33820ec..41308a2694 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3049,6 +3049,9 @@ msgstr "Resend invitation" msgid "labels.restore" msgstr "Restore" +msgid "labels.exit" +msgstr "Exit" + #: src/app/main/ui/components/progress.cljs:80, src/app/main/ui/static.cljs:299, src/app/main/ui/static.cljs:308, src/app/main/ui/static.cljs:419 msgid "labels.retry" msgstr "Retry" @@ -9064,6 +9067,9 @@ msgstr "Autosaved %s" msgid "workspace.versions.button.pin" msgstr "Pin version" +msgid "workspace.versions.button.preview" +msgstr "Preview version" + #: src/app/main/ui/workspace/sidebar/versions.cljs:273 msgid "workspace.versions.button.restore" msgstr "Restore version" @@ -9108,6 +9114,12 @@ msgstr "This version is locked by %s and cannot be modified" msgid "workspace.versions.locked-by-you" msgstr "This version is locked by you" +msgid "workspace.versions.preview-banner-title" +msgstr "Previewing version: %s" + +msgid "workspace.versions.preview.unnamed" +msgstr "Unnamed version" + #: src/app/main/ui/workspace/sidebar/versions.cljs:83 msgid "workspace.versions.restore-warning" msgstr "Do you want to restore this version?" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 6855d5fa84..d0da81200b 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -2976,6 +2976,9 @@ msgstr "Reenviar invitacion" msgid "labels.restore" msgstr "Restaurar" +msgid "labels.exit" +msgstr "Salir" + #: src/app/main/ui/components/progress.cljs:80, src/app/main/ui/static.cljs:299, src/app/main/ui/static.cljs:308, src/app/main/ui/static.cljs:419 msgid "labels.retry" msgstr "Reintentar" @@ -8869,6 +8872,9 @@ msgstr "Versiones de %s" msgid "workspace.versions.loading" msgstr "Cargando..." +msgid "workspace.versions.preview-banner-title" +msgstr "Previsualizando version: %s" + #: src/app/main/ui/workspace/sidebar/versions.cljs:83 msgid "workspace.versions.restore-warning" msgstr "¿Quieres restaurar esta versión?"