0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-12 15:01:28 -05:00

Merge pull request #4816 from penpot/alotor-plugins-2

Improvements over plugins subsystem
This commit is contained in:
Andrey Antukh 2024-07-02 13:43:15 +02:00 committed by GitHub
commit 62cea62356
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 3316 additions and 1526 deletions

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M7.32 2.25H4.417A2.168 2.168 0 0 0 2.25 4.418v2.61M7.32 21.75H4.417c-1.197 0-2.167-1.263-2.167-2.46v-2.61m19.5-9.36V4.418a2.168 2.168 0 0 0-2.168-2.168H16.68m5.07 14.43v2.903a2.168 2.168 0 0 1-2.168 2.167H16.68M15.496 7.5H8.499a1.001 1.001 0 0 0-1.001 1.001V15.5a1 1 0 0 0 1.001 1h6.997a1 1 0 0 0 1.001-1V8.501A1 1 0 0 0 15.496 7.5Z" style="stroke-width: 1; stroke-linecap: round;"/></svg>

After

Width:  |  Height:  |  Size: 462 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M20.888 22v-2.222a4.444 4.444 0 0 0-4.443-4.444h-8.89a4.444 4.444 0 0 0-4.443 4.444V22M12 10.889a4.445 4.445 0 1 0 .172-8.888A4.445 4.445 0 0 0 12 10.889Z" style="stroke-width: 1; stroke-opacity: 1; stroke-linecap: round;"/></svg>

After

Width:  |  Height:  |  Size: 303 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M6.5 2H20v20H6.5a2.5 2.5 0 0 1-2.499-2.436L4 19.5v-15A2.5 2.5 0 0 1 6.5 2ZM20 17h0ZH6.5a2.5 2.5 0 0 0-2.499 2.436" style="stroke-width: 1; stroke-linecap: round;" /></svg>

After

Width:  |  Height:  |  Size: 244 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19.439 7.85c-.049.322.059.648.289.878l1.568 1.568c.47.47.706 1.087.706 1.704s-.235 1.233-.706 1.704l-1.611 1.611a.98.98 0 0 1-.837.276c-.47-.07-.802-.48-.968-.925a2.501 2.501 0 1 0-3.214 3.214c.446.166.855.497.925.968a.979.979 0 0 1-.276.837l-1.61 1.61a2.404 2.404 0 0 1-1.705.707 2.402 2.402 0 0 1-1.704-.706l-1.568-1.568a1.026 1.026 0 0 0-.877-.29c-.493.074-.84.504-1.02.968a2.5 2.5 0 1 1-3.237-3.237c.464-.18.894-.527.967-1.02a1.026 1.026 0 0 0-.289-.877l-1.568-1.568A2.402 2.402 0 0 1 1.998 12c0-.617.236-1.234.706-1.704L4.23 8.77c.24-.24.581-.353.917-.303.515.077.877.528 1.073 1.01a2.5 2.5 0 1 0 3.259-3.259c-.482-.196-.933-.558-1.01-1.073-.05-.336.062-.676.303-.917l1.525-1.525A2.402 2.402 0 0 1 12 1.998c.617 0 1.234.236 1.704.706l1.568 1.568c.23.23.556.338.877.29.493-.074.84-.504 1.02-.968a2.5 2.5 0 1 1 3.237 3.237c-.464.18-.894.527-.967 1.02Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 1,021 B

File diff suppressed because it is too large Load diff

View file

@ -110,6 +110,7 @@
(def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI" "https://penpot.app/privacy"))
(def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/"))
(def grid-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/"))
(def plugins-list-uri (obj/get global "penpotPluginsListUri" "https://penpot-docs-plugins.netlify.app/technical-guide/plugins/getting-started/#examples"))
(defn- normalize-uri
[uri-str]

View file

@ -24,6 +24,7 @@
[app.util.color :as uc]
[app.util.storage :refer [storage]]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk]))
;; A set of keys that are used for shared state identifiers
@ -377,7 +378,7 @@
(defn color-att->text
[color]
{:fill-color (:color color)
{:fill-color (when (:color color) (str/lower (:color color)))
:fill-opacity (:opacity color)
:fill-color-ref-id (:id color)
:fill-color-ref-file (:file-id color)

View file

@ -71,6 +71,13 @@
(defn get-font-data [id]
(get @fontsdb id))
(defn find-font-data [data]
(d/seek
(fn [font]
(= (select-keys font (keys data))
data))
(vals @fontsdb)))
(defn resolve-variants
[id]
(get-in @fontsdb [id :variants]))
@ -249,6 +256,11 @@
(or (d/seek #(= (:id %) font-variant-id) variants)
(get-default-variant font)))
(defn find-variant
[{:keys [variants] :as font} variant-data]
(let [props (keys variant-data)]
(d/seek #(= (select-keys % props) variant-data) variants)))
;; Font embedding functions
(defn get-node-fonts
"Extracts the fonts used by some node"

View file

@ -178,6 +178,9 @@
(def ^:icon msg-success (icon-xref :msg-success))
(def ^:icon msg-warning (icon-xref :msg-warning))
(def ^:icon open-link (icon-xref :open-link))
(def ^:icon oauth-1 (icon-xref :oauth-1))
(def ^:icon oauth-2 (icon-xref :oauth-2))
(def ^:icon oauth-3 (icon-xref :oauth-3))
(def ^:icon padding-bottom (icon-xref :padding-bottom))
(def ^:icon padding-extended (icon-xref :padding-extended))
(def ^:icon padding-left (icon-xref :padding-left))
@ -190,6 +193,7 @@
(def ^:icon picker (icon-xref :picker))
(def ^:icon pin (icon-xref :pin))
(def ^:icon play (icon-xref :play))
(def ^:icon puzzle (icon-xref :puzzle))
(def ^:icon rectangle (icon-xref :rectangle))
(def ^:icon reload (icon-xref :reload))
(def ^:icon remove-icon (icon-xref :remove))

View file

@ -30,6 +30,7 @@
[app.main.ui.hooks.resize :as r]
[app.main.ui.icons :as i]
[app.main.ui.workspace.plugins :as uwp]
[app.plugins :as plugins]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
@ -608,7 +609,7 @@
::mf/wrap [mf/memo]}
[{:keys [open-plugins on-close]}]
(when (features/active-feature? @st/state "plugins/runtime")
(let [plugins (uwp/load-from-store)]
(let [plugins @plugins/pluginsdb]
[:& dropdown-menu {:show true
:list-class (stl/css-case :sub-menu true :plugins true)
:on-close on-close}

View file

@ -9,22 +9,23 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.modal :as modal]
[app.main.store :as st]
[app.main.ui.components.search-bar :refer [search-bar]]
[app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.icons :as i]
[app.plugins :as plugins]
[app.util.avatars :as avatars]
[app.util.dom :as dom]
[app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]]
[app.util.object :as obj]
[beicon.v2.core :as rx]
[rumext.v2 :as mf]))
(def ^:private close-icon
(i/icon-xref :close (stl/css :close-icon)))
(mf/defc plugin-entry
[{:keys [index manifest on-open-plugin on-remove-plugin]}]
@ -55,20 +56,6 @@
[:button {:class (stl/css :trash-button)
:on-click handle-delete-click} i/delete]]))
(defn load-from-store
[]
(let [ls (.-localStorage js/window)
plugins-val (.getItem ls "plugins")]
(when plugins-val
(let [plugins-js (.parse js/JSON plugins-val)]
(js->clj plugins-js {:keywordize-keys true})))))
(defn save-to-store
[plugins]
(let [ls (.-localStorage js/window)
plugins-js (clj->js plugins)
plugins-val (.stringify js/JSON plugins-js)]
(.setItem ls "plugins" plugins-val)))
(defn open-plugin!
[{:keys [plugin-id name description host code icon permissions]}]
@ -90,7 +77,7 @@
::mf/register-as :plugin-management}
[]
(let [plugins-state* (mf/use-state [])
(let [plugins-state* (mf/use-state @plugins/pluginsdb)
plugins-state @plugins-state*
plugin-url* (mf/use-state "")
@ -123,27 +110,16 @@
(rx/map :body)
(rx/subs!
(fn [body]
(let [name (obj/get body "name")
desc (obj/get body "description")
code (obj/get body "code")
icon (obj/get body "icon")
permissions (obj/get body "permissions")
origin (obj/get (js/URL. plugin-url) "origin")
plugin-id (str (uuid/next))
new-state
(conj plugins-state
{:plugin-id plugin-id
:name name
:description desc
:host origin
:code code
:icon icon
:permissions (->> permissions (mapv str))})]
(let [plugin (plugins/parser-manifest plugin-url body)]
(modal/show!
:plugin-permissions
{:plugin plugin
:on-accept
#(do
(plugins/install-plugin! plugin)
(modal/show! :plugin-management {}))})
(reset! input-status* :success)
(reset! plugin-url* "")
(reset! plugins-state* new-state)
(save-to-store new-state)))
(reset! plugin-url* "")))
(fn [_]
(reset! input-status* :error-url))))))
@ -157,21 +133,12 @@
(mf/use-callback
(mf/deps plugins-state)
(fn [plugin-index]
(let [new-state
(into []
(keep-indexed (fn [idx item]
(when (not= idx plugin-index) item)))
plugins-state)]
(reset! plugins-state* new-state)
(save-to-store new-state))))]
(mf/use-effect
(fn []
(reset! plugins-state* (d/nilv (load-from-store) []))))
(let [plugin (nth @plugins/pluginsdb plugin-index)]
(plugins/remove-plugin! plugin)
(reset! plugins-state* @plugins/pluginsdb))))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)}
[:div {:class (stl/css :modal-dialog :plugin-management)}
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon]
[:div {:class (stl/css :modal-title)} (tr "workspace.plugins.title")]
@ -183,7 +150,6 @@
:class (stl/css-case :input-error error?)}]
[:button {:class (stl/css :primary-button)
:disabled (empty? plugin-url)
:on-click handle-install-click} (tr "workspace.plugins.install")]]
(when error?
@ -194,9 +160,9 @@
(if (empty? plugins-state)
[:div {:class (stl/css :plugins-empty)}
[:div {:class (stl/css :plugins-empty-logo)} i/rocket]
[:div {:class (stl/css :plugins-empty-logo)} i/puzzle]
[:div {:class (stl/css :plugins-empty-text)} (tr "workspace.plugins.empty-plugins")]
[:a {:class (stl/css :plugins-link) :href "#"}
[:a {:class (stl/css :plugins-link) :href cf/plugins-list-uri :target "_blank"}
(tr "workspace.plugins.plugin-list-link") i/external-link]]
[:*
@ -204,10 +170,84 @@
:title (tr "workspace.plugins.installed-plugins")}]
[:div {:class (stl/css :plugins-list)}
(for [[idx manifest] (d/enumerate plugins-state)]
[:& plugin-entry {:key (dm/str "plugin-" idx)
:index idx
:manifest manifest
:on-open-plugin handle-open-plugin
:on-remove-plugin handle-remove-plugin}])]])]]]))
(mf/defc plugins-permissions-dialog
{::mf/register modal/components
::mf/register-as :plugin-permissions}
[{:keys [plugin on-accept]}]
(let [{:keys [permissions]} plugin
permissions (set permissions)
handle-accept-dialog
(mf/use-callback
(fn [event]
(dom/prevent-default event)
(st/emit! (modal/hide))
(on-accept)))
handle-close-dialog
(mf/use-callback
(fn [event]
(dom/prevent-default event)
(st/emit! (modal/hide))))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog :plugin-permissions)}
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon]
[:div {:class (stl/css :modal-title)} (tr "workspace.plugins.permissions.title")]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :permissions-list)}
(when (contains? permissions "content:read")
[:div {:class (stl/css :permissions-list-entry)}
i/oauth-1
[:p {:class (stl/css :permissions-list-text)}
(tr "workspace.plugins.permissions.disclaimer")]])
(when (contains? permissions "content:write")
[:div {:class (stl/css :permissions-list-entry)}
i/oauth-1
[:p {:class (stl/css :permissions-list-text)}
(tr "workspace.plugins.permissions.content-read")]])
(when (contains? permissions "user:read")
[:div {:class (stl/css :permissions-list-entry)}
i/oauth-2
[:p {:class (stl/css :permissions-list-text)}
(tr "workspace.plugins.permissions.content-write")]])
(when (contains? permissions "library:read")
[:div {:class (stl/css :permissions-list-entry)}
i/oauth-3
[:p {:class (stl/css :permissions-list-text)}
(tr "workspace.plugins.permissions.user-read")]])
(when (contains? permissions "library:write")
[:div {:class (stl/css :permissions-list-entry)}
i/oauth-3
[:p {:class (stl/css :permissions-list-text)}
(tr "workspace.plugins.permissions.library-read")]])]
[:div {:class (stl/css :permissions-disclaimer)}
(tr "workspace.plugins.permissions.library-write")]]
[:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons)}
[:input
{:class (stl/css :cancel-button :button-expand)
:type "button"
:value (tr "ds.confirm-cancel")
:on-click handle-close-dialog}]
[:input
{:class (stl/css :primary-button :button-expand)
:type "button"
:value (tr "ds.confirm-allow")
:on-click handle-accept-dialog}]]]]]))

View file

@ -13,15 +13,27 @@
.modal-dialog {
@extend .modal-container-base;
display: grid;
grid-template-rows: auto 1fr;
width: $s-472;
max-width: $s-472;
grid-template-rows: auto 1fr auto;
&.plugin-permissions {
width: $s-412;
max-width: $s-412;
}
&.plugin-management {
width: $s-472;
max-width: $s-472;
}
hr {
border-color: $db-tertiary;
}
}
.modal-footer {
margin-top: 2rem;
}
.close-btn {
@extend .modal-close-btn-base;
}
@ -40,8 +52,15 @@
.modal-content {
display: flex;
flex-direction: column;
height: $s-380;
max-height: $s-380;
.plugin-permissions & {
gap: $s-20;
}
.plugin-management & {
height: $s-380;
max-height: $s-380;
}
}
.primary-button {
@ -50,6 +69,17 @@
padding: $s-0 $s-16;
}
.button-expand {
width: 100%;
margin: 0;
}
.cancel-button {
@extend .button-secondary;
@include headlineSmallTypography;
padding: $s-0 $s-16;
}
.search-icon {
@include flexCenter;
width: $s-20;
@ -87,7 +117,7 @@
.plugins-list {
padding-top: $s-20;
overflow-x: hidden;
overflow-y: scroll;
overflow-y: auto;
flex: 1;
display: flex;
flex-direction: column;
@ -148,8 +178,8 @@
svg {
width: $s-16;
height: $s-16;
fill: $df-secondary;
stroke-width: 0;
stroke: $df-secondary;
fill: none;
}
}
@ -190,3 +220,45 @@ div.input-error {
fill: none;
}
}
.plugin-permissions {
}
.permissions-list {
display: flex;
flex-direction: column;
gap: $s-24;
}
.permissions-list-entry {
display: grid;
grid-template-columns: 24px 1fr;
gap: $s-16;
align-items: center;
svg {
width: $s-24;
height: $s-24;
stroke: $da-primary;
fill: none;
}
}
.permissions-list-text {
@include bodySmallTypography;
margin: 0;
color: $df-secondary;
}
.permissions-disclaimer {
@include bodySmallTypography;
padding: $s-16;
background: $db-tertiary;
color: $df-secondary;
border-radius: $br-4;
}
.action-buttons {
display: flex;
gap: $s-12;
}

View file

@ -11,11 +11,13 @@
[app.common.geom.point :as gpt]
[app.common.media :as cm]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.media :as dwm]
[app.main.data.workspace.path.state :as pst]
[app.main.data.workspace.shortcuts :as sc]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]]
@ -192,6 +194,17 @@
:data-testid "path-btn"}
i/path]]
(when (features/active-feature? @st/state "plugins/runtime")
[:li
[:button
{:title (tr "workspace.toolbar.plugins" (sc/get-tooltip :plugins))
:aria-label (tr "workspace.toolbar.plugins" (sc/get-tooltip :plugins))
:class (stl/css :main-toolbar-options-button)
:on-click #(modal/show! :plugin-management {})
:data-tool "plugins"
:data-testid "plugins-btn"}
i/puzzle]])
(when *assert*
[:li
[:button

View file

@ -7,18 +7,25 @@
(ns app.plugins
"RPC for plugins runtime."
(:require
[app.common.uuid :as uuid]
[app.main.features :as features]
[app.main.store :as st]
[app.plugins.api :as api]
[app.plugins.public-utils]
[app.plugins.register :as register]
[app.util.globals :refer [global]]
[app.util.object :as obj]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(def pluginsdb register/pluginsdb)
(def install-plugin! register/install-plugin!)
(def remove-plugin! register/remove-plugin!)
(defn init-plugins-runtime!
[]
(when-let [init-runtime (obj/get global "initPluginsRuntime")]
(register/init)
(init-runtime (fn [plugin-id] (api/create-context plugin-id)))))
(defn initialize
@ -33,3 +40,20 @@
(rx/take 1)
(rx/tap init-plugins-runtime!)
(rx/ignore)))))
(defn parser-manifest
[plugin-url ^js manifest]
(let [name (obj/get manifest "name")
desc (obj/get manifest "description")
code (obj/get manifest "code")
icon (obj/get manifest "icon")
permissions (obj/get manifest "permissions")
origin (obj/get (js/URL. plugin-url) "origin")
plugin-id (str (uuid/next))]
{:plugin-id plugin-id
:name name
:description desc
:host origin
:code code
:icon icon
:permissions (->> permissions (mapv str))}))

View file

@ -13,20 +13,24 @@
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.record :as cr]
[app.common.schema :as sm]
[app.common.text :as txt]
[app.common.types.color :as ctc]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.main.data.changes :as ch]
[app.main.data.workspace.bool :as dwb]
[app.main.data.workspace.colors :as dwc]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.media :as dwm]
[app.main.store :as st]
[app.plugins.events :as events]
[app.plugins.file :as file]
[app.plugins.fonts :as fonts]
[app.plugins.format :as format]
[app.plugins.library :as library]
[app.plugins.page :as page]
[app.plugins.parser :as parser]
[app.plugins.shape :as shape]
[app.plugins.user :as user]
[app.plugins.utils :as u]
@ -87,35 +91,52 @@
(let [selection (get-in @st/state [:workspace-local :selected])]
(apply array (sequence (map (partial shape/shape-proxy $plugin)) selection))))
(getColors
(shapesColors
[_ shapes]
(let [objects (u/locate-objects)
shapes (->> shapes
(map #(obj/get % "$id"))
(mapcat #(cfh/get-children-with-self objects %)))
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :shapesColors-shapes shapes)
file-id (:current-file-id @st/state)
shared-libs (:workspace-libraries @st/state)
:else
(let [objects (u/locate-objects)
shapes (->> shapes
(map #(obj/get % "$id"))
(mapcat #(cfh/get-children-with-self objects %)))
file-id (:current-file-id @st/state)
shared-libs (:workspace-libraries @st/state)]
colors
(apply
array
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs)
(map (fn [[color attrs]]
(let [shapes-info (apply array (map (fn [{:keys [prop shape-id index]}]
#js {:property (d/name prop)
:index index
:shapeId (str shape-id)}) attrs))
color (u/to-js color)]
(obj/set! color "shapeInfo" shapes-info)
color)))))]
colors))
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs)
(format/format-array format/format-color-result)))))
(changeColor
[_ _shapes _old-color _new-color]
;; TODO
)
(replaceColor
[_ shapes old-color new-color]
(let [old-color (parser/parse-color old-color)
new-color (parser/parse-color new-color)]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :replaceColor-shapes shapes)
(not (sm/validate ::ctc/color old-color))
(u/display-not-valid :replaceColor-oldColor old-color)
(not (sm/validate ::ctc/color new-color))
(u/display-not-valid :replaceColor-newColor new-color)
:else
(let [file-id (:current-file-id @st/state)
shared-libs (:workspace-libraries @st/state)
objects (u/locate-objects)
shapes
(->> shapes
(map #(obj/get % "$id"))
(mapcat #(cfh/get-children-with-self objects %)))
shapes-by-color
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs))]
(st/emit! (dwc/change-color-in-selected new-color (get shapes-by-color old-color) old-color))))))
(getRoot
[_]
@ -154,8 +175,8 @@
(p/create
(fn [resolve reject]
(->> (dwm/upload-media-url name file-id url)
(rx/map u/to-js)
(rx/take 1)
(rx/map format/format-image)
(rx/subs! resolve reject)))))))
(uploadMediaData
@ -171,7 +192,7 @@
:on-image identity
:on-svg identity})
(rx/take 1)
(rx/map u/to-js)
(rx/map format/format-image)
(rx/subs! resolve reject))))))
(group

View file

@ -6,7 +6,6 @@
(ns app.plugins.events
(:require
[app.common.data.macros :as dm]
[app.main.store :as st]
[app.plugins.file :as file]
[app.plugins.page :as page]
@ -24,25 +23,19 @@
(defmethod handle-state-change "filechange"
[_ plugin-id old-val new-val]
(let [old-file (:workspace-file old-val)
new-file (:workspace-file new-val)
old-data (:workspace-data old-val)
new-data (:workspace-data new-val)]
(if (and (identical? old-file new-file)
(identical? old-data new-data))
(let [old-file-id (:current-file-id old-val)
new-file-id (:current-file-id new-val)]
(if (identical? old-file-id new-file-id)
::not-changed
(file/file-proxy plugin-id (:id new-file)))))
(file/file-proxy plugin-id new-file-id))))
(defmethod handle-state-change "pagechange"
[_ plugin-id old-val new-val]
(let [file-id (:current-file-id new-val)
old-page-id (:current-page-id old-val)
new-page-id (:current-page-id new-val)
old-page (dm/get-in old-val [:workspace-data :pages-index old-page-id])
new-page (dm/get-in new-val [:workspace-data :pages-index new-page-id])]
(if (identical? old-page new-page)
(let [old-page-id (:current-page-id old-val)
new-page-id (:current-page-id new-val)]
(if (identical? old-page-id new-page-id)
::not-changed
(page/page-proxy plugin-id file-id new-page-id))))
(page/page-proxy plugin-id (:current-file-id new-val) new-page-id))))
(defmethod handle-state-change "selectionchange"
[_ _ old-val new-val]
@ -75,7 +68,10 @@
(fn [_ _ old-val new-val]
(let [result (handle-state-change type plugin-id old-val new-val)]
(when (not= ::not-changed result)
(callback result)))))
(try
(callback result)
(catch :default cause
(.error js/console cause)))))))
;; return the generated key
key))

View file

@ -12,6 +12,7 @@
[app.main.data.workspace :as dw]
[app.main.store :as st]
[app.plugins.page :as page]
[app.plugins.register :as r]
[app.plugins.utils :as u]
[app.util.object :as obj]))
@ -26,7 +27,7 @@
[self key]
(cond
(not (string? key))
(u/display-not-valid :file-plugin-data-key key)
(u/display-not-valid :getPluginData-key key)
:else
(let [file (u/proxy->file self)]
@ -36,10 +37,13 @@
[_ key value]
(cond
(or (not (string? key)) (empty? key))
(u/display-not-valid :file-plugin-data-key key)
(u/display-not-valid :setPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :file-plugin-data value)
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data $id :file (keyword "plugin" (str $plugin)) key value))))
@ -53,10 +57,10 @@
[self namespace key]
(cond
(not (string? namespace))
(u/display-not-valid :file-plugin-data-namespace namespace)
(u/display-not-valid :getSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :file-plugin-data-key key)
(u/display-not-valid :getSharedPluginData-key key)
:else
(let [file (u/proxy->file self)]
@ -67,13 +71,16 @@
(cond
(or (not (string? namespace)) (empty? namespace))
(u/display-not-valid :file-plugin-data-namespace namespace)
(u/display-not-valid :setSharedPluginData-namespace namespace)
(or (not (string? key)) (empty? key))
(u/display-not-valid :file-plugin-data-key key)
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :file-plugin-data value)
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data $id :file (keyword "shared" namespace) key value))))
@ -82,7 +89,7 @@
[self namespace]
(cond
(not (string? namespace))
(u/display-not-valid :file-plugin-data-namespace namespace)
(u/display-not-valid :getSharedPluginDataKeys namespace)
:else
(let [file (u/proxy->file self)]

View file

@ -13,6 +13,7 @@
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.plugins.register :as r]
[app.plugins.utils :as u]
[app.util.object :as obj]
[potok.v2.core :as ptk]))
@ -58,6 +59,9 @@
(not (contains? ctl/flex-direction-types value))
(u/display-not-valid :dir value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :dir "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-flex-dir value}))))))}
@ -71,6 +75,9 @@
(not (contains? ctl/align-items-types value))
(u/display-not-valid :alignItems value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignItems "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-align-items value}))))))}
@ -84,6 +91,9 @@
(not (contains? ctl/align-content-types value))
(u/display-not-valid :alignContent value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignContent "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-align-content value}))))))}
@ -97,6 +107,9 @@
(not (contains? ctl/justify-items-types value))
(u/display-not-valid :justifyItems value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifyItems "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-justify-items value}))))))}
@ -110,6 +123,9 @@
(not (contains? ctl/justify-content-types value))
(u/display-not-valid :justifyContent value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifyContent "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-justify-content value}))))))}
@ -122,6 +138,9 @@
(not (us/safe-int? value))
(u/display-not-valid :rowGap value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rowGap "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))}
@ -134,6 +153,9 @@
(not (us/safe-int? value))
(u/display-not-valid :columnGap value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :columnGap "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))}
@ -146,6 +168,9 @@
(not (us/safe-int? value))
(u/display-not-valid :verticalPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))}
@ -158,6 +183,9 @@
(not (us/safe-int? value))
(u/display-not-valid :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))}
@ -171,6 +199,9 @@
(not (us/safe-int? value))
(u/display-not-valid :topPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :topPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}})))))}
@ -183,6 +214,9 @@
(not (us/safe-int? value))
(u/display-not-valid :rightPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rightPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}})))))}
@ -195,6 +229,9 @@
(not (us/safe-int? value))
(u/display-not-valid :bottomPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :bottomPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}})))))}
@ -207,6 +244,9 @@
(not (us/safe-int? value))
(u/display-not-valid :leftPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :leftPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}})))))})))
@ -234,6 +274,9 @@
(not (boolean? value))
(u/display-not-valid :absolute value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :absolute "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-item-absolute value})))))}
@ -246,6 +289,9 @@
(us/safe-int? value)
(u/display-not-valid :zIndex value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :zIndex "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-z-index value})))))}
@ -259,6 +305,9 @@
(not (contains? ctl/item-h-sizing-types value))
(u/display-not-valid :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-h-sizing value}))))))}
@ -272,6 +321,9 @@
(not (contains? ctl/item-v-sizing-types value))
(u/display-not-valid :verticalSizing value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalSizing "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-v-sizing value}))))))}
@ -285,6 +337,9 @@
(not (contains? ctl/item-align-self-types value))
(u/display-not-valid :alignSelf value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignSelf "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-align-self value}))))))}
@ -297,6 +352,9 @@
(not (us/safe-number? value))
(u/display-not-valid :verticalMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value :m3 value}})))))}
@ -309,6 +367,9 @@
(not (us/safe-number? value))
(u/display-not-valid :horizontalMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value :m4 value}})))))}
@ -321,6 +382,9 @@
(not (us/safe-number? value))
(u/display-not-valid :topMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :topMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value}})))))}
@ -333,6 +397,9 @@
(not (us/safe-number? value))
(u/display-not-valid :rightMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rightMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value}})))))}
@ -345,6 +412,9 @@
(not (us/safe-number? value))
(u/display-not-valid :bottomMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :bottomMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m3 value}})))))}
@ -357,6 +427,9 @@
(not (us/safe-number? value))
(u/display-not-valid :leftMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :leftMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m4 value}})))))}
@ -369,6 +442,9 @@
(not (us/safe-number? value))
(u/display-not-valid :maxWidth value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :maxWidth "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-w value})))))}
@ -381,6 +457,9 @@
(not (us/safe-number? value))
(u/display-not-valid :minWidth value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :minWidth "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-w value})))))}
@ -393,6 +472,9 @@
(not (us/safe-number? value))
(u/display-not-valid :maxHeight value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :maxHeight "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-h value})))))}
@ -405,6 +487,9 @@
(not (us/safe-number? value))
(u/display-not-valid :minHeight value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :minHeight "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-h value})))))})))

View file

@ -11,13 +11,18 @@
[app.main.data.workspace.texts :as dwt]
[app.main.fonts :as fonts]
[app.main.store :as st]
[app.plugins.register :as r]
[app.plugins.shape :as shape]
[app.plugins.text :as text]
[app.plugins.utils :as u]
[app.util.object :as obj]
[cuerdas.core :as str]))
(deftype PenpotFontVariant [name fontVariantId fontWeight fontStyle])
(defn variant-proxy? [p]
(instance? PenpotFontVariant p))
(deftype PenpotFont [name fontId fontFamily fontStyle fontVariantId fontWeight variants]
Object
@ -26,7 +31,8 @@
(not (shape/shape-proxy? text))
(u/display-not-valid :applyToText text)
;; TODO: Check variant inside font variants
(not (r/check-permission (obj/get text "$plugin") "content:write"))
(u/display-not-valid :applyToText "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get text "$id")
@ -39,10 +45,11 @@
(applyToRange [_ range variant]
(cond
(not (shape/text-range? range))
(not (text/text-range? range))
(u/display-not-valid :applyToRange range)
;; TODO: Check variant inside font variants
(not (r/check-permission (obj/get range "$plugin") "content:write"))
(u/display-not-valid :applyToRange "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get range "$id")
@ -59,13 +66,13 @@
(instance? PenpotFont p))
(defn font-proxy
[{:keys [id name variants] :as font}]
[{:keys [id family name variants] :as font}]
(when (some? font)
(let [default-variant (fonts/get-default-variant font)]
(PenpotFont.
name
id
id
family
(:style default-variant)
(:id default-variant)
(:weight default-variant)

View file

@ -0,0 +1,424 @@
;; 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.plugins.format
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.util.object :as obj]))
(defn format-id
[id]
(when id (dm/str id)))
(defn format-key
[kw]
(when kw (d/name kw)))
(defn format-array
[format-fn coll]
(when (some? coll)
(apply array (keep format-fn coll))))
(defn format-mixed
[value]
(if (= value :multiple)
"mixed"
value))
;; export type PenpotPoint = { x: number; y: number };
(defn format-point
[{:keys [x y] :as point}]
(when (some? point)
(obj/clear-empty
#js {:x x :y y})))
;;export type PenpotBounds = {
;; x: number;
;; y: number;
;; width: number;
;; height: number;
;;};
(defn format-bounds
[{:keys [x y width height] :as bounds}]
(when (some? bounds)
(obj/clear-empty
#js {:x x :y y :width width :height height})))
;; export interface PenpotColorShapeInfoEntry {
;; readonly property: string;
;; readonly index?: number;
;; readonly shapeId: string;
;; }
(defn format-shape-info
[{:keys [prop shape-id index] :as info}]
(when (some? info)
(obj/clear-empty
#js {:property (d/name prop)
:index index
:shapeId (dm/str shape-id)})))
;; export type PenpotGradient = {
;; type: 'linear' | 'radial';
;; startX: number;
;; startY: number;
;; endX: number;
;; endY: number;
;; width: number;
;; stops: Array<{ color: string; opacity?: number; offset: number }>;
;; };
(defn format-stop
[{:keys [color opacity offset] :as stop}]
(when (some? stop)
(obj/clear-empty #js {:color color :opacity opacity :offset offset})))
(defn format-gradient
[{:keys [type start-x start-y end-x end-y width stops] :as gradient}]
(when (some? gradient)
(obj/clear-empty
#js {:type (format-key type)
:startX start-x
:startY start-y
:endX end-x
:endY end-y
:width width
:stops (format-array format-stop stops)})))
;; export type PenpotImageData = {
;; name?: string;
;; width: number;
;; height: number;
;; mtype?: string;
;; id: string;
;; keepApectRatio?: boolean;
;; };
(defn format-image
[{:keys [name width height mtype id keep-aspect-ratio] :as image}]
(when (some? image)
(obj/clear-empty
#js {:name name
:width width
:height height
:mtype mtype
:id (format-id id)
:keepAspectRatio keep-aspect-ratio})))
;; export interface PenpotColor {
;; id?: string;
;; name?: string;
;; path?: string;
;; color?: string;
;; opacity?: number;
;; refId?: string;
;; refFile?: string;
;; gradient?: PenpotGradient;
;; image?: PenpotImageData;
;; }
(defn format-color
[{:keys [id name path color opacity ref-id ref-file gradient image] :as color-data}]
(when (some? color-data)
(obj/clear-empty
#js {:id (format-id id)
:name name
:path path
:color color
:opacity opacity
:refId (format-id ref-id)
:refFile (format-id ref-file)
:gradient (format-gradient gradient)
:image (format-image image)})))
;; PenpotColor & PenpotColorShapeInfo
(defn format-color-result
[[color attrs]]
(let [shapes-info (apply array (map format-shape-info attrs))
color (format-color color)]
(obj/set! color "shapeInfo" shapes-info)
color))
;; export interface PenpotShadow {
;; id?: string;
;; style?: 'drop-shadow' | 'inner-shadow';
;; offsetX?: number;
;; offsetY?: number;
;; blur?: number;
;; spread?: number;
;; hidden?: boolean;
;; color?: PenpotColor;
;; }
(defn format-shadow
[{:keys [id style offset-x offset-y blur spread hidden color] :as shadow}]
(when (some? shadow)
(obj/clear-empty
#js {:id (-> id format-id)
:style (-> style format-key)
:offsetX offset-x
:offsetY offset-y
:blur blur
:spread spread
:hidden hidden
:color (format-color color)})))
(defn format-shadows
[shadows]
(when (some? shadows)
(format-array format-shadow shadows)))
;;export interface PenpotFill {
;; fillColor?: string;
;; fillOpacity?: number;
;; fillColorGradient?: PenpotGradient;
;; fillColorRefFile?: string;
;; fillColorRefId?: string;
;; fillImage?: PenpotImageData;
;;}
(defn format-fill
[{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-file fill-color-ref-id fill-image] :as fill}]
(when (some? fill)
(obj/clear-empty
#js {:fillColor fill-color
:fillOpacity fill-opacity
:fillColorGradient (format-gradient fill-color-gradient)
:fillColorRefFile (format-id fill-color-ref-file)
:fillColorRefId (format-id fill-color-ref-id)
:fillImage (format-image fill-image)})))
(defn format-fills
[fills]
(cond
(= fills :multiple)
"mixed"
(= fills "mixed")
"mixed"
(some? fills)
(format-array format-fill fills)))
;; export interface PenpotStroke {
;; strokeColor?: string;
;; strokeColorRefFile?: string;
;; strokeColorRefId?: string;
;; strokeOpacity?: number;
;; strokeStyle?: 'solid' | 'dotted' | 'dashed' | 'mixed' | 'none' | 'svg';
;; strokeWidth?: number;
;; strokeAlignment?: 'center' | 'inner' | 'outer';
;; strokeCapStart?: PenpotStrokeCap;
;; strokeCapEnd?: PenpotStrokeCap;
;; strokeColorGradient?: PenpotGradient;
;; }
(defn format-stroke
[{:keys [stroke-color stroke-color-ref-file stroke-color-ref-id
stroke-opacity stroke-style stroke-width stroke-alignment
stroke-cap-start stroke-cap-end stroke-color-gradient] :as stroke}]
(when (some? stroke)
(obj/clear-empty
#js {:strokeColor stroke-color
:strokeColorRefFile (format-id stroke-color-ref-file)
:strokeColorRefId (format-id stroke-color-ref-id)
:strokeOpacity stroke-opacity
:strokeStyle (format-key stroke-style)
:strokeWidth stroke-width
:strokeAlignment (format-key stroke-alignment)
:strokeCapStart (format-key stroke-cap-start)
:strokeCapEnd (format-key stroke-cap-end)
:strokeColorGradient (format-gradient stroke-color-gradient)})))
(defn format-strokes
[strokes]
(when (some? strokes)
(format-array format-stroke strokes)))
;; export interface PenpotBlur {
;; id?: string;
;; type?: 'layer-blur';
;; value?: number;
;; hidden?: boolean;
;; }
(defn format-blur
[{:keys [id type value hidden] :as blur}]
(when (some? blur)
(obj/clear-empty
#js {:id (format-id id)
:type (format-key type)
:value value
:hidden hidden})))
;; export interface PenpotExport {
;; type: 'png' | 'jpeg' | 'svg' | 'pdf';
;; scale: number;
;; suffix: string;
;; }
(defn format-export
[{:keys [type scale suffix] :as export}]
(when (some? export)
(obj/clear-empty
#js {:type (format-key type)
:scale scale
:suffix suffix})))
(defn format-exports
[exports]
(when (some? exports)
(format-array format-export exports)))
;; export interface PenpotFrameGuideColumnParams {
;; color: { color: string; opacity: number };
;; type?: 'stretch' | 'left' | 'center' | 'right';
;; size?: number;
;; margin?: number;
;; itemLength?: number;
;; gutter?: number;
;; }
(defn format-frame-guide-column-params
[{:keys [color type size margin item-length gutter] :as params}]
(when (some? params)
(obj/clear-empty
#js {:color (format-color color)
:type (format-key type)
:size size
:margin margin
:itemLength item-length
:gutter gutter})))
;; export interface PenpotFrameGuideColumn {
;; type: 'column';
;; display: boolean;
;; params: PenpotFrameGuideColumnParams;
;; }
(defn format-frame-guide-column
[{:keys [type display params] :as guide}]
(when (some? guide)
(obj/clear-empty
#js {:type (format-key type)
:display display
:params (format-frame-guide-column-params params)})))
;; export interface PenpotFrameGuideRow {
;; type: 'row';
;; display: boolean;
;; params: PenpotFrameGuideColumnParams;
;; }
(defn format-frame-guide-row
[{:keys [type display params] :as guide}]
(when (some? guide)
(obj/clear-empty
#js {:type (format-key type)
:display display
:params (format-frame-guide-column-params params)})))
;;export interface PenpotFrameGuideSquareParams {
;; color: { color: string; opacity: number };
;; size?: number;
;;}
(defn format-frame-guide-square-params
[{:keys [color size] :as params}]
(when (some? params)
(obj/clear-empty
#js {:color (format-color color)
:size size})))
;; export interface PenpotFrameGuideSquare {
;; type: 'square';
;; display: boolean;
;; params: PenpotFrameGuideSquareParams;
;; }
(defn format-frame-guide-square
[{:keys [type display params] :as guide}]
(when (some? guide)
(obj/clear-empty
#js {:type (format-key type)
:display display
:params (format-frame-guide-column-params params)})))
(defn format-frame-guide
[{:keys [type] :as guide}]
(when (some? guide)
(case type
:column (format-frame-guide-column guide)
:row (format-frame-guide-row guide)
:square (format-frame-guide-square guide))))
(defn format-frame-guides
[guides]
(when (some? guides)
(format-array format-frame-guide guides)))
;;interface PenpotPathCommand {
;; command:
;; | 'M' | 'move-to'
;; | 'Z' | 'close-path'
;; | 'L' | 'line-to'
;; | 'H' | 'line-to-horizontal'
;; | 'V' | 'line-to-vertical'
;; | 'C' | 'curve-to'
;; | 'S' | 'smooth-curve-to'
;; | 'Q' | 'quadratic-bezier-curve-to'
;; | 'T' | 'smooth-quadratic-bezier-curve-to'
;; | 'A' | 'elliptical-arc';
;;
;; params?: {
;; x?: number;
;; y?: number;
;; c1x: number;
;; c1y: number;
;; c2x: number;
;; c2y: number;
;; rx?: number;
;; ry?: number;
;; xAxisRotation?: number;
;; largeArcFlag?: boolean;
;; sweepFlag?: boolean;
;; };
;;}
(defn format-command-params
[{:keys [x y c1x c1y c2x c2y rx ry x-axis-rotation large-arc-flag sweep-flag] :as props}]
(when (some? props)
(obj/clear-empty
#js {:x x
:y y
:c1x c1x
:c1y c1y
:c2x c2x
:c2y c2y
:rx rx
:ry ry
:xAxisRotation x-axis-rotation
:largeArcFlag large-arc-flag
:sweepFlag sweep-flag})))
(defn format-command
[{:keys [command params] :as props}]
(when (some? props)
(obj/clear-empty
#js {:command (format-key command)
:params (format-command-params params)})))
(defn format-path-content
[content]
(when (some? content)
(format-array format-command content)))
;; export type PenpotTrackType = 'flex' | 'fixed' | 'percent' | 'auto';
;;
;; export interface PenpotTrack {
;; type: PenpotTrackType;
;; value: number | null;
;; }
(defn format-track
[{:keys [type value] :as track}]
(when (some? track)
(obj/clear-empty
#js {:type (-> type format-key)
:value value})))
(defn format-tracks
[tracks]
(when (some? tracks)
(format-array format-track tracks)))

View file

@ -13,6 +13,8 @@
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.register :as r]
[app.plugins.utils :as u]
[app.util.object :as obj]
[potok.v2.core :as ptk]))
@ -20,12 +22,6 @@
;; Define in `app.plugins.shape` we do this way to prevent circular dependency
(def shape-proxy? nil)
(defn- make-tracks
[tracks]
(.freeze
js/Object
(apply array (->> tracks (map u/to-js)))))
(deftype GridLayout [$plugin $file $page $id]
Object
@ -40,6 +36,9 @@
(not (us/safe-number? value)))
(u/display-not-valid :addRow-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :addRow "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/add-layout-track #{$id} :row {:type type :value value})))))
@ -57,6 +56,9 @@
(not (us/safe-number? value)))
(u/display-not-valid :addRowAtIndex-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :addRowAtIndex "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/add-layout-track #{$id} :row {:type type :value value} index)))))
@ -71,6 +73,9 @@
(not (us/safe-number? value)))
(u/display-not-valid :addColumn-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :addColumn "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/add-layout-track #{$id} :column {:type type :value value})))))
@ -87,6 +92,9 @@
(not (us/safe-number? value)))
(u/display-not-valid :addColumnAtIndex-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :addColumnAtIndex "Plugin doesn't have 'content:write' permission")
:else
(let [type (keyword type)]
(st/emit! (dwsl/add-layout-track #{$id} :column {:type type :value value} index)))))
@ -97,6 +105,9 @@
(not (us/safe-int? index))
(u/display-not-valid :removeRow index)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :removeRow "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/remove-layout-track #{$id} :row index))))
@ -106,6 +117,9 @@
(not (us/safe-int? index))
(u/display-not-valid :removeColumn index)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :removeColumn "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/remove-layout-track #{$id} :column index))))
@ -123,6 +137,9 @@
(not (us/safe-number? value)))
(u/display-not-valid :setColumn-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setColumn "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/change-layout-track #{$id} :column index (d/without-nils {:type type :value value}))))))
@ -140,12 +157,20 @@
(not (us/safe-number? value)))
(u/display-not-valid :setRow-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setRow "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/change-layout-track #{$id} :row index (d/without-nils {:type type :value value}))))))
(remove
[_]
(st/emit! (dwsl/remove-layout #{$id})))
(cond
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :remove "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/remove-layout #{$id}))))
(appendChild
[_ child row column]
@ -159,6 +184,9 @@
(or (< column 0) (not (us/safe-int? column)))
(u/display-not-valid :appendChild-column column)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :appendChild "Plugin doesn't have 'content:write' permission")
:else
(let [child-id (obj/get child "$id")]
(st/emit! (dwt/move-shapes-to-frame #{child-id} $id nil [row column])
@ -185,15 +213,18 @@
(not (contains? ctl/grid-direction-types value))
(u/display-not-valid :dir value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :dir "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value}))))))}
{:name "rows"
:get #(-> % u/proxy->shape :layout-grid-rows make-tracks)}
:get #(-> % u/proxy->shape :layout-grid-rows format/format-tracks)}
{:name "columns"
:get #(-> % u/proxy->shape :layout-grid-columns make-tracks)}
:get #(-> % u/proxy->shape :layout-grid-columns format/format-tracks)}
{:name "alignItems"
:get #(-> % u/proxy->shape :layout-align-items d/name)
@ -204,6 +235,9 @@
(not (contains? ctl/align-items-types value))
(u/display-not-valid :alignItems value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignItems "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-align-items value}))))))}
@ -217,6 +251,9 @@
(not (contains? ctl/align-content-types value))
(u/display-not-valid :alignContent value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignContent "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-align-content value}))))))}
@ -230,6 +267,9 @@
(not (contains? ctl/justify-items-types value))
(u/display-not-valid :justifyItems value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifyItems "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-justify-items value}))))))}
@ -243,6 +283,9 @@
(not (contains? ctl/justify-content-types value))
(u/display-not-valid :justifyContent value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifyContent "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-justify-content value}))))))}
@ -255,6 +298,9 @@
(not (us/safe-int? value))
(u/display-not-valid :rowGap value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rowGap "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))}
@ -267,6 +313,9 @@
(not (us/safe-int? value))
(u/display-not-valid :columnGap value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :columnGap "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))}
@ -279,6 +328,9 @@
(not (us/safe-int? value))
(u/display-not-valid :verticalPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))}
@ -291,6 +343,9 @@
(not (us/safe-int? value))
(u/display-not-valid :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))}
@ -303,6 +358,9 @@
(not (us/safe-int? value))
(u/display-not-valid :topPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :topPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}})))))}
@ -315,6 +373,9 @@
(not (us/safe-int? value))
(u/display-not-valid :rightPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :righPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}})))))}
@ -327,6 +388,9 @@
(not (us/safe-int? value))
(u/display-not-valid :bottomPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :bottomPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}})))))}
@ -339,6 +403,9 @@
(not (us/safe-int? value))
(u/display-not-valid :leftPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :leftPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}})))))})))
@ -370,10 +437,13 @@
shape (u/proxy->shape self)]
(cond
(not (us/safe-int? value))
(u/display-not-valid :row value)
(u/display-not-valid :row-value value)
(nil? cell)
(u/display-not-valid :cell "cell not found")
(u/display-not-valid :row-cell "cell not found")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :row "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:row value})))))}
@ -391,6 +461,9 @@
(nil? cell)
(u/display-not-valid :rowSpan-cell "cell not found")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rowSpan "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:row-span value})))))}
@ -407,6 +480,9 @@
(nil? cell)
(u/display-not-valid :column-cell "cell not found")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :column "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:column value})))))}
@ -423,6 +499,9 @@
(nil? cell)
(u/display-not-valid :columnSpan-cell "cell not found")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :columnSpan "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:column-span value})))))}
@ -439,6 +518,9 @@
(nil? cell)
(u/display-not-valid :areaName-cell "cell not found")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :areaName "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:area-name value})))))}
@ -456,6 +538,9 @@
(nil? cell)
(u/display-not-valid :position-cell "cell not found")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :position "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/change-cells-mode (:parent-id shape) #{(:id cell)} value)))))}
@ -473,6 +558,9 @@
(nil? cell)
(u/display-not-valid :alignSelf-cell "cell not found")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignSelf "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:align-self value})))))}
@ -490,5 +578,8 @@
(nil? cell)
(u/display-not-valid :justifySelf-cell "cell not found")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifySelf "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:justify-self value})))))}))))

View file

@ -22,7 +22,11 @@
[app.main.data.workspace.texts :as dwt]
[app.main.repo :as rp]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.shape :as shape]
[app.plugins.text :as text]
[app.plugins.utils :as u]
[app.util.object :as obj]
[beicon.v2.core :as rx]
@ -37,45 +41,53 @@
(remove
[_]
(st/emit! (dwl/delete-color {:id $id})))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :remove "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dwl/delete-color {:id $id}))))
(clone
[_]
(let [color-id (uuid/next)
color (-> (u/locate-library-color $file $id)
(assoc :id color-id))]
(st/emit! (dwl/add-color color {:rename? false}))
(lib-color-proxy $plugin $id color-id)))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :clone "Plugin doesn't have 'library:write' permission")
:else
(let [color-id (uuid/next)
color (-> (u/locate-library-color $file $id)
(assoc :id color-id))]
(st/emit! (dwl/add-color color {:rename? false}))
(lib-color-proxy $plugin $id color-id))))
(asFill [_]
(let [color (u/locate-library-color $file $id)]
(u/to-js
(d/without-nils
{:fill-color (:color color)
:fill-opacity (:opacity color)
:fill-color-gradient (:gradient color)
:fill-color-ref-file $file
:fill-color-ref-id $id
:fill-image (:image color)}))))
(format/format-fill
{:fill-color (:color color)
:fill-opacity (:opacity color)
:fill-color-gradient (:gradient color)
:fill-color-ref-file $file
:fill-color-ref-id $id
:fill-image (:image color)})))
(asStroke [_]
(let [color (u/locate-library-color $file $id)]
(u/to-js
(d/without-nils
{:stroke-color (:color color)
:stroke-opacity (:opacity color)
:stroke-color-gradient (:gradient color)
:stroke-color-ref-file $file
:stroke-color-ref-id $id
:stroke-image (:image color)
:stroke-style :solid
:stroke-alignment :inner}))))
(format/format-stroke
{:stroke-color (:color color)
:stroke-opacity (:opacity color)
:stroke-color-gradient (:gradient color)
:stroke-color-ref-file $file
:stroke-color-ref-id $id
:stroke-image (:image color)
:stroke-style :solid
:stroke-alignment :inner})))
(getPluginData
[self key]
(cond
(not (string? key))
(u/display-not-valid :color-plugin-data-key key)
(u/display-not-valid :getPluginData-key key)
:else
(let [color (u/proxy->library-color self)]
@ -85,13 +97,16 @@
[_ key value]
(cond
(not= $file (:current-file-id @st/state))
(u/display-not-valid :color-edit-non-local-library $file)
(u/display-not-valid :setPluginData-non-local-library $file)
(not (string? key))
(u/display-not-valid :color-plugin-data-key key)
(u/display-not-valid :setPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :color-plugin-data value)
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :color $id (keyword "plugin" (str $plugin)) key value))))
@ -105,10 +120,10 @@
[self namespace key]
(cond
(not (string? namespace))
(u/display-not-valid :color-plugin-data-namespace namespace)
(u/display-not-valid :getSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :color-plugin-data-key key)
(u/display-not-valid :getSharedPluginData-key key)
:else
(let [color (u/proxy->library-color self)]
@ -118,16 +133,19 @@
[_ namespace key value]
(cond
(not= $file (:current-file-id @st/state))
(u/display-not-valid :color-edit-non-local-library $file)
(u/display-not-valid :setSharedPluginData-non-local-library $file)
(not (string? namespace))
(u/display-not-valid :color-plugin-data-namespace namespace)
(u/display-not-valid :setSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :color-plugin-data-key key)
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :color-plugin-data value)
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :color $id (keyword "shared" namespace) key value))))
@ -136,7 +154,7 @@
[self namespace]
(cond
(not (string? namespace))
(u/display-not-valid :color-plugin-data-namespace namespace)
(u/display-not-valid :getSharedPluginDataKeys-namespace namespace)
:else
(let [color (u/proxy->library-color self)]
@ -164,7 +182,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-color-name value)
(u/display-not-valid :name value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :name "Plugin doesn't have 'library:write' permission")
:else
(let [color (u/proxy->library-color self)
@ -177,7 +198,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-color-path value)
(u/display-not-valid :path value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :path "Plugin doesn't have 'library:write' permission")
:else
(let [color (-> (u/proxy->library-color self)
@ -190,7 +214,10 @@
(fn [self value]
(cond
(or (not (string? value)) (not (cc/valid-hex-color? value)))
(u/display-not-valid :library-color-color value)
(u/display-not-valid :color value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :color "Plugin doesn't have 'library:write' permission")
:else
(let [color (-> (u/proxy->library-color self)
@ -203,7 +230,10 @@
(fn [self value]
(cond
(or (not (number? value)) (< value 0) (> value 1))
(u/display-not-valid :library-color-opacity value)
(u/display-not-valid :opacity value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :opacity "Plugin doesn't have 'library:write' permission")
:else
(let [color (-> (u/proxy->library-color self)
@ -211,13 +241,16 @@
(st/emit! (dwl/update-color color file-id)))))}
{:name "gradient"
:get #(-> % u/proxy->library-color :gradient u/to-js)
:get #(-> % u/proxy->library-color :gradient format/format-gradient)
:set
(fn [self value]
(let [value (u/from-js value)]
(let [value (parser/parse-gradient value)]
(cond
(not (sm/validate ::ctc/gradient value))
(u/display-not-valid :library-color-gradient value)
(u/display-not-valid :gradient value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :gradient "Plugin doesn't have 'library:write' permission")
:else
(let [color (-> (u/proxy->library-color self)
@ -225,13 +258,16 @@
(st/emit! (dwl/update-color color file-id))))))}
{:name "image"
:get #(-> % u/proxy->library-color :image u/to-js)
:get #(-> % u/proxy->library-color :image format/format-image)
:set
(fn [self value]
(let [value (u/from-js value)]
(let [value (parser/parse-image-data value)]
(cond
(not (sm/validate ::ctc/image-color value))
(u/display-not-valid :library-color-image value)
(u/display-not-valid :image value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :image "Plugin doesn't have 'library:write' permission")
:else
(let [color (-> (u/proxy->library-color self)
@ -242,15 +278,25 @@
Object
(remove
[_]
(st/emit! (dwl/delete-typography {:id $id})))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :remove "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dwl/delete-typography {:id $id}))))
(clone
[_]
(let [typo-id (uuid/next)
typo (-> (u/locate-library-typography $file $id)
(assoc :id typo-id))]
(st/emit! (dwl/add-typography typo false))
(lib-typography-proxy $plugin $id typo-id)))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :clone "Plugin doesn't have 'library:write' permission")
:else
(let [typo-id (uuid/next)
typo (-> (u/locate-library-typography $file $id)
(assoc :id typo-id))]
(st/emit! (dwl/add-typography typo false))
(lib-typography-proxy $plugin $id typo-id))))
(applyToText
[_ shape]
@ -258,6 +304,9 @@
(not (shape/shape-proxy? shape))
(u/display-not-valid :applyToText shape)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :applyToText "Plugin doesn't have 'content:write' permission")
:else
(let [shape-id (obj/get shape "$id")
typography (u/locate-library-typography $file $id)]
@ -266,9 +315,12 @@
(applyToTextRange
[self range]
(cond
(not (shape/text-range? range))
(not (text/text-range? range))
(u/display-not-valid :applyToText range)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :applyToText "Plugin doesn't have 'content:write' permission")
:else
(let [shape-id (obj/get range "$id")
start (obj/get range "start")
@ -295,13 +347,16 @@
[_ key value]
(cond
(not= $file (:current-file-id @st/state))
(u/display-not-valid :typography-edit-non-local-library $file)
(u/display-not-valid :setPluginData-non-local-library $file)
(not (string? key))
(u/display-not-valid :typography-plugin-data-key key)
(u/display-not-valid :setPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :typography-plugin-data value)
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :typography $id (keyword "plugin" (str $plugin)) key value))))
@ -315,10 +370,10 @@
[self namespace key]
(cond
(not (string? namespace))
(u/display-not-valid :typography-plugin-data-namespace namespace)
(u/display-not-valid :getSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :typography-plugin-data-key key)
(u/display-not-valid :getSharedPluginData-key key)
:else
(let [typography (u/proxy->library-typography self)]
@ -328,16 +383,19 @@
[_ namespace key value]
(cond
(not= $file (:current-file-id @st/state))
(u/display-not-valid :typography-edit-non-local-library $file)
(u/display-not-valid :setSharedPluginData-non-local-library $file)
(not (string? namespace))
(u/display-not-valid :typography-plugin-data-namespace namespace)
(u/display-not-valid :setSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :typography-plugin-data-key key)
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :typography-plugin-data value)
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :typography $id (keyword "shared" namespace) key value))))
@ -346,7 +404,7 @@
[self namespace]
(cond
(not (string? namespace))
(u/display-not-valid :typography-plugin-data-namespace namespace)
(u/display-not-valid :getSharedPluginDataKeys-namespace namespace)
:else
(let [typography (u/proxy->library-typography self)]
@ -375,7 +433,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-name value)
(u/display-not-valid :name value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :name "Plugin doesn't have 'library:write' permission")
:else
(let [typo (u/proxy->library-typography self)
@ -388,7 +449,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-path value)
(u/display-not-valid :path value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :path "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -401,7 +465,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-font-id value)
(u/display-not-valid :fontId value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :fontId "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -414,7 +481,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-font-family value)
(u/display-not-valid :fontFamily value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :fontFamily "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -427,7 +497,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-font-variant-id value)
(u/display-not-valid :fontVariantId value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :fontVariantId "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -440,7 +513,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-font-size value)
(u/display-not-valid :fontSize value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :fontSize "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -453,7 +529,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-font-weight value)
(u/display-not-valid :fontWeight value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :fontWeight "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -466,7 +545,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-font-style value)
(u/display-not-valid :fontStyle value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :fontStyle "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -479,7 +561,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-font-height value)
(u/display-not-valid :lineHeight value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :lineHeight "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -492,7 +577,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-letter-spacing value)
(u/display-not-valid :letterSpacing value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :letterSpacing "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -505,7 +593,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-typography-text-transform value)
(u/display-not-valid :textTransform value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :textTransform "Plugin doesn't have 'library:write' permission")
:else
(let [typo (-> (u/proxy->library-typography self)
@ -517,13 +608,23 @@
(remove
[_]
(st/emit! (dwl/delete-component {:id $id})))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :remove "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dwl/delete-component {:id $id}))))
(instance
[_]
(let [id-ref (atom nil)]
(st/emit! (dwl/instantiate-component $file $id (gpt/point 0 0) {:id-ref id-ref}))
(shape/shape-proxy $plugin @id-ref)))
(cond
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :instance "Plugin doesn't have 'content:write' permission")
:else
(let [id-ref (atom nil)]
(st/emit! (dwl/instantiate-component $file $id (gpt/point 0 0) {:id-ref id-ref}))
(shape/shape-proxy $plugin @id-ref))))
(getPluginData
[self key]
@ -539,13 +640,16 @@
[_ key value]
(cond
(not= $file (:current-file-id @st/state))
(u/display-not-valid :component-edit-non-local-library $file)
(u/display-not-valid :setPluginData-non-local-library $file)
(not (string? key))
(u/display-not-valid :component-plugin-data-key key)
(u/display-not-valid :setPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :component-plugin-data value)
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :component $id (keyword "plugin" (str $plugin)) key value))))
@ -572,16 +676,19 @@
[_ namespace key value]
(cond
(not= $file (:current-file-id @st/state))
(u/display-not-valid :component-edit-non-local-library $file)
(u/display-not-valid :setSharedPluginData-non-local-library $file)
(not (string? namespace))
(u/display-not-valid :component-plugin-data-namespace namespace)
(u/display-not-valid :setSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :component-plugin-data-key key)
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :component-plugin-data value)
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :component $id (keyword "shared" namespace) key value))))
@ -625,7 +732,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-component-name value)
(u/display-not-valid :name value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :name "Plugin doesn't have 'library:write' permission")
:else
(let [component (u/proxy->library-component self)
@ -638,7 +748,10 @@
(fn [self value]
(cond
(not (string? value))
(u/display-not-valid :library-component-path value)
(u/display-not-valid :path value)
(not (r/check-permission plugin-id "library:write"))
(u/display-not-valid :path "Plugin doesn't have 'library:write' permission")
:else
(let [component (u/proxy->library-component self)
@ -652,22 +765,37 @@
(createColor
[_]
(let [color-id (uuid/next)]
(st/emit! (dwl/add-color {:id color-id :name "Color" :color "#000000" :opacity 1} {:rename? false}))
(lib-color-proxy $plugin $id color-id)))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :createColor "Plugin doesn't have 'library:write' permission")
:else
(let [color-id (uuid/next)]
(st/emit! (dwl/add-color {:id color-id :name "Color" :color "#000000" :opacity 1} {:rename? false}))
(lib-color-proxy $plugin $id color-id))))
(createTypography
[_]
(let [typography-id (uuid/next)]
(st/emit! (dwl/add-typography (ctt/make-typography {:id typography-id :name "Typography"}) false))
(lib-typography-proxy $plugin $id typography-id)))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :createTypography "Plugin doesn't have 'library:write' permission")
:else
(let [typography-id (uuid/next)]
(st/emit! (dwl/add-typography (ctt/make-typography {:id typography-id :name "Typography"}) false))
(lib-typography-proxy $plugin $id typography-id))))
(createComponent
[_ shapes]
(let [id-ref (atom nil)
ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwl/add-component id-ref ids))
(lib-component-proxy $plugin $id @id-ref)))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :createComponent "Plugin doesn't have 'library:write' permission")
:else
(let [id-ref (atom nil)
ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwl/add-component id-ref ids))
(lib-component-proxy $plugin $id @id-ref))))
;; Plugin data
(getPluginData
@ -684,10 +812,13 @@
[_ key value]
(cond
(not (string? key))
(u/display-not-valid :file-plugin-data-key key)
(u/display-not-valid :setPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :file-plugin-data value)
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dw/set-plugin-data $id :file (keyword "plugin" (str $plugin)) key value))))
@ -715,13 +846,16 @@
(cond
(not (string? namespace))
(u/display-not-valid :file-plugin-data-namespace namespace)
(u/display-not-valid :setSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :file-plugin-data-key key)
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :file-plugin-data value)
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'library:write' permission")
:else
(st/emit! (dw/set-plugin-data $id :file (keyword "shared" namespace) key value))))
@ -730,7 +864,7 @@
[self namespace]
(cond
(not (string? namespace))
(u/display-not-valid :file-plugin-data-namespace namespace)
(u/display-not-valid :namespace namespace)
:else
(let [file (u/proxy->file self)]
@ -804,21 +938,26 @@
(connectLibrary
[_ library-id]
(p/create
(fn [resolve reject]
(cond
(not (string? library-id))
(do (u/display-not-valid :connectLibrary library-id)
(reject nil))
(cond
(not (r/check-permission $plugin "library:write"))
(u/display-not-valid :connectLibrary "Plugin doesn't have 'library:write' permission")
:else
(let [file-id (:current-file-id @st/state)
library-id (uuid/uuid library-id)]
(->> st/stream
(rx/filter (ptk/type? ::dwl/attach-library-finished))
(rx/take 1)
(rx/subs! #(resolve (library-proxy $plugin library-id)) reject))
(st/emit! (dwl/link-file-to-library file-id library-id))))))))
:else
(p/create
(fn [resolve reject]
(cond
(not (string? library-id))
(do (u/display-not-valid :connectLibrary library-id)
(reject nil))
:else
(let [file-id (:current-file-id @st/state)
library-id (uuid/uuid library-id)]
(->> st/stream
(rx/filter (ptk/type? ::dwl/attach-library-finished))
(rx/take 1)
(rx/subs! #(resolve (library-proxy $plugin library-id)) reject))
(st/emit! (dwl/link-file-to-library file-id library-id)))))))))
(defn library-subcontext
[plugin-id]

View file

@ -13,9 +13,12 @@
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
[app.main.store :as st]
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.shape :as shape]
[app.plugins.utils :as u]
[app.util.object :as obj]))
[app.util.object :as obj]
[cuerdas.core :as str]))
(deftype PageProxy [$plugin $file $id]
Object
@ -34,11 +37,28 @@
(shape/shape-proxy $plugin $file $id uuid/zero))
(findShapes
[_]
[_ criteria]
;; Returns a lazy (iterable) of all available shapes
(when (and (some? $file) (some? $id))
(let [page (u/locate-page $file $id)]
(apply array (sequence (map (partial shape/shape-proxy $plugin)) (keys (:objects page)))))))
(let [criteria (parser/parse-criteria criteria)
match-criteria?
(if (some? criteria)
(fn [[_ shape]]
(and
(or (not (:name criteria))
(= (str/lower (:name criteria)) (str/lower (:name shape))))
(or (not (:name-like criteria))
(str/includes? (str/lower (:name shape)) (str/lower (:name-like criteria))))
(or (not (:type criteria))
(= (:type criteria) (:type shape)))))
identity)]
(when (and (some? $file) (some? $id))
(let [page (u/locate-page $file $id)
xf (comp
(filter match-criteria?)
(map #(shape/shape-proxy $plugin $file $id (first %))))]
(apply array (sequence xf (:objects page)))))))
;; Plugin data
(getPluginData
@ -55,10 +75,13 @@
[_ key value]
(cond
(not (string? key))
(u/display-not-valid :page-plugin-data-key key)
(u/display-not-valid :setPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :page-plugin-data value)
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :page $id (keyword "plugin" (str $plugin)) key value))))
@ -86,13 +109,16 @@
(cond
(not (string? namespace))
(u/display-not-valid :page-plugin-data-namespace namespace)
(u/display-not-valid :setSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :page-plugin-data-key key)
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :page-plugin-data value)
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :page $id (keyword "shared" namespace) key value))))
@ -132,7 +158,10 @@
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :page-name value)
(u/display-not-valid :name value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :name "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/rename-page id value))))}
@ -148,7 +177,10 @@
(fn [_ value]
(cond
(or (not (string? value)) (not (cc/valid-hex-color? value)))
(u/display-not-valid :page-background-color value)
(u/display-not-valid :background value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :background "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/change-canvas-color id {:color value}))))}))

View file

@ -0,0 +1,396 @@
;; 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.plugins.parser
(:require
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.util.object :as obj]
[cuerdas.core :as str]))
(defn parse-id
[id]
(when id (uuid/uuid id)))
(defn parse-keyword
[kw]
(when kw (keyword kw)))
(defn parse-hex
[color]
(if (string? color) (-> color str/lower) color))
;; {
;; name?: string;
;; nameLike?: string;
;; type?:
;; | 'frame'
;; | 'group'
;; | 'bool'
;; | 'rect'
;; | 'path'
;; | 'text'
;; | 'circle'
;; | 'svg-raw'
;; | 'image';
;; }
(defn parse-criteria
[^js criteria]
(when (some? criteria)
(d/without-nils
{:name (obj/get criteria "name")
:name-like (obj/get criteria "nameLike")
:type (-> (obj/get criteria "type") parse-keyword)})))
;;export type PenpotImageData = {
;; name?: string;
;; width: number;
;; height: number;
;; mtype?: string;
;; id: string;
;; keepApectRatio?: boolean;
;;}
(defn parse-image-data
[^js image-data]
(when (some? image-data)
(d/without-nils
{:id (-> (obj/get image-data "id") parse-id)
:name (obj/get image-data "name")
:width (obj/get image-data "width")
:height (obj/get image-data "height")
:mtype (obj/get image-data "mtype")
:keep-aspect-ratio (obj/get image-data "keepApectRatio")})))
;; export type PenpotGradient = {
;; type: 'linear' | 'radial';
;; startX: number;
;; startY: number;
;; endX: number;
;; endY: number;
;; width: number;
;; stops: Array<{ color: string; opacity?: number; offset: number }>;
;; }
(defn parse-gradient-stop
[^js stop]
(when (some? stop)
(d/without-nils
{:color (-> (obj/get stop "color") parse-hex)
:opacity (obj/get stop "opacity")
:offset (obj/get stop "offset")})))
(defn parse-gradient
[^js gradient]
(when (some? gradient)
(d/without-nils
{:type (-> (obj/get gradient "type") parse-keyword)
:start-x (obj/get gradient "startX")
:start-y (obj/get gradient "startY")
:end-x (obj/get gradient "endX")
:end-y (obj/get gradient "endY")
:width (obj/get gradient "width")
:stops (->> (obj/get gradient "stops")
(mapv parse-gradient-stop))})))
;; export interface PenpotColor {
;; id?: string;
;; name?: string;
;; path?: string;
;; color?: string;
;; opacity?: number;
;; refId?: string;
;; refFile?: string;
;; gradient?: PenpotGradient;
;; image?: PenpotImageData;
;; }
(defn parse-color
[^js color]
(when (some? color)
(d/without-nils
{:id (-> (obj/get color "id") parse-id)
:name (obj/get color "name")
:path (obj/get color "path")
:color (-> (obj/get color "color") parse-hex)
:opacity (obj/get color "opacity")
:ref-id (-> (obj/get color "refId") parse-id)
:ref-file (-> (obj/get color "refFile") parse-id)
:gradient (-> (obj/get color "gradient") parse-gradient)
:image (-> (obj/get color "image") parse-image-data)})))
;; export interface PenpotShadow {
;; id?: string;
;; style?: 'drop-shadow' | 'inner-shadow';
;; offsetX?: number;
;; offsetY?: number;
;; blur?: number;
;; spread?: number;
;; hidden?: boolean;
;; color?: PenpotColor;
;; }
(defn parse-shadow
[^js shadow]
(when (some? shadow)
(d/without-nils
{:id (-> (obj/get shadow "id") parse-id)
:style (-> (obj/get shadow "style") parse-keyword)
:offset-x (obj/get shadow "offsetX")
:offset-y (obj/get shadow "offsetY")
:blur (obj/get shadow "blur")
:spread (obj/get shadow "spread")
:hidden (obj/get shadow "hidden")
:color (-> (obj/get shadow "color") parse-color)})))
(defn parse-shadows
[^js shadows]
(when (some? shadows)
(into [] (map parse-shadow) shadows)))
;;export interface PenpotFill {
;; fillColor?: string;
;; fillOpacity?: number;
;; fillColorGradient?: PenpotGradient;
;; fillColorRefFile?: string;
;; fillColorRefId?: string;
;; fillImage?: PenpotImageData;
;;}
(defn parse-fill
[^js fill]
(when (some? fill)
(d/without-nils
{:fill-color (-> (obj/get fill "fillColor") parse-hex)
:fill-opacity (obj/get fill "fillOpacity")
:fill-color-gradient (-> (obj/get fill "fillColorGradient") parse-gradient)
:fill-color-ref-file (-> (obj/get fill "fillColorRefFile") parse-id)
:fill-color-ref-id (-> (obj/get fill "fillColorRefId") parse-id)
:fill-image (-> (obj/get fill "fillImage") parse-image-data)})))
(defn parse-fills
[^js fills]
(when (some? fills)
(into [] (map parse-fill) fills)))
;; export interface PenpotStroke {
;; strokeColor?: string;
;; strokeColorRefFile?: string;
;; strokeColorRefId?: string;
;; strokeOpacity?: number;
;; strokeStyle?: 'solid' | 'dotted' | 'dashed' | 'mixed' | 'none' | 'svg';
;; strokeWidth?: number;
;; strokeAlignment?: 'center' | 'inner' | 'outer';
;; strokeCapStart?: PenpotStrokeCap;
;; strokeCapEnd?: PenpotStrokeCap;
;; strokeColorGradient?: PenpotGradient;
;; }
(defn parse-stroke
[^js stroke]
(when (some? stroke)
(d/without-nils
{:stroke-color (-> (obj/get stroke "strokeColor") parse-hex)
:stroke-color-ref-file (-> (obj/get stroke "strokeColorRefFile") parse-id)
:stroke-color-ref-id (-> (obj/get stroke "strokeColorRefId") parse-id)
:stroke-opacity (obj/get stroke "strokeOpacity")
:stroke-style (-> (obj/get stroke "strokeStyle") parse-keyword)
:stroke-width (obj/get stroke "strokeWidth")
:stroke-alignment (-> (obj/get stroke "strokeAlignment") parse-keyword)
:stroke-cap-start (-> (obj/get stroke "strokeCapStart") parse-keyword)
:stroke-cap-end (-> (obj/get stroke "strokeCapEnd") parse-keyword)
:stroke-color-gradient (-> (obj/get stroke "strokeColorGradient") parse-gradient)})))
(defn parse-strokes
[^js strokes]
(when (some? strokes)
(into [] (map parse-stroke) strokes)))
;; export interface PenpotBlur {
;; id?: string;
;; type?: 'layer-blur';
;; value?: number;
;; hidden?: boolean;
;; }
(defn parse-blur
[^js blur]
(when (some? blur)
(d/without-nils
{:id (-> (obj/get blur "id") parse-id)
:type (-> (obj/get blur "type") parse-keyword)
:value (obj/get blur "value")
:hidden (obj/get blur "hidden")})))
;; export interface PenpotExport {
;; type: 'png' | 'jpeg' | 'svg' | 'pdf';
;; scale: number;
;; suffix: string;
;; }
(defn parse-export
[^js export]
(when (some? export)
(d/without-nils
{:type (-> (obj/get export "type") parse-keyword)
:scale (obj/get export "scale")
:suffix (obj/get export "suffix")})))
(defn parse-exports
[^js exports]
(when (some? exports)
(into [] (map parse-export) exports)))
;; export interface PenpotFrameGuideColumnParams {
;; color: { color: string; opacity: number };
;; type?: 'stretch' | 'left' | 'center' | 'right';
;; size?: number;
;; margin?: number;
;; itemLength?: number;
;; gutter?: number;
;; }
(defn parse-frame-guide-column-params
[^js params]
(when params
(d/without-nils
{:color (-> (obj/get params "color") parse-color)
:type (-> (obj/get params "type") parse-keyword)
:size (obj/get params "size")
:margin (obj/get params "margin")
:item-length (obj/get params "itemLength")
:gutter (obj/get params "gutter")})))
;; export interface PenpotFrameGuideColumn {
;; type: 'column';
;; display: boolean;
;; params: PenpotFrameGuideColumnParams;
;; }
(defn parse-frame-guide-column
[^js guide]
(when guide
(d/without-nils
{:type (-> (obj/get guide "type") parse-keyword)
:display (obj/get guide "display")
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
;; export interface PenpotFrameGuideRow {
;; type: 'row';
;; display: boolean;
;; params: PenpotFrameGuideColumnParams;
;; }
(defn parse-frame-guide-row
[^js guide]
(when guide
(d/without-nils
{:type (-> (obj/get guide "type") parse-keyword)
:display (obj/get guide "display")
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
;;export interface PenpotFrameGuideSquareParams {
;; color: { color: string; opacity: number };
;; size?: number;
;;}
(defn parse-frame-guide-square-params
[^js params]
(when (some? params)
(d/without-nils
{:color (-> (obj/get params "color") parse-color)
:size (obj/get params "size")})))
;; export interface PenpotFrameGuideSquare {
;; type: 'square';
;; display: boolean;
;; params: PenpotFrameGuideSquareParams;
;; }
(defn parse-frame-guide-square
[^js guide]
(when guide
(d/without-nils
{:type (-> (obj/get guide "type") parse-keyword)
:display (obj/get guide "display")
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
(defn parse-frame-guide
[^js guide]
(when (some? guide)
(case (obj/get guide "type")
"column"
parse-frame-guide-column
"row"
parse-frame-guide-row
"square"
(parse-frame-guide-square guide))))
(defn parse-frame-guides
[^js guides]
(when (some? guides)
(into [] (map parse-frame-guide) guides)))
;;interface PenpotPathCommand {
;; command:
;; | 'M' | 'move-to'
;; | 'Z' | 'close-path'
;; | 'L' | 'line-to'
;; | 'H' | 'line-to-horizontal'
;; | 'V' | 'line-to-vertical'
;; | 'C' | 'curve-to'
;; | 'S' | 'smooth-curve-to'
;; | 'Q' | 'quadratic-bezier-curve-to'
;; | 'T' | 'smooth-quadratic-bezier-curve-to'
;; | 'A' | 'elliptical-arc';
;;
;; params?: {
;; x?: number;
;; y?: number;
;; c1x: number;
;; c1y: number;
;; c2x: number;
;; c2y: number;
;; rx?: number;
;; ry?: number;
;; xAxisRotation?: number;
;; largeArcFlag?: boolean;
;; sweepFlag?: boolean;
;; };
;;}
(defn parse-command-type
[^string command-type]
(case command-type
"M" :move-to
"Z" :close-path
"L" :line-to
"H" :line-to-horizontal
"V" :line-to-vertical
"C" :curve-to
"S" :smooth-curve-to
"Q" :quadratic-bezier-curve-to
"T" :smooth-quadratic-bezier-curve-to
"A" :elliptical-arc
(parse-keyword command-type)))
(defn parse-command-params
[^js params]
(when (some? params)
(d/without-nils
{:x (obj/get params "x")
:y (obj/get params "y")
:c1x (obj/get params "c1x")
:c1y (obj/get params "c1y")
:c2x (obj/get params "c2x")
:c2y (obj/get params "c2y")
:rx (obj/get params "rx")
:ry (obj/get params "ry")
:x-axis-rotation (obj/get params "xAxisRotation")
:large-arc-flag (obj/get params "largeArcFlag")
:sweep-flag (obj/get params "sweepFlag")})))
(defn parse-command
[^js command]
(when (some? command)
(d/without-nils
{:command (-> (obj/get command "command") parse-command-type)
:params (-> (obj/get command "paras") parse-command-params)})))
(defn parse-path-content
[^js content]
(when (some? content)
(into [] (map parse-command) content)))

View file

@ -9,6 +9,7 @@
(:require
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.plugins.format :as format]
[app.plugins.shape :as shape]
[app.plugins.utils :as u]))
@ -22,4 +23,4 @@
(let [shapes (->> shapes (map u/proxy->shape))]
(-> (gsh/shapes->rect shapes)
(grc/rect->center)
(u/to-js)))))
(format/format-point)))))

View file

@ -0,0 +1,55 @@
;; 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.plugins.register
"RPC for plugins runtime."
(:require
[app.common.data :as d]))
;; TODO: Remove clj->js and parse into a better data structure for accessing the permissions
(def pluginsdb (atom nil))
(defn load-from-store
[]
(let [ls (.-localStorage js/window)
plugins-val (.getItem ls "plugins")]
(when plugins-val
(let [plugins-js (.parse js/JSON plugins-val)]
(js->clj plugins-js {:keywordize-keys true})))))
(defn save-to-store
[plugins]
(let [ls (.-localStorage js/window)
plugins-js (clj->js plugins)
plugins-val (.stringify js/JSON plugins-js)]
(.setItem ls "plugins" plugins-val)))
(defn init
[]
(reset! pluginsdb (load-from-store)))
(defn install-plugin!
[plugin]
(let [plugins (vec (conj (seq @pluginsdb) plugin))]
(reset! pluginsdb plugins)
(save-to-store plugins)))
(defn remove-plugin!
[{:keys [plugin-id]}]
(let [plugins
(into []
(keep (fn [plugin]
(when (not= plugin-id (:plugin-id plugin)) plugin)))
@pluginsdb)]
(reset! pluginsdb plugins)
(save-to-store plugins)))
(defn check-permission
[plugin-id permission]
(or (= plugin-id "TEST")
(let [{:keys [permissions]} (->> @pluginsdb (d/seek #(= (:plugin-id %) plugin-id)))]
(->> permissions (d/seek #(= % permission))))))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,575 @@
;; 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.plugins.text
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.record :as crc]
[app.common.schema :as sm]
[app.common.text :as txt]
[app.common.types.shape :as cts]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.texts :as dwt]
[app.main.fonts :as fonts]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.utils :as u]
[app.util.object :as obj]
[app.util.text-editor :as ted]
[cuerdas.core :as str]))
;; This regex seems duplicated but probably in the future when we support diferent units
;; this will need to reflect changes for each property
(def font-size-re #"^\d*\.?\d*$")
(def line-height-re #"^\d*\.?\d*$")
(def letter-spacing-re #"^\d*\.?\d*$")
(def text-transform-re #"uppercase|capitalize|lowercase|none")
(def text-decoration-re #"underline|line-through|none")
(def text-direction-re #"ltr|rtl")
(def text-align-re #"left|center|right|justify")
(def vertical-align-re #"top|center|bottom")
(defn mixed-value
[values]
(let [s (set values)]
(if (= (count s) 1) (first s) "mixed")))
(defn font-data
[font variant]
(d/without-nils
{:font-id (:id font)
:font-family (:family font)
:font-variant-id (:id variant)
:font-style (:style variant)
:font-weight (:weight variant)}))
(defn variant-data
[variant]
(d/without-nils
{:font-variant-id (:id variant)
:font-style (:style variant)
:font-weight (:weight variant)}))
(deftype TextRange [$plugin $file $page $id start end]
Object
(applyTypography [_ typography]
(let [typography (u/proxy->library-typography typography)
attrs (-> typography
(assoc :typography-ref-file $file)
(assoc :typography-ref-id (:id typography))
(dissoc :id :name))]
(st/emit! (dwt/update-text-range $id start end attrs)))))
(defn text-range?
[range]
(instance? TextRange range))
(defn text-props
[shape]
(d/merge
(dwt/current-root-values {:shape shape :attrs txt/root-attrs})
(dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs})
(dwt/current-text-values {:shape shape :attrs txt/text-node-attrs})))
(defn text-range
[plugin-id file-id page-id id start end]
(-> (TextRange. plugin-id file-id page-id id start end)
(crc/add-properties!
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "shape"
:get #(-> % u/proxy->shape)}
{:name "characters"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text) (str/join "")))}
{:name "fontId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-id) mixed-value))
:set
(fn [_ value]
(let [font (when (string? value) (fonts/get-font-data value))
variant (fonts/get-default-variant font)]
(cond
(not (some? font))
(u/display-not-valid :fontId value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontId "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end (font-data font variant))))))}
{:name "fontFamily"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-family) mixed-value))
:set
(fn [_ value]
(let [font (fonts/find-font-data {:font-family value})
variant (fonts/get-default-variant font)]
(cond
(not (string? value))
(u/display-not-valid :fontFamily value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontFamily "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end (font-data font variant))))))}
{:name "fontVariantId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-variant-id) mixed-value))
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
variant (fonts/get-variant font value)]
(cond
(not (string? value))
(u/display-not-valid :fontVariantId value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontVariantId "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
{:name "fontSize"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-size) mixed-value))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (not (re-matches font-size-re value)))
(u/display-not-valid :fontSize value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontSize "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:font-size value})))))}
{:name "fontWeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-weight) mixed-value))
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
variant (fonts/find-variant font {:weight (dm/str value)})]
(cond
(nil? variant)
(u/display-not-valid :fontWeight (dm/str "Font weight '" value "' not supported for the current font"))
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontWeight "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
{:name "fontStyle"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-style) mixed-value))
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
variant (fonts/find-variant font {:weight (dm/str value)})]
(cond
(nil? variant)
(u/display-not-valid :fontStyle (dm/str "Font style '" value "' not supported for the current font"))
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontStyle "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
{:name "lineHeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :line-height) mixed-value))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (not (re-matches line-height-re value)))
(u/display-not-valid :lineHeight value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :lineHeight "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:line-height value})))))}
{:name "letterSpacing"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :letter-spacing) mixed-value))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (re-matches letter-spacing-re value))
(u/display-not-valid :letterSpacing value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :letterSpacing "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:letter-spacing value})))))}
{:name "textTransform"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-transform) mixed-value))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-transform-re value))
(u/display-not-valid :textTransform value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textTransform "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:text-transform value}))))}
{:name "textDecoration"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-decoration) mixed-value))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-decoration-re value))
(u/display-not-valid :textDecoration value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textDecoration "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:text-decoration value}))))}
{:name "direction"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :direction) mixed-value))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-direction-re value))
(u/display-not-valid :direction value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :direction "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:direction value}))))}
{:name "align"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-align) mixed-value))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-align-re value))
(u/display-not-valid :align value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :align "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:text-align value}))))}
{:name "fills"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :fills) mixed-value format/format-fills))
:set
(fn [_ value]
(let [value (parser/parse-fills value)]
(cond
(not (sm/validate [:vector ::cts/fill] value))
(u/display-not-valid :fills value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fills "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:fills value})))))})))
(defn add-text-props
[plugin-id shape-proxy]
(crc/add-properties!
shape-proxy
{:name "characters"
:get #(-> % u/proxy->shape :content txt/content->text)
:set
(fn [self value]
(let [id (obj/get self "$id")]
;; The user is currently editing the text. We need to update the
;; editor as well
(cond
(or (not (string? value)) (empty? value))
(u/display-not-valid :characters value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :characters "Plugin doesn't have 'content:write' permission")
(contains? (:workspace-editor-state @st/state) id)
(let [shape (u/proxy->shape self)
editor
(-> shape
(txt/change-text value)
:content
ted/import-content
ted/create-editor-state)]
(st/emit! (dwt/update-editor-state shape editor)))
:else
(st/emit! (dwsh/update-shapes [id] #(txt/change-text % value))))))}
{:name "growType"
:get #(-> % u/proxy->shape :grow-type d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(cond
(not (contains? #{:auto-width :auto-height :fixed} value))
(u/display-not-valid :growType value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :growType "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwsh/update-shapes [id] #(assoc % :grow-type value))))))}
{:name "fontId"
:get #(-> % u/proxy->shape text-props :font-id format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")
font (when (string? value) (fonts/get-font-data value))
variant (fonts/get-default-variant font)]
(cond
(not (some? font))
(u/display-not-valid :fontId value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontId "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id (font-data font variant))))))}
{:name "fontFamily"
:get #(-> % u/proxy->shape text-props :font-family format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")
font (fonts/find-font-data {:font-family value})
variant (fonts/get-default-variant font)]
(cond
(not (some? font))
(u/display-not-valid :fontFamily value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontFamily "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id (font-data font variant))))))}
{:name "fontVariantId"
:get #(-> % u/proxy->shape text-props :font-variant-id format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")
font (fonts/get-font-data (obj/get self "fontId"))
variant (fonts/get-variant font value)]
(cond
(not (some? variant))
(u/display-not-valid :fontVariantId value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontVariantId "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id (variant-data variant))))))}
{:name "fontSize"
:get #(-> % u/proxy->shape text-props :font-size format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (str/trim (dm/str value))]
(cond
(or (empty? value) (not (re-matches font-size-re value)))
(u/display-not-valid :fontSize value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontSize "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id {:font-size value})))))}
{:name "fontWeight"
:get #(-> % u/proxy->shape text-props :font-weight format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")
font (fonts/get-font-data (obj/get self "fontId"))
variant (fonts/find-variant font {:weight (dm/str value)})]
(cond
(nil? variant)
(u/display-not-valid :fontWeight (dm/str "Font weight '" value "' not supported for the current font"))
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontWeight "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id (variant-data variant))))))}
{:name "fontStyle"
:get #(-> % u/proxy->shape text-props :font-style format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")
font (fonts/get-font-data (obj/get self "fontId"))
variant (fonts/find-variant font {:weight (dm/str value)})]
(cond
(nil? variant)
(u/display-not-valid :fontStyle (dm/str "Font style '" value "' not supported for the current font"))
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontStyle "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id (variant-data variant))))))}
{:name "lineHeight"
:get #(-> % u/proxy->shape text-props :line-height format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (str/trim (dm/str value))]
(cond
(or (empty? value) (not (re-matches line-height-re value)))
(u/display-not-valid :lineHeight value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :lineHeight "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id {:line-height value})))))}
{:name "letterSpacing"
:get #(-> % u/proxy->shape text-props :letter-spacing format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (str/trim (dm/str value))]
(cond
(or (empty? value) (re-matches letter-spacing-re value))
(u/display-not-valid :letterSpacing value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :letterSpacing "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id {:letter-spacing value})))))}
{:name "textTransform"
:get #(-> % u/proxy->shape text-props :text-transform format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(and (string? value) (re-matches text-transform-re value))
(u/display-not-valid :textTransform value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textTransform "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id {:text-transform value})))))}
{:name "textDecoration"
:get #(-> % u/proxy->shape text-props :text-decoration format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(and (string? value) (re-matches text-decoration-re value))
(u/display-not-valid :textDecoration value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textDecoration "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id {:text-decoration value})))))}
{:name "direction"
:get #(-> % u/proxy->shape text-props :text-direction format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(and (string? value) (re-matches text-direction-re value))
(u/display-not-valid :textDirection value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textDirection "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id {:text-direction value})))))}
{:name "align"
:get #(-> % u/proxy->shape text-props :text-align format/format-mixed)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(and (string? value) (re-matches text-align-re value))
(u/display-not-valid :align value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :align "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id {:text-align value})))))}
{:name "verticalAlign"
:get #(-> % u/proxy->shape text-props :vertical-align)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(and (string? value) (re-matches vertical-align-re value))
(u/display-not-valid :verticalAlign value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalAlign "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-attrs id {:vertical-align value})))))}))

View file

@ -8,6 +8,7 @@
(:require
[app.common.record :as crc]
[app.config :as cfg]
[app.plugins.format :as format]
[app.plugins.utils :as u]
[app.util.object :as obj]))
@ -54,5 +55,5 @@
(-> (ActiveUserProxy. plugin-id session-id)
(add-user-properties)
(crc/add-properties!
{:name "position" :get (fn [_] (-> (u/locate-presence session-id) :point u/to-js))}
{:name "position" :get (fn [_] (-> (u/locate-presence session-id) :point format/format-point))}
{:name "zoom" :get (fn [_] (-> (u/locate-presence session-id) :zoom))})))

View file

@ -9,13 +9,10 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.spec :as us]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.main.store :as st]
[app.util.object :as obj]
[cuerdas.core :as str]
[promesa.core :as p]))
(defn locate-file
@ -143,58 +140,6 @@
(-> (get-state self attr)
(mapfn))))
(defn from-js
"Converts the object back to js"
([obj]
(from-js obj #{:type}))
([obj keyword-keys]
(when (some? obj)
(let [process-node
(fn process-node [node]
(reduce-kv
(fn [m k v]
(let [k (keyword (str/kebab k))
v (cond (map? v)
(process-node v)
(vector? v)
(mapv process-node v)
(and (string? v) (re-matches us/uuid-rx v))
(uuid/uuid v)
(contains? keyword-keys k)
(keyword v)
:else v)]
(assoc m k v)))
{}
node))]
(process-node (js->clj obj))))))
(defn to-js
"Converts to javascript an camelize the keys"
[obj]
(when (some? obj)
(let [result
(reduce-kv
(fn [m k v]
(let [v (cond (object? v) (to-js v)
(uuid? v) (dm/str v)
:else v)]
(assoc m (str/camel (name k)) v)))
{}
obj)]
(clj->js result))))
(defn array-to-js
[value]
(if (coll? value)
(.freeze
js/Object
(apply array (->> value (map to-js))))
value))
(defn result-p
"Creates a pair of atom+promise. The promise will be resolved when the atom gets a value.
We use this to return the promise to the library clients and resolve its value when a value is passed

View file

@ -14,6 +14,7 @@
[app.main.data.workspace.viewport :as dwv]
[app.main.data.workspace.zoom :as dwz]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.utils :as u]
[app.util.object :as obj]))
@ -88,6 +89,6 @@
:get
(fn [_]
(let [vport (dm/get-in @st/state [:workspace-local :vport])]
(.freeze js/Object (clj->js vport))))}))
(.freeze js/Object (format/format-bounds vport))))}))

View file

@ -156,3 +156,8 @@
x)
:else x)))
(defn clear-empty
[^js obj]
(when (some? obj)
(js* "Object.entries(~{}).reduce((a, [k,v]) => (v == null ? a : (a[k]=v, a)), {}) " obj)))

View file

@ -23,7 +23,7 @@
_ (set! st/state store)
context (api/create-context "tests")
context (api/create-context "TEST")
page (. context -currentPage)
@ -162,9 +162,8 @@
:offset-y 4
:blur 4
:spread 0
:color {:color "#FABADA" :opacity 1}
:color {:color "#fabada" :opacity 1}
:hidden false}]))))
(let [shadow #js {:style "fail"}]
(set! (.-shadows shape) #js [shadow])
(t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow"))))
@ -208,20 +207,20 @@
(t/testing " - fills"
(set! (.-fills shape) #js [#js {:fillColor 100}])
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#B1B2B5"))
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#B1B2B5" :fill-opacity 1}]))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#B1B2B5"))
(set! (.-fills shape) #js [#js {:fillColor "#FABADA" :fillOpacity 1}])
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#FABADA"))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillOpacity")) 1))
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#FABADA" :fill-opacity 1}])))
(set! (.-fills shape) #js [#js {:fillColor "#fabada" :fillOpacity 1}])
(t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#fabada" :fill-opacity 1}]))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#fabada"))
(t/is (= (-> (. shape -fills) (aget 0) (aget "fillOpacity")) 1)))
(t/testing " - strokes"
(set! (.-fills shape) #js [#js {:strokeColor "#FABADA" :strokeOpacity 1 :stroke-width 5}])
(t/is (= (-> (. shape -fills) (aget 0) (aget "strokeColor")) "#FABADA"))
(t/is (= (-> (. shape -fills) (aget 0) (aget "strokeOpacity")) 1))
(t/is (= (-> (. shape -fills) (aget 0) (aget "strokeWidth")) 5))
(t/is (= (get-in @store (get-shape-path :fills)) [{:stroke-color "#FABADA" :stroke-opacity 1 :stroke-width 5}]))))
(set! (.-strokes shape) #js [#js {:strokeColor "#fabada" :strokeOpacity 1 :strokeWidth 5}])
(t/is (= (get-in @store (get-shape-path :strokes)) [{:stroke-color "#fabada" :stroke-opacity 1 :stroke-width 5}]))
(t/is (= (-> (. shape -strokes) (aget 0) (aget "strokeColor")) "#fabada"))
(t/is (= (-> (. shape -strokes) (aget 0) (aget "strokeOpacity")) 1))
(t/is (= (-> (. shape -strokes) (aget 0) (aget "strokeWidth")) 5))))
(t/testing "Relative properties"
(let [frame (.createFrame context)]

View file

@ -847,6 +847,9 @@ msgstr "Cancel"
msgid "ds.confirm-ok"
msgstr "Ok"
msgid "ds.confirm-allow"
msgstr "Allow"
#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs
msgid "ds.confirm-title"
msgstr "Are you sure?"
@ -5290,3 +5293,24 @@ msgstr "Plugins manager"
msgid "workspace.plugins.plugin-list-link"
msgstr "Plugins List"
msgid "workspace.plugins.permissions.title"
msgstr "THIS PLUGIN WANTS ACCESS TO:"
msgid "workspace.plugins.permissions.disclaimer"
msgstr "Note that this plugin has been created by an external party."
msgid "workspace.plugins.permissions.content-read"
msgstr "Read the content of files that users have access to."
msgid "workspace.plugins.permissions.content-write"
msgstr "Read and modify the content of files that users have access to."
msgid "workspace.plugins.permissions.user-read"
msgstr "Read the profile information of the current user."
msgid "workspace.plugins.permissions.library-read"
msgstr "Read your libraries and assets."
msgid "workspace.plugins.permissions.library-write"
msgstr "Read and modify your libraries and assets."

View file

@ -869,6 +869,9 @@ msgstr "Cancelar"
msgid "ds.confirm-ok"
msgstr "Ok"
msgid "ds.confirm-allow"
msgstr "Permitir"
#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs
msgid "ds.confirm-title"
msgstr "¿Está Seguro?"
@ -5394,3 +5397,24 @@ msgstr "Gestor de extensiones"
msgid "workspace.plugins.plugin-list-link"
msgstr "Lista de extensiones"
msgid "workspace.plugins.permissions.title"
msgstr "LA EXTENSIÓN SOLICITA PERMISO PARA ACCEDER:"
msgid "workspace.plugins.permissions.disclaimer"
msgstr "Tenga en cuenta que esta extensión ha sido desarrollada por terceros."
msgid "workspace.plugins.permissions.content-read"
msgstr "Leer el contenido de sus archivos."
msgid "workspace.plugins.permissions.content-write"
msgstr "Leer y modificar el contenido de sus archivos."
msgid "workspace.plugins.permissions.user-read"
msgstr "Leer la información del usuario actual."
msgid "workspace.plugins.permissions.library-read"
msgstr "Leer la información de sus bibliotecas y recursos."
msgid "workspace.plugins.permissions.library-write"
msgstr "Leer y modificar la información de sus bibliotecas y recursos."