Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2024-02-06 20:10:55 +01:00
commit 4d7a572daa
44 changed files with 547 additions and 532 deletions

View file

@ -110,6 +110,7 @@
"Get the parent top shape linked to a component for this shape, if any" "Get the parent top shape linked to a component for this shape, if any"
([objects shape] (get-component-shape objects shape nil)) ([objects shape] (get-component-shape objects shape nil))
([objects shape {:keys [allow-main?] :or {allow-main? false} :as options}] ([objects shape {:keys [allow-main?] :or {allow-main? false} :as options}]
(let [parent (get objects (:parent-id shape))]
(cond (cond
(nil? shape) (nil? shape)
nil nil
@ -123,8 +124,11 @@
(and (not (ctk/in-component-copy? shape)) (not allow-main?)) (and (not (ctk/in-component-copy? shape)) (not allow-main?))
nil nil
(and (ctk/instance-head? shape) (not (ctk/in-component-copy? parent)))
shape ; This case is a copy root inside a main component
:else :else
(get-component-shape objects (get objects (:parent-id shape)) options)))) (get-component-shape objects parent options)))))
(defn get-head-shape (defn get-head-shape
"Get the parent top or nested shape linked to a component for this shape, if any" "Get the parent top or nested shape linked to a component for this shape, if any"

View file

@ -583,6 +583,7 @@
padding: $s-32; padding: $s-32;
border-radius: $br-8; border-radius: $br-8;
background-color: var(--modal-background-color); background-color: var(--modal-background-color);
border: $s-2 solid var(--modal-border-color);
min-width: $s-364; min-width: $s-364;
min-height: $s-192; min-height: $s-192;
max-width: $s-512; max-width: $s-512;
@ -843,6 +844,7 @@
z-index: $z-index-10; z-index: $z-index-10;
color: var(--title-foreground-color-hover); color: var(--title-foreground-color-hover);
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
border: $s-2 solid var(--panel-border-color);
} }
.menu-item-base { .menu-item-base {
@ -904,6 +906,7 @@
overflow-x: hidden; overflow-x: hidden;
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
color: var(--menu-foreground-color); color: var(--menu-foreground-color);
border: $s-2 solid var(--panel-border-color);
} }
.select-wrapper { .select-wrapper {

View file

@ -13,6 +13,8 @@
--scrollbar-background-color: var(--color-foreground-secondary); --scrollbar-background-color: var(--color-foreground-secondary);
--panel-background-color: var(--color-background-primary); --panel-background-color: var(--color-background-primary);
--panel-border-color: var(--color-background-quaternary);
--app-background: var(--color-background-primary); --app-background: var(--color-background-primary);
--loader-background: var(--color-background-primary); --loader-background: var(--color-background-primary);
--panel-title-background-color: var(--color-background-secondary); --panel-title-background-color: var(--color-background-secondary);

View file

@ -70,6 +70,21 @@
line-height: 1.2; 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 { @mixin textEllipsis {
max-width: 99%; max-width: 99%;
overflow: hidden; overflow: hidden;

View file

@ -20,7 +20,7 @@
:document-history :document-history
:colorpalette :colorpalette
:element-options :element-options
:rules :rulers
:display-grid :display-grid
:snap-grid :snap-grid
:scale-text :scale-text
@ -50,7 +50,7 @@
#{:sitemap #{:sitemap
:layers :layers
:element-options :element-options
:rules :rulers
:display-grid :display-grid
:snap-grid :snap-grid
:dynamic-alignment :dynamic-alignment

View file

@ -640,6 +640,21 @@
:path-params path-params :path-params path-params
:query-params query-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 (defn ext-library-changed
[library-id modified-at revn changes] [library-id modified-at revn changes]
(dm/assert! (uuid? library-id)) (dm/assert! (uuid? library-id))
@ -654,11 +669,15 @@
ch/process-changes changes))) ch/process-changes changes)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ stream]
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) (let [stopper-s (rx/filter (ptk/type? ::ext-library-changed) stream)]
(rx/map (fn [thumbnails] (->>
(fn [state] (rx/merge
(assoc-in state [:workspace-libraries library-id :thumbnails] thumbnails)))))))) (->> (rx/of library-id)
(rx/delay 5000)
(rx/map fetch-library-thumbnails)))
(rx/take-until stopper-s))))))
(defn reset-component (defn reset-component
"Cancels all modifications in the shape with the given id, and all its children, in "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"}) (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"})
(rx/map (fn [thumbnails] (rx/map (fn [thumbnails]
(fn [state] (fn [state]
(assoc-in state [:workspace-libraries library-id :thumbnails] thumbnails)))))))))) (update state :workspace-thumbnails merge thumbnails))))))))))
(defn unlink-file-from-library (defn unlink-file-from-library
[file-id library-id] [file-id library-id]

View file

@ -1081,7 +1081,10 @@
(:id component-parent-shape) (:id component-parent-shape)
(get page :objects) (get page :objects)
:update-new-shape update-new-shape :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'] add-obj-change (fn [changes shape']
(update changes :redo-changes conj (update changes :redo-changes conj
@ -1090,14 +1093,10 @@
{:type :add-obj {:type :add-obj
:id (:id shape') :id (:id shape')
:parent-id (:parent-id shape') :parent-id (:parent-id shape')
:frame-id (:frame-id shape')
:index index :index index
:ignore-touched true :ignore-touched true
:obj shape'}) :obj shape'}))))
(ctn/page? component-container)
(assoc :frame-id (if (= (:type shape') :frame)
(:id shape')
(:frame-id shape'))))))
mod-obj-change (fn [changes shape'] mod-obj-change (fn [changes shape']
(update changes :redo-changes conj (update changes :redo-changes conj

View file

@ -351,10 +351,10 @@
;; MAIN MENU ;; MAIN MENU
:toggle-rules {:tooltip (ds/meta-shift "R") :toggle-rulers {:tooltip (ds/meta-shift "R")
:command (ds/c-mod "shift+r") :command (ds/c-mod "shift+r")
:subsections [:main-menu] :subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :rules))} :fn #(st/emit! (toggle-layout-flag :rulers))}
:select-all {:tooltip (ds/meta "A") :select-all {:tooltip (ds/meta "A")
:command (ds/c-mod "a") :command (ds/c-mod "a")

View file

@ -115,16 +115,10 @@
(ptk/reify ::assoc-thumbnail (ptk/reify ::assoc-thumbnail
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [prev-uri (dm/get-in state [:workspace-thumbnails object-id]) (let [prev-uri (dm/get-in state [:workspace-thumbnails object-id])]
current-file-id (:current-file-id state)]
(some->> prev-uri (vreset! prev-uri*)) (some->> prev-uri (vreset! prev-uri*))
(l/trc :hint "assoc thumbnail" :object-id object-id :uri uri) (l/trc :hint "assoc thumbnail" :object-id object-id :uri uri)
(update state :workspace-thumbnails assoc object-id 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)))))
ptk/EffectEvent ptk/EffectEvent
(effect [_ _ _] (effect [_ _ _]

View file

@ -212,8 +212,8 @@
(def snap-pixel? (def snap-pixel?
(l/derived #(contains? % :snap-pixel-grid) workspace-layout)) (l/derived #(contains? % :snap-pixel-grid) workspace-layout))
(def rules? (def rulers?
(l/derived #(contains? % :rules) workspace-layout)) (l/derived #(contains? % :rulers) workspace-layout))
(def workspace-file (def workspace-file
"A ref to a striped vision of file (without data)." "A ref to a striped vision of file (without data)."
@ -490,6 +490,9 @@
(dm/get-in state [:viewer-local :zoom-type])) (dm/get-in state [:viewer-local :zoom-type]))
st/state)) st/state))
(def workspace-thumbnails
(l/derived :workspace-thumbnails st/state))
(defn workspace-thumbnail-by-id (defn workspace-thumbnail-by-id
[object-id] [object-id]
(l/derived (l/derived

View file

@ -46,7 +46,7 @@
(not (contains? focus id)))) (not (contains? focus id))))
(= type :guide) (= type :guide)
(or (not (contains? layout :rules)) (or (not (contains? layout :rulers))
(not (contains? layout :snap-guides)) (not (contains? layout :snap-guides))
(and (d/not-empty? focus) (and (d/not-empty? focus)
(not (contains? focus frame-id)))) (not (contains? focus frame-id))))

View file

@ -21,7 +21,7 @@
} }
.modal-title { .modal-title {
@include tabTitleTipography; @include headlineMediumTypography;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
@ -30,12 +30,12 @@
} }
.modal-content { .modal-content {
@include titleTipography; @include bodyLargeTypography;
margin-bottom: $s-24; margin-bottom: $s-24;
} }
.modal-hint { .modal-hint {
@include titleTipography; @include bodyLargeTypography;
} }
.action-buttons { .action-buttons {
@ -56,7 +56,7 @@
.modal-scd-msg, .modal-scd-msg,
.modal-subtitle, .modal-subtitle,
.modal-msg { .modal-msg {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
line-height: 1.5; line-height: 1.5;
} }

View file

@ -32,6 +32,7 @@
margin: $s-0; margin: $s-0;
padding: $s-4; padding: $s-4;
border-radius: $br-8; border-radius: $br-8;
border: $s-2 solid var(--panel-border-color);
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
overflow: auto; overflow: auto;
& .separator { & .separator {

View file

@ -22,7 +22,7 @@
} }
.modal-title { .modal-title {
@include tabTitleTipography; @include headlineMediumTypography;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
@ -31,7 +31,7 @@
} }
.modal-content { .modal-content {
@include titleTipography; @include bodyLargeTypography;
margin-bottom: $s-24; margin-bottom: $s-24;
} }
@ -49,7 +49,7 @@
} }
} }
.modal-component-name { .modal-component-name {
@include titleTipography; @include bodyLargeTypography;
} }
.modal-hint { .modal-hint {
@ -74,7 +74,6 @@
.modal-scd-msg, .modal-scd-msg,
.modal-subtitle, .modal-subtitle,
.modal-msg { .modal-msg {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
line-height: 1.5;
} }

View file

@ -12,7 +12,6 @@
.modal-container { .modal-container {
@extend .modal-container-base; @extend .modal-container-base;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {

View file

@ -12,7 +12,6 @@
.modal-container { .modal-container {
@extend .modal-container-base; @extend .modal-container-base;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {

View file

@ -178,6 +178,7 @@
[:div {:class (stl/css :invitation-row)} [:div {:class (stl/css :invitation-row)}
[:& fm/multi-input {:type "email" [:& fm/multi-input {:type "email"
:class (stl/css :email-input)
:name :emails :name :emails
:auto-focus? true :auto-focus? true
:trim true :trim true

View file

@ -588,13 +588,12 @@
width: $s-400; width: $s-400;
padding: $s-32; padding: $s-32;
background-color: var(--modal-background-color); background-color: var(--modal-background-color);
border: $s-1 solid var(--modal-border-color);
&.hero { &.hero {
top: $s-216; top: $s-216;
right: $s-32; right: $s-32;
} }
.modal-title { .modal-title {
@include tabTitleTipography; @include headlineMediumTypography;
height: $s-32; height: $s-32;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
@ -644,8 +643,9 @@
.role-select { .role-select {
@include flexColumn; @include flexColumn;
row-gap: $s-8;
.role-title { .role-title {
@include titleTipography; @include bodyLargeTypography;
margin: 0; margin: 0;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
@ -676,7 +676,6 @@
.modal-container { .modal-container {
@extend .modal-container-base; @extend .modal-container-base;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {
@ -725,3 +724,7 @@
@extend .modal-cancel-btn; @extend .modal-cancel-btn;
} }
} }
.email-input {
@extend .input-base;
}

View file

@ -12,7 +12,6 @@
.modal-container { .modal-container {
@extend .modal-container-base; @extend .modal-container-base;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {

View file

@ -22,7 +22,7 @@
} }
.modal-title { .modal-title {
@include tabTitleTipography; @include headlineMediumTypography;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
@ -40,10 +40,8 @@
} }
.element-list { .element-list {
@include titleTipography; @include bodyLargeTypography;
.list-item { color: var(--modal-text-foreground-color);
@include titleTipography;
}
} }
.action-buttons { .action-buttons {
@ -64,7 +62,7 @@
.modal-scd-msg, .modal-scd-msg,
.modal-subtitle, .modal-subtitle,
.modal-msg { .modal-msg {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
line-height: 1.5; line-height: 1.5;
} }

View file

@ -138,8 +138,8 @@
(cond-> (:name shape) suffix (str suffix))] (cond-> (:name shape) suffix (str suffix))]
(when (:scale export) (when (:scale export)
[:div {:class (stl/css :selection-scale)} [:div {:class (stl/css :selection-scale)}
(dm/str (ust/format-precision (* width (:scale export)) 2) "px" (dm/str (ust/format-precision (* width (:scale export)) 2) "x"
(ust/format-precision (* height (:scale export)) 2) "px")]) (ust/format-precision (* height (:scale export)) 2))])
(when (:type export) (when (:type export)
[:div {:class (stl/css :selection-extension)} [:div {:class (stl/css :selection-extension)}

View file

@ -41,14 +41,14 @@
.title-text { .title-text {
@include flexCenter; @include flexCenter;
@include titleTipography; @include bodyLargeTypography;
padding: 0; padding: 0;
margin: 0; margin: 0;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
padding-left: $s-4; padding-left: $s-4;
} }
.progress { .progress {
@include titleTipography; @include bodyLargeTypography;
padding-left: $s-8; padding-left: $s-8;
margin: 0; margin: 0;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
@ -86,7 +86,7 @@
} }
.modal-title { .modal-title {
@include tabTitleTipography; @include headlineMediumTypography;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
@ -125,7 +125,7 @@
} }
} }
.selection-title { .selection-title {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
} }
} }
@ -160,7 +160,9 @@
border-radius: $br-8; border-radius: $br-8;
.selection-btn { .selection-btn {
@include buttonStyle; @include buttonStyle;
@include flexRow; display: grid;
grid-template-columns: min-content auto 1fr auto auto;
align-items: center;
width: 100%; width: 100%;
height: 10%; height: 10%;
gap: $s-8; gap: $s-8;
@ -176,21 +178,21 @@
} }
} }
.selection-name { .selection-name {
@include titleTipography; @include bodyLargeTypography;
@include textEllipsis; @include textEllipsis;
flex-grow: 1; flex-grow: 1;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
text-align: start; text-align: start;
} }
.selection-scale { .selection-scale {
@include titleTipography; @include bodyLargeTypography;
@include textEllipsis; @include textEllipsis;
min-width: $s-108; min-width: $s-108;
padding: $s-12; padding: $s-12;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
} }
.selection-extension { .selection-extension {
@include titleTipography; @include bodyLargeTypography;
@include textEllipsis; @include textEllipsis;
min-width: $s-72; min-width: $s-72;
padding: $s-12; padding: $s-12;
@ -199,7 +201,6 @@
} }
.image-wrapper { .image-wrapper {
@include flexCenter; @include flexCenter;
height: 100%;
min-height: $s-32; min-height: $s-32;
min-width: $s-32; min-width: $s-32;
background-color: var(--app-white); background-color: var(--app-white);
@ -231,9 +232,8 @@
.modal-scd-msg, .modal-scd-msg,
.modal-subtitle, .modal-subtitle,
.modal-msg { .modal-msg {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
line-height: 1.5;
} }
.export-option { .export-option {
@ -243,7 +243,8 @@
label { label {
align-items: flex-start; align-items: flex-start;
.modal-subtitle { .modal-subtitle {
@include tabTitleTipography; // @include tabTitleTipography;
@include bodyLargeTypography;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
} }
@ -254,7 +255,7 @@
.option-content { .option-content {
@include flexColumn; @include flexColumn;
@include titleTipography; @include bodyLargeTypography;
} }
.file-entry { .file-entry {
@ -271,7 +272,7 @@
} }
} }
.file-name-label { .file-name-label {
@include titleTipography; @include bodyLargeTypography;
} }
} }
&.loading { &.loading {

View file

@ -17,7 +17,6 @@
padding: 0; padding: 0;
margin: 0; margin: 0;
min-width: $s-712; min-width: $s-712;
border: $s-1 solid var(--modal-border-color);
} }
.modal-left { .modal-left {

View file

@ -14,7 +14,6 @@
@extend .modal-container-base; @extend .modal-container-base;
position: relative; position: relative;
min-width: $s-712; min-width: $s-712;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {

View file

@ -14,7 +14,6 @@
@extend .modal-container-base; @extend .modal-container-base;
min-width: $s-512; min-width: $s-512;
position: relative; position: relative;
border: $s-1 solid var(--modal-border-color);
} }
// STEP CONTAINER // STEP CONTAINER

View file

@ -16,7 +16,6 @@
display: flex; display: flex;
position: relative; position: relative;
min-width: $s-712; min-width: $s-712;
border: $s-1 solid var(--modal-border-color);
} }
.modal-left { .modal-left {

View file

@ -175,7 +175,6 @@
.modal-container { .modal-container {
@extend .modal-container-base; @extend .modal-container-base;
min-width: $s-408; min-width: $s-408;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {

View file

@ -13,7 +13,6 @@
.modal-container { .modal-container {
@extend .modal-container-base; @extend .modal-container-base;
min-width: $s-408; min-width: $s-408;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {

View file

@ -13,7 +13,6 @@
.modal-container { .modal-container {
@extend .modal-container-base; @extend .modal-container-base;
min-width: $s-408; min-width: $s-408;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {

View file

@ -17,6 +17,7 @@
width: $s-240; width: $s-240;
padding: $s-4; padding: $s-4;
border-radius: $br-8; border-radius: $br-8;
border: $s-2 solid var(--panel-border-color);
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
z-index: $z-index-3; z-index: $z-index-3;
} }

View file

@ -70,36 +70,22 @@
(mf/defc describe-library-blocks (mf/defc describe-library-blocks
[{:keys [components-count graphics-count colors-count typography-count] :as props}] [{:keys [components-count graphics-count colors-count typography-count] :as props}]
(let [last-one (cond
(> colors-count 0) :color
(> graphics-count 0) :graphics
(> components-count 0) :components)]
[:* [:*
(when (pos? components-count) (when (pos? components-count)
[:* [:li {:class (stl/css :element-count)}
[:span {:class (stl/css :element-count)} (tr "workspace.libraries.components" components-count)])
(tr "workspace.libraries.components" components-count)]
(when (not= last-one :components)
[:span " · "])])
(when (pos? graphics-count) (when (pos? graphics-count)
[:* [:li {:class (stl/css :element-count)}
[:span {:class (stl/css :element-count)} (tr "workspace.libraries.graphics" graphics-count)])
(tr "workspace.libraries.graphics" graphics-count)]
(when (not= last-one :graphics)
[:span " · "])])
(when (pos? colors-count) (when (pos? colors-count)
[:* [:li {:class (stl/css :element-count)}
[:span {:class (stl/css :element-count)} (tr "workspace.libraries.colors" colors-count)])
(tr "workspace.libraries.colors" colors-count)]
(when (not= last-one :colors)
[:span " · "])])
(when (pos? typography-count) (when (pos? typography-count)
[:span {:class (stl/css :element-count)} [:li {:class (stl/css :element-count)}
(tr "workspace.libraries.typography" typography-count)])])) (tr "workspace.libraries.typography" typography-count)])])
(mf/defc libraries-tab (mf/defc libraries-tab
@ -208,7 +194,7 @@
[:div {:class (stl/css :section-list-item)} [:div {:class (stl/css :section-list-item)}
[:div [:div
[:div {:class (stl/css :item-name)} (tr "workspace.libraries.file-library")] [: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) [:& describe-library-blocks {:components-count (count components)
:graphics-count (count media) :graphics-count (count media)
:colors-count (count colors) :colors-count (count colors)
@ -229,7 +215,7 @@
:key (dm/str id)} :key (dm/str id)}
[:div [:div
[:div {:class (stl/css :item-name)} name] [: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)) [])) (let [components-count (count (or (ctkl/components-seq (:data library)) []))
graphics-count (count (dm/get-in library [:data :media] [])) graphics-count (count (dm/get-in library [:data :media] []))
colors-count (count (dm/get-in library [:data :colors] [])) colors-count (count (dm/get-in library [:data :colors] []))
@ -262,7 +248,7 @@
:key (dm/str id)} :key (dm/str id)}
[:div [:div
[:div {:class (stl/css :item-name)} name] [: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) (let [components-count (dm/get-in library [:library-summary :components :count] 0)
graphics-count (dm/get-in library [:library-summary :media :count] 0) graphics-count (dm/get-in library [:library-summary :media :count] 0)
colors-count (dm/get-in library [:library-summary :colors :count] 0) colors-count (dm/get-in library [:library-summary :colors :count] 0)
@ -376,7 +362,7 @@
:key (dm/str id)} :key (dm/str id)}
[:div [:div
[:div {:class (stl/css :item-name)} name] [:div {:class (stl/css :item-name)} name]
[:div {:class (stl/css :item-contents)} (describe-library [:ul {:class (stl/css :item-contents)} (describe-library
(count components) (count components)
0 0
(count colors) (count colors)

View file

@ -39,7 +39,7 @@
} }
.modal-title { .modal-title {
@include tabTitleTipography; @include headlineMediumTypography;
margin-bottom: $s-16; margin-bottom: $s-16;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
@ -70,24 +70,18 @@
max-height: $s-320; max-height: $s-320;
margin-top: $s-12; margin-top: $s-12;
overflow: auto; overflow: auto;
padding-right: $s-8;
.section-list-item { .section-list-item {
display: grid; display: grid;
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
column-gap: $s-12;
margin-bottom: $s-24; margin-bottom: $s-24;
&:last-child { &:last-child {
margin-bottom: $s-8; margin-bottom: $s-8;
} }
.item-name { .item-name {
@include titleTipography; @include bodyLargeTypography;
color: var(--library-name-foreground-color); color: var(--library-name-foreground-color);
} }
.item-contents {
@include titleTipography;
color: var(--library-content-foreground-color);
}
.item-publish, .item-publish,
.item-unpublish { .item-unpublish {
@extend .button-primary; @extend .button-primary;
@ -99,7 +93,7 @@
} }
.item-update { .item-update {
@extend .button-warning; @extend .button-warning;
@include tabTitleTipography; @include headlineMediumTypography;
height: $s-32; height: $s-32;
min-width: $s-92; min-width: $s-92;
padding: $s-8 $s-24; padding: $s-8 $s-24;
@ -143,7 +137,7 @@
} }
.section-title { .section-title {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
margin-bottom: $s-12; margin-bottom: $s-12;
} }
@ -161,7 +155,7 @@
} }
} }
.section-list-empty { .section-list-empty {
@include titleTipography; @include bodyLargeTypography;
@include flexCenter; @include flexCenter;
color: var(--empty-message-foreground-color); 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 { .element-count {
white-space: nowrap; white-space: nowrap;
&:not(:last-child)::after {
content: "·";
margin-inline: $s-4;
}
} }

View file

@ -305,14 +305,14 @@
:on-key-down (fn [event] :on-key-down (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
(toggle-flag event))) (toggle-flag event)))
:data-test "rules" :data-test "rulers"
:id "file-menu-rules"} :id "file-menu-rulers"}
[:span {:class (stl/css :item-name)} [:span {:class (stl/css :item-name)}
(if (contains? layout :rules) (if (contains? layout :rulers)
(tr "workspace.header.menu.hide-rules") (tr "workspace.header.menu.hide-rules")
(tr "workspace.header.menu.show-rules"))] (tr "workspace.header.menu.show-rules"))]
[:span {:class (stl/css :shortcut)} [: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])]] [:span {:class (stl/css :shortcut-key) :key sc} sc])]]

View file

@ -13,7 +13,6 @@
.modal-container { .modal-container {
@extend .modal-container-base; @extend .modal-container-base;
min-width: $s-408; min-width: $s-408;
border: $s-1 solid var(--modal-border-color);
} }
.modal-header { .modal-header {
@ -21,7 +20,7 @@
} }
.modal-title { .modal-title {
@include tabTitleTipography; @include headlineMediumTypography;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
.modal-close-btn { .modal-close-btn {
@ -31,7 +30,7 @@
.modal-content { .modal-content {
@include flexColumn; @include flexColumn;
gap: $s-24; gap: $s-24;
@include titleTipography; @include bodyLargeTypography;
margin-bottom: $s-24; margin-bottom: $s-24;
} }
@ -43,7 +42,7 @@
} }
.modal-msg { .modal-msg {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
line-height: 1.5; line-height: 1.5;
} }

View file

@ -60,7 +60,7 @@
selected-text* (mf/use-state :file) selected-text* (mf/use-state :file)
selected-text (deref selected-text*) selected-text (deref selected-text*)
on-select (mf/use-fn #(reset! selected %)) 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]} {: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) (r/use-resize-hook :palette 72 54 80 :y true :bottom on-change-palette-size)

View file

@ -31,6 +31,7 @@
padding: $s-0 $s-0 $s-8 $s-8; padding: $s-0 $s-0 $s-8 $s-8;
border-radius: $br-8; border-radius: $br-8;
background-color: var(--palette-background-color); background-color: var(--palette-background-color);
border: $s-2 solid var(--panel-border-color);
transition: transition:
right 0.3s, right 0.3s,
opacity 0.2s, opacity 0.2s,

View file

@ -8,7 +8,6 @@
(ns app.main.ui.workspace.sidebar.assets.common (ns app.main.ui.workspace.sidebar.assets.common
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.thumbnails :as thc] [app.common.thumbnails :as thc]
@ -263,39 +262,32 @@
(:id target-asset) (:id target-asset)
(cfh/merge-path-item prefix (:name 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 (mf/defc component-item-thumbnail
"Component that renders the thumbnail image or the original SVG." "Component that renders the thumbnail image or the original SVG."
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [file-id root-shape component container class]}] [{:keys [file-id root-shape component container class]}]
(let [retry (mf/use-state 0) (let [page-id (:main-instance-page component)
thumbnail-uri (get-component-thumbnail-uri file-id component) root-id (:main-instance-id component)
handle-error
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/use-fn
(mf/deps @retry) (mf/deps @retry)
(fn [] (fn []
(when (@retry < 3) (when (< @retry 3)
(inc retry))))] (inc retry))))]
(if (some? thumbnail-uri) (if (some? thumbnail-uri)
[:& component-svg-thumbnail [:& component-svg-thumbnail
{:thumbnail-uri thumbnail-uri {:thumbnail-uri thumbnail-uri
:class class :class class
:on-error handle-error :on-error on-error
:root-shape root-shape :root-shape root-shape
:objects (:objects container) :objects (:objects container)
:show-grids? true}] :show-grids? true}]

View file

@ -103,7 +103,7 @@
fonts (mf/use-memo (mf/deps @state) #(filter-fonts @state @fonts/fonts)) fonts (mf/use-memo (mf/deps @state) #(filter-fonts @state @fonts/fonts))
recent-fonts (mf/deref refs/workspace-recent-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 select-next
(mf/use-fn (mf/use-fn

View file

@ -71,7 +71,7 @@
read-only? (mf/use-ctx ctx/workspace-read-only?) 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) hide-toolbar? (mf/deref refs/toolbar-visibility)
interrupt interrupt

View file

@ -17,6 +17,7 @@
height: $s-56; height: $s-56;
padding: $s-8 $s-16; padding: $s-8 $s-16;
border-radius: $s-8; border-radius: $s-8;
border: $s-2 solid var(--panel-border-color);
z-index: $z-index-10; z-index: $z-index-10;
background-color: var(--color-background-primary); background-color: var(--color-background-primary);
transition: transition:

View file

@ -37,7 +37,7 @@
[app.main.ui.workspace.viewport.outline :as outline] [app.main.ui.workspace.viewport.outline :as outline]
[app.main.ui.workspace.viewport.pixel-overlay :as pixel-overlay] [app.main.ui.workspace.viewport.pixel-overlay :as pixel-overlay]
[app.main.ui.workspace.viewport.presence :as presence] [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.scroll-bars :as scroll-bars]
[app.main.ui.workspace.viewport.selection :as selection] [app.main.ui.workspace.viewport.selection :as selection]
[app.main.ui.workspace.viewport.snap-distances :as snap-distances] [app.main.ui.workspace.viewport.snap-distances :as snap-distances]
@ -231,7 +231,7 @@
(or show-distances? mode-inspect?)) (or show-distances? mode-inspect?))
show-artboard-names? (contains? layout :display-artboard-names) show-artboard-names? (contains? layout :display-artboard-names)
hide-ui? (contains? layout :hide-ui) 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?) disabled-guides? (or drawing-tool transform drawing-path? node-editing?)
@ -264,7 +264,7 @@
(:y first-shape) (:y first-shape)
(:y selected-frame)) (: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-dom-events zoom disable-paste in-viewport? workspace-read-only?)
(hooks/setup-viewport-size vport viewport-ref) (hooks/setup-viewport-size vport viewport-ref)
@ -377,7 +377,7 @@
:on-pointer-up on-pointer-up} :on-pointer-up on-pointer-up}
[:defs [: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"} [:clipPath {:id "clip-handlers"}
[:rect {:x (+ (:x vbox) rule-area-size) [:rect {:x (+ (:x vbox) rule-area-size)
:y (+ (:y vbox) rule-area-size) :y (+ (:y vbox) rule-area-size)
@ -544,16 +544,16 @@
{:page-id page-id}]) {:page-id page-id}])
(when-not hide-ui? (when-not hide-ui?
[:& rules/rules [:& rulers/rulers
{:zoom zoom {:zoom zoom
:zoom-inverse zoom-inverse :zoom-inverse zoom-inverse
:vbox vbox :vbox vbox
:selected-shapes selected-shapes :selected-shapes selected-shapes
:offset-x offset-x :offset-x offset-x
:offset-y offset-y :offset-y offset-y
:show-rules? show-rules?}]) :show-rulers? show-rulers?}])
(when show-rules? (when show-rulers?
[:& guides/viewport-guides [:& guides/viewport-guides
{:zoom zoom {:zoom zoom
:vbox vbox :vbox vbox

View file

@ -19,7 +19,7 @@
[app.main.streams :as ms] [app.main.streams :as ms]
[app.main.ui.css-cursors :as cur] [app.main.ui.css-cursors :as cur]
[app.main.ui.formats :as fmt] [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] [app.util.dom :as dom]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -129,7 +129,7 @@
(defn guide-area-axis (defn guide-area-axis
[pos vbox zoom frame 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)] guide-active-area (/ guide-active-area zoom)]
(cond (cond
(and (some? frame) (= axis :x)) (and (some? frame) (= axis :x))
@ -146,12 +146,12 @@
(= axis :x) (= axis :x)
{:x (- pos (/ guide-active-area 2)) {:x (- pos (/ guide-active-area 2))
:y (+ (:y vbox) rules-pos) :y (+ (:y vbox) rulers-pos)
:width guide-active-area :width guide-active-area
:height (:height vbox)} :height (:height vbox)}
:else :else
{:x (+ (:x vbox) rules-pos) {:x (+ (:x vbox) rulers-pos)
:y (- pos (/ guide-active-area 2)) :y (- pos (/ guide-active-area 2))
:width (:width vbox) :width (:width vbox)
:height guide-active-area}))) :height guide-active-area})))
@ -198,23 +198,23 @@
(defn guide-pill-axis (defn guide-pill-axis
[pos vbox zoom 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-width (/ guide-pill-width zoom)
guide-pill-height (/ guide-pill-height zoom)] guide-pill-height (/ guide-pill-height zoom)]
(if (= axis :x) (if (= axis :x)
{:rect-x (- pos (/ guide-pill-width 2)) {: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-width guide-pill-width
:rect-height guide-pill-height :rect-height guide-pill-height
:text-x pos :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-y (- pos (/ guide-pill-width 2))
:rect-width guide-pill-height :rect-width guide-pill-height
:rect-height guide-pill-width :rect-height guide-pill-width
:text-x (+ (:x vbox) rules-pos (- (/ 3 zoom))) :text-x (+ (:x vbox) rulers-pos (- (/ 3 zoom)))
:text-y pos}))) :text-y pos})))
(defn guide-inside-vbox? (defn guide-inside-vbox?
@ -222,7 +222,7 @@
(partial guide-inside-vbox? zoom vbox)) (partial guide-inside-vbox? zoom vbox))
([zoom {:keys [x y width height]} {:keys [axis position]}] ([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 x1 x
x2 (+ x width) x2 (+ x width)
y1 y y1 y
@ -376,8 +376,8 @@
:text-anchor "middle" :text-anchor "middle"
:dominant-baseline "middle" :dominant-baseline "middle"
:transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")")) :transform (when (= axis :y) (str "rotate(-90 " text-x "," text-y ")"))
:style {:font-size (/ rules/font-size zoom) :style {:font-size (/ rulers/font-size zoom)
:font-family rules/font-family :font-family rulers/font-family
:fill colors/black}} :fill colors/black}}
;; If the guide is associated to a frame we show the position relative to the frame ;; 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))))]]))]))) (fmt/format-number (- pos (if (= axis :x) (:x frame) (:y frame))))]]))])))

View file

@ -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}])])))

View file

@ -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}])])))