diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index cdb8f7e3d9..258f7cb732 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -110,21 +110,25 @@ "Get the parent top shape linked to a component for this shape, if any" ([objects shape] (get-component-shape objects shape nil)) ([objects shape {:keys [allow-main?] :or {allow-main? false} :as options}] - (cond - (nil? shape) - nil + (let [parent (get objects (:parent-id shape))] + (cond + (nil? shape) + nil - (cfh/root? shape) - nil + (cfh/root? shape) + nil - (ctk/instance-root? shape) - shape + (ctk/instance-root? shape) + shape - (and (not (ctk/in-component-copy? shape)) (not allow-main?)) - nil + (and (not (ctk/in-component-copy? shape)) (not allow-main?)) + nil - :else - (get-component-shape objects (get objects (:parent-id shape)) options)))) + (and (ctk/instance-head? shape) (not (ctk/in-component-copy? parent))) + shape ; This case is a copy root inside a main component + + :else + (get-component-shape objects parent options))))) (defn get-head-shape "Get the parent top or nested shape linked to a component for this shape, if any" diff --git a/frontend/resources/styles/common/refactor/basic-rules.scss b/frontend/resources/styles/common/refactor/basic-rules.scss index 2393046820..29a9219f0b 100644 --- a/frontend/resources/styles/common/refactor/basic-rules.scss +++ b/frontend/resources/styles/common/refactor/basic-rules.scss @@ -583,6 +583,7 @@ padding: $s-32; border-radius: $br-8; background-color: var(--modal-background-color); + border: $s-2 solid var(--modal-border-color); min-width: $s-364; min-height: $s-192; max-width: $s-512; @@ -843,6 +844,7 @@ z-index: $z-index-10; color: var(--title-foreground-color-hover); background-color: var(--menu-background-color); + border: $s-2 solid var(--panel-border-color); } .menu-item-base { @@ -904,6 +906,7 @@ overflow-x: hidden; background-color: var(--menu-background-color); color: var(--menu-foreground-color); + border: $s-2 solid var(--panel-border-color); } .select-wrapper { diff --git a/frontend/resources/styles/common/refactor/design-tokens.scss b/frontend/resources/styles/common/refactor/design-tokens.scss index 6e227fd21b..d19935e44f 100644 --- a/frontend/resources/styles/common/refactor/design-tokens.scss +++ b/frontend/resources/styles/common/refactor/design-tokens.scss @@ -13,6 +13,8 @@ --scrollbar-background-color: var(--color-foreground-secondary); --panel-background-color: var(--color-background-primary); + --panel-border-color: var(--color-background-quaternary); + --app-background: var(--color-background-primary); --loader-background: var(--color-background-primary); --panel-title-background-color: var(--color-background-secondary); diff --git a/frontend/resources/styles/common/refactor/mixins.scss b/frontend/resources/styles/common/refactor/mixins.scss index 856d6ae033..f988cd0df8 100644 --- a/frontend/resources/styles/common/refactor/mixins.scss +++ b/frontend/resources/styles/common/refactor/mixins.scss @@ -70,6 +70,21 @@ line-height: 1.2; } +@mixin headlineMediumTypography { + font-family: "worksans", sans-serif; + font-size: $fs-16; + line-height: 1.4; + text-transform: uppercase; + font-weight: normal; +} + +@mixin bodyLargeTypography { + font-family: "worksans", sans-serif; + font-size: $fs-16; + line-height: 1.5; + font-weight: normal; +} + @mixin textEllipsis { max-width: 99%; overflow: hidden; diff --git a/frontend/src/app/main/data/workspace/layout.cljs b/frontend/src/app/main/data/workspace/layout.cljs index 5b7209c670..9c7ad57a0d 100644 --- a/frontend/src/app/main/data/workspace/layout.cljs +++ b/frontend/src/app/main/data/workspace/layout.cljs @@ -20,7 +20,7 @@ :document-history :colorpalette :element-options - :rules + :rulers :display-grid :snap-grid :scale-text @@ -50,7 +50,7 @@ #{:sitemap :layers :element-options - :rules + :rulers :display-grid :snap-grid :dynamic-alignment diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 59864afbbc..269701c78d 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -640,6 +640,21 @@ :path-params path-params :query-params query-params})))))) +(defn library-thumbnails-fetched + [thumbnails] + (ptk/reify ::library-thumbnails-fetched + ptk/UpdateEvent + (update [_ state] + (update state :workspace-thumbnails merge thumbnails)))) + +(defn fetch-library-thumbnails + [library-id] + (ptk/reify ::fetch-library-thumbnails + ptk/WatchEvent + (watch [_ _ _] + (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) + (rx/map library-thumbnails-fetched))))) + (defn ext-library-changed [library-id modified-at revn changes] (dm/assert! (uuid? library-id)) @@ -654,11 +669,15 @@ ch/process-changes changes))) ptk/WatchEvent - (watch [_ _ _] - (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) - (rx/map (fn [thumbnails] - (fn [state] - (assoc-in state [:workspace-libraries library-id :thumbnails] thumbnails)))))))) + (watch [_ _ stream] + (let [stopper-s (rx/filter (ptk/type? ::ext-library-changed) stream)] + (->> + (rx/merge + (->> (rx/of library-id) + (rx/delay 5000) + (rx/map fetch-library-thumbnails))) + + (rx/take-until stopper-s)))))) (defn reset-component "Cancels all modifications in the shape with the given id, and all its children, in @@ -1261,7 +1280,7 @@ (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) (rx/map (fn [thumbnails] (fn [state] - (assoc-in state [:workspace-libraries library-id :thumbnails] thumbnails)))))))))) + (update state :workspace-thumbnails merge thumbnails)))))))))) (defn unlink-file-from-library [file-id library-id] diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 322c4c637e..8c622b318d 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -1081,7 +1081,10 @@ (:id component-parent-shape) (get page :objects) :update-new-shape update-new-shape - :update-original-shape update-original-shape) + :update-original-shape update-original-shape + :frame-id (if (cfh/frame-shape? component-parent-shape) + (:id component-parent-shape) + (:frame-id component-parent-shape))) add-obj-change (fn [changes shape'] (update changes :redo-changes conj @@ -1090,14 +1093,10 @@ {:type :add-obj :id (:id shape') :parent-id (:parent-id shape') + :frame-id (:frame-id shape') :index index :ignore-touched true - :obj shape'}) - - (ctn/page? component-container) - (assoc :frame-id (if (= (:type shape') :frame) - (:id shape') - (:frame-id shape')))))) + :obj shape'})))) mod-obj-change (fn [changes shape'] (update changes :redo-changes conj diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 28e2be350d..ccbb35e7db 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -351,10 +351,10 @@ ;; MAIN MENU - :toggle-rules {:tooltip (ds/meta-shift "R") + :toggle-rulers {:tooltip (ds/meta-shift "R") :command (ds/c-mod "shift+r") :subsections [:main-menu] - :fn #(st/emit! (toggle-layout-flag :rules))} + :fn #(st/emit! (toggle-layout-flag :rulers))} :select-all {:tooltip (ds/meta "A") :command (ds/c-mod "a") diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 6582a01c88..bc24e69e0f 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -115,16 +115,10 @@ (ptk/reify ::assoc-thumbnail ptk/UpdateEvent (update [_ state] - (let [prev-uri (dm/get-in state [:workspace-thumbnails object-id]) - current-file-id (:current-file-id state)] + (let [prev-uri (dm/get-in state [:workspace-thumbnails object-id])] (some->> prev-uri (vreset! prev-uri*)) (l/trc :hint "assoc thumbnail" :object-id object-id :uri uri) - - #_(update state :workspace-thumbnails assoc object-id uri) - (if (thc/file-id? object-id current-file-id) - (update state :workspace-thumbnails assoc object-id uri) - (let [file-id (thc/get-file-id object-id)] - (update-in state [:workspace-libraries file-id :thumbnails] assoc object-id uri))))) + (update state :workspace-thumbnails assoc object-id uri))) ptk/EffectEvent (effect [_ _ _] diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 434bca57e9..7d3ee4fec8 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -212,8 +212,8 @@ (def snap-pixel? (l/derived #(contains? % :snap-pixel-grid) workspace-layout)) -(def rules? - (l/derived #(contains? % :rules) workspace-layout)) +(def rulers? + (l/derived #(contains? % :rulers) workspace-layout)) (def workspace-file "A ref to a striped vision of file (without data)." @@ -490,6 +490,9 @@ (dm/get-in state [:viewer-local :zoom-type])) st/state)) +(def workspace-thumbnails + (l/derived :workspace-thumbnails st/state)) + (defn workspace-thumbnail-by-id [object-id] (l/derived diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index 5b164429db..374ec676bb 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -46,7 +46,7 @@ (not (contains? focus id)))) (= type :guide) - (or (not (contains? layout :rules)) + (or (not (contains? layout :rulers)) (not (contains? layout :snap-guides)) (and (d/not-empty? focus) (not (contains? focus frame-id)))) diff --git a/frontend/src/app/main/ui/alert.scss b/frontend/src/app/main/ui/alert.scss index 2d4a8b1d78..33e202118d 100644 --- a/frontend/src/app/main/ui/alert.scss +++ b/frontend/src/app/main/ui/alert.scss @@ -21,7 +21,7 @@ } .modal-title { - @include tabTitleTipography; + @include headlineMediumTypography; color: var(--modal-title-foreground-color); } @@ -30,12 +30,12 @@ } .modal-content { - @include titleTipography; + @include bodyLargeTypography; margin-bottom: $s-24; } .modal-hint { - @include titleTipography; + @include bodyLargeTypography; } .action-buttons { @@ -56,7 +56,7 @@ .modal-scd-msg, .modal-subtitle, .modal-msg { - @include titleTipography; + @include bodyLargeTypography; color: var(--modal-text-foreground-color); line-height: 1.5; } diff --git a/frontend/src/app/main/ui/components/context_menu_a11y.scss b/frontend/src/app/main/ui/components/context_menu_a11y.scss index bd4e89fabf..2aa9fcea96 100644 --- a/frontend/src/app/main/ui/components/context_menu_a11y.scss +++ b/frontend/src/app/main/ui/components/context_menu_a11y.scss @@ -32,6 +32,7 @@ margin: $s-0; padding: $s-4; border-radius: $br-8; + border: $s-2 solid var(--panel-border-color); background-color: var(--menu-background-color); overflow: auto; & .separator { diff --git a/frontend/src/app/main/ui/confirm.scss b/frontend/src/app/main/ui/confirm.scss index ea944f3e5f..0b4b08202b 100644 --- a/frontend/src/app/main/ui/confirm.scss +++ b/frontend/src/app/main/ui/confirm.scss @@ -22,7 +22,7 @@ } .modal-title { - @include tabTitleTipography; + @include headlineMediumTypography; color: var(--modal-title-foreground-color); } @@ -31,7 +31,7 @@ } .modal-content { - @include titleTipography; + @include bodyLargeTypography; margin-bottom: $s-24; } @@ -49,7 +49,7 @@ } } .modal-component-name { - @include titleTipography; + @include bodyLargeTypography; } .modal-hint { @@ -74,7 +74,6 @@ .modal-scd-msg, .modal-subtitle, .modal-msg { - @include titleTipography; + @include bodyLargeTypography; color: var(--modal-text-foreground-color); - line-height: 1.5; } diff --git a/frontend/src/app/main/ui/dashboard/change_owner.scss b/frontend/src/app/main/ui/dashboard/change_owner.scss index 3c91c1a92b..2da4ec233c 100644 --- a/frontend/src/app/main/ui/dashboard/change_owner.scss +++ b/frontend/src/app/main/ui/dashboard/change_owner.scss @@ -12,7 +12,6 @@ .modal-container { @extend .modal-container-base; - border: $s-1 solid var(--modal-border-color); } .modal-header { diff --git a/frontend/src/app/main/ui/dashboard/import.scss b/frontend/src/app/main/ui/dashboard/import.scss index ade895916e..4b76f7d6d2 100644 --- a/frontend/src/app/main/ui/dashboard/import.scss +++ b/frontend/src/app/main/ui/dashboard/import.scss @@ -12,7 +12,6 @@ .modal-container { @extend .modal-container-base; - border: $s-1 solid var(--modal-border-color); } .modal-header { diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 1cb240817c..cbbbb421ec 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -178,6 +178,7 @@ [:div {:class (stl/css :invitation-row)} [:& fm/multi-input {:type "email" + :class (stl/css :email-input) :name :emails :auto-focus? true :trim true diff --git a/frontend/src/app/main/ui/dashboard/team.scss b/frontend/src/app/main/ui/dashboard/team.scss index 32afa09a09..be1f9a6139 100644 --- a/frontend/src/app/main/ui/dashboard/team.scss +++ b/frontend/src/app/main/ui/dashboard/team.scss @@ -588,13 +588,12 @@ width: $s-400; padding: $s-32; background-color: var(--modal-background-color); - border: $s-1 solid var(--modal-border-color); &.hero { top: $s-216; right: $s-32; } .modal-title { - @include tabTitleTipography; + @include headlineMediumTypography; height: $s-32; color: var(--modal-title-foreground-color); } @@ -644,8 +643,9 @@ .role-select { @include flexColumn; + row-gap: $s-8; .role-title { - @include titleTipography; + @include bodyLargeTypography; margin: 0; color: var(--modal-title-foreground-color); } @@ -676,7 +676,6 @@ .modal-container { @extend .modal-container-base; - border: $s-1 solid var(--modal-border-color); } .modal-header { @@ -725,3 +724,7 @@ @extend .modal-cancel-btn; } } + +.email-input { + @extend .input-base; +} diff --git a/frontend/src/app/main/ui/dashboard/team_form.scss b/frontend/src/app/main/ui/dashboard/team_form.scss index 12f33182fc..8f401e2273 100644 --- a/frontend/src/app/main/ui/dashboard/team_form.scss +++ b/frontend/src/app/main/ui/dashboard/team_form.scss @@ -12,7 +12,6 @@ .modal-container { @extend .modal-container-base; - border: $s-1 solid var(--modal-border-color); } .modal-header { diff --git a/frontend/src/app/main/ui/delete_shared.scss b/frontend/src/app/main/ui/delete_shared.scss index 84ec2e9b54..bcd69bfba2 100644 --- a/frontend/src/app/main/ui/delete_shared.scss +++ b/frontend/src/app/main/ui/delete_shared.scss @@ -22,7 +22,7 @@ } .modal-title { - @include tabTitleTipography; + @include headlineMediumTypography; color: var(--modal-title-foreground-color); } @@ -40,10 +40,8 @@ } .element-list { - @include titleTipography; - .list-item { - @include titleTipography; - } + @include bodyLargeTypography; + color: var(--modal-text-foreground-color); } .action-buttons { @@ -64,7 +62,7 @@ .modal-scd-msg, .modal-subtitle, .modal-msg { - @include titleTipography; + @include bodyLargeTypography; color: var(--modal-text-foreground-color); line-height: 1.5; } diff --git a/frontend/src/app/main/ui/export.cljs b/frontend/src/app/main/ui/export.cljs index a9085b3174..35dcc244d7 100644 --- a/frontend/src/app/main/ui/export.cljs +++ b/frontend/src/app/main/ui/export.cljs @@ -138,8 +138,8 @@ (cond-> (:name shape) suffix (str suffix))] (when (:scale export) [:div {:class (stl/css :selection-scale)} - (dm/str (ust/format-precision (* width (:scale export)) 2) "px" - (ust/format-precision (* height (:scale export)) 2) "px")]) + (dm/str (ust/format-precision (* width (:scale export)) 2) "x" + (ust/format-precision (* height (:scale export)) 2))]) (when (:type export) [:div {:class (stl/css :selection-extension)} diff --git a/frontend/src/app/main/ui/export.scss b/frontend/src/app/main/ui/export.scss index 16e280ba9c..0f687fe618 100644 --- a/frontend/src/app/main/ui/export.scss +++ b/frontend/src/app/main/ui/export.scss @@ -41,14 +41,14 @@ .title-text { @include flexCenter; - @include titleTipography; + @include bodyLargeTypography; padding: 0; margin: 0; color: var(--modal-title-foreground-color); padding-left: $s-4; } .progress { - @include titleTipography; + @include bodyLargeTypography; padding-left: $s-8; margin: 0; color: var(--modal-text-foreground-color); @@ -86,7 +86,7 @@ } .modal-title { - @include tabTitleTipography; + @include headlineMediumTypography; color: var(--modal-title-foreground-color); } @@ -125,7 +125,7 @@ } } .selection-title { - @include titleTipography; + @include bodyLargeTypography; color: var(--modal-text-foreground-color); } } @@ -160,7 +160,9 @@ border-radius: $br-8; .selection-btn { @include buttonStyle; - @include flexRow; + display: grid; + grid-template-columns: min-content auto 1fr auto auto; + align-items: center; width: 100%; height: 10%; gap: $s-8; @@ -176,21 +178,21 @@ } } .selection-name { - @include titleTipography; + @include bodyLargeTypography; @include textEllipsis; flex-grow: 1; color: var(--modal-text-foreground-color); text-align: start; } .selection-scale { - @include titleTipography; + @include bodyLargeTypography; @include textEllipsis; min-width: $s-108; padding: $s-12; color: var(--modal-text-foreground-color); } .selection-extension { - @include titleTipography; + @include bodyLargeTypography; @include textEllipsis; min-width: $s-72; padding: $s-12; @@ -199,7 +201,6 @@ } .image-wrapper { @include flexCenter; - height: 100%; min-height: $s-32; min-width: $s-32; background-color: var(--app-white); @@ -231,9 +232,8 @@ .modal-scd-msg, .modal-subtitle, .modal-msg { - @include titleTipography; + @include bodyLargeTypography; color: var(--modal-text-foreground-color); - line-height: 1.5; } .export-option { @@ -243,7 +243,8 @@ label { align-items: flex-start; .modal-subtitle { - @include tabTitleTipography; + // @include tabTitleTipography; + @include bodyLargeTypography; color: var(--modal-title-foreground-color); } } @@ -254,7 +255,7 @@ .option-content { @include flexColumn; - @include titleTipography; + @include bodyLargeTypography; } .file-entry { @@ -271,7 +272,7 @@ } } .file-name-label { - @include titleTipography; + @include bodyLargeTypography; } } &.loading { diff --git a/frontend/src/app/main/ui/onboarding.scss b/frontend/src/app/main/ui/onboarding.scss index b820261663..7c2e1b22f6 100644 --- a/frontend/src/app/main/ui/onboarding.scss +++ b/frontend/src/app/main/ui/onboarding.scss @@ -17,7 +17,6 @@ padding: 0; margin: 0; min-width: $s-712; - border: $s-1 solid var(--modal-border-color); } .modal-left { diff --git a/frontend/src/app/main/ui/onboarding/newsletter.scss b/frontend/src/app/main/ui/onboarding/newsletter.scss index a407dcfa86..350b75499b 100644 --- a/frontend/src/app/main/ui/onboarding/newsletter.scss +++ b/frontend/src/app/main/ui/onboarding/newsletter.scss @@ -14,7 +14,6 @@ @extend .modal-container-base; position: relative; min-width: $s-712; - border: $s-1 solid var(--modal-border-color); } .modal-header { diff --git a/frontend/src/app/main/ui/onboarding/questions.scss b/frontend/src/app/main/ui/onboarding/questions.scss index e2024f7dd3..999f41d12e 100644 --- a/frontend/src/app/main/ui/onboarding/questions.scss +++ b/frontend/src/app/main/ui/onboarding/questions.scss @@ -14,7 +14,6 @@ @extend .modal-container-base; min-width: $s-512; position: relative; - border: $s-1 solid var(--modal-border-color); } // STEP CONTAINER diff --git a/frontend/src/app/main/ui/onboarding/team_choice.scss b/frontend/src/app/main/ui/onboarding/team_choice.scss index 1ebf067988..42aaaef3d2 100644 --- a/frontend/src/app/main/ui/onboarding/team_choice.scss +++ b/frontend/src/app/main/ui/onboarding/team_choice.scss @@ -16,7 +16,6 @@ display: flex; position: relative; min-width: $s-712; - border: $s-1 solid var(--modal-border-color); } .modal-left { diff --git a/frontend/src/app/main/ui/settings/access_tokens.scss b/frontend/src/app/main/ui/settings/access_tokens.scss index 44bfaf9c0a..e843c83f0d 100644 --- a/frontend/src/app/main/ui/settings/access_tokens.scss +++ b/frontend/src/app/main/ui/settings/access_tokens.scss @@ -175,7 +175,6 @@ .modal-container { @extend .modal-container-base; min-width: $s-408; - border: $s-1 solid var(--modal-border-color); } .modal-header { diff --git a/frontend/src/app/main/ui/settings/change_email.scss b/frontend/src/app/main/ui/settings/change_email.scss index 97da12aeca..e6f1500764 100644 --- a/frontend/src/app/main/ui/settings/change_email.scss +++ b/frontend/src/app/main/ui/settings/change_email.scss @@ -13,7 +13,6 @@ .modal-container { @extend .modal-container-base; min-width: $s-408; - border: $s-1 solid var(--modal-border-color); } .modal-header { diff --git a/frontend/src/app/main/ui/settings/delete_account.scss b/frontend/src/app/main/ui/settings/delete_account.scss index 33e72dd4ac..148781eba1 100644 --- a/frontend/src/app/main/ui/settings/delete_account.scss +++ b/frontend/src/app/main/ui/settings/delete_account.scss @@ -13,7 +13,6 @@ .modal-container { @extend .modal-container-base; min-width: $s-408; - border: $s-1 solid var(--modal-border-color); } .modal-header { diff --git a/frontend/src/app/main/ui/workspace/context_menu.scss b/frontend/src/app/main/ui/workspace/context_menu.scss index 4a178c88f8..b83f67bb45 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.scss +++ b/frontend/src/app/main/ui/workspace/context_menu.scss @@ -17,6 +17,7 @@ width: $s-240; padding: $s-4; border-radius: $br-8; + border: $s-2 solid var(--panel-border-color); background-color: var(--menu-background-color); z-index: $z-index-3; } diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 6232f7c231..3262f202b0 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -70,36 +70,22 @@ (mf/defc describe-library-blocks [{:keys [components-count graphics-count colors-count typography-count] :as props}] + [:* + (when (pos? components-count) + [:li {:class (stl/css :element-count)} + (tr "workspace.libraries.components" components-count)]) - (let [last-one (cond - (> colors-count 0) :color - (> graphics-count 0) :graphics - (> components-count 0) :components)] - [:* - (when (pos? components-count) - [:* - [:span {:class (stl/css :element-count)} - (tr "workspace.libraries.components" components-count)] - (when (not= last-one :components) - [:span " · "])]) + (when (pos? graphics-count) + [:li {:class (stl/css :element-count)} + (tr "workspace.libraries.graphics" graphics-count)]) - (when (pos? graphics-count) - [:* - [:span {:class (stl/css :element-count)} - (tr "workspace.libraries.graphics" graphics-count)] - (when (not= last-one :graphics) - [:span " · "])]) + (when (pos? colors-count) + [:li {:class (stl/css :element-count)} + (tr "workspace.libraries.colors" colors-count)]) - (when (pos? colors-count) - [:* - [:span {:class (stl/css :element-count)} - (tr "workspace.libraries.colors" colors-count)] - (when (not= last-one :colors) - [:span " · "])]) - - (when (pos? typography-count) - [:span {:class (stl/css :element-count)} - (tr "workspace.libraries.typography" typography-count)])])) + (when (pos? typography-count) + [:li {:class (stl/css :element-count)} + (tr "workspace.libraries.typography" typography-count)])]) (mf/defc libraries-tab @@ -208,7 +194,7 @@ [:div {:class (stl/css :section-list-item)} [:div [:div {:class (stl/css :item-name)} (tr "workspace.libraries.file-library")] - [:div {:class (stl/css :item-contents)} + [:ul {:class (stl/css :item-contents)} [:& describe-library-blocks {:components-count (count components) :graphics-count (count media) :colors-count (count colors) @@ -229,7 +215,7 @@ :key (dm/str id)} [:div [:div {:class (stl/css :item-name)} name] - [:div {:class (stl/css :item-contents)} + [:ul {:class (stl/css :item-contents)} (let [components-count (count (or (ctkl/components-seq (:data library)) [])) graphics-count (count (dm/get-in library [:data :media] [])) colors-count (count (dm/get-in library [:data :colors] [])) @@ -262,7 +248,7 @@ :key (dm/str id)} [:div [:div {:class (stl/css :item-name)} name] - [:div {:class (stl/css :item-contents)} + [:ul {:class (stl/css :item-contents)} (let [components-count (dm/get-in library [:library-summary :components :count] 0) graphics-count (dm/get-in library [:library-summary :media :count] 0) colors-count (dm/get-in library [:library-summary :colors :count] 0) @@ -376,11 +362,11 @@ :key (dm/str id)} [:div [:div {:class (stl/css :item-name)} name] - [:div {:class (stl/css :item-contents)} (describe-library - (count components) - 0 - (count colors) - (count typographies))]] + [:ul {:class (stl/css :item-contents)} (describe-library + (count components) + 0 + (count colors) + (count typographies))]] [:input {:type "button" :class (stl/css-case :item-update true :disabled updating?) diff --git a/frontend/src/app/main/ui/workspace/libraries.scss b/frontend/src/app/main/ui/workspace/libraries.scss index 38e6836c8d..2664f32095 100644 --- a/frontend/src/app/main/ui/workspace/libraries.scss +++ b/frontend/src/app/main/ui/workspace/libraries.scss @@ -39,7 +39,7 @@ } .modal-title { - @include tabTitleTipography; + @include headlineMediumTypography; margin-bottom: $s-16; color: var(--modal-title-foreground-color); } @@ -70,24 +70,18 @@ max-height: $s-320; margin-top: $s-12; overflow: auto; - padding-right: $s-8; .section-list-item { display: grid; grid-template-columns: 1fr auto; + column-gap: $s-12; margin-bottom: $s-24; &:last-child { margin-bottom: $s-8; } - .item-name { - @include titleTipography; + @include bodyLargeTypography; color: var(--library-name-foreground-color); } - .item-contents { - @include titleTipography; - color: var(--library-content-foreground-color); - } - .item-publish, .item-unpublish { @extend .button-primary; @@ -99,7 +93,7 @@ } .item-update { @extend .button-warning; - @include tabTitleTipography; + @include headlineMediumTypography; height: $s-32; min-width: $s-92; padding: $s-8 $s-24; @@ -143,7 +137,7 @@ } .section-title { - @include titleTipography; + @include bodyLargeTypography; color: var(--modal-title-foreground-color); margin-bottom: $s-12; } @@ -161,7 +155,7 @@ } } .section-list-empty { - @include titleTipography; + @include bodyLargeTypography; @include flexCenter; color: var(--empty-message-foreground-color); @@ -236,6 +230,18 @@ } } +.item-contents { + @include bodyLargeTypography; + color: var(--library-content-foreground-color); + display: flex; + flex-wrap: wrap; +} + .element-count { white-space: nowrap; + + &:not(:last-child)::after { + content: "·"; + margin-inline: $s-4; + } } diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 42d17f70b1..7f964061e7 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -305,14 +305,14 @@ :on-key-down (fn [event] (when (kbd/enter? event) (toggle-flag event))) - :data-test "rules" - :id "file-menu-rules"} + :data-test "rulers" + :id "file-menu-rulers"} [:span {:class (stl/css :item-name)} - (if (contains? layout :rules) + (if (contains? layout :rulers) (tr "workspace.header.menu.hide-rules") (tr "workspace.header.menu.show-rules"))] [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc (sc/get-tooltip :toggle-rules))] + (for [sc (scd/split-sc (sc/get-tooltip :toggle-rulers))] [:span {:class (stl/css :shortcut-key) :key sc} sc])]] diff --git a/frontend/src/app/main/ui/workspace/nudge.scss b/frontend/src/app/main/ui/workspace/nudge.scss index 28d4985791..084645b45f 100644 --- a/frontend/src/app/main/ui/workspace/nudge.scss +++ b/frontend/src/app/main/ui/workspace/nudge.scss @@ -13,7 +13,6 @@ .modal-container { @extend .modal-container-base; min-width: $s-408; - border: $s-1 solid var(--modal-border-color); } .modal-header { @@ -21,7 +20,7 @@ } .modal-title { - @include tabTitleTipography; + @include headlineMediumTypography; color: var(--modal-title-foreground-color); } .modal-close-btn { @@ -31,7 +30,7 @@ .modal-content { @include flexColumn; gap: $s-24; - @include titleTipography; + @include bodyLargeTypography; margin-bottom: $s-24; } @@ -43,7 +42,7 @@ } .modal-msg { - @include titleTipography; + @include bodyLargeTypography; color: var(--modal-text-foreground-color); line-height: 1.5; } diff --git a/frontend/src/app/main/ui/workspace/palette.cljs b/frontend/src/app/main/ui/workspace/palette.cljs index e30a18e5bf..8eb2ac84a9 100644 --- a/frontend/src/app/main/ui/workspace/palette.cljs +++ b/frontend/src/app/main/ui/workspace/palette.cljs @@ -60,7 +60,7 @@ selected-text* (mf/use-state :file) selected-text (deref selected-text*) on-select (mf/use-fn #(reset! selected %)) - rulers? (mf/deref refs/rules?) + rulers? (mf/deref refs/rulers?) {:keys [on-pointer-down on-lost-pointer-capture on-pointer-move parent-ref size]} (r/use-resize-hook :palette 72 54 80 :y true :bottom on-change-palette-size) diff --git a/frontend/src/app/main/ui/workspace/palette.scss b/frontend/src/app/main/ui/workspace/palette.scss index 9d88d9e033..995d886a00 100644 --- a/frontend/src/app/main/ui/workspace/palette.scss +++ b/frontend/src/app/main/ui/workspace/palette.scss @@ -31,6 +31,7 @@ padding: $s-0 $s-0 $s-8 $s-8; border-radius: $br-8; background-color: var(--palette-background-color); + border: $s-2 solid var(--panel-border-color); transition: right 0.3s, opacity 0.2s, 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 b46e6a8093..58b4a1c0f8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs @@ -8,7 +8,6 @@ (ns app.main.ui.workspace.sidebar.assets.common (:require-macros [app.main.style :as stl]) (:require - [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.spec :as us] [app.common.thumbnails :as thc] @@ -263,39 +262,32 @@ (:id target-asset) (cfh/merge-path-item prefix (:name target-asset)))))))) - -(defn- get-component-thumbnail-uri - "Returns the component thumbnail uri" - [file-id component] - (let [page-id (:main-instance-page component) - root-id (:main-instance-id component) - object-id (thc/fmt-object-id file-id page-id root-id "component") - current-file? (= file-id (:id @refs/workspace-file))] - - (if current-file? - (mf/deref (refs/workspace-thumbnail-by-id object-id)) - (let [libraries @refs/workspace-libraries - thumbnail (dm/get-in libraries [file-id :thumbnails object-id])] - thumbnail)))) - (mf/defc component-item-thumbnail "Component that renders the thumbnail image or the original SVG." {::mf/wrap-props false} [{:keys [file-id root-shape component container class]}] - (let [retry (mf/use-state 0) - thumbnail-uri (get-component-thumbnail-uri file-id component) - handle-error + (let [page-id (:main-instance-page component) + root-id (:main-instance-id component) + + retry (mf/use-state 0) + + thumbnail-uri* (mf/with-memo [file-id page-id root-id] + (let [object-id (thc/fmt-object-id file-id page-id root-id "component")] + (refs/workspace-thumbnail-by-id object-id))) + thumbnail-uri (mf/deref thumbnail-uri*) + + on-error (mf/use-fn (mf/deps @retry) (fn [] - (when (@retry < 3) + (when (< @retry 3) (inc retry))))] (if (some? thumbnail-uri) [:& component-svg-thumbnail {:thumbnail-uri thumbnail-uri :class class - :on-error handle-error + :on-error on-error :root-shape root-shape :objects (:objects container) :show-grids? true}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index f637274cb4..f759c634a6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -103,7 +103,7 @@ fonts (mf/use-memo (mf/deps @state) #(filter-fonts @state @fonts/fonts)) recent-fonts (mf/deref refs/workspace-recent-fonts) - full-size? (boolean (and full-size recent-fonts show-recent)) + full-size? (boolean (and full-size show-recent)) select-next (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.cljs b/frontend/src/app/main/ui/workspace/top_toolbar.cljs index ee4de48ac5..86f3484dff 100644 --- a/frontend/src/app/main/ui/workspace/top_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/top_toolbar.cljs @@ -71,7 +71,7 @@ read-only? (mf/use-ctx ctx/workspace-read-only?) - rulers? (mf/deref refs/rules?) + rulers? (mf/deref refs/rulers?) hide-toolbar? (mf/deref refs/toolbar-visibility) interrupt diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.scss b/frontend/src/app/main/ui/workspace/top_toolbar.scss index d6140e99ec..993953c898 100644 --- a/frontend/src/app/main/ui/workspace/top_toolbar.scss +++ b/frontend/src/app/main/ui/workspace/top_toolbar.scss @@ -17,6 +17,7 @@ height: $s-56; padding: $s-8 $s-16; border-radius: $s-8; + border: $s-2 solid var(--panel-border-color); z-index: $z-index-10; background-color: var(--color-background-primary); transition: diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 181e5f5da6..58e5548c82 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -37,7 +37,7 @@ [app.main.ui.workspace.viewport.outline :as outline] [app.main.ui.workspace.viewport.pixel-overlay :as pixel-overlay] [app.main.ui.workspace.viewport.presence :as presence] - [app.main.ui.workspace.viewport.rules :as rules] + [app.main.ui.workspace.viewport.rulers :as rulers] [app.main.ui.workspace.viewport.scroll-bars :as scroll-bars] [app.main.ui.workspace.viewport.selection :as selection] [app.main.ui.workspace.viewport.snap-distances :as snap-distances] @@ -231,7 +231,7 @@ (or show-distances? mode-inspect?)) show-artboard-names? (contains? layout :display-artboard-names) hide-ui? (contains? layout :hide-ui) - show-rules? (and (contains? layout :rules) (not hide-ui?)) + show-rulers? (and (contains? layout :rulers) (not hide-ui?)) disabled-guides? (or drawing-tool transform drawing-path? node-editing?) @@ -264,7 +264,7 @@ (:y first-shape) (:y selected-frame)) - rule-area-size (/ rules/rule-area-size zoom)] + rule-area-size (/ rulers/ruler-area-size zoom)] (hooks/setup-dom-events zoom disable-paste in-viewport? workspace-read-only?) (hooks/setup-viewport-size vport viewport-ref) @@ -377,7 +377,7 @@ :on-pointer-up on-pointer-up} [:defs - ;; This clip is so the handlers are not over the rules + ;; This clip is so the handlers are not over the rulers [:clipPath {:id "clip-handlers"} [:rect {:x (+ (:x vbox) rule-area-size) :y (+ (:y vbox) rule-area-size) @@ -544,16 +544,16 @@ {:page-id page-id}]) (when-not hide-ui? - [:& rules/rules + [:& rulers/rulers {:zoom zoom :zoom-inverse zoom-inverse :vbox vbox :selected-shapes selected-shapes :offset-x offset-x :offset-y offset-y - :show-rules? show-rules?}]) + :show-rulers? show-rulers?}]) - (when show-rules? + (when show-rulers? [:& guides/viewport-guides {:zoom zoom :vbox vbox diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index 9d1b481d28..13ba8503b1 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -19,7 +19,7 @@ [app.main.streams :as ms] [app.main.ui.css-cursors :as cur] [app.main.ui.formats :as fmt] - [app.main.ui.workspace.viewport.rules :as rules] + [app.main.ui.workspace.viewport.rulers :as rulers] [app.util.dom :as dom] [rumext.v2 :as mf])) @@ -129,7 +129,7 @@ (defn guide-area-axis [pos vbox zoom frame axis] - (let [rules-pos (/ rules/rules-pos zoom) + (let [rulers-pos (/ rulers/rulers-pos zoom) guide-active-area (/ guide-active-area zoom)] (cond (and (some? frame) (= axis :x)) @@ -146,12 +146,12 @@ (= axis :x) {:x (- pos (/ guide-active-area 2)) - :y (+ (:y vbox) rules-pos) + :y (+ (:y vbox) rulers-pos) :width guide-active-area :height (:height vbox)} :else - {:x (+ (:x vbox) rules-pos) + {:x (+ (:x vbox) rulers-pos) :y (- pos (/ guide-active-area 2)) :width (:width vbox) :height guide-active-area}))) @@ -198,23 +198,23 @@ (defn guide-pill-axis [pos vbox zoom axis] - (let [rules-pos (/ rules/rules-pos zoom) + (let [rulers-pos (/ rulers/rulers-pos zoom) guide-pill-width (/ guide-pill-width zoom) guide-pill-height (/ guide-pill-height zoom)] (if (= axis :x) {:rect-x (- pos (/ guide-pill-width 2)) - :rect-y (+ (:y vbox) rules-pos (- (/ guide-pill-width 2)) (/ 3 zoom)) + :rect-y (+ (:y vbox) rulers-pos (- (/ guide-pill-width 2)) (/ 3 zoom)) :rect-width guide-pill-width :rect-height guide-pill-height :text-x pos - :text-y (+ (:y vbox) rules-pos (- (/ 3 zoom)))} + :text-y (+ (:y vbox) rulers-pos (- (/ 3 zoom)))} - {:rect-x (+ (:x vbox) rules-pos (- (/ guide-pill-height 2)) (- (/ 4 zoom))) + {:rect-x (+ (:x vbox) rulers-pos (- (/ guide-pill-height 2)) (- (/ 4 zoom))) :rect-y (- pos (/ guide-pill-width 2)) :rect-width guide-pill-height :rect-height guide-pill-width - :text-x (+ (:x vbox) rules-pos (- (/ 3 zoom))) + :text-x (+ (:x vbox) rulers-pos (- (/ 3 zoom))) :text-y pos}))) (defn guide-inside-vbox? @@ -222,7 +222,7 @@ (partial guide-inside-vbox? zoom vbox)) ([zoom {:keys [x y width height]} {:keys [axis position]}] - (let [rule-area-size (/ rules/rule-area-size zoom) + (let [rule-area-size (/ rulers/ruler-area-size zoom) x1 x x2 (+ x width) y1 y @@ -376,8 +376,8 @@ :text-anchor "middle" :dominant-baseline "middle" :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) - :style {:font-size (/ rules/font-size zoom) - :font-family rules/font-family + :style {:font-size (/ rulers/font-size zoom) + :font-family rulers/font-family :fill colors/black}} ;; If the guide is associated to a frame we show the position relative to the frame (fmt/format-number (- pos (if (= axis :x) (:x frame) (:y frame))))]]))]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/rulers.cljs b/frontend/src/app/main/ui/workspace/viewport/rulers.cljs new file mode 100644 index 0000000000..46206b5c7a --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/rulers.cljs @@ -0,0 +1,347 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.workspace.viewport.rulers + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.main.ui.formats :as fmt] + [app.main.ui.hooks :as hooks] + [app.util.object :as obj] + [rumext.v2 :as mf])) + +(def rulers-pos 15) +(def rulers-size 4) +(def rulers-width 1) +(def ruler-area-size 22) +(def ruler-area-half-size (/ ruler-area-size 2)) +(def rulers-background "var(--panel-background-color)") +(def selection-area-color "var(--color-accent-tertiary)") +(def selection-area-opacity 0.3) +(def over-number-size 100) +(def over-number-opacity 0.8) +(def over-number-percent 0.75) + +(def font-size 12) +(def font-family "worksans") +(def font-color "var(--layer-row-foreground-color)") +(def canvas-border-radius 12) + +;; ---------------- +;; RULERS +;; ---------------- + +(defn- calculate-step-size + [zoom] + (cond + (< 0 zoom 0.008) 10000 + (< 0.008 zoom 0.015) 5000 + (< 0.015 zoom 0.04) 2500 + (< 0.04 zoom 0.07) 1000 + (< 0.07 zoom 0.2) 500 + (< 0.2 zoom 0.5) 250 + (< 0.5 zoom 1) 100 + (<= 1 zoom 2) 50 + (< 2 zoom 4) 25 + (< 4 zoom 6) 10 + (< 6 zoom 15) 5 + (< 15 zoom 25) 2 + (< 25 zoom) 1 + :else 1)) + +(defn get-clip-area + [vbox zoom-inverse axis] + (if (= axis :x) + (let [x (+ (:x vbox) (* 25 zoom-inverse)) + y (:y vbox) + width (- (:width vbox) (* 21 zoom-inverse)) + height (* 25 zoom-inverse)] + {:x x :y y :width width :height height}) + + (let [x (:x vbox) + y (+ (:y vbox) (* 25 zoom-inverse)) + width (* 25 zoom-inverse) + height (- (:height vbox) (* 21 zoom-inverse))] + {:x x :y y :width width :height height}))) + +(defn get-background-area + [vbox zoom-inverse axis] + (if (= axis :x) + (let [x (:x vbox) + y (:y vbox) + width (:width vbox) + height (* ruler-area-size zoom-inverse)] + {:x x :y y :width width :height height}) + + (let [x (:x vbox) + y (+ (:y vbox) (* ruler-area-size zoom-inverse)) + width (* ruler-area-size zoom-inverse) + height (- (:height vbox) (* 21 zoom-inverse))] + {:x x :y y :width width :height height}))) + +(defn get-ruler-params + [vbox axis] + (if (= axis :x) + (let [start (:x vbox) + end (+ start (:width vbox))] + {:start start :end end}) + + (let [start (:y vbox) + end (+ start (:height vbox))] + {:start start :end end}))) + +(defn get-ruler-axis + [val vbox zoom-inverse axis] + (let [rulers-pos (* rulers-pos zoom-inverse) + rulers-size (* rulers-size zoom-inverse)] + (if (= axis :x) + {:text-x val + :text-y (+ (:y vbox) (- rulers-pos (* 4 zoom-inverse))) + :line-x1 val + :line-y1 (+ (:y vbox) rulers-pos (* 2 zoom-inverse)) + :line-x2 val + :line-y2 (+ (:y vbox) rulers-pos (* 2 zoom-inverse) rulers-size)} + + {:text-x (+ (:x vbox) (- rulers-pos (* 4 zoom-inverse))) + :text-y val + :line-x1 (+ (:x vbox) rulers-pos (* 2 zoom-inverse)) + :line-y1 val + :line-x2 (+ (:x vbox) rulers-pos (* 2 zoom-inverse) rulers-size) + :line-y2 val}))) + +(defn rulers-outside-path + "Path data for the viewport outside" + [x1 y1 x2 y2] + (dm/str + "M" x1 "," y1 + "L" x2 "," y1 + "L" x2 "," y2 + "L" x1 "," y2 + "Z")) + +(defn rulers-inside-path + "Calculates the path for the inside of the viewport frame" + [x1 y1 x2 y2 br bw] + (dm/str + "M" (+ x1 bw) "," (+ y1 bw br) + "Q" (+ x1 bw) "," (+ y1 bw) "," (+ x1 bw br) "," (+ y1 bw) + + "L" (- x2 br) "," (+ y1 bw) + "Q" x2 "," (+ y1 bw) "," x2 "," (+ y1 bw br) + + "L" x2 "," (- y2 br) + "Q" x2 "," y2 "," (- x2 br) "," y2 + + "L" (+ x1 bw br) "," y2 + "Q" (+ x1 bw) "," y2 "," (+ x1 bw) "," (- y2 br) + + "Z")) + +(mf/defc rulers-text + "Draws the text for the rulers in a specific axis" + [{:keys [vbox step offset axis zoom-inverse]}] + (let [clip-id (str "clip-ruler-" (d/name axis)) + {:keys [start end]} (get-ruler-params vbox axis) + minv (max start -100000) + minv (* (mth/ceil (/ minv step)) step) + maxv (min end 100000) + maxv (* (mth/floor (/ maxv step)) step) + + ;; These extra operations ensure that we are selecting a frame its initial location is rendered in the ruler + minv (+ minv (mod offset step)) + maxv (+ maxv (mod offset step))] + + [:g.rulers {:clipPath (str "url(#" clip-id ")")} + [:defs + [:clipPath {:id clip-id} + (let [{:keys [x y width height]} (get-clip-area vbox zoom-inverse axis)] + [:rect {:x x :y y :width width :height height}])]] + + (for [step-val (range minv (inc maxv) step)] + (let [{:keys [text-x text-y line-x1 line-y1 line-x2 line-y2]} + (get-ruler-axis step-val vbox zoom-inverse axis)] + [:* {:key (dm/str "text-" (d/name axis) "-" step-val)} + [:text {:x text-x + :y text-y + :text-anchor "middle" + :dominant-baseline "middle" + :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill font-color}} + ;; If the guide is associated to a frame we show the position relative to the frame + (fmt/format-number (- step-val offset))] + + [:line {:key (str "line-" (d/name axis) "-" step-val) + :x1 line-x1 + :y1 line-y1 + :x2 line-x2 + :y2 line-y2 + :style {:stroke font-color + :stroke-width rulers-width}}]]))])) + +(mf/defc viewport-frame + [{:keys [show-rulers? zoom zoom-inverse vbox offset-x offset-y]}] + + (let [{:keys [width height] x1 :x y1 :y} vbox + x2 (+ x1 width) + y2 (+ y1 height) + bw (if show-rulers? (* ruler-area-size zoom-inverse) 0) + br (/ canvas-border-radius zoom) + bs (* 4 zoom-inverse)] + [:* + [:g.viewport-frame-background + ;; This goes behind because if it goes in front the background bleeds through + [:path {:d (rulers-inside-path x1 y1 x2 y2 br bw) + :fill "none" + :stroke-width bs + :stroke "var(--panel-border-color)"}] + + [:path {:d (dm/str (rulers-outside-path x1 y1 x2 y2) + (rulers-inside-path x1 y1 x2 y2 br bw)) + :fill-rule "evenodd" + :fill rulers-background}]] + + (when show-rulers? + (let [step (calculate-step-size zoom)] + [:g.viewport-frame-rulers + [:& rulers-text {:vbox vbox :offset offset-x :step step :zoom-inverse zoom-inverse :axis :x}] + [:& rulers-text {:vbox vbox :offset offset-y :step step :zoom-inverse zoom-inverse :axis :y}]]))])) + +(mf/defc selection-area + [{:keys [vbox zoom-inverse selection-rect offset-x offset-y]}] + ;; When using the format-number callls we consider if the guide is associated to a frame and we show the position relative to it with the offset + [:g.selection-area + [:defs + [:linearGradient {:id "selection-gradient-start"} + [:stop {:offset "0%" :stop-color rulers-background :stop-opacity 0}] + [:stop {:offset "40%" :stop-color rulers-background :stop-opacity 1}] + [:stop {:offset "100%" :stop-color rulers-background :stop-opacity 1}]] + + [:linearGradient {:id "selection-gradient-end"} + [:stop {:offset "0%" :stop-color rulers-background :stop-opacity 1}] + [:stop {:offset "60%" :stop-color rulers-background :stop-opacity 1}] + [:stop {:offset "100%" :stop-color rulers-background :stop-opacity 0}]]] + [:g + [:rect {:x (- (:x selection-rect) (* (* over-number-size over-number-percent) zoom-inverse)) + :y (:y vbox) + :width (* over-number-size zoom-inverse) + :height (* ruler-area-size zoom-inverse) + :fill "url('#selection-gradient-start')"}] + + [:rect {:x (- (:x2 selection-rect) (* over-number-size (- 1 over-number-percent))) + :y (:y vbox) + :width (* over-number-size zoom-inverse) + :height (* ruler-area-size zoom-inverse) + :fill "url('#selection-gradient-end')"}] + + [:rect {:x (:x selection-rect) + :y (:y vbox) + :width (:width selection-rect) + :height (* ruler-area-size zoom-inverse) + :style {:fill selection-area-color + :fill-opacity selection-area-opacity}}] + + [:text {:x (- (:x1 selection-rect) (* 4 zoom-inverse)) + :y (+ (:y vbox) (* 10.6 zoom-inverse)) + :text-anchor "end" + :dominant-baseline "middle" + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill selection-area-color}} + (fmt/format-number (- (:x1 selection-rect) offset-x))] + + [:text {:x (+ (:x2 selection-rect) (* 4 zoom-inverse)) + :y (+ (:y vbox) (* 10.6 zoom-inverse)) + :text-anchor "start" + :dominant-baseline "middle" + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill selection-area-color}} + (fmt/format-number (- (:x2 selection-rect) offset-x))]] + + (let [center-x (+ (:x vbox) (* ruler-area-half-size zoom-inverse)) + center-y (- (+ (:y selection-rect) (/ (:height selection-rect) 2)) (* ruler-area-half-size zoom-inverse))] + + [:g {:transform (str "rotate(-90 " center-x "," center-y ")")} + [:rect {:x (- center-x (/ (:height selection-rect) 2) (* ruler-area-half-size zoom-inverse)) + :y (- center-y (* ruler-area-half-size zoom-inverse)) + :width (:height selection-rect) + :height (* ruler-area-size zoom-inverse) + :style {:fill selection-area-color + :fill-opacity selection-area-opacity}}] + + [:rect {:x (- center-x (/ (:height selection-rect) 2) (* ruler-area-half-size zoom-inverse) (* over-number-size zoom-inverse)) + :y (- center-y (* ruler-area-half-size zoom-inverse)) + :width (* over-number-size zoom-inverse) + :height (* ruler-area-size zoom-inverse) + :style {:fill rulers-background + :fill-opacity over-number-opacity}}] + + [:rect {:x (+ (- center-x (/ (:height selection-rect) 2) (* ruler-area-half-size zoom-inverse)) (:height selection-rect)) + :y (- center-y (* ruler-area-half-size zoom-inverse)) + :width (* over-number-size zoom-inverse) + :height (* ruler-area-size zoom-inverse) + :style {:fill rulers-background + :fill-opacity over-number-opacity}}] + + [:text {:x (- center-x (/ (:height selection-rect) 2) (* 15 zoom-inverse)) + :y center-y + :text-anchor "end" + :dominant-baseline "middle" + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill selection-area-color}} + (fmt/format-number (- (:y2 selection-rect) offset-y))] + + [:text {:x (+ center-x (/ (:height selection-rect) 2)) + :y center-y + :text-anchor "start" + :dominant-baseline "middle" + :style {:font-size (* font-size zoom-inverse) + :font-family font-family + :fill selection-area-color}} + (fmt/format-number (- (:y1 selection-rect) offset-y))]])]) + +(mf/defc rulers + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % (mf/check-props ["zoom" "vbox" "selected-shapes" "show-rulers?"]))]} + [props] + (let [zoom (obj/get props "zoom") + zoom-inverse (obj/get props "zoom-inverse") + vbox (obj/get props "vbox") + offset-x (obj/get props "offset-x") + offset-y (obj/get props "offset-y") + selected-shapes (-> (obj/get props "selected-shapes") + (hooks/use-equal-memo)) + show-rulers? (obj/get props "show-rulers?") + + selection-rect + (mf/use-memo + (mf/deps selected-shapes) + #(when (d/not-empty? selected-shapes) + (gsh/shapes->rect selected-shapes)))] + + (when (some? vbox) + [:g.viewport-frame {:pointer-events "none"} + [:& viewport-frame + {:show-rulers? show-rulers? + :zoom zoom + :zoom-inverse zoom-inverse + :vbox vbox + :offset-x offset-x + :offset-y offset-y}] + + (when (and show-rulers? (some? selection-rect)) + [:& selection-area + {:zoom zoom + :zoom-inverse zoom-inverse + :vbox vbox + :selection-rect selection-rect + :offset-x offset-x + :offset-y offset-y}])]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/rules.cljs b/frontend/src/app/main/ui/workspace/viewport/rules.cljs deleted file mode 100644 index 0b2833e297..0000000000 --- a/frontend/src/app/main/ui/workspace/viewport/rules.cljs +++ /dev/null @@ -1,350 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.main.ui.workspace.viewport.rules - (:require - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.geom.shapes :as gsh] - [app.common.math :as mth] - [app.main.ui.formats :as fmt] - [app.main.ui.hooks :as hooks] - [app.util.object :as obj] - [rumext.v2 :as mf])) - -(def rules-pos 15) -(def rules-size 4) -(def rules-width 1) -(def rule-area-size 22) -(def rule-area-half-size (/ rule-area-size 2)) -(def rules-background "var(--panel-background-color)") -(def selection-area-color "var(--color-accent-tertiary)") -(def selection-area-opacity 0.3) -(def over-number-size 100) -(def over-number-opacity 0.8) -(def over-number-percent 0.75) - -(def font-size 12) -(def font-family "worksans") -(def font-color "var(--layer-row-foreground-color)") -(def canvas-border-radius 12) - -;; ---------------- -;; RULES -;; ---------------- - -(defn- calculate-step-size - [zoom] - (cond - (< 0 zoom 0.008) 10000 - (< 0.008 zoom 0.015) 5000 - (< 0.015 zoom 0.04) 2500 - (< 0.04 zoom 0.07) 1000 - (< 0.07 zoom 0.2) 500 - (< 0.2 zoom 0.5) 250 - (< 0.5 zoom 1) 100 - (<= 1 zoom 2) 50 - (< 2 zoom 4) 25 - (< 4 zoom 6) 10 - (< 6 zoom 15) 5 - (< 15 zoom 25) 2 - (< 25 zoom) 1 - :else 1)) - -(defn get-clip-area - [vbox zoom-inverse axis] - (if (= axis :x) - (let [x (+ (:x vbox) (* 25 zoom-inverse)) - y (:y vbox) - width (- (:width vbox) (* 21 zoom-inverse)) - height (* 25 zoom-inverse)] - {:x x :y y :width width :height height}) - - (let [x (:x vbox) - y (+ (:y vbox) (* 25 zoom-inverse)) - width (* 25 zoom-inverse) - height (- (:height vbox) (* 21 zoom-inverse))] - {:x x :y y :width width :height height}))) - -(defn get-background-area - [vbox zoom-inverse axis] - (if (= axis :x) - (let [x (:x vbox) - y (:y vbox) - width (:width vbox) - height (* rule-area-size zoom-inverse)] - {:x x :y y :width width :height height}) - - (let [x (:x vbox) - y (+ (:y vbox) (* rule-area-size zoom-inverse)) - width (* rule-area-size zoom-inverse) - height (- (:height vbox) (* 21 zoom-inverse))] - {:x x :y y :width width :height height}))) - -(defn get-rule-params - [vbox axis] - (if (= axis :x) - (let [start (:x vbox) - end (+ start (:width vbox))] - {:start start :end end}) - - (let [start (:y vbox) - end (+ start (:height vbox))] - {:start start :end end}))) - -(defn get-rule-axis - [val vbox zoom-inverse axis] - (let [rules-pos (* rules-pos zoom-inverse) - rules-size (* rules-size zoom-inverse)] - (if (= axis :x) - {:text-x val - :text-y (+ (:y vbox) (- rules-pos (* 4 zoom-inverse))) - :line-x1 val - :line-y1 (+ (:y vbox) rules-pos (* 2 zoom-inverse)) - :line-x2 val - :line-y2 (+ (:y vbox) rules-pos (* 2 zoom-inverse) rules-size)} - - {:text-x (+ (:x vbox) (- rules-pos (* 4 zoom-inverse))) - :text-y val - :line-x1 (+ (:x vbox) rules-pos (* 2 zoom-inverse)) - :line-y1 val - :line-x2 (+ (:x vbox) rules-pos (* 2 zoom-inverse) rules-size) - :line-y2 val}))) - -(defn- round-corner-path-tl - [cx cy radius] - (dm/str - "M" cx "," cy - "L" (+ cx radius) "," cy - "Q" cx "," cy "," cx "," (+ cy radius) - "Z")) - -(defn- round-corner-path-tr - [cx cy radius] - (dm/str - "M" cx "," cy - "L" (+ cx radius) "," cy - "L" (+ cx radius) "," (+ cy radius) - "Q" (+ cx radius) "," cy "," cx "," cy - "Z")) - -(defn- round-corner-path-bl - [cx cy radius] - (dm/str - "M" cx "," cy - "Q" cx "," (+ cy radius) "," (+ cx radius) "," (+ cy radius) - "L" cx "," (+ cy radius) - "Z")) - -(defn- round-corner-path-br - [cx cy radius] - (dm/str - "M" (+ cx radius) "," cy - "L" (+ cx radius) "," (+ cy radius) - "L" cx "," (+ cy radius) - "Q" (+ cx radius) "," (+ cy radius) "," (+ cx radius) "," cy - "Z")) - -(mf/defc rules-axis - [{:keys [zoom zoom-inverse vbox axis offset]}] - (let [rules-width (* rules-width zoom-inverse) - step (calculate-step-size zoom) - clip-id (str "clip-rule-" (d/name axis)) - font-color font-color - rules-background rules-background] - - [:* - (let [{:keys [x y width height]} (get-background-area vbox zoom-inverse axis)] - [:rect {:x x :y y :width width :height height :style {:fill rules-background}}]) - - [:g.rules {:clipPath (str "url(#" clip-id ")")} - - [:defs - [:clipPath {:id clip-id} - (let [{:keys [x y width height]} (get-clip-area vbox zoom-inverse axis)] - [:rect {:x x :y y :width width :height height}])]] - - (let [{:keys [start end]} (get-rule-params vbox axis) - minv (max start -100000) - minv (* (mth/ceil (/ minv step)) step) - maxv (min end 100000) - maxv (* (mth/floor (/ maxv step)) step) - - ;; These extra operations ensure that we are selecting a frame its initial location is rendered in the rule - minv (+ minv (mod offset step)) - maxv (+ maxv (mod offset step))] - - (for [step-val (range minv (inc maxv) step)] - (let [{:keys [text-x text-y line-x1 line-y1 line-x2 line-y2]} - (get-rule-axis step-val vbox zoom-inverse axis)] - [:* {:key (dm/str "text-" (d/name axis) "-" step-val)} - [:text {:x text-x - :y text-y - :text-anchor "middle" - :dominant-baseline "middle" - :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill font-color}} - ;; If the guide is associated to a frame we show the position relative to the frame - (fmt/format-number (- step-val offset))] - - [:line {:key (str "line-" (d/name axis) "-" step-val) - :x1 line-x1 - :y1 line-y1 - :x2 line-x2 - :y2 line-y2 - :style {:stroke font-color - :stroke-width rules-width}}]])))]])) - -(mf/defc selection-area - [{:keys [vbox zoom-inverse selection-rect offset-x offset-y]}] - ;; When using the format-number callls we consider if the guide is associated to a frame and we show the position relative to it with the offset - (let [rules-background rules-background] - [:g.selection-area - [:defs - [:linearGradient {:id "selection-gradient-start"} - [:stop {:offset "0%" :stop-color rules-background :stop-opacity 0}] - [:stop {:offset "40%" :stop-color rules-background :stop-opacity 1}] - [:stop {:offset "100%" :stop-color rules-background :stop-opacity 1}]] - - [:linearGradient {:id "selection-gradient-end"} - [:stop {:offset "0%" :stop-color rules-background :stop-opacity 1}] - [:stop {:offset "60%" :stop-color rules-background :stop-opacity 1}] - [:stop {:offset "100%" :stop-color rules-background :stop-opacity 0}]]] - [:g - [:rect {:x (- (:x selection-rect) (* (* over-number-size over-number-percent) zoom-inverse)) - :y (:y vbox) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :fill "url('#selection-gradient-start')"}] - - [:rect {:x (- (:x2 selection-rect) (* over-number-size (- 1 over-number-percent))) - :y (:y vbox) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :fill "url('#selection-gradient-end')"}] - - [:rect {:x (:x selection-rect) - :y (:y vbox) - :width (:width selection-rect) - :height (* rule-area-size zoom-inverse) - :style {:fill selection-area-color - :fill-opacity selection-area-opacity}}] - - [:text {:x (- (:x1 selection-rect) (* 4 zoom-inverse)) - :y (+ (:y vbox) (* 10.6 zoom-inverse)) - :text-anchor "end" - :dominant-baseline "middle" - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill selection-area-color}} - (fmt/format-number (- (:x1 selection-rect) offset-x))] - - [:text {:x (+ (:x2 selection-rect) (* 4 zoom-inverse)) - :y (+ (:y vbox) (* 10.6 zoom-inverse)) - :text-anchor "start" - :dominant-baseline "middle" - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill selection-area-color}} - (fmt/format-number (- (:x2 selection-rect) offset-x))]] - - (let [center-x (+ (:x vbox) (* rule-area-half-size zoom-inverse)) - center-y (- (+ (:y selection-rect) (/ (:height selection-rect) 2)) (* rule-area-half-size zoom-inverse))] - - [:g {:transform (str "rotate(-90 " center-x "," center-y ")")} - [:rect {:x (- center-x (/ (:height selection-rect) 2) (* rule-area-half-size zoom-inverse)) - :y (- center-y (* rule-area-half-size zoom-inverse)) - :width (:height selection-rect) - :height (* rule-area-size zoom-inverse) - :style {:fill selection-area-color - :fill-opacity selection-area-opacity}}] - - [:rect {:x (- center-x (/ (:height selection-rect) 2) (* rule-area-half-size zoom-inverse) (* over-number-size zoom-inverse)) - :y (- center-y (* rule-area-half-size zoom-inverse)) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :style {:fill rules-background - :fill-opacity over-number-opacity}}] - - [:rect {:x (+ (- center-x (/ (:height selection-rect) 2) (* rule-area-half-size zoom-inverse)) (:height selection-rect)) - :y (- center-y (* rule-area-half-size zoom-inverse)) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :style {:fill rules-background - :fill-opacity over-number-opacity}}] - - [:text {:x (- center-x (/ (:height selection-rect) 2) (* 15 zoom-inverse)) - :y center-y - :text-anchor "end" - :dominant-baseline "middle" - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill selection-area-color}} - (fmt/format-number (- (:y2 selection-rect) offset-y))] - - [:text {:x (+ center-x (/ (:height selection-rect) 2)) - :y center-y - :text-anchor "start" - :dominant-baseline "middle" - :style {:font-size (* font-size zoom-inverse) - :font-family font-family - :fill selection-area-color}} - (fmt/format-number (- (:y1 selection-rect) offset-y))]])])) - -(mf/defc rules - {::mf/wrap-props false - ::mf/wrap [#(mf/memo' % (mf/check-props ["zoom" "vbox" "selected-shapes" "show-rules?"]))]} - [props] - (let [zoom (obj/get props "zoom") - zoom-inverse (obj/get props "zoom-inverse") - vbox (obj/get props "vbox") - offset-x (obj/get props "offset-x") - offset-y (obj/get props "offset-y") - selected-shapes (-> (obj/get props "selected-shapes") - (hooks/use-equal-memo)) - show-rules? (obj/get props "show-rules?") - - rules-background rules-background - border-radius (/ canvas-border-radius zoom) - - selection-rect - (mf/use-memo - (mf/deps selected-shapes) - #(when (d/not-empty? selected-shapes) - (gsh/shapes->rect selected-shapes)))] - - (when (some? vbox) - [:g.rules {:pointer-events "none"} - (when show-rules? - [:* - [:& rules-axis {:zoom zoom :zoom-inverse zoom-inverse :vbox vbox :axis :x :offset offset-x}] - [:& rules-axis {:zoom zoom :zoom-inverse zoom-inverse :vbox vbox :axis :y :offset offset-y}]]) - - ;; Draw the rules' rounded corners in the viewport corners - (let [{:keys [x y width height]} vbox - rule-area-size (if show-rules? (/ rule-area-size zoom) 0)] - [:* - [:path {:d (round-corner-path-tl (+ x rule-area-size) (+ y rule-area-size) border-radius) - :style {:fill rules-background}}] - - [:path {:d (round-corner-path-tr (+ x width (- border-radius)) (+ y rule-area-size) border-radius) - :style {:fill rules-background}}] - - [:path {:d (round-corner-path-bl (+ x rule-area-size) (+ y height (- border-radius)) border-radius) - :style {:fill rules-background}}] - - [:path {:d (round-corner-path-br (+ x (:width vbox) (- border-radius)) (+ y height (- border-radius)) border-radius) - :style {:fill rules-background}}]]) - - (when (and show-rules? (some? selection-rect)) - [:& selection-area {:zoom zoom - :zoom-inverse zoom-inverse - :vbox vbox - :selection-rect selection-rect - :offset-x offset-x - :offset-y offset-y}])])))