mirror of
https://github.com/penpot/penpot.git
synced 2025-05-31 11:26:10 +02:00
Merge remote-tracking branch 'origin/plugins-beta-test' into develop
This commit is contained in:
commit
19b2f330dd
24 changed files with 1911 additions and 1040 deletions
|
@ -428,6 +428,8 @@
|
|||
[shape text]
|
||||
(let [content (:content shape)
|
||||
|
||||
root-styles (select-keys content root-attrs)
|
||||
|
||||
paragraph-style (merge
|
||||
default-text-attrs
|
||||
(select-keys (->> content (node-seq is-paragraph-node?) first) text-all-attrs))
|
||||
|
@ -447,10 +449,12 @@
|
|||
:children [(merge {:text pt} text-style)]}))))
|
||||
|
||||
new-content
|
||||
{:type "root"
|
||||
:children
|
||||
[{:type "paragraph-set"
|
||||
:children paragraphs}]}]
|
||||
(d/patch-object
|
||||
{:type "root"
|
||||
:children
|
||||
[{:type "paragraph-set"
|
||||
:children paragraphs}]}
|
||||
root-styles)]
|
||||
|
||||
(assoc shape :content new-content)))
|
||||
|
||||
|
|
|
@ -48,8 +48,7 @@
|
|||
#{:flex :grid})
|
||||
|
||||
(def flex-direction-types
|
||||
;;TODO remove reverse-column and reverse-row after script
|
||||
#{:row :reverse-row :row-reverse :column :reverse-column :column-reverse})
|
||||
#{:row :row-reverse :column :column-reverse})
|
||||
|
||||
(def grid-direction-types
|
||||
#{:row :column})
|
||||
|
@ -58,7 +57,7 @@
|
|||
#{:simple :multiple})
|
||||
|
||||
(def wrap-types
|
||||
#{:wrap :nowrap :no-wrap}) ;;TODO remove no-wrap after script
|
||||
#{:wrap :nowrap})
|
||||
|
||||
(def padding-type
|
||||
#{:simple :multiple})
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -479,8 +479,8 @@
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn create-page
|
||||
[{:keys [file-id]}]
|
||||
(let [id (uuid/next)]
|
||||
[{:keys [page-id file-id]}]
|
||||
(let [id (or page-id (uuid/next))]
|
||||
(ptk/reify ::create-page
|
||||
ev/Event
|
||||
(-data [_]
|
||||
|
|
|
@ -28,29 +28,33 @@
|
|||
;; --- Flows
|
||||
|
||||
(defn add-flow
|
||||
[starting-frame]
|
||||
([starting-frame]
|
||||
(add-flow nil nil nil starting-frame))
|
||||
|
||||
(dm/assert!
|
||||
"expect uuid"
|
||||
(uuid? starting-frame))
|
||||
([flow-id page-id name starting-frame]
|
||||
(dm/assert!
|
||||
"expect uuid"
|
||||
(uuid? starting-frame))
|
||||
|
||||
(ptk/reify ::add-flow
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (wsh/lookup-page state)
|
||||
(ptk/reify ::add-flow
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (if page-id
|
||||
(wsh/lookup-page state page-id)
|
||||
(wsh/lookup-page state))
|
||||
|
||||
flows (get-in page [:options :flows] [])
|
||||
unames (cfh/get-used-names flows)
|
||||
name (cfh/generate-unique-name unames "Flow 1")
|
||||
flows (get-in page [:options :flows] [])
|
||||
unames (cfh/get-used-names flows)
|
||||
name (or name (cfh/generate-unique-name unames "Flow 1"))
|
||||
|
||||
new-flow {:id (uuid/next)
|
||||
:name name
|
||||
:starting-frame starting-frame}]
|
||||
new-flow {:id (or flow-id (uuid/next))
|
||||
:name name
|
||||
:starting-frame starting-frame}]
|
||||
|
||||
(rx/of (dch/commit-changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/update-page-option :flows ctp/add-flow new-flow))))))))
|
||||
(rx/of (dch/commit-changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/update-page-option :flows ctp/add-flow new-flow)))))))))
|
||||
|
||||
(defn add-flow-selected-frame
|
||||
[]
|
||||
|
@ -61,16 +65,35 @@
|
|||
(rx/of (add-flow (first selected)))))))
|
||||
|
||||
(defn remove-flow
|
||||
[flow-id]
|
||||
([flow-id]
|
||||
(remove-flow nil flow-id))
|
||||
|
||||
([page-id flow-id]
|
||||
(dm/assert! (uuid? flow-id))
|
||||
(ptk/reify ::remove-flow
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (if page-id
|
||||
(wsh/lookup-page state page-id)
|
||||
(wsh/lookup-page state))]
|
||||
(rx/of (dch/commit-changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/update-page-option :flows ctp/remove-flow flow-id)))))))))
|
||||
|
||||
(defn update-flow
|
||||
[page-id flow-id update-fn]
|
||||
(dm/assert! (uuid? flow-id))
|
||||
(ptk/reify ::remove-flow
|
||||
(ptk/reify ::update-flow
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (wsh/lookup-page state)]
|
||||
(let [page (if page-id
|
||||
(wsh/lookup-page state page-id)
|
||||
(wsh/lookup-page state))]
|
||||
(rx/of (dch/commit-changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/update-page-option :flows ctp/remove-flow flow-id))))))))
|
||||
(pcb/update-page-option :flows ctp/update-flow flow-id update-fn))))))))
|
||||
|
||||
(defn rename-flow
|
||||
[flow-id name]
|
||||
|
@ -111,6 +134,18 @@
|
|||
(or (some ctsi/flow-origin? (map :interactions children))
|
||||
(some #(ctsi/flow-to? % frame-id) (map :interactions (vals objects))))))
|
||||
|
||||
(defn add-interaction
|
||||
[page-id shape-id interaction]
|
||||
(ptk/reify ::add-interaction
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [page-id (or page-id (:current-page-id state))]
|
||||
(rx/of (dwsh/update-shapes
|
||||
[shape-id]
|
||||
(fn [shape]
|
||||
(cls/add-new-interaction shape interaction))
|
||||
{:page-id page-id}))))))
|
||||
|
||||
(defn add-new-interaction
|
||||
([shape] (add-new-interaction shape nil))
|
||||
([shape destination]
|
||||
|
@ -138,23 +173,29 @@
|
|||
(rx/of (add-flow (:id frame))))))))))
|
||||
|
||||
(defn remove-interaction
|
||||
[shape index]
|
||||
(ptk/reify ::remove-interaction
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwsh/update-shapes [(:id shape)]
|
||||
(fn [shape]
|
||||
(update shape :interactions
|
||||
ctsi/remove-interaction index)))))))
|
||||
([shape index]
|
||||
(remove-interaction nil shape index))
|
||||
([page-id shape index]
|
||||
(ptk/reify ::remove-interaction
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwsh/update-shapes [(:id shape)]
|
||||
(fn [shape]
|
||||
(update shape :interactions
|
||||
ctsi/remove-interaction index))
|
||||
{:page-id page-id}))))))
|
||||
(defn update-interaction
|
||||
[shape index update-fn]
|
||||
(ptk/reify ::update-interaction
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwsh/update-shapes [(:id shape)]
|
||||
(fn [shape]
|
||||
(update shape :interactions
|
||||
ctsi/update-interaction index update-fn)))))))
|
||||
([shape index update-fn]
|
||||
(update-interaction shape index update-fn nil))
|
||||
([shape index update-fn options]
|
||||
(ptk/reify ::update-interaction
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwsh/update-shapes [(:id shape)]
|
||||
(fn [shape]
|
||||
(update shape :interactions
|
||||
ctsi/update-interaction index update-fn))
|
||||
options))))))
|
||||
|
||||
(defn remove-all-interactions-nav-to
|
||||
"Remove all interactions that navigate to the given frame."
|
||||
|
|
|
@ -30,7 +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.plugins.register :as preg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
|
@ -609,7 +609,7 @@
|
|||
::mf/wrap [mf/memo]}
|
||||
[{:keys [open-plugins on-close]}]
|
||||
(when (features/active-feature? @st/state "plugins/runtime")
|
||||
(let [plugins @plugins/pluginsdb]
|
||||
(let [plugins (preg/plugins-list)]
|
||||
[:& dropdown-menu {:show true
|
||||
:list-class (stl/css-case :sub-menu true :plugins true)
|
||||
:on-close on-close}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
[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.plugins.register :as preg]
|
||||
[app.util.avatars :as avatars]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.http :as http]
|
||||
|
@ -80,12 +80,14 @@
|
|||
::mf/register-as :plugin-management}
|
||||
[]
|
||||
|
||||
(let [plugins-state* (mf/use-state @plugins/pluginsdb)
|
||||
(let [plugins-state* (mf/use-state #(preg/plugins-list))
|
||||
plugins-state @plugins-state*
|
||||
|
||||
plugin-url* (mf/use-state "")
|
||||
plugin-url @plugin-url*
|
||||
|
||||
fetching-manifest? (mf/use-state false)
|
||||
|
||||
input-status* (mf/use-state nil) ;; :error-url :error-manifest :success
|
||||
input-status @input-status*
|
||||
|
||||
|
@ -106,6 +108,7 @@
|
|||
(mf/use-callback
|
||||
(mf/deps plugins-state plugin-url)
|
||||
(fn []
|
||||
(reset! fetching-manifest? true)
|
||||
(->> (http/send! {:method :get
|
||||
:uri plugin-url
|
||||
:omit-default-headers true
|
||||
|
@ -113,18 +116,20 @@
|
|||
(rx/map :body)
|
||||
(rx/subs!
|
||||
(fn [body]
|
||||
(let [plugin (plugins/parser-manifest plugin-url body)]
|
||||
(reset! fetching-manifest? false)
|
||||
(let [plugin (preg/parse-manifest plugin-url body)]
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
|
||||
(modal/show!
|
||||
:plugin-permissions
|
||||
{:plugin plugin
|
||||
:on-accept
|
||||
#(do
|
||||
(plugins/install-plugin! plugin)
|
||||
(preg/install-plugin! plugin)
|
||||
(modal/show! :plugin-management {}))})
|
||||
(reset! input-status* :success)
|
||||
(reset! plugin-url* "")))
|
||||
(fn [_]
|
||||
(reset! fetching-manifest? false)
|
||||
(reset! input-status* :error-url))))))
|
||||
|
||||
handle-open-plugin
|
||||
|
@ -141,12 +146,13 @@
|
|||
(mf/use-callback
|
||||
(mf/deps plugins-state)
|
||||
(fn [plugin-index]
|
||||
(let [plugin (nth @plugins/pluginsdb plugin-index)]
|
||||
(let [plugins-list (preg/plugins-list)
|
||||
plugin (nth plugins-list plugin-index)]
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "remove-plugin"
|
||||
:name (:name plugin)
|
||||
:host (:host plugin)}))
|
||||
(plugins/remove-plugin! plugin)
|
||||
(reset! plugins-state* @plugins/pluginsdb))))]
|
||||
(preg/remove-plugin! plugin)
|
||||
(reset! plugins-state* (preg/plugins-list)))))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-dialog :plugin-management)}
|
||||
|
@ -161,12 +167,18 @@
|
|||
:class (stl/css-case :input-error error?)}]
|
||||
|
||||
[:button {:class (stl/css :primary-button)
|
||||
:disabled @fetching-manifest?
|
||||
:on-click handle-install-click} (tr "workspace.plugins.install")]]
|
||||
|
||||
(when error?
|
||||
[:div {:class (stl/css-case :info true :error error?)}
|
||||
(tr "workspace.plugins.error.url")])
|
||||
|
||||
[:> i18n/tr-html*
|
||||
{:class (stl/css :discover)
|
||||
:on-click #(st/emit! (ptk/event ::ev/event {::ev/name "open-plugins-list"}))
|
||||
:content (tr "workspace.plugins.discover" cf/plugins-list-uri)}]
|
||||
|
||||
[:hr]
|
||||
|
||||
(if (empty? plugins-state)
|
||||
|
|
|
@ -213,7 +213,7 @@ div.input-error {
|
|||
gap: $s-4;
|
||||
|
||||
svg {
|
||||
margin-top: calc(-1 * var($s-2));
|
||||
margin-top: calc(-1 * $s-2);
|
||||
width: $s-12;
|
||||
height: $s-12;
|
||||
stroke: $da-primary;
|
||||
|
@ -262,3 +262,13 @@ div.input-error {
|
|||
display: flex;
|
||||
gap: $s-12;
|
||||
}
|
||||
|
||||
.discover {
|
||||
@include bodySmallTypography;
|
||||
color: $df-secondary;
|
||||
margin-top: $s-24;
|
||||
|
||||
a {
|
||||
color: $da-primary;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
(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]
|
||||
|
@ -18,10 +17,6 @@
|
|||
[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")]
|
||||
|
@ -41,28 +36,3 @@
|
|||
(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 (into #{} (obj/get manifest "permissions" []))
|
||||
permissions
|
||||
(cond-> permissions
|
||||
(contains? permissions "content:write")
|
||||
(conj "content:read")
|
||||
|
||||
(contains? permissions "library:write")
|
||||
(conj "content:write"))
|
||||
|
||||
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))}))
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.changes :as ch]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.bool :as dwb]
|
||||
[app.main.data.workspace.colors :as dwc]
|
||||
[app.main.data.workspace.groups :as dwg]
|
||||
|
@ -29,6 +30,7 @@
|
|||
[app.plugins.file :as file]
|
||||
[app.plugins.fonts :as fonts]
|
||||
[app.plugins.format :as format]
|
||||
[app.plugins.history :as history]
|
||||
[app.plugins.library :as library]
|
||||
[app.plugins.page :as page]
|
||||
[app.plugins.parser :as parser]
|
||||
|
@ -61,8 +63,8 @@
|
|||
(deftype PenpotContext [$plugin]
|
||||
Object
|
||||
(addListener
|
||||
[_ type callback]
|
||||
(events/add-listener type $plugin callback))
|
||||
[_ type callback props]
|
||||
(events/add-listener type $plugin callback props))
|
||||
|
||||
(removeListener
|
||||
[_ listener-id]
|
||||
|
@ -347,7 +349,26 @@
|
|||
(mapcat #(cfh/get-children-with-self objects (:id %))))
|
||||
shapes)]
|
||||
(cg/generate-style-code
|
||||
objects type shapes shapes-with-children {:with-prelude? prelude?}))))))
|
||||
objects type shapes shapes-with-children {:with-prelude? prelude?})))))
|
||||
|
||||
(openViewer
|
||||
[_]
|
||||
(let [params {:page-id (:current-page-id @st/state)
|
||||
:file-id (:current-file-id @st/state)
|
||||
:section "interactions"}]
|
||||
(st/emit! (dw/go-to-viewer params))))
|
||||
|
||||
(createPage
|
||||
[_]
|
||||
(let [file-id (:current-file-id @st/state)
|
||||
id (uuid/next)]
|
||||
(st/emit! (dw/create-page {:page-id id :file-id file-id}))
|
||||
(page/page-proxy $plugin file-id id)))
|
||||
|
||||
(openPage
|
||||
[_ page]
|
||||
(let [id (obj/get page "$id")]
|
||||
(st/emit! (dw/go-to-page id)))))
|
||||
|
||||
(defn create-context
|
||||
[plugin-id]
|
||||
|
@ -374,4 +395,5 @@
|
|||
{:name "currentUser" :get #(.getCurrentUser ^js %)}
|
||||
{:name "activeUsers" :get #(.getActiveUsers ^js %)}
|
||||
{:name "fonts" :get (fn [_] (fonts/fonts-subcontext plugin-id))}
|
||||
{:name "library" :get (fn [_] (library/library-subcontext plugin-id))}))
|
||||
{:name "library" :get (fn [_] (library/library-subcontext plugin-id))}
|
||||
{:name "history" :get (fn [_] (history/history-subcontext plugin-id))}))
|
||||
|
|
|
@ -6,15 +6,20 @@
|
|||
|
||||
(ns app.plugins.events
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.file :as file]
|
||||
[app.plugins.page :as page]
|
||||
[app.plugins.parser :as parser]
|
||||
[app.plugins.shape :as shape]
|
||||
[app.util.object :as obj]
|
||||
[goog.functions :as gf]))
|
||||
|
||||
(defmulti handle-state-change (fn [type _] type))
|
||||
|
||||
(defmethod handle-state-change "finish"
|
||||
[_ _ old-val new-val]
|
||||
[_ _ old-val new-val _]
|
||||
(let [old-file-id (:current-file-id old-val)
|
||||
new-file-id (:current-file-id new-val)]
|
||||
(if (and (some? old-file-id) (nil? new-file-id))
|
||||
|
@ -22,7 +27,7 @@
|
|||
::not-changed)))
|
||||
|
||||
(defmethod handle-state-change "filechange"
|
||||
[_ plugin-id old-val new-val]
|
||||
[_ plugin-id old-val new-val _]
|
||||
(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)
|
||||
|
@ -30,7 +35,7 @@
|
|||
(file/file-proxy plugin-id new-file-id))))
|
||||
|
||||
(defmethod handle-state-change "pagechange"
|
||||
[_ plugin-id old-val new-val]
|
||||
[_ plugin-id old-val new-val _]
|
||||
(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)
|
||||
|
@ -38,7 +43,7 @@
|
|||
(page/page-proxy plugin-id (:current-file-id new-val) new-page-id))))
|
||||
|
||||
(defmethod handle-state-change "selectionchange"
|
||||
[_ _ old-val new-val]
|
||||
[_ _ old-val new-val _]
|
||||
(let [old-selection (get-in old-val [:workspace-local :selected])
|
||||
new-selection (get-in new-val [:workspace-local :selected])]
|
||||
(if (identical? old-selection new-selection)
|
||||
|
@ -46,7 +51,7 @@
|
|||
(apply array (map str new-selection)))))
|
||||
|
||||
(defmethod handle-state-change "themechange"
|
||||
[_ _ old-val new-val]
|
||||
[_ _ old-val new-val _]
|
||||
(let [old-theme (get-in old-val [:profile :theme])
|
||||
new-theme (get-in new-val [:profile :theme])]
|
||||
(if (identical? old-theme new-theme)
|
||||
|
@ -55,23 +60,55 @@
|
|||
"dark"
|
||||
new-theme))))
|
||||
|
||||
(defmethod handle-state-change "shapechange"
|
||||
[_ plugin-id old-val new-val props]
|
||||
(let [shape-id (-> (obj/get props "shapeId") parser/parse-id)
|
||||
old-shape (wsh/lookup-shape old-val shape-id)
|
||||
new-shape (wsh/lookup-shape new-val shape-id)
|
||||
|
||||
file-id (:current-file-id new-val)
|
||||
page-id (:current-page-id new-val)]
|
||||
(if (and (identical? old-shape new-shape) (some? plugin-id) (some? file-id) (some? page-id) (some? shape-id))
|
||||
::not-changed
|
||||
(shape/shape-proxy plugin-id file-id page-id shape-id))))
|
||||
|
||||
(defmethod handle-state-change "contentsave"
|
||||
[_ _ old-val new-val _]
|
||||
(let [old-status (dm/get-in old-val [:persistence :status])
|
||||
new-status (dm/get-in new-val [:persistence :status])]
|
||||
(if (and (= :saved new-status) (not= new-status old-status))
|
||||
::void ;; Changed but void
|
||||
::not-changed)))
|
||||
|
||||
(defmethod handle-state-change :default
|
||||
[_ _ _ _]
|
||||
::not-changed)
|
||||
|
||||
(defn add-listener
|
||||
[type plugin-id callback]
|
||||
(let [key (js/Symbol)
|
||||
callback (gf/debounce callback 10)]
|
||||
[type plugin-id callback props]
|
||||
(let [plugin-id (parser/parse-id plugin-id)
|
||||
key (js/Symbol)
|
||||
|
||||
;; We wrap the callback in an exception handler so the plugins
|
||||
;; don't crash the application
|
||||
safe-callback
|
||||
(fn [value]
|
||||
(try
|
||||
(if (= ::void value)
|
||||
(callback)
|
||||
(callback value))
|
||||
(catch :default cause
|
||||
(.error js/console cause))))
|
||||
|
||||
;; We also debounce the callbacks so we don't get too many at the same time
|
||||
debounced-callback (gf/debounce safe-callback 10)]
|
||||
|
||||
(add-watch
|
||||
st/state key
|
||||
(fn [_ _ old-val new-val]
|
||||
(let [result (handle-state-change type plugin-id old-val new-val)]
|
||||
(let [result (handle-state-change type plugin-id old-val new-val props)]
|
||||
(when (not= ::not-changed result)
|
||||
(try
|
||||
(callback result)
|
||||
(catch :default cause
|
||||
(.error js/console cause)))))))
|
||||
(debounced-callback result)))))
|
||||
|
||||
;; return the generated key
|
||||
key))
|
||||
|
|
|
@ -5,16 +5,23 @@
|
|||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.plugins.file
|
||||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.record :as crc]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.export :as mue]
|
||||
[app.main.worker :as uw]
|
||||
[app.plugins.page :as page]
|
||||
[app.plugins.parser :as parser]
|
||||
[app.plugins.register :as r]
|
||||
[app.plugins.utils :as u]
|
||||
[app.util.object :as obj]))
|
||||
[app.util.http :as http]
|
||||
[app.util.object :as obj]
|
||||
[beicon.v2.core :as rx]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(deftype FileProxy [$plugin $id]
|
||||
Object
|
||||
|
@ -93,7 +100,58 @@
|
|||
|
||||
:else
|
||||
(let [file (u/proxy->file self)]
|
||||
(apply array (keys (dm/get-in file [:data :plugin-data (keyword "shared" namespace)])))))))
|
||||
(apply array (keys (dm/get-in file [:data :plugin-data (keyword "shared" namespace)]))))))
|
||||
|
||||
(createPage
|
||||
[_]
|
||||
(cond
|
||||
(not (r/check-permission $plugin "content:write"))
|
||||
(u/display-not-valid :createPage "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [page-id (uuid/next)]
|
||||
(st/emit! (dw/create-page {:page-id page-id :file-id $id}))
|
||||
(page/page-proxy $plugin $id page-id))))
|
||||
|
||||
(export
|
||||
[self type export-type]
|
||||
(let [export-type (or (parser/parse-keyword export-type) :all)]
|
||||
(cond
|
||||
(not (contains? #{"penpot" "zip"} type))
|
||||
(u/display-not-valid :export-type type)
|
||||
|
||||
(not (contains? (set mue/export-types) export-type))
|
||||
(u/display-not-valid :export-exportType export-type)
|
||||
|
||||
:else
|
||||
(let [export-cmd (if (= type "penpot") :export-binary-file :export-standard-file)
|
||||
file (u/proxy->file self)
|
||||
features (features/get-team-enabled-features @st/state)
|
||||
team-id (:current-team-id @st/state)]
|
||||
(p/create
|
||||
(fn [resolve reject]
|
||||
(->> (uw/ask-many!
|
||||
{:cmd export-cmd
|
||||
:team-id team-id
|
||||
:features features
|
||||
:export-type export-type
|
||||
:files [file]})
|
||||
(rx/mapcat #(->> (rx/of %) (rx/delay 1000)))
|
||||
(rx/mapcat
|
||||
(fn [msg]
|
||||
(case (:type msg)
|
||||
:error
|
||||
(rx/throw (ex-info "cannot export file" {:type :export-file}))
|
||||
|
||||
:progress
|
||||
(rx/empty)
|
||||
|
||||
:finish
|
||||
(http/send! {:method :get :uri (:uri msg) :mode :no-cors :response-type :blob}))))
|
||||
(rx/first)
|
||||
(rx/mapcat (fn [{:keys [body]}] (.arrayBuffer ^js body)))
|
||||
(rx/map (fn [data] (js/Uint8Array. data)))
|
||||
(rx/subs! resolve reject)))))))))
|
||||
|
||||
(crc/define-properties!
|
||||
FileProxy
|
||||
|
|
|
@ -66,6 +66,22 @@
|
|||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-flex-dir value}))))))}
|
||||
|
||||
{:name "wrap"
|
||||
:get #(-> % u/proxy->shape :layout-wrap-type d/name)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [value (keyword value)]
|
||||
(cond
|
||||
(not (contains? ctl/wrap-types value))
|
||||
(u/display-not-valid :wrap value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/display-not-valid :wrap "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-wrap-type value}))))))}
|
||||
|
||||
{:name "alignItems"
|
||||
:get #(-> % u/proxy->shape :layout-align-items d/name)
|
||||
:set
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.util.object :as obj]))
|
||||
|
||||
(def shape-proxy nil)
|
||||
|
||||
(defn format-id
|
||||
[id]
|
||||
(when id (dm/str id)))
|
||||
|
@ -422,3 +424,163 @@
|
|||
[tracks]
|
||||
(when (some? tracks)
|
||||
(format-array format-track tracks)))
|
||||
|
||||
|
||||
;; export interface PenpotDissolve {
|
||||
;; type: 'dissolve';
|
||||
;; duration: number;
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export interface PenpotSlide {
|
||||
;; type: 'slide';
|
||||
;; way: 'in' | 'out';
|
||||
;; direction?:
|
||||
;; | 'right'
|
||||
;; | 'left'
|
||||
;; | 'up'
|
||||
;; | 'down';
|
||||
;; duration: number;
|
||||
;; offsetEffect?: boolean;
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export interface PenpotPush {
|
||||
;; type: 'push';
|
||||
;; direction?:
|
||||
;; | 'right'
|
||||
;; | 'left'
|
||||
;; | 'up'
|
||||
;; | 'down';
|
||||
;;
|
||||
;; duration: number;
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export type PenpotAnimation = PenpotDissolve | PenpotSlide | PenpotPush;
|
||||
|
||||
(defn format-animation
|
||||
[animation]
|
||||
(when animation
|
||||
(obj/clear-empty
|
||||
(case (:animation-type animation)
|
||||
|
||||
:dissolve
|
||||
#js {:type "dissolve"
|
||||
:duration (:duration animation)
|
||||
:easing (format-key (:easing animation))}
|
||||
|
||||
:slide
|
||||
#js {:type "slide"
|
||||
:way (format-key (:way animation))
|
||||
:direction (format-key (:direction animation))
|
||||
:duration (:duration animation)
|
||||
:easing (format-key (:easing animation))
|
||||
:offsetEffect (:offset-effect animation)}
|
||||
|
||||
:push
|
||||
#js {:type "push"
|
||||
:direction (format-key (:direction animation))
|
||||
:duration (:duration animation)
|
||||
:easing (format-key (:easing animation))}
|
||||
nil))))
|
||||
|
||||
;;export type PenpotAction =
|
||||
;; | PenpotNavigateTo
|
||||
;; | PenpotOpenOverlay
|
||||
;; | PenpotToggleOverlay
|
||||
;; | PenpotCloseOverlay
|
||||
;; | PenpotPreviousScreen
|
||||
;; | PenpotOpenUrl;
|
||||
;;
|
||||
;;export interface PenpotNavigateTo {
|
||||
;; type: 'navigate-to';
|
||||
;; destination: PenpotFrame;
|
||||
;; preserveScrollPosition?: boolean;
|
||||
;; animation: PenpotAnimation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOverlayAction {
|
||||
;; destination: PenpotFrame;
|
||||
;; relativeTo?: PenpotShape;
|
||||
;; position?:
|
||||
;; | 'manual'
|
||||
;; | 'center'
|
||||
;; | 'top-left'
|
||||
;; | 'top-right'
|
||||
;; | 'top-center'
|
||||
;; | 'bottom-left'
|
||||
;; | 'bottom-right'
|
||||
;; | 'bottom-center';
|
||||
;; manualPositionLocation?: PenpotPoint;
|
||||
;; closeWhenClickOutside?: boolean;
|
||||
;; addBackgroundOverlay?: boolean;
|
||||
;; animation: PenpotAnimation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOpenOverlay extends PenpotOverlayAction {
|
||||
;; type: 'open-overlay';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotToggleOverlay extends PenpotOverlayAction {
|
||||
;; type: 'toggle-overlay';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotCloseOverlay {
|
||||
;; type: 'close-overlay';
|
||||
;; destination?: PenpotFrame;
|
||||
;; animation: PenpotAnimation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotPreviousScreen {
|
||||
;; type: 'previous-screen';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOpenUrl {
|
||||
;; type: 'open-url';
|
||||
;; url: string;
|
||||
;;}
|
||||
(defn format-action
|
||||
[interaction plugin file-id page-id]
|
||||
(when interaction
|
||||
(obj/clear-empty
|
||||
(case (:action-type interaction)
|
||||
:navigate
|
||||
#js {:type "navigate-to"
|
||||
:destination (when (:destination interaction) (shape-proxy plugin file-id page-id (:destination interaction)))
|
||||
:preserveScrollPosition (:preserve-scroll interaction false)
|
||||
:animation (format-animation (:animation interaction))}
|
||||
|
||||
:open-overlay
|
||||
#js {:type "open-overlay"
|
||||
:destination (when (:destination interaction) (shape-proxy plugin file-id page-id (:destination interaction)))
|
||||
:relativeTo (when (:relative-to interaction) (shape-proxy plugin file-id page-id (:relative-to interaction)))
|
||||
:position (format-key (:overlay-pos-type interaction))
|
||||
:manualPositionLocation (format-point (:overlay-position interaction))
|
||||
:closeWhenClickOutside (:close-click-outside interaction)
|
||||
:addBackgroundOverlay (:background-overlay interaction)
|
||||
:animation (format-animation (:animation interaction))}
|
||||
|
||||
:toggle-overlay
|
||||
#js {:type "toggle-overlay"
|
||||
:destination (when (:destination interaction) (shape-proxy plugin file-id page-id (:destination interaction)))
|
||||
:relativeTo (when (:relative-to interaction) (shape-proxy plugin file-id page-id (:relative-to interaction)))
|
||||
:position (format-key (:overlay-pos-type interaction))
|
||||
:manualPositionLocation (format-point (:overlay-position interaction))
|
||||
:closeWhenClickOutside (:close-click-outside interaction)
|
||||
:addBackgroundOverlay (:background-overlay interaction)
|
||||
:animation (format-animation (:animation interaction))}
|
||||
|
||||
:close-overlay
|
||||
#js {:type "close-overlay"
|
||||
:destination (when (:destination interaction) (shape-proxy plugin file-id page-id (:destination interaction)))
|
||||
:animation (format-animation (:animation interaction))}
|
||||
|
||||
:prev-screen
|
||||
#js {:type "previous-screen"}
|
||||
|
||||
:open-url
|
||||
#js {:type "open-url"
|
||||
:url (:url interaction)}
|
||||
|
||||
nil))))
|
||||
|
|
52
frontend/src/app/plugins/history.cljs
Normal file
52
frontend/src/app/plugins/history.cljs
Normal file
|
@ -0,0 +1,52 @@
|
|||
;; 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.history
|
||||
(:require
|
||||
[app.common.record :as crc]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.register :as r]
|
||||
[app.plugins.utils :as u]))
|
||||
|
||||
(deftype HistorySubcontext [$plugin]
|
||||
Object
|
||||
(undoBlockBegin
|
||||
[_]
|
||||
(cond
|
||||
(not (r/check-permission $plugin "content:write"))
|
||||
(u/display-not-valid :resize "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [id (js/Symbol)]
|
||||
(st/emit! (dwu/start-undo-transaction id))
|
||||
id)))
|
||||
|
||||
(undoBlockFinish
|
||||
[_ block-id]
|
||||
(cond
|
||||
(not (r/check-permission $plugin "content:write"))
|
||||
(u/display-not-valid :resize "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not block-id)
|
||||
(u/display-not-valid :undoBlockFinish block-id)
|
||||
|
||||
:else
|
||||
(st/emit! (dwu/commit-undo-transaction block-id)))))
|
||||
|
||||
(crc/define-properties!
|
||||
HistorySubcontext
|
||||
{:name js/Symbol.toStringTag
|
||||
:get (fn [] (str "HistorySubcontext"))})
|
||||
|
||||
(defn history-subcontext? [p]
|
||||
(instance? HistorySubcontext p))
|
||||
|
||||
(defn history-subcontext
|
||||
[plugin-id]
|
||||
(HistorySubcontext. plugin-id))
|
||||
|
||||
|
|
@ -8,11 +8,14 @@
|
|||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.record :as crc]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.interactions :as dwi]
|
||||
[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]
|
||||
|
@ -20,6 +23,49 @@
|
|||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(deftype FlowProxy [$plugin $file $page $id]
|
||||
Object
|
||||
(remove [_]
|
||||
(st/emit! (dwi/remove-flow $page $id))))
|
||||
|
||||
(defn flow-proxy? [p]
|
||||
(instance? FlowProxy p))
|
||||
|
||||
(defn flow-proxy
|
||||
[plugin-id file-id page-id id]
|
||||
(crc/add-properties!
|
||||
(FlowProxy. plugin-id file-id page-id id)
|
||||
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
|
||||
{:name "$file" :enumerable false :get (constantly file-id)}
|
||||
{:name "$page" :enumerable false :get (constantly page-id)}
|
||||
{:name "$id" :enumerable false :get (constantly id)}
|
||||
{:name "page" :enumerable false :get (fn [_] (u/locate-page file-id page-id))}
|
||||
|
||||
{:name "name"
|
||||
:get #(-> % u/proxy->flow :name)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(or (not (string? value)) (empty? value))
|
||||
(u/display-not-valid :name value)
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/update-flow page-id id #(assoc % :name value)))))}
|
||||
|
||||
{:name "startingFrame"
|
||||
:get
|
||||
(fn [self]
|
||||
(let [frame (-> self u/proxy->flow :starting-frame)]
|
||||
(u/locate-shape file-id page-id frame)))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(not (shape/shape-proxy? value))
|
||||
(u/display-not-valid :startingFrame value)
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id"))))))}))
|
||||
|
||||
(deftype PageProxy [$plugin $file $id]
|
||||
Object
|
||||
(getShapeById
|
||||
|
@ -131,7 +177,39 @@
|
|||
|
||||
:else
|
||||
(let [page (u/proxy->page self)]
|
||||
(apply array (keys (dm/get-in page [:options :plugin-data (keyword "shared" namespace)])))))))
|
||||
(apply array (keys (dm/get-in page [:options :plugin-data (keyword "shared" namespace)]))))))
|
||||
|
||||
(openPage
|
||||
[_]
|
||||
(cond
|
||||
(not (r/check-permission $plugin "content:read"))
|
||||
(u/display-not-valid :openPage "Plugin doesn't have 'content:read' permission")
|
||||
|
||||
:else
|
||||
(st/emit! (dw/go-to-page $id))))
|
||||
|
||||
(createFlow
|
||||
[_ name frame]
|
||||
(cond
|
||||
(or (not (string? name)) (empty? name))
|
||||
(u/display-not-valid :createFlow-name name)
|
||||
|
||||
(not (shape/shape-proxy? frame))
|
||||
(u/display-not-valid :createFlow-frame frame)
|
||||
|
||||
:else
|
||||
(let [flow-id (uuid/next)]
|
||||
(st/emit! (dwi/add-flow flow-id $id name (obj/get frame "$id")))
|
||||
(flow-proxy $plugin $file $id flow-id))))
|
||||
|
||||
(removeFlow
|
||||
[_ flow]
|
||||
(cond
|
||||
(not (flow-proxy? flow))
|
||||
(u/display-not-valid :removeFlow-flow flow)
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/remove-flow $id (obj/get flow "$id"))))))
|
||||
|
||||
(crc/define-properties!
|
||||
PageProxy
|
||||
|
@ -183,4 +261,10 @@
|
|||
(u/display-not-valid :background "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(st/emit! (dw/change-canvas-color id {:color value}))))}))
|
||||
(st/emit! (dw/change-canvas-color id {:color value}))))}
|
||||
|
||||
{:name "flows"
|
||||
:get
|
||||
(fn [self]
|
||||
(let [flows (d/nilv (-> (u/proxy->page self) :options :flows) [])]
|
||||
(format/format-array #(flow-proxy plugin-id file-id id (:id %)) flows)))}))
|
||||
|
|
|
@ -23,6 +23,12 @@
|
|||
[color]
|
||||
(if (string? color) (-> color str/lower) color))
|
||||
|
||||
(defn parse-point
|
||||
[^js point]
|
||||
(when point
|
||||
{:x (obj/get point "x")
|
||||
:y (obj/get point "y")}))
|
||||
|
||||
;; {
|
||||
;; name?: string;
|
||||
;; nameLike?: string;
|
||||
|
@ -394,3 +400,164 @@
|
|||
[^js content]
|
||||
(when (some? content)
|
||||
(into [] (map parse-command) content)))
|
||||
|
||||
;; export interface PenpotDissolve {
|
||||
;; type: 'dissolve';
|
||||
;; duration: number;
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export interface PenpotSlide {
|
||||
;; type: 'slide';
|
||||
;; way: 'in' | 'out';
|
||||
;; direction?:
|
||||
;; | 'right'
|
||||
;; | 'left'
|
||||
;; | 'up'
|
||||
;; | 'down';
|
||||
;; duration: number;
|
||||
;; offsetEffect?: boolean;
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export interface PenpotPush {
|
||||
;; type: 'push';
|
||||
;; direction?:
|
||||
;; | 'right'
|
||||
;; | 'left'
|
||||
;; | 'up'
|
||||
;; | 'down';
|
||||
;;
|
||||
;; duration: number;
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export type PenpotAnimation = PenpotDissolve | PenpotSlide | PenpotPush;
|
||||
|
||||
(defn parse-animation
|
||||
[^js animation]
|
||||
(when animation
|
||||
(let [animation-type (-> (obj/get animation "type") parse-keyword)]
|
||||
(d/without-nils
|
||||
(case animation-type
|
||||
:dissolve
|
||||
{:type animation-type
|
||||
:duration (obj/get animation "duration")
|
||||
:easing (-> (obj/get animation "easing") parse-keyword)}
|
||||
|
||||
:slide
|
||||
{:type animation-type
|
||||
:way (-> (obj/get animation "way") parse-keyword)
|
||||
:direction (-> (obj/get animation "direction") parse-keyword)
|
||||
:duration (obj/get animation "duration")
|
||||
:easing (-> (obj/get animation "easing") parse-keyword)
|
||||
:offset-effect (obj/get animation "offsetEffect")}
|
||||
|
||||
:push
|
||||
{:type animation-type
|
||||
:direction (-> (obj/get animation "direction") parse-keyword)
|
||||
:duration (obj/get animation "duration")
|
||||
:easing (-> (obj/get animation "easing") parse-keyword)}
|
||||
|
||||
nil)))))
|
||||
|
||||
;;export type PenpotAction =
|
||||
;; | PenpotNavigateTo
|
||||
;; | PenpotOpenOverlay
|
||||
;; | PenpotToggleOverlay
|
||||
;; | PenpotCloseOverlay
|
||||
;; | PenpotPreviousScreen
|
||||
;; | PenpotOpenUrl;
|
||||
;;
|
||||
;;export interface PenpotNavigateTo {
|
||||
;; type: 'navigate-to';
|
||||
;; destination: PenpotFrame;
|
||||
;; preserveScrollPosition?: boolean;
|
||||
;; animation: PenpotAnimation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOverlayAction {
|
||||
;; destination: PenpotFrame;
|
||||
;; relativeTo?: PenpotShape;
|
||||
;; position?:
|
||||
;; | 'manual'
|
||||
;; | 'center'
|
||||
;; | 'top-left'
|
||||
;; | 'top-right'
|
||||
;; | 'top-center'
|
||||
;; | 'bottom-left'
|
||||
;; | 'bottom-right'
|
||||
;; | 'bottom-center';
|
||||
;; manualPositionLocation?: PenpotPoint;
|
||||
;; closeWhenClickOutside?: boolean;
|
||||
;; addBackgroundOverlay?: boolean;
|
||||
;; animation: PenpotAnimation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOpenOverlay extends PenpotOverlayAction {
|
||||
;; type: 'open-overlay';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotToggleOverlay extends PenpotOverlayAction {
|
||||
;; type: 'toggle-overlay';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotCloseOverlay {
|
||||
;; type: 'close-overlay';
|
||||
;; destination?: PenpotFrame;
|
||||
;; animation: PenpotAnimation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotPreviousScreen {
|
||||
;; type: 'previous-screen';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOpenUrl {
|
||||
;; type: 'open-url';
|
||||
;; url: string;
|
||||
;;}
|
||||
(defn parse-action
|
||||
[action]
|
||||
(when action
|
||||
(let [action-type (-> (obj/get action "type") parse-keyword)]
|
||||
(d/without-nils
|
||||
(case action-type
|
||||
:navigate-to
|
||||
{:action-type :navigate
|
||||
:destination (-> (obj/get action "destination") (obj/get "$id"))
|
||||
:preserve-scroll (obj/get action "preserveScrollPosition")
|
||||
:animation (-> (obj/get action "animation") parse-animation)}
|
||||
|
||||
(:open-overlay
|
||||
:toggle-overlay)
|
||||
{:action-type action-type
|
||||
:destination (-> (obj/get action "destination") (obj/get "$id"))
|
||||
:relative-to (-> (obj/get action "relativeTo") (obj/get "$id"))
|
||||
:overlay-pos-type (-> (obj/get action "position") parse-keyword)
|
||||
:overlay-position (-> (obj/get action "manualPositionLocation") parse-point)
|
||||
:close-click-outside (obj/get action "closeWhenClickOutside")
|
||||
:background-overlay (obj/get action "addBackgroundOverlay")
|
||||
:animation (-> (obj/get action "animation") parse-animation)}
|
||||
|
||||
:close-overlay
|
||||
{:action-type action-type
|
||||
:destination (-> (obj/get action "destination") (obj/get "$id"))
|
||||
:animation (-> (obj/get action "animation") parse-animation)}
|
||||
|
||||
:previous-screen
|
||||
{:action-type :prev-screen}
|
||||
|
||||
:open-url
|
||||
{:action-type action-type
|
||||
:url (obj/get action "url")}
|
||||
|
||||
nil)))))
|
||||
|
||||
(defn parse-interaction
|
||||
[^js interaction]
|
||||
(when interaction
|
||||
(let [trigger (-> (obj/get interaction "trigger") parse-keyword)
|
||||
delay (obj/get interaction "trigger")
|
||||
action (-> (obj/get interaction "action") parse-action)]
|
||||
(d/without-nils
|
||||
(d/patch-object {:event-type trigger :delay delay} action)))))
|
||||
|
|
|
@ -7,49 +7,119 @@
|
|||
(ns app.plugins.register
|
||||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.common.data :as d]))
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.object :as obj]
|
||||
[app.util.storage :refer [storage]]))
|
||||
|
||||
;; TODO: Remove clj->js and parse into a better data structure for accessing the permissions
|
||||
;; Stores the installed plugins information
|
||||
(defonce ^:private registry (atom {}))
|
||||
|
||||
(def pluginsdb (atom nil))
|
||||
(defn plugins-list
|
||||
"Retrieves the plugin data as an ordered list of plugin elements"
|
||||
[]
|
||||
(->> (:ids @registry)
|
||||
(mapv #(dm/get-in @registry [:data %]))))
|
||||
|
||||
(defn parse-manifest
|
||||
"Read the manifest.json defined by the plugins definition and transforms it into an
|
||||
object that will be stored in the register."
|
||||
[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 (into #{} (obj/get manifest "permissions" []))
|
||||
permissions
|
||||
(cond-> permissions
|
||||
(contains? permissions "content:write")
|
||||
(conj "content:read")
|
||||
|
||||
(contains? permissions "library:write")
|
||||
(conj "content:write"))
|
||||
|
||||
origin (obj/get (js/URL. plugin-url) "origin")
|
||||
|
||||
prev-plugin
|
||||
(->> (:data @registry)
|
||||
(vals)
|
||||
(d/seek (fn [plugin]
|
||||
(and (= name (:name plugin))
|
||||
(= origin (:host plugin))))))
|
||||
|
||||
plugin-id (d/nilv (:plugin-id prev-plugin) (str (uuid/next)))]
|
||||
{:plugin-id plugin-id
|
||||
:name name
|
||||
:description desc
|
||||
:host origin
|
||||
:code code
|
||||
:icon icon
|
||||
:permissions (into #{} (map str) permissions)}))
|
||||
|
||||
;; FIXME: LEGACY version of the load from store
|
||||
;; can be removed before deploying plugins to production
|
||||
;; Needs to be preserved for the beta users
|
||||
(defn legacy-load-from-store
|
||||
[]
|
||||
(let [parse-plugin-data
|
||||
(fn [^js data]
|
||||
{:plugin-id (obj/get data "plugin-id")
|
||||
:name (obj/get data "name")
|
||||
:description (obj/get data "description")
|
||||
:host (obj/get data "host")
|
||||
:code (obj/get data "code")
|
||||
:icon (obj/get data "icon")
|
||||
:permissions (into #{} (obj/get data "permissions"))})
|
||||
|
||||
ls (.-localStorage js/window)
|
||||
plugins-val (.getItem ls "plugins")]
|
||||
(when plugins-val
|
||||
(let [stored (->> (.parse js/JSON plugins-val)
|
||||
(map parse-plugin-data))]
|
||||
(reset! registry
|
||||
{:ids (->> stored (map :plugin-id))
|
||||
:data (d/index-by :plugin-id stored)})))))
|
||||
|
||||
(defn save-to-store
|
||||
[]
|
||||
(swap! storage assoc :plugins @registry))
|
||||
|
||||
(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)))
|
||||
(if (:plugins @storage)
|
||||
(reset! registry (:plugins @storage))
|
||||
(do (legacy-load-from-store)
|
||||
(save-to-store))))
|
||||
|
||||
(defn init
|
||||
[]
|
||||
(reset! pluginsdb (load-from-store)))
|
||||
(load-from-store))
|
||||
|
||||
(defn install-plugin!
|
||||
[plugin]
|
||||
(let [plugins (vec (conj (seq @pluginsdb) plugin))]
|
||||
(reset! pluginsdb plugins)
|
||||
(save-to-store plugins)))
|
||||
(letfn [(update-ids [ids]
|
||||
(conj
|
||||
(->> ids (remove #(= % (:plugin-id plugin))))
|
||||
(:plugin-id plugin)))]
|
||||
(swap! registry #(-> %
|
||||
(update :ids update-ids)
|
||||
(update :data assoc (:plugin-id plugin) plugin)))
|
||||
(save-to-store)))
|
||||
|
||||
(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)))
|
||||
(letfn [(update-ids [ids]
|
||||
(->> ids
|
||||
(remove #(= % plugin-id))))]
|
||||
(swap! registry #(-> %
|
||||
(update :ids update-ids)
|
||||
(update :data dissoc plugin-id)))
|
||||
(save-to-store)))
|
||||
|
||||
(defn check-permission
|
||||
[plugin-id permission]
|
||||
(or (= plugin-id "TEST")
|
||||
(let [{:keys [permissions]} (->> @pluginsdb (d/seek #(= (:plugin-id %) plugin-id)))]
|
||||
(->> permissions (d/seek #(= % permission))))))
|
||||
(let [{:keys [permissions]} (dm/get-in @registry [:data plugin-id])]
|
||||
(contains? permissions permission))))
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.blur :as ctsb]
|
||||
[app.common.types.shape.export :as ctse]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.shape.path :as ctsp]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
|
@ -32,6 +33,8 @@
|
|||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.groups :as dwg]
|
||||
[app.main.data.workspace.interactions :as dwi]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
|
@ -51,6 +54,81 @@
|
|||
[cuerdas.core :as str]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(declare shape-proxy)
|
||||
(declare shape-proxy?)
|
||||
|
||||
(deftype InteractionProxy [$plugin $file $page $shape $index]
|
||||
Object
|
||||
(remove [_]
|
||||
(st/emit! (dwi/remove-interaction {:id $shape} $index))))
|
||||
|
||||
(defn interaction-proxy? [p]
|
||||
(instance? InteractionProxy p))
|
||||
|
||||
(defn interaction-proxy
|
||||
[plugin-id file-id page-id shape-id index]
|
||||
(crc/add-properties!
|
||||
(InteractionProxy. plugin-id file-id page-id shape-id index)
|
||||
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
|
||||
{:name "$file" :enumerable false :get (constantly file-id)}
|
||||
{:name "$page" :enumerable false :get (constantly page-id)}
|
||||
{:name "$shape" :enumerable false :get (constantly shape-id)}
|
||||
{:name "$index" :enumerable false :get (constantly index)}
|
||||
|
||||
;; Not enumerable so we don't have an infinite loop
|
||||
{:name "shape" :enumerable false
|
||||
:get (fn [_] (shape-proxy plugin-id file-id page-id shape-id))}
|
||||
|
||||
{:name "trigger"
|
||||
:get #(-> % u/proxy->interaction :event-type format/format-key)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [value (parser/parse-keyword value)]
|
||||
(cond
|
||||
(not (contains? ctsi/event-types value))
|
||||
(u/display-not-valid :trigger value)
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/update-interaction
|
||||
{:id shape-id}
|
||||
index
|
||||
#(assoc % :event-type value)
|
||||
{:page-id page-id})))))}
|
||||
|
||||
{:name "delay"
|
||||
:get #(-> % u/proxy->interaction :delay)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(cond
|
||||
(or (not (number? value)) (not (pos? value)))
|
||||
(u/display-not-valid :delay value)
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/update-interaction
|
||||
{:id shape-id}
|
||||
index
|
||||
#(assoc % :delay value)
|
||||
{:page-id page-id}))))}
|
||||
|
||||
{:name "action"
|
||||
:get #(-> % u/proxy->interaction (format/format-action plugin-id file-id page-id))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [params (parser/parse-action value)
|
||||
interaction
|
||||
(-> (u/proxy->interaction self)
|
||||
(d/patch-object params))]
|
||||
(cond
|
||||
(not (sm/validate ::ctsi/interaction interaction))
|
||||
(u/display-not-valid :action interaction)
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/update-interaction
|
||||
{:id shape-id}
|
||||
index
|
||||
#(d/patch-object % params)
|
||||
{:page-id page-id})))))}))
|
||||
|
||||
(def lib-typography-proxy? nil)
|
||||
(def lib-component-proxy nil)
|
||||
|
||||
|
@ -61,8 +139,6 @@
|
|||
(dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs})
|
||||
(dwt/current-text-values {:shape shape :attrs txt/text-node-attrs})))
|
||||
|
||||
(declare shape-proxy)
|
||||
(declare shape-proxy?)
|
||||
|
||||
(defn- shadow-defaults
|
||||
[shadow]
|
||||
|
@ -441,6 +517,11 @@
|
|||
(let [[root component] (u/locate-component objects shape)]
|
||||
(lib-component-proxy $plugin (:component-file root) (:id component))))))
|
||||
|
||||
(detach
|
||||
[_]
|
||||
(st/emit! (dwl/detach-component $id)))
|
||||
|
||||
;; Export
|
||||
(export
|
||||
[self value]
|
||||
(let [value (parser/parse-export value)]
|
||||
|
@ -466,7 +547,31 @@
|
|||
(rx/mapcat #(rp/cmd! :export {:cmd :get-resource :wait true :id (:id %) :blob? true}))
|
||||
(rx/mapcat #(.arrayBuffer %))
|
||||
(rx/map #(js/Uint8Array. %))
|
||||
(rx/subs! resolve reject)))))))))
|
||||
(rx/subs! resolve reject))))))))
|
||||
|
||||
;; Interactions
|
||||
(addInteraction
|
||||
[self interaction]
|
||||
(let [interaction
|
||||
(-> ctsi/default-interaction
|
||||
(d/patch-object (parser/parse-interaction interaction)))]
|
||||
(cond
|
||||
(not (sm/validate ::ctsi/interaction interaction))
|
||||
(u/display-not-valid :addInteraction interaction)
|
||||
|
||||
:else
|
||||
(let [index (-> (u/proxy->shape self) (:interactions []) count)]
|
||||
(st/emit! (dwi/add-interaction $page $id interaction))
|
||||
(interaction-proxy $plugin $file $page $id index)))))
|
||||
|
||||
(removeInteraction
|
||||
[_ interaction]
|
||||
(cond
|
||||
(not (interaction-proxy? interaction))
|
||||
(u/display-not-valid :removeInteraction interaction)
|
||||
|
||||
:else
|
||||
(st/emit! (dwi/remove-interaction {:id $id} (obj/get interaction "$index"))))))
|
||||
|
||||
(defn shape-proxy? [p]
|
||||
(instance? ShapeProxy p))
|
||||
|
@ -475,6 +580,8 @@
|
|||
(do (set! flex/shape-proxy? shape-proxy?)
|
||||
(set! grid/shape-proxy? shape-proxy?))
|
||||
|
||||
(set! format/shape-proxy shape-proxy)
|
||||
|
||||
(crc/define-properties!
|
||||
ShapeProxy
|
||||
{:name js/Symbol.toStringTag
|
||||
|
@ -819,6 +926,13 @@
|
|||
:else
|
||||
(st/emit! (dw/update-position id {:y value})))))}
|
||||
|
||||
{:name "parent"
|
||||
;; not enumerable so there are no infinite loops
|
||||
:enumerable false
|
||||
:get (fn [self]
|
||||
(let [shape (u/proxy->shape self)
|
||||
parent-id (:parent-id shape)]
|
||||
(shape-proxy (obj/get self "$file") (obj/get self "$page") parent-id)))}
|
||||
{:name "parentX"
|
||||
:get (fn [self]
|
||||
(let [shape (u/proxy->shape self)
|
||||
|
@ -1024,7 +1138,17 @@
|
|||
id (obj/get self "$id")
|
||||
objects (u/locate-objects file-id page-id)]
|
||||
(when (ctl/grid-layout-immediate-child-id? objects id)
|
||||
(grid/layout-cell-proxy plugin-id file-id page-id id))))})
|
||||
(grid/layout-cell-proxy plugin-id file-id page-id id))))}
|
||||
|
||||
|
||||
;; Interactions
|
||||
{:name "interactions"
|
||||
:get
|
||||
(fn [self]
|
||||
(let [interactions (-> self u/proxy->shape :interactions)]
|
||||
(format/format-array
|
||||
#(interaction-proxy plugin-id file-id page-id id %)
|
||||
(range 0 (count interactions)))))})
|
||||
|
||||
(cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))
|
||||
(crc/add-properties!
|
||||
|
|
|
@ -113,6 +113,25 @@
|
|||
(when (and (some? file-id) (some? id))
|
||||
(locate-library-component file-id id))))
|
||||
|
||||
(defn proxy->flow
|
||||
[proxy]
|
||||
(let [file-id (obj/get proxy "$file")
|
||||
page-id (obj/get proxy "$page")
|
||||
flow-id (obj/get proxy "$id")
|
||||
page (locate-page file-id page-id)]
|
||||
(when (some? page)
|
||||
(d/seek #(= (:id %) flow-id) (-> page :options :flows)))))
|
||||
|
||||
(defn proxy->interaction
|
||||
[proxy]
|
||||
(let [file-id (obj/get proxy "$file")
|
||||
page-id (obj/get proxy "$page")
|
||||
shape-id (obj/get proxy "$shape")
|
||||
index (obj/get proxy "$index")
|
||||
shape (locate-shape file-id page-id shape-id)]
|
||||
(when (some? shape)
|
||||
(get-in shape [:interactions index]))))
|
||||
|
||||
(defn get-data
|
||||
([self attr]
|
||||
(-> (obj/get self "_data")
|
||||
|
|
|
@ -156,10 +156,11 @@
|
|||
|
||||
(mf/defc tr-html*
|
||||
{::mf/props :obj}
|
||||
[{:keys [content class tag-name]}]
|
||||
[{:keys [content class tag-name on-click]}]
|
||||
(let [tag-name (d/nilv tag-name "p")]
|
||||
[:> tag-name {:dangerouslySetInnerHTML #js {:__html content}
|
||||
:className class}]))
|
||||
:className class
|
||||
:on-click on-click}]))
|
||||
|
||||
;; DEPRECATED
|
||||
(defn use-locale
|
||||
|
|
|
@ -134,3 +134,4 @@
|
|||
(catch :default err
|
||||
(.error js/console err)
|
||||
nil)))
|
||||
|
||||
|
|
|
@ -5286,7 +5286,7 @@ msgid "workspace.plugins.menu.title"
|
|||
msgstr "Plugins"
|
||||
|
||||
msgid "workspace.toolbar.plugins"
|
||||
msgstr "Plugins"
|
||||
msgstr "Plugins (%s)"
|
||||
|
||||
msgid "workspace.plugins.menu.plugins-manager"
|
||||
msgstr "Plugins manager"
|
||||
|
@ -5314,3 +5314,8 @@ msgstr "Read your libraries and assets."
|
|||
|
||||
msgid "workspace.plugins.permissions.library-write"
|
||||
msgstr "Read and modify your libraries and assets."
|
||||
|
||||
#, markdown
|
||||
msgid "workspace.plugins.discover"
|
||||
msgstr ""
|
||||
"Discover [more plugins](%s)"
|
||||
|
|
|
@ -5366,7 +5366,7 @@ msgid "workspace.plugins.title"
|
|||
msgstr "Extensiones"
|
||||
|
||||
msgid "workspace.toolbar.plugins"
|
||||
msgstr "Extensiones"
|
||||
msgstr "Extensiones (%s)"
|
||||
|
||||
msgid "workspace.plugins.search-placeholder"
|
||||
msgstr "Intruduzca URL de la extensión"
|
||||
|
@ -5418,3 +5418,8 @@ 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."
|
||||
|
||||
#, markdown
|
||||
msgid "workspace.plugins.discover"
|
||||
msgstr ""
|
||||
"Descubre [más extensiones](%s)"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue