diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 4b47c19451..20cb0c150b 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -96,6 +96,7 @@ (fn [{:keys [params path-params method] :as request}] (let [handler-name (:method-name path-params) etag (yreq/get-header request "if-none-match") + session-id (yreq/get-header request "x-session-id") key-id (get request ::http/auth-key-id) profile-id (or (::session/profile-id request) @@ -108,6 +109,7 @@ (assoc ::handler-name handler-name) (assoc ::ip-addr ip-addr) (assoc ::request-at (ct/now)) + (assoc ::session-id (some-> session-id uuid/parse*)) (assoc ::cond/key etag) (cond-> (uuid? profile-id) (assoc ::profile-id profile-id))) diff --git a/backend/src/app/rpc/commands/files_snapshot.clj b/backend/src/app/rpc/commands/files_snapshot.clj index cd3cbcdf0f..8325772361 100644 --- a/backend/src/app/rpc/commands/files_snapshot.clj +++ b/backend/src/app/rpc/commands/files_snapshot.clj @@ -71,7 +71,7 @@ {::doc/added "1.20" ::sm/params schema:restore-file-snapshot ::db/transaction true} - [{:keys [::db/conn ::mbus/msgbus] :as cfg} {:keys [::rpc/profile-id file-id id] :as params}] + [{:keys [::db/conn ::mbus/msgbus] :as cfg} {:keys [::rpc/profile-id ::rpc/session-id file-id id] :as params}] (files/check-edition-permissions! conn profile-id file-id) (let [file (bfc/get-file cfg file-id) team (teams/get-team conn @@ -88,7 +88,8 @@ ;; Send to the clients a notification to reload the file (mbus/pub! msgbus :topic (:id file) - :message {:type :file-restore + :message {:type :file-restored + :session-id session-id :file-id (:id file) :vern vern}) nil))) diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index efda9a9356..11c59e5a65 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -12,6 +12,7 @@ [app.common.logging :as log] [app.common.time :as ct] [app.common.uri :as u] + [app.common.uuid :as uuid] [app.common.version :as v] [app.util.avatars :as avatars] [app.util.extends] @@ -112,10 +113,12 @@ (def target (parse-target global)) (def browser (parse-browser)) (def platform (parse-platform)) +(def session-id (uuid/next)) (def version (parse-version global)) (def version-tag (obj/get global "penpotVersionTag")) + (defn stale-build? "Returns true when the compiled JS was built with a different version tag than the one present in the current index.html. This indicates diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 9583b86686..20830e78c7 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -11,7 +11,6 @@ [app.common.time :as ct] [app.common.transit :as t] [app.common.types.objects-map] - [app.common.uuid :as uuid] [app.config :as cf] [app.main.data.auth :as da] [app.main.data.event :as ev] @@ -45,7 +44,8 @@ (log/inf :version (:full cf/version) :asserts *assert* :build-date cf/build-date - :public-uri (dm/str cf/public-uri)) + :public-uri (dm/str cf/public-uri) + :session-id (str cf/session-id)) (log/inf :hint "enabled flags" :flags (str/join " " (map name cf/flags)))) (declare reinit) @@ -71,7 +71,7 @@ (ptk/reify ::initialize ptk/UpdateEvent (update [_ state] - (assoc state :session-id (uuid/next))) + (assoc state :session-id cf/session-id)) ptk/WatchEvent (watch [_ _ stream] diff --git a/frontend/src/app/main/data/event.cljs b/frontend/src/app/main/data/event.cljs index cfd2cc841c..dfc925cce8 100644 --- a/frontend/src/app/main/data/event.cljs +++ b/frontend/src/app/main/data/event.cljs @@ -431,6 +431,7 @@ context (-> @context (merge (:context event)) (assoc :session session*) + (assoc :session-id cf/session-id) (assoc :external-session-id (cf/external-session-id)) (add-external-context-info) (d/without-nils))] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 32dd2ae5e9..e49bc026b6 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -224,18 +224,12 @@ IDeref (-deref [_] bundle) + ptk/UpdateEvent (update [_ state] - (let [pending-version-id (:workspace-pending-file-version-id state) - state (-> state - (assoc :thumbnails thumbnails) - (update :files assoc file-id file) - (dissoc :workspace-pending-file-version-id))] - (cond-> state - (some? pending-version-id) - (assoc :workspace-file-version-id pending-version-id) - (nil? pending-version-id) - (dissoc :workspace-file-version-id)))))) + (-> state + (assoc :thumbnails thumbnails) + (update :files assoc file-id file))))) (defn zoom-to-frame [] @@ -253,6 +247,7 @@ (rx/of (dws/select-shapes frames-id) dwz/zoom-to-selected-shape))))) +;; FIXME: rename to `fetch-file` (defn- fetch-bundle "Multi-stage file bundle fetch coordinator" [file-id features] @@ -289,204 +284,215 @@ ;; This prevents errors when processing changes from other pages (when shape (wasm.api/process-object shape)))))) + +(defn initialize-file + [team-id file-id] + (assert (uuid? team-id) "expected valud uuid for `team-id`") + (assert (uuid? file-id) "expected valud uuid for `file-id`") + + (ptk/reify ::initialize-file + ptk/WatchEvent + (watch [_ state _] + (let [features (features/get-enabled-features state team-id)] + (log/dbg :hint "initialize-file" + :team-id (dm/str team-id) + :file-id (dm/str file-id)) + (rx/of (fetch-bundle file-id features)))))) + (defn initialize-workspace - ([team-id file-id] - (initialize-workspace team-id file-id nil)) - ([team-id file-id version-id] - (assert (uuid? team-id) "expected valud uuid for `team-id`") - (assert (uuid? file-id) "expected valud uuid for `file-id`") + [team-id file-id] + (assert (uuid? team-id) "expected valud uuid for `team-id`") + (assert (uuid? file-id) "expected valud uuid for `file-id`") - (ptk/reify ::initialize-workspace - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc :recent-colors (:recent-colors storage/user)) - (assoc :recent-fonts (:recent-fonts storage/user)) - (assoc :current-file-id file-id) - (assoc :workspace-presence {}) - ;; Store pending version-id; bundle-fetched will set workspace-file-version-id - ;; when the new bundle is applied so the viewport re-inits with new data - (assoc :workspace-pending-file-version-id version-id))) + (ptk/reify ::initialize-workspace + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc :recent-colors (:recent-colors storage/user)) + (assoc :recent-fonts (:recent-fonts storage/user)) + (assoc :current-file-id file-id) + (assoc :workspace-presence {}))) - ptk/WatchEvent - (watch [_ state stream] - (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) - rparams (rt/get-params state) - features (features/get-enabled-features state team-id) - render-wasm? (contains? features "render-wasm/v1")] + ptk/WatchEvent + (watch [_ state stream] + (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) + rparams (rt/get-params state) + features (features/get-enabled-features state team-id) + render-wasm? (contains? features "render-wasm/v1")] - (log/debug :hint "initialize-workspace" - :team-id (dm/str team-id) - :file-id (dm/str file-id)) + (log/debug :hint "initialize-workspace" + :team-id (dm/str team-id) + :file-id (dm/str file-id)) - (rx/concat - (->> (rx/merge - (rx/concat - ;; Fetch all essential data that should be loaded before the file - (rx/merge - (if ^boolean render-wasm? - (->> (rx/from @wasm/module) - (rx/filter true?) - (rx/tap (fn [_] - (let [event (ug/event "penpot:wasm:loaded")] - (ug/dispatch! event)))) - (rx/ignore)) - (rx/empty)) + (rx/concat + (->> (rx/merge + (rx/concat + ;; Fetch all essential data that should be loaded before the file + (rx/merge + (if ^boolean render-wasm? + (->> (rx/from @wasm/module) + (rx/filter true?) + (rx/tap (fn [_] + (let [event (ug/event "penpot:wasm:loaded")] + (ug/dispatch! event)))) + (rx/ignore)) + (rx/empty)) - (->> stream - (rx/filter (ptk/type? ::df/fonts-loaded)) - (rx/take 1) - (rx/ignore)) + (->> stream + (rx/filter (ptk/type? ::df/fonts-loaded)) + (rx/take 1) + (rx/ignore)) - (rx/of (ntf/hide) - (dcmt/retrieve-comment-threads file-id) - (dcmt/fetch-profiles) - (df/fetch-fonts team-id)) + (rx/of (ntf/hide) + (dcmt/retrieve-comment-threads file-id) + (dcmt/fetch-profiles) + (df/fetch-fonts team-id)) - (when (contains? cf/flags :mcp) - (rx/of (du/fetch-access-tokens)))) + (when (contains? cf/flags :mcp) + (rx/of (du/fetch-access-tokens)))) - ;; Once the essential data is fetched, lets proceed to - ;; fetch teh file bunldle - (rx/of (fetch-bundle file-id features))) + ;; Once the essential data is fetched, lets proceed to + ;; fetch teh file bunldle + (rx/of (initialize-file team-id file-id))) - (->> stream - (rx/filter (ptk/type? ::bundle-fetched)) - (rx/take 1) - (rx/map deref) - (rx/mapcat - (fn [{:keys [file]}] - (log/debug :hint "bundle fetched" - :team-id (dm/str team-id) - :file-id (dm/str file-id)) + (->> stream + (rx/filter (ptk/type? ::bundle-fetched)) + (rx/take 1) + (rx/map deref) + (rx/mapcat + (fn [{:keys [file]}] + (log/debug :hint "bundle fetched" + :team-id (dm/str team-id) + :file-id (dm/str file-id)) - (rx/of (dpj/initialize-project (:project-id file)) - (dwn/initialize team-id file-id) - (dwsl/initialize-shape-layout) - (fetch-libraries file-id features) - (-> (workspace-initialized file-id) - (with-meta {:team-id team-id - :file-id file-id})))))) + (rx/of (dpj/initialize-project (:project-id file)) + (dwn/initialize team-id file-id) + (dwsl/initialize-shape-layout) + (fetch-libraries file-id features) + (-> (workspace-initialized file-id) + (with-meta {:team-id team-id + :file-id file-id})))))) - ;; Install dev perf observers once the workspace is ready - (when (contains? cf/flags :perf-logs) - (->> stream - (rx/filter (ptk/type? ::workspace-initialized)) - (rx/take 1) - (rx/tap (fn [_] (perf/setup))))) + ;; Install dev perf observers once the workspace is ready + (when (contains? cf/flags :perf-logs) + (->> stream + (rx/filter (ptk/type? ::workspace-initialized)) + (rx/take 1) + (rx/tap (fn [_] (perf/setup))))) - (->> stream - (rx/filter (ptk/type? ::dps/persistence-notification)) - (rx/take 1) - (rx/map dwc/set-workspace-visited)) - (when-let [component-id (some-> rparams :component-id uuid/parse)] - (->> stream - (rx/filter (ptk/type? ::workspace-initialized)) - (rx/observe-on :async) - (rx/take 1) - (rx/map #(dwl/go-to-local-component :id component-id :update-layout? (:update-layout rparams))))) + (->> stream + (rx/filter (ptk/type? ::dps/persistence-notification)) + (rx/take 1) + (rx/map dwc/set-workspace-visited)) - (when (:board-id rparams) - (->> stream - (rx/filter (ptk/type? ::dwv/initialize-viewport)) - (rx/take 1) - (rx/map zoom-to-frame))) + (when-let [component-id (some-> rparams :component-id uuid/parse)] + (->> stream + (rx/filter (ptk/type? ::workspace-initialized)) + (rx/observe-on :async) + (rx/take 1) + (rx/map #(dwl/go-to-local-component :id component-id :update-layout? (:update-layout rparams))))) - (when-let [comment-id (some-> rparams :comment-id uuid/parse)] - (->> stream - (rx/filter (ptk/type? ::workspace-initialized)) - (rx/observe-on :async) - (rx/take 1) - (rx/map #(dwcm/navigate-to-comment-id comment-id)))) + (when (:board-id rparams) + (->> stream + (rx/filter (ptk/type? ::dwv/initialize-viewport)) + (rx/take 1) + (rx/map zoom-to-frame))) - (when render-wasm? - (->> stream - (rx/filter dch/commit?) - (rx/map deref) - (rx/mapcat - (fn [{:keys [redo-changes]}] - (let [added (->> redo-changes - (filter #(= (:type %) :add-obj)) - (map :id))] - (->> (rx/from added) - (rx/map process-wasm-object))))))) + (when-let [comment-id (some-> rparams :comment-id uuid/parse)] + (->> stream + (rx/filter (ptk/type? ::workspace-initialized)) + (rx/observe-on :async) + (rx/take 1) + (rx/map #(dwcm/navigate-to-comment-id comment-id)))) - (when render-wasm? - (let [local-commits-s - (->> stream - (rx/filter dch/commit?) - (rx/map deref) - (rx/filter #(and (= :local (:source %)) - (not (contains? (:tags %) :position-data)))) - (rx/filter (complement empty?))) + (when render-wasm? + (->> stream + (rx/filter dch/commit?) + (rx/map deref) + (rx/mapcat + (fn [{:keys [redo-changes]}] + (let [added (->> redo-changes + (filter #(= (:type %) :add-obj)) + (map :id))] + (->> (rx/from added) + (rx/map process-wasm-object))))))) - notifier-s - (rx/merge - (->> local-commits-s (rx/debounce 1000)) - (->> stream (rx/filter dps/force-persist?))) + (when render-wasm? + (let [local-commits-s + (->> stream + (rx/filter dch/commit?) + (rx/map deref) + (rx/filter #(and (= :local (:source %)) + (not (contains? (:tags %) :position-data)))) + (rx/filter (complement empty?))) - objects-s - (rx/from-atom refs/workspace-page-objects {:emit-current-value? true}) + notifier-s + (rx/merge + (->> local-commits-s (rx/debounce 1000)) + (->> stream (rx/filter dps/force-persist?))) - current-page-id-s - (rx/from-atom refs/current-page-id {:emit-current-value? true})] + objects-s + (rx/from-atom refs/workspace-page-objects {:emit-current-value? true}) - (->> local-commits-s - (rx/buffer-until notifier-s) - (rx/with-latest-from objects-s) - (rx/map - (fn [[commits objects]] - (->> commits - (mapcat :redo-changes) - (filter #(contains? #{:mod-obj :add-obj} (:type %))) - (filter #(cfh/text-shape? objects (:id %))) - (map #(vector - (:id %) - (wasm.api/calculate-position-data (get objects (:id %)))))))) + current-page-id-s + (rx/from-atom refs/current-page-id {:emit-current-value? true})] - (rx/with-latest-from current-page-id-s) - (rx/map - (fn [[text-position-data page-id]] - (let [changes - (->> text-position-data - (mapv (fn [[id position-data]] - {:type :mod-obj - :id id - :page-id page-id - :operations - [{:type :set - :attr :position-data - :val position-data - :ignore-touched true - :ignore-geometry true}]})))] - (when (d/not-empty? changes) - (dch/commit-changes - {:redo-changes changes :undo-changes [] - :save-undo? false - :tags #{:position-data}}))))) - (rx/take-until stoper-s)))) + (->> local-commits-s + (rx/buffer-until notifier-s) + (rx/with-latest-from objects-s) + (rx/map + (fn [[commits objects]] + (->> commits + (mapcat :redo-changes) + (filter #(contains? #{:mod-obj :add-obj} (:type %))) + (filter #(cfh/text-shape? objects (:id %))) + (map #(vector + (:id %) + (wasm.api/calculate-position-data (get objects (:id %)))))))) - (->> stream - (rx/filter dch/commit?) - (rx/map deref) - (rx/mapcat - (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}] - (if (and save-undo? (seq undo-changes)) - (let [entry {:undo-changes undo-changes - :redo-changes redo-changes - :undo-group undo-group - :tags tags}] - (rx/of (dwu/append-undo entry stack-undo?))) - (rx/empty)))))) - (rx/take-until stoper-s)) + (rx/with-latest-from current-page-id-s) + (rx/map + (fn [[text-position-data page-id]] + (let [changes + (->> text-position-data + (mapv (fn [[id position-data]] + {:type :mod-obj + :id id + :page-id page-id + :operations + [{:type :set + :attr :position-data + :val position-data + :ignore-touched true + :ignore-geometry true}]})))] + (when (d/not-empty? changes) + (dch/commit-changes + {:redo-changes changes :undo-changes [] + :save-undo? false + :tags #{:position-data}}))))) + (rx/take-until stoper-s)))) - (rx/of (mcp/notify-other-tabs-disconnect))))) + (->> stream + (rx/filter dch/commit?) + (rx/map deref) + (rx/mapcat + (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}] + (if (and save-undo? (seq undo-changes)) + (let [entry {:undo-changes undo-changes + :redo-changes redo-changes + :undo-group undo-group + :tags tags}] + (rx/of (dwu/append-undo entry stack-undo?))) + (rx/empty)))))) + (rx/take-until stoper-s)) - ptk/EffectEvent - (effect [_ _ _] - (let [name (dm/str "workspace-" file-id)] - (unchecked-set ug/global "name" name)))))) + (rx/of (mcp/notify-other-tabs-disconnect))))) + + ptk/EffectEvent + (effect [_ _ _] + (let [name (dm/str "workspace-" file-id)] + (unchecked-set ug/global "name" name))))) (defn finalize-workspace [_team-id file-id] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 28057cfd09..be0b8b6dc6 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -1350,9 +1350,10 @@ (watch [_ _ stream] (let [stopper-s (->> stream - (rx/filter #(or (= ::dwpg/finalize-page (ptk/type %)) - (= ::watch-component-changes (ptk/type %))))) - + (rx/map ptk/type) + (rx/filter (fn [event-type] + (or (= ::dwpg/finalize-page event-type) + (= ::watch-component-changes event-type))))) workspace-data-s (->> (rx/from-atom refs/workspace-data {:emit-current-value? true}) (rx/share)) diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index 5e01fd4486..2dc6450abc 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -40,7 +40,7 @@ (declare handle-pointer-update) (declare handle-file-change) (declare handle-file-deleted) -(declare handle-file-restore) +(declare handle-file-restored) (declare handle-library-change) (declare handle-pointer-send) (declare handle-export-update) @@ -132,7 +132,7 @@ :pointer-update (handle-pointer-update msg) :file-change (handle-file-change msg) :file-deleted (handle-file-deleted msg) - :file-restore (handle-file-restore msg) + :file-restored (handle-file-restored msg) :library-change (handle-library-change msg) :notification (dc/handle-notification msg) :team-role-change (handle-change-team-role msg) @@ -283,22 +283,22 @@ (rt/nav :dashboard-recent {:team-id team-id}))))))) (def ^:private - schema:handle-file-restore - [:map {:title "handle-file-restore"} + schema:handle-file-restored + [:map {:title "handle-file-restored"} [:type :keyword] [:file-id ::sm/uuid] [:vern :int]]) -(def ^:private check-file-restore-params - (sm/check-fn schema:handle-file-restore)) +(def ^:private check-file-restored-params + (sm/check-fn schema:handle-file-restored)) -(defn handle-file-restore +(defn handle-file-restored [{:keys [file-id vern] :as msg}] - (assert (check-file-restore-params msg) + (assert (check-file-restored-params msg) "expected valid parameters") - (ptk/reify ::handle-file-restore + (ptk/reify ::handle-file-restored ptk/WatchEvent (watch [_ state _] (let [curr-file-id (:current-file-id state) diff --git a/frontend/src/app/main/data/workspace/versions.cljs b/frontend/src/app/main/data/workspace/versions.cljs index 11ed461366..85630cfccb 100644 --- a/frontend/src/app/main/data/workspace/versions.cljs +++ b/frontend/src/app/main/data/workspace/versions.cljs @@ -11,9 +11,10 @@ [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.refs :as refs] [app.main.repo :as rp] @@ -92,33 +93,59 @@ (->> (rp/cmd! :update-file-snapshot {:id id :label label}) (rx/map fetch-versions))))))) +(defn- initialize-version + [] + (ptk/reify ::initialize-version + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + file-id (:current-file-id state) + team-id (:current-team-id state)] + + (rx/merge + (->> stream + (rx/filter (ptk/type? ::dw/bundle-fetched)) + (rx/take 1) + (rx/map #(dwpg/initialize-page file-id page-id))) + + (rx/of (ntf/hide :tag :restore-dialog) + (dw/initialize-file team-id file-id))))) + + ptk/EffectEvent + (effect [_ _ _] + (th/clear-queue!)))) + +(defn- wait-for-persistence + [file-id snapshot-id] + (->> (rx/from-atom refs/persistence-state {:emit-current-value? true}) + (rx/filter #(or (nil? %) (= :saved %))) + (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)] + (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)) - (->> (rx/from-atom refs/persistence-state {:emit-current-value? true}) - (rx/filter #(or (nil? %) (= :saved %))) - (rx/take 1) - (rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id})) - (rx/tap #(th/clear-queue!)) - (rx/map #(dw/initialize-workspace team-id file-id id))) - (case origin - :version - (rx/of (ptk/event ::ev/event {::ev/name "restore-pin-version"})) - :snapshot - (rx/of (ptk/event ::ev/event {::ev/name "restore-autosave"})) - - :plugin - (rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"})) + (->> (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 @@ -220,18 +247,15 @@ (ptk/reify ::restore-version-from-plugins ptk/WatchEvent (watch [_ state _] - (let [file (dsh/lookup-file state file-id) - team-id (or (:team-id file) (:current-file-id state))] + (let [team-id (:current-team-id state)] (rx/concat - (rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"}) + (rx/of (ev/event {::ev/name "restore-version-plugin" + :file-id file-id + :team-id team-id}) ::dwp/force-persist) - ;; FIXME: we should abstract this - (->> (rx/from-atom refs/persistence-state {:emit-current-value? true}) - (rx/filter #(or (nil? %) (= :saved %))) - (rx/take 1) - (rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id})) - (rx/map #(dw/initialize-workspace team-id file-id id))) + (->> (wait-for-persistence file-id id) + (rx/map #(initialize-version))) (->> (rx/of 1) (rx/tap resolve) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index e84f92954b..48dc9529cd 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -259,9 +259,6 @@ (def workspace-layout (l/derived :workspace-layout st/state)) -(def workspace-file-version-id - (l/derived :workspace-file-version-id st/state)) - (def snap-pixel? (l/derived #(contains? % :snap-pixel-grid) workspace-layout)) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 1e6dd417a6..9b5b9872ab 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -182,6 +182,7 @@ :credentials "include" :headers {"accept" "application/transit+json,text/event-stream,*/*" "x-external-session-id" (cf/external-session-id) + "x-session-id" (str cf/session-id) "x-event-origin" (::ev/origin (meta params))} :body (when (= method :post) (if form-data? diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index c0258ccb16..c0e600b835 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -51,7 +51,7 @@ (mf/defc workspace-content* {::mf/private true} - [{:keys [file layout page wglobal file-version-id]}] + [{:keys [file layout page wglobal]}] (let [palete-size (mf/use-state nil) selected (mf/deref refs/selected-shapes) @@ -109,7 +109,6 @@ :wglobal wglobal :selected selected :layout layout - :file-version-id file-version-id :palete-size (when (and (or colorpalette? textpalette?) (not hide-ui?)) @palete-size)}]]] @@ -169,7 +168,7 @@ (mf/defc workspace-inner* {::mf/private true} - [{:keys [page-id file-id file layout wglobal file-version-id]}] + [{:keys [page-id file-id file layout wglobal]}] (let [page-ref (mf/with-memo [file-id page-id] (make-page-ref file-id page-id)) page (mf/deref page-ref)] @@ -188,8 +187,7 @@ [:> workspace-content* {:file file :page page :wglobal wglobal - :layout layout - :file-version-id file-version-id}] + :layout layout}] [:> workspace-loader*]))) (mf/defc workspace* @@ -201,7 +199,6 @@ layout (mf/deref refs/workspace-layout) wglobal (mf/deref refs/workspace-global) - file-version-id (mf/deref refs/workspace-file-version-id) team-ref (mf/with-memo [team-id] (make-team-ref team-id)) @@ -277,8 +274,7 @@ :file-id file-id :file file :wglobal wglobal - :layout layout - :file-version-id file-version-id}]) + :layout layout}]) (when (or (not (and file-loaded? page-id)) ;; in wasm renderer, extend the pixel loader until the first frame is rendered ;; but do not apply it when switching pages diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index db8e378e19..d239dbd1e3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -539,16 +539,18 @@ [:map [:values schema:layout-item-props-schema] [:applied-tokens [:maybe [:map-of :keyword :string]]] - [:ids [::sm/vec ::sm/uuid]] - [:v-sizing {:optional true} [:maybe [:enum :fill :fix :auto]]]]) + [:ids [::sm/vec ::sm/uuid]]]) (mf/defc layout-size-constraints* {::mf/private true ::mf/schema (sm/schema schema:layout-size-constraints)} - [{:keys [values v-sizing ids applied-tokens] :as props}] + [{:keys [values ids applied-tokens] :as props}] (let [token-numeric-inputs (features/use-feature "tokens/numeric-input") + v-sizing + (:layout-item-v-sizing values) + min-w (get values :layout-item-min-w) max-w (get values :layout-item-max-w) @@ -914,5 +916,4 @@ (= v-sizing :fill)) [:> layout-size-constraints* {:ids ids :values values - :applied-tokens applied-tokens - :v-sizing v-sizing}])])])) + :applied-tokens applied-tokens}])])])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 7d1b778466..98b1178b75 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -81,6 +81,7 @@ selected)) (mf/defc viewport-classic* + {::mf/private true} [{:keys [selected wglobal layout file page palete-size]}] (let [{:keys [edit-path panning @@ -108,8 +109,8 @@ ;; DEREFS drawing (mf/deref refs/workspace-drawing) focus (mf/deref refs/workspace-focus-selected) - file-id (get file :id) + vern (get file :vern) page-id (get page :id) objects (get page :objects) background (get page :background clr/canvas) @@ -340,7 +341,7 @@ :opacity 0.6}} (when (and (:can-edit permissions) (not read-only?)) [:& stvh/viewport-texts - {:key (dm/str "texts-" page-id) + {:key (dm/str "viewport-texts-" page-id "-" vern) :page-id page-id :objects objects :modifiers modifiers @@ -366,7 +367,7 @@ :xmlnsXlink "http://www.w3.org/1999/xlink" :xmlns:penpot "https://penpot.app/xmlns" :preserveAspectRatio "xMidYMid meet" - :key (str "render" page-id) + :key (dm/str "viewport-svg-" page-id "-" vern) :width (:width vport 0) :height (:height vport 0) :view-box (utils/format-viewbox vbox) @@ -400,7 +401,7 @@ [:& (mf/provider ctx/current-vbox) {:value vbox'} [:& (mf/provider use/include-metadata-ctx) {:value (dbg/enabled? :show-export-metadata)} ;; Render root shape - [:& shapes/root-shape {:key page-id + [:& shapes/root-shape {:key (str page-id) :objects base-objects :active-frames @active-frames}]]]] @@ -408,7 +409,7 @@ {:xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" :preserveAspectRatio "xMidYMid meet" - :key (str "viewport" page-id) + :key (dm/str "viewport-controls-" page-id "-" vern) :view-box (utils/format-viewbox vbox) :ref on-viewport-ref :class (dm/str @cursor (when drawing-tool " drawing") " " (stl/css :viewport-controls)) @@ -719,7 +720,7 @@ (not= @hover-top-frame-id (:id frame))) [:& grid-layout/editor {:zoom zoom - :key (dm/str (:id frame)) + :key (dm/str "viewport-frame-" (:id frame)) :objects base-objects :modifiers modifiers :shape frame @@ -733,8 +734,11 @@ :bottom-padding (when palete-size (+ palete-size 8))}]]]]])) (mf/defc viewport* - [props] - (let [wasm-renderer-enabled? (features/use-feature "render-wasm/v1")] - (if ^boolean wasm-renderer-enabled? - [:> viewport.wasm/viewport* props] - [:> viewport-classic* props]))) + [{:keys [file page] :as props}] + (let [vern (get file :vern) + page-id (get page :id) + render-wasm? (features/use-feature "render-wasm/v1")] + [:* {:key (dm/str "viewport-" page-id "-" vern)} + (if ^boolean render-wasm? + [:> viewport.wasm/viewport* props] + [:> viewport-classic* props])])) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 8cb0910f1f..c49034de3a 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -79,7 +79,7 @@ (apply-modifiers-to-objects objects (select-keys (into {} modifiers) selected))) (mf/defc viewport* - [{:keys [selected wglobal layout file page palete-size file-version-id]}] + [{:keys [selected wglobal layout file page palete-size]}] (let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check ;; that the new parameter is sent @@ -111,6 +111,7 @@ workspace-editor-state (mf/deref refs/workspace-editor-state) file-id (get file :id) + vern (get file :vern) objects (get page :objects) page-id (get page :id) background (get page :background clr/canvas) @@ -154,7 +155,7 @@ canvas-ref (mf/use-ref nil) text-editor-ref (mf/use-ref nil) - last-file-version-id-ref (mf/use-ref nil) + last-vern-ref (mf/use-ref nil) ;; STATE REFS disable-paste-ref (mf/use-ref false) @@ -391,10 +392,11 @@ (when (and @canvas-init? preview-blend) (wasm.api/request-render "with-effect"))) - (mf/with-effect [@canvas-init? file-version-id zoom vbox background] + (mf/with-effect [@canvas-init? vern zoom vbox background] (when @canvas-init? (if (not @initialized?) (do + (mf/set-ref-val! last-vern-ref vern) ;; Initial file open uses the same transition workflow as page switches, ;; but with a solid background-color blurred placeholder. (wasm.api/start-initial-load-transition! background) @@ -402,14 +404,12 @@ ;; blank canvas (first load) visible while shapes load. ;; The loading overlay is suppressed because on-shapes-ready ;; is set. - (wasm.api/initialize-viewport - base-objects zoom vbox :background background) - (reset! initialized? true) - (mf/set-ref-val! last-file-version-id-ref file-version-id)) - (when (and (some? file-version-id) - (not= file-version-id (mf/ref-val last-file-version-id-ref))) (wasm.api/initialize-viewport base-objects zoom vbox :background background) - (mf/set-ref-val! last-file-version-id-ref file-version-id))))) + (reset! initialized? true)) + + (when (and (some? vern) (not= vern (mf/ref-val last-vern-ref))) + (wasm.api/initialize-viewport base-objects zoom vbox :background background) + (mf/set-ref-val! last-vern-ref vern))))) (mf/with-effect [focus] (when (and @canvas-init? @initialized?)