diff --git a/common/src/app/common/record.cljc b/common/src/app/common/record.cljc index f5ac3a39d..1db90dee2 100644 --- a/common/src/app/common/record.cljc +++ b/common/src/app/common/record.cljc @@ -430,23 +430,31 @@ (defmacro define-properties! [rsym & properties] - (let [rsym (with-meta rsym {:tag 'js}) - self-sym (gensym "self-") - get-fn-sym (gensym "get-fn-") - set-fn-sym (gensym "set-fn-") - params-sym (gensym "params-") - args-sym (gensym "args-")] + (let [rsym (with-meta rsym {:tag 'js})] `(do ~@(for [params properties :let [pname (get params :name) get-fn (get params :get) - set-fn (get params :set)]] + set-fn (get params :set) + enum-p (get params :enumerable) + conf-p (get params :configurable) + writ-p (get params :writable)]] `(.defineProperty js/Object (.-prototype ~rsym) ~pname (cljs.core/js-obj - "enumerable" true - "configurable" true ~@(concat + (if (some? enum-p) + ["enumerable" enum-p] + ["enumerable" true]) + + (if (some? conf-p) + ["configurable" conf-p] + ["configurable" true]) + + (when (some? writ-p) + ["writable" writ-p]) + (when get-fn ["get" get-fn]) + (when set-fn ["set" set-fn])))))))) diff --git a/frontend/resources/public/js/plugins-runtime.mjs b/frontend/resources/public/js/plugins-runtime.mjs deleted file mode 100644 index 2651e233b..000000000 --- a/frontend/resources/public/js/plugins-runtime.mjs +++ /dev/null @@ -1,16 +0,0 @@ -export class PluginsElement extends HTMLElement { - connectedCallback() { - console.log('PluginsElement.connectedCallback'); - } -} - -customElements.define('penpot-plugins', PluginsElement); - -// Alternative to message passing -export function initialize(api) { - console.log("PluginsRuntime:initialize", api) - - api.addListener("foobar", "page", (page) => { - console.log("Page Changed:", page.name); - }); -}; diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache index 138f244f5..9a9fe6e08 100644 --- a/frontend/resources/templates/index.mustache +++ b/frontend/resources/templates/index.mustache @@ -23,6 +23,7 @@ {{/isDebug}} + @@ -58,10 +59,7 @@ - diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index de3e2dc99..7a5b4c4ff 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -18,7 +18,7 @@ {:entries []} :main - {:entries [app.main app.plugins] + {:entries [app.main app.plugins.api] :depends-on #{:shared} :init-fn app.main/init} diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 381556fc0..ccdb0b617 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -25,7 +25,7 @@ [app.main.ui.modal :refer [modal]] [app.main.ui.routes :as rt] [app.main.worker :as worker] - [app.plugins] + [app.plugins.api] [app.util.dom :as dom] [app.util.i18n :as i18n] [app.util.theme :as theme] diff --git a/frontend/src/app/plugins.cljs b/frontend/src/app/plugins.cljs deleted file mode 100644 index dfcbb7191..000000000 --- a/frontend/src/app/plugins.cljs +++ /dev/null @@ -1,155 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.plugins - "RPC for plugins runtime." - (:require - [app.common.data.macros :as dm] - [app.common.exceptions :as ex] - [app.common.record :as crc] - [app.main.refs :as refs] - [app.main.store :as st] - [goog.functions :as gf] - [app.util.array :as array] - [app.util.rxops :as rxops] - [app.util.timers :as tm])) - -;; ---- TYPES - -(deftype ShapeProxy [id name type _data]) - -(defn data->shape-proxy - [data] - (->ShapeProxy (str (:id data)) - (:name data) - (name (:type data)) - data)) - -(def ^:private - xf-map-shape-proxy - (comp - (map val) - (map data->shape-proxy))) - -(deftype PageProxy [id name _data] - Object - (getShapes [_] - ;; Returns a lazy (iterable) of all available shapes - (sequence xf-map-shape-proxy (:objects _data)))) - -(defn- data->page-proxy - [data] - (->PageProxy (str (:id data)) - (:name data) - data)) - -(def ^:private - xf-map-page-proxy - (comp - (map val) - (map data->page-proxy))) - -(deftype FileProxy [id name revn _data] - Object - (getPages [_] - ;; Returns a lazy (iterable) of all available pages - (sequence xf-map-page-proxy (:pages-index _data)))) - -;; ---- PROPERTIES - -(crc/define-properties! - FileProxy - {:name js/Symbol.toStringTag - :get (fn [] (str "FileProxy"))}) - -(crc/define-properties! - PageProxy - {:name js/Symbol.toStringTag - :get (fn [] (str "PageProxy"))}) - -(crc/define-properties! - ShapeProxy - {:name js/Symbol.toStringTag - :get (fn [] (str "ShapeProxy"))}) - -;; ---- PUBLIC API - -(defn ^:export getCurrentFile - [] - (let [data (:workspace-data @st/state)] - (when (some? data) - (let [file (:workspace-file @st/state)] - (->FileProxy (str (:id file)) - (:name file) - (:revn file) - data))))) - -(defn ^:export getCurrentPage - [] - (when-let [page-id (:current-page-id @st/state)] - (when-let [data (get-in @st/state [:workspace-data :pages-index page-id])] - (data->page-proxy data)))) - -(defn ^:export getCurrentSelection - [] - (let [selection (get-in @st/state [:workspace-local :selected])] - (when (some? selection) - selection))) - -(defn ^:export getCurrentTheme - [] - (get-in @st/state [:profile :theme])) - -(defn ^:export getState - [] - @st/state) - -;; (defonce listeners -;; (atom {})) - -(defn ^:export addListener - [key type f] - (let [f (gf/debounce f 500)] - (case type - "file" - (add-watch st/state key - (fn [_ _ 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)] - (when-not (and (identical? old-file new-file) - (identical? old-data new-data)) - (f (->FileProxy (str (:id new-file)) - (:name new-file) - (:revn new-file) - new-data)))))) - "page" - (add-watch st/state key - (fn [_ _ old-val new-val] - (let [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])] - (when-not (identical? old-page new-page) - (f (data->page-proxy new-page)))))) - "selection" - (add-watch st/state key - (fn [_ _ old-val new-val] - (let [old-selection (get-in old-val [:workspace-local :selected]) - new-selection (get-in new-val [:workspace-local :selected])] - (when-not (identical? old-selection new-selection) - (f (clj->js new-selection)))))) - - "theme" - (add-watch st/state key - (fn [_ _ old-val new-val] - (let [old-theme (get-in old-val [:profile :theme]) - new-theme (get-in new-val [:profile :theme])] - (when-not (identical? old-theme new-theme) - (f new-theme))))) - ))) - diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs new file mode 100644 index 000000000..b2f2b8dff --- /dev/null +++ b/frontend/src/app/plugins/api.cljs @@ -0,0 +1,57 @@ +;; 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.api + "RPC for plugins runtime." + (:require + [app.common.data.macros :as dm] + [app.main.store :as st] + [app.plugins.events :as events] + [app.plugins.file :as file] + [app.plugins.page :as page] + [app.plugins.shape :as shape])) + +;; +;; PLUGINS PUBLIC API - The plugins will able to access this functions +;; +(def ^:private + xf-map-shape-proxy + (comp + (map val) + (map shape/data->shape-proxy))) + +(defn ^:export addListener + [type callback] + (events/add-listener type callback)) + +(defn ^:export getFile + [] + (file/data->file-proxy (:workspace-file @st/state) (:workspace-data @st/state))) + +(defn ^:export getPage + [] + (let [page-id (:current-page-id @st/state)] + (page/data->page-proxy (dm/get-in @st/state [:workspace-data :pages-index page-id])))) + +(defn ^:export getSelected + [] + (let [selection (get-in @st/state [:workspace-local :selected])] + (apply array (map str selection)))) + +(defn ^:export getSelectedShapes + [] + (let [page-id (:current-page-id @st/state) + selection (get-in @st/state [:workspace-local :selected]) + objects (dm/get-in @st/state [:workspace-data :pages-index page-id :objects]) + shapes (select-keys objects selection)] + (apply array (sequence xf-map-shape-proxy shapes)))) + +(defn ^:export getTheme + [] + (let [theme (get-in @st/state [:profile :theme])] + (if (or (not theme) (= theme "default")) + "dark" + (get-in @st/state [:profile :theme])))) diff --git a/frontend/src/app/plugins/events.cljs b/frontend/src/app/plugins/events.cljs new file mode 100644 index 000000000..babbe590c --- /dev/null +++ b/frontend/src/app/plugins/events.cljs @@ -0,0 +1,72 @@ +;; 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.events + (:require + [app.common.data.macros :as dm] + [app.main.store :as st] + [app.plugins.file :as file] + [app.plugins.page :as page] + [goog.functions :as gf])) + +(defmulti handle-state-change (fn [type _] type)) + +(defmethod handle-state-change "filechange" + [_ 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)) + ::not-changed + (file/data->file-proxy new-file new-data)))) + +(defmethod handle-state-change "pagechange" + [_ old-val new-val] + (let [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) + ::not-changed + (page/data->page-proxy new-page)))) + +(defmethod handle-state-change "selectionchange" + [_ 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) + ::not-changed + (apply array (map str new-selection))))) + +(defmethod handle-state-change "themechange" + [_ 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) + ::not-changed + new-theme))) + +(defmethod handle-state-change :default + [_ _ _] + ::not-changed) + + +(defn add-listener + [type callback] + (let [key (js/Symbol) + callback (gf/debounce callback 10)] + (add-watch + st/state key + (fn [_ _ old-val new-val] + (let [result (handle-state-change type old-val new-val)] + (when (not= ::not-changed result) + (callback result))))) + + ;; return the generated key + key)) + diff --git a/frontend/src/app/plugins/file.cljs b/frontend/src/app/plugins/file.cljs new file mode 100644 index 000000000..cd3ac84ae --- /dev/null +++ b/frontend/src/app/plugins/file.cljs @@ -0,0 +1,42 @@ +;; 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.file + "RPC for plugins runtime." + (:require + [app.common.record :as crc] + [app.plugins.page :as page] + [app.plugins.utils :as utils])) + +(def ^:private + xf-map-page-proxy + (comp + (map val) + (map page/data->page-proxy))) + +(deftype FileProxy [id name revn + #_:clj-kondo/ignore _data] + Object + (getPages [_] + ;; Returns a lazy (iterable) of all available pages + (apply array (sequence xf-map-page-proxy (:pages-index _data))))) + +(crc/define-properties! + FileProxy + {:name js/Symbol.toStringTag + :get (fn [] (str "FileProxy"))} + {:name "pages" + :get (fn [] (this-as this (.getPages ^js this)))}) + +(defn data->file-proxy + [file data] + (utils/hide-data! + (->FileProxy (str (:id file)) + (:name file) + (:revn file) + data))) + + diff --git a/frontend/src/app/plugins/page.cljs b/frontend/src/app/plugins/page.cljs new file mode 100644 index 000000000..1310a4979 --- /dev/null +++ b/frontend/src/app/plugins/page.cljs @@ -0,0 +1,38 @@ +;; 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.page + "RPC for plugins runtime." + (:require + [app.common.record :as crc] + [app.plugins.shape :as shape] + [app.plugins.utils :as utils])) + +(def ^:private + xf-map-shape-proxy + (comp + (map val) + (map shape/data->shape-proxy))) + +(deftype PageProxy [id name + #_:clj-kondo/ignore _data] + Object + (findShapes [_] + ;; Returns a lazy (iterable) of all available shapes + (apply array (sequence xf-map-shape-proxy (:objects _data))))) + +(crc/define-properties! + PageProxy + {:name js/Symbol.toStringTag + :get (fn [] (str "PageProxy"))}) + +(defn data->page-proxy + [data] + (utils/hide-data! + (->PageProxy + (str (:id data)) + (:name data) + data))) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs new file mode 100644 index 000000000..e56373a40 --- /dev/null +++ b/frontend/src/app/plugins/shape.cljs @@ -0,0 +1,43 @@ +;; 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.shape + "RPC for plugins runtime." + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.record :as crc] + [app.plugins.utils :as utils] + [cuerdas.core :as str])) + +(defn- fills + [shape] + ;; TODO: Transform explicitly? + (apply array + (->> (:fills shape) + (map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))}))))) + +(deftype ShapeProxy + [id + name + type + fills + _data]) + +(crc/define-properties! + ShapeProxy + {:name js/Symbol.toStringTag + :get (fn [] (str "ShapeProxy"))}) + +(defn data->shape-proxy + [data] + (utils/hide-data! + (->ShapeProxy (dm/str (:id data)) + (:name data) + (d/name (:type data)) + (fills data) + data))) + diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs new file mode 100644 index 000000000..0c12b8192 --- /dev/null +++ b/frontend/src/app/plugins/utils.cljs @@ -0,0 +1,12 @@ +;; 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.utils + "RPC for plugins runtime.") + +(defn hide-data! + [proxy] + (.defineProperty js/Object proxy "_data" #js {:enumerable false}))