diff --git a/common/src/app/common/path_names.cljc b/common/src/app/common/path_names.cljc index de96093155..07b66f5773 100644 --- a/common/src/app/common/path_names.cljc +++ b/common/src/app/common/path_names.cljc @@ -55,6 +55,17 @@ Some naming conventions: (->> (split-path path) (join-path))) +(defn clean-valid-name + "The path string `name` with its empty items removed (`clean-path`), or nil + when it is not a valid name: blank, or ending in a separator." + [name] + (let [name (str/trim name) + clean (clean-path name)] + (when (and (not (str/ends-with? name "/")) + (string? clean) + (not (str/blank? clean))) + clean))) + (defn merge-path-item "Put the item at the end of the path." [path name] diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index f61e08e783..5e405a0995 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -388,6 +388,13 @@ [(fix-parent-id new-shape) (map fix-parent-id new-shapes)]))) +(defn head-into-variant? + "Whether placing `shapes` under `parent-id` puts a component head inside + a variant." + [objects parent-id shapes] + (and (some ctk/instance-head? shapes) + (some ctk/is-variant? (cfh/get-parents-with-self objects parent-id)))) + (defn get-first-valid-parent "Go through the parents until we find a shape that is not a copy of a component nor a variant container." diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index ee58c88edd..adfc71521b 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -761,15 +761,11 @@ (watch [_ state _] (when-let [shape-id (d/nilv shape-id (dm/get-in state [:workspace-local :shape-for-rename]))] (let [shape (dsh/lookup-shape state shape-id) - name (str/trim name) - clean-name (cpn/clean-path name) - valid? (and (not (str/ends-with? name "/")) - (string? clean-name) - (not (str/blank? clean-name))) + clean-name (cpn/clean-valid-name name) component-id (:component-id shape) undo-id (js/Symbol)] - (when valid? + (when (some? clean-name) (if (ctc/is-variant-container? shape) ;; Rename the full variant when it is a variant container (rx/of (dwva/rename-variant shape-id clean-name)) diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 95a60c5d32..55234ccd8b 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -975,12 +975,7 @@ (pcb/update-shapes [parent-id] #(ctl/add-children-to-cell % selected all-objects drop-cell))) - add-component-to-variant? (and - ;; Any of the shapes is a head - (some ctk/instance-head? orig-shapes) - ;; Any ancestor of the destination parent is a variant - (->> (cfh/get-parents-with-self page-objects parent-id) - (some ctk/is-variant?))) + add-component-to-variant? (ctn/head-into-variant? page-objects parent-id orig-shapes) undo-id (js/Symbol)] (rx/concat diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 92637af807..19dd7fb236 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -389,7 +389,7 @@ (cfh/clean-loops objects)) selected-objects (map #(get objects %) selected) ;; We don't want to change the structure of component copies - can-make-component (every? true? (map #(ctn/valid-shape-for-component? objects %) selected-objects))] + can-make-component (every? #(ctn/valid-shape-for-component? objects %) selected-objects)] (when can-make-component (rx/of (-> (add-component2 id-ref selected) @@ -406,7 +406,7 @@ (cfh/clean-loops objects)) selected-objects (map #(get objects %) selected) ;; We don't want to change the structure of component copies - can-make-component (every? true? (map #(ctn/valid-shape-for-component? objects %) selected-objects)) + can-make-component (every? #(ctn/valid-shape-for-component? objects %) selected-objects) added-components (map (fn [id] (with-meta (add-component2 [id]) {:multiple true})) @@ -445,15 +445,11 @@ (ptk/reify ::rename-component-and-main-instance ptk/WatchEvent (watch [_ state _] - (let [name (str/trim name) - clean-name (cpn/clean-path name) - valid? (and (not (str/ends-with? name "/")) - (string? clean-name) - (not (str/blank? clean-name))) + (let [clean-name (cpn/clean-valid-name name) data (dsh/lookup-file-data state) component (dm/get-in data [:components component-id])] - (when (and valid? component) + (when (and (some? clean-name) component) (let [shape-id (:main-instance-id component) page-id (:main-instance-page component)] @@ -690,6 +686,18 @@ (assoc :update-layout update-layout?))] (rx/of (rt/nav :workspace params ::rt/new-window true)))))) +(defn go-to-page-then + "Open the page `page-id` and, once it is initialized, emit the events of + `(f)`." + [stream page-id f] + (rx/merge + (->> stream + (rx/filter (ptk/type? ::dwpg/initialize-page)) + (rx/take 1) + (rx/observe-on :async) + (rx/mapcat (fn [_] (f)))) + (rx/of (dcm/go-to-workspace :page-id page-id)))) + (defn go-to-local-component ;; id is the id of the component to go ;; additional-ids are ids of additional components on the same page @@ -719,16 +727,6 @@ (when update-layout? (rx/of (ptk/data-event :layout/update {:ids parent-ids}))))))) - redirect-to-page - (fn [page-id ids] - (rx/merge - (->> stream - (rx/filter (ptk/type? ::dwpg/initialize-page)) - (rx/take 1) - (rx/observe-on :async) - (rx/mapcat (fn [_] (select-and-zoom ids)))) - (rx/of (dcm/go-to-workspace :page-id page-id)))) - get-main-instance-id (fn [id page-id] (let [component (dm/get-in data [:components id])] @@ -744,7 +742,7 @@ (when (some? page-id) (if (= page-id current-page-id) (select-and-zoom ids) - (redirect-to-page page-id ids))))))))) + (go-to-page-then stream page-id #(select-and-zoom ids)))))))))) (defn library-thumbnails-fetched [thumbnails] @@ -958,20 +956,6 @@ (component-sync-events state component-id file-id undo-group) [(dwu/commit-undo-transaction undo-id)]))))))) -(defn launch-component-sync - "Launch a sync of the current file and of the library file of the given - component, in one undo entry." - [component-id file-id undo-group] - (ptk/reify ::launch-component-sync - ptk/WatchEvent - (watch [_ state _] - (let [undo-id (js/Symbol)] - (apply rx/of - (concat - [(dwu/start-undo-transaction undo-id)] - (component-sync-events state component-id file-id undo-group) - [(dwu/commit-undo-transaction undo-id)])))))) - (defn update-component-thumbnail "Update the thumbnail of the component with the given id, in the current file and in the imported libraries. @@ -1281,19 +1265,19 @@ ;; This runs asynchronously on the next tick. ;; Filter libraries to only those that would produce actual sync changes. (let [libraries (dsh/lookup-libraries state)] - (filter (fn [library] - (seq (:redo-changes - (cll/generate-sync-file-changes - (pcb/empty-changes) - nil - nil - file-id - nil - (:id library) - libraries - file-id - true)))) - libraries-need-sync)))) + (filterv (fn [library] + (seq (:redo-changes + (cll/generate-sync-file-changes + (pcb/empty-changes) + nil + nil + file-id + nil + (:id library) + libraries + file-id + true)))) + libraries-need-sync)))) (rx/filter seq) (rx/map (fn [libraries-with-changes] (let [do-more-info @@ -1344,18 +1328,23 @@ :save-undo? false})))))) (defn component-changed - "Notify that the component with the given id has changed, so it needs to be updated - in the current file and in the copies. And also update its thumbnails." + "Notify that the component with the given id has changed: mark it modified + and sync its copies, in the current file and in its library, in one undo + entry. The watcher also updates its thumbnails." [component-id file-id undo-group] (ptk/reify ::component-changed cljs.core/IDeref (-deref [_] [component-id file-id]) ptk/WatchEvent - (watch [_ _ _] - (rx/of - (touch-component component-id) - (launch-component-sync component-id file-id undo-group))))) + (watch [_ state _] + (let [undo-id (js/Symbol)] + (apply rx/of + (concat + [(touch-component component-id) + (dwu/start-undo-transaction undo-id)] + (component-sync-events state component-id file-id undo-group) + [(dwu/commit-undo-transaction undo-id)])))))) (defn watch-component-changes "Watch the state for changes that affect to any main instance. If a change is detected will call diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 587956c084..347c997f6e 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -627,12 +627,7 @@ ids :ignore-parents? ignore-parents?)) - add-component-to-variant? (and - ;; Any of the shapes is a head - (some (comp ctc/instance-head? objects) ids) - ;; Any ancestor of the destination parent is a variant - (->> (cfh/get-parents-with-self objects parent-id) - (some ctc/is-variant?))) + add-component-to-variant? (ctn/head-into-variant? objects parent-id (map objects ids)) add-new-variant? (and ;; The parent is a variant container diff --git a/frontend/src/app/main/data/workspace/variants.cljs b/frontend/src/app/main/data/workspace/variants.cljs index 9dfd89a3f5..f8f320f5d4 100644 --- a/frontend/src/app/main/data/workspace/variants.cljs +++ b/frontend/src/app/main/data/workspace/variants.cljs @@ -28,7 +28,6 @@ [app.main.data.helpers :as dsh] [app.main.data.workspace.colors :as cl] [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.pages :as dwpg] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] @@ -620,20 +619,10 @@ ;; microtask of previous events execute before the ;; commit (->> (rx/of (dwu/commit-undo-transaction undo-id)) - (rx/observe-on :async))))))) - - redirect-to-page - (fn [page-id] - (rx/merge - (->> stream - (rx/filter (ptk/type? ::dwpg/initialize-page)) - (rx/take 1) - (rx/observe-on :async) - (rx/mapcat (fn [_] (combine page-id)))) - (rx/of (dcm/go-to-workspace :page-id page-id))))] + (rx/observe-on :async)))))))] (if (and page-id (not= page-id current-page)) - (redirect-to-page page-id) + (dwl/go-to-page-then stream page-id #(combine page-id)) (combine current-page)))))) (defn valid-components-for-variants? diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 7cb59b75e5..cbd77eacba 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -676,7 +676,7 @@ single? (= (count shapes) 1) objects (deref refs/workspace-page-objects) can-make-component (every? true? (map #(ctn/valid-shape-for-component? objects %) shapes)) - components-menu-entries (cmm/generate-components-menu-entries shapes) + components-menu-entries (cmm/use-components-menu-entries shapes) variant-container? (and single? (ctk/is-variant-container? (first shapes))) all-main? (every? ctk/main-instance? shapes) any-variant? (some ctk/is-variant? shapes) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs index ba3ff9786e..243f47c196 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs @@ -392,7 +392,9 @@ :show-grids? true :is-hidden is-hidden}]))) -(defn generate-components-menu-entries +(defn use-components-menu-entries + "The entries of the component menu of `shapes`: a hook, as it reads the + current file and page from the context." [shapes & {:keys [for-design-tab?]}] (let [multi (> (count shapes) 1) copies (filter ctk/in-component-copy? shapes) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 62b2cea1e3..9d7bd0bc36 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -1009,7 +1009,7 @@ (fn [] (swap! state* update :render inc))) - menu-entries (cmm/generate-components-menu-entries shapes {:for-design-tab? true}) + menu-entries (cmm/use-components-menu-entries shapes {:for-design-tab? true}) path (->> component (:path) (cpn/split-path) (cpn/join-path-with-dot))] (when (seq shapes)