Merge pull request #5090 from penpot/alotor-new-apis

Plugins - API's modifications
This commit is contained in:
Andrey Antukh 2024-09-16 18:29:51 +02:00 committed by GitHub
commit b50fcee079
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 2280 additions and 1603 deletions

View file

@ -10,7 +10,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.plugins :refer [schema:plugin-data]] [app.common.types.plugins :refer [schema:plugin-registry]]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
@ -43,7 +43,7 @@
(def schema:props (def schema:props
[:map {:title "ProfileProps"} [:map {:title "ProfileProps"}
[:plugins {:optional true} schema:plugin-data] [:plugins {:optional true} schema:plugin-registry]
[:newsletter-updates {:optional true} ::sm/boolean] [:newsletter-updates {:optional true} ::sm/boolean]
[:newsletter-news {:optional true} ::sm/boolean] [:newsletter-news {:optional true} ::sm/boolean]
[:onboarding-team-id {:optional true} ::sm/uuid] [:onboarding-team-id {:optional true} ::sm/uuid]

View file

@ -29,3 +29,25 @@
schema:string]]) schema:string]])
(sm/register! ::plugin-data schema:plugin-data) (sm/register! ::plugin-data schema:plugin-data)
(def ^:private schema:registry-entry
[:map
[:plugin-id :string]
[:name :string]
[:description {:optional true} :string]
[:host :string]
[:code :string]
[:icon {:optional true} :string]
[:permissions [:set :string]]])
(def schema:plugin-registry
[:map
[:ids [:vector :string]]
[:data
[:map-of {:gen/max 5}
:string
schema:registry-entry]]])
(sm/register! ::plugin-registry schema:plugin-registry)
(sm/register! ::registry-entry schema:registry-entry)

File diff suppressed because it is too large Load diff

View file

@ -56,27 +56,30 @@
(declare refresh-comment-thread) (declare refresh-comment-thread)
(defn created-thread-on-workspace (defn created-thread-on-workspace
[{:keys [id comment page-id] :as thread}] ([params]
(ptk/reify ::created-thread-on-workspace (created-thread-on-workspace params true))
ptk/UpdateEvent ([{:keys [id comment page-id] :as thread} open?]
(update [_ state] (ptk/reify ::created-thread-on-workspace
(let [position (select-keys thread [:position :frame-id])] ptk/UpdateEvent
(-> state (update [_ state]
(update :comment-threads assoc id (dissoc thread :comment)) (let [position (select-keys thread [:position :frame-id])]
(update-in [:workspace-data :pages-index page-id :options :comment-threads-position] assoc id position) (-> state
(update :comments-local assoc :open id) (update :comment-threads assoc id (dissoc thread :comment))
(update :comments-local assoc :options nil) (update-in [:workspace-data :pages-index page-id :options :comment-threads-position] assoc id position)
(update :comments-local dissoc :draft) (cond-> open?
(update :workspace-drawing dissoc :comment) (update :comments-local assoc :open id))
(update-in [:comments id] assoc (:id comment) comment)))) (update :comments-local assoc :options nil)
(update :comments-local dissoc :draft)
(update :workspace-drawing dissoc :comment)
(update-in [:comments id] assoc (:id comment) comment))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (ptk/data-event ::ev/event (rx/of (ptk/data-event ::ev/event
{::ev/name "create-comment-thread" {::ev/name "create-comment-thread"
::ev/origin "workspace" ::ev/origin "workspace"
:id id :id id
:content-size (count (:content comment))}))))) :content-size (count (:content comment))}))))))
@ -89,24 +92,27 @@
[:content :string]]) [:content :string]])
(defn create-thread-on-workspace (defn create-thread-on-workspace
[params] ([params]
(dm/assert! (sm/check! schema:create-thread-on-workspace params)) (create-thread-on-workspace params identity true))
([params on-thread-created open?]
(dm/assert! (sm/check! schema:create-thread-on-workspace params))
(ptk/reify ::create-thread-on-workspace (ptk/reify ::create-thread-on-workspace
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
frame-id (ctst/get-frame-id-by-position objects (:position params)) frame-id (ctst/get-frame-id-by-position objects (:position params))
params (assoc params :frame-id frame-id)] params (assoc params :frame-id frame-id)]
(->> (rp/cmd! :create-comment-thread params) (->> (rp/cmd! :create-comment-thread params)
(rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %)})) (rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %)}))
(rx/map created-thread-on-workspace) (rx/tap on-thread-created)
(rx/catch (fn [{:keys [type code] :as cause}] (rx/map #(created-thread-on-workspace % open?))
(if (and (= type :restriction) (rx/catch (fn [{:keys [type code] :as cause}]
(= code :max-quote-reached)) (if (and (= type :restriction)
(rx/throw cause) (= code :max-quote-reached))
(rx/throw {:type :comment-error}))))))))) (rx/throw cause)
(rx/throw {:type :comment-error}))))))))))
(defn created-thread-on-viewer (defn created-thread-on-viewer
[{:keys [id comment page-id] :as thread}] [{:keys [id comment page-id] :as thread}]
@ -257,29 +263,31 @@
(rx/map #(retrieve-comment-threads file-id))))))) (rx/map #(retrieve-comment-threads file-id)))))))
(defn delete-comment-thread-on-workspace (defn delete-comment-thread-on-workspace
[{:keys [id] :as thread}] ([params]
(dm/assert! (delete-comment-thread-on-workspace params identity))
"expected valid comment thread" ([{:keys [id] :as thread} on-delete]
(check-comment-thread! thread)) (dm/assert! (uuid? id))
(ptk/reify ::delete-comment-thread-on-workspace
ptk/UpdateEvent
(update [_ state]
(let [page-id (:current-page-id state)]
(-> state
(update-in [:workspace-data :pages-index page-id :options :comment-threads-position] dissoc id)
(update :comments dissoc id)
(update :comment-threads dissoc id))))
ptk/WatchEvent (ptk/reify ::delete-comment-thread-on-workspace
(watch [_ _ _] ptk/UpdateEvent
(rx/concat (update [_ state]
(->> (rp/cmd! :delete-comment-thread {:id id}) (let [page-id (:current-page-id state)]
(rx/catch #(rx/throw {:type :comment-error})) (-> state
(rx/ignore)) (update-in [:workspace-data :pages-index page-id :options :comment-threads-position] dissoc id)
(rx/of (ptk/data-event ::ev/event (update :comments dissoc id)
{::ev/name "delete-comment-thread" (update :comment-threads dissoc id))))
::ev/origin "workspace"
:id id})))))) ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(->> (rp/cmd! :delete-comment-thread {:id id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/tap on-delete)
(rx/ignore))
(rx/of (ptk/data-event ::ev/event
{::ev/name "delete-comment-thread"
::ev/origin "workspace"
:id id})))))))
(defn delete-comment-thread-on-viewer (defn delete-comment-thread-on-viewer
[{:keys [id] :as thread}] [{:keys [id] :as thread}]

View file

@ -970,25 +970,27 @@
(map #(gal/align-to-rect % rect axis) selected-objs))) (map #(gal/align-to-rect % rect axis) selected-objs)))
(defn align-objects (defn align-objects
[axis] ([axis]
(dm/assert! (align-objects axis nil))
"expected valid align axis value" ([axis selected]
(contains? gal/valid-align-axis axis)) (dm/assert!
"expected valid align axis value"
(contains? gal/valid-align-axis axis))
(ptk/reify ::align-objects (ptk/reify ::align-objects
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [objects (wsh/lookup-page-objects state) (let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state) selected (or selected (wsh/lookup-selected state))
moved (if (= 1 (count selected)) moved (if (= 1 (count selected))
(align-object-to-parent objects (first selected) axis) (align-object-to-parent objects (first selected) axis)
(align-objects-list objects selected axis)) (align-objects-list objects selected axis))
undo-id (js/Symbol)] undo-id (js/Symbol)]
(when (can-align? selected objects) (when (can-align? selected objects)
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dwt/position-shapes moved) (dwt/position-shapes moved)
(ptk/data-event :layout/update {:ids selected}) (ptk/data-event :layout/update {:ids selected})
(dwu/commit-undo-transaction undo-id))))))) (dwu/commit-undo-transaction undo-id))))))))
(defn can-distribute? [selected] (defn can-distribute? [selected]
(cond (cond
@ -997,25 +999,27 @@
:else true)) :else true))
(defn distribute-objects (defn distribute-objects
[axis] ([axis]
(dm/assert! (distribute-objects axis nil))
"expected valid distribute axis value" ([axis ids]
(contains? gal/valid-dist-axis axis)) (dm/assert!
"expected valid distribute axis value"
(contains? gal/valid-dist-axis axis))
(ptk/reify ::distribute-objects (ptk/reify ::distribute-objects
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state) selected (or ids (wsh/lookup-selected state))
moved (-> (map #(get objects %) selected) moved (-> (map #(get objects %) selected)
(gal/distribute-space axis)) (gal/distribute-space axis))
undo-id (js/Symbol)] undo-id (js/Symbol)]
(when (can-distribute? selected) (when (can-distribute? selected)
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dwt/position-shapes moved) (dwt/position-shapes moved)
(ptk/data-event :layout/update {:ids selected}) (ptk/data-event :layout/update {:ids selected})
(dwu/commit-undo-transaction undo-id))))))) (dwu/commit-undo-transaction undo-id))))))))
;; --- Shape Proportions ;; --- Shape Proportions

View file

@ -15,24 +15,27 @@
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
(defn convert-selected-to-path [] (defn convert-selected-to-path
(ptk/reify ::convert-selected-to-path ([]
ptk/WatchEvent (convert-selected-to-path nil))
(watch [it state _] ([ids]
(let [page-id (:current-page-id state) (ptk/reify ::convert-selected-to-path
objects (wsh/lookup-page-objects state) ptk/WatchEvent
selected (->> (wsh/lookup-selected state) (watch [it state _]
(remove #(ctn/has-any-copy-parent? objects (get objects %)))) (let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state)
selected (->> (or ids (wsh/lookup-selected state))
(remove #(ctn/has-any-copy-parent? objects (get objects %))))
children-ids children-ids
(into #{} (into #{}
(mapcat #(cph/get-children-ids objects %)) (mapcat #(cph/get-children-ids objects %))
selected) selected)
changes changes
(-> (pcb/empty-changes it page-id) (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects) (pcb/with-objects objects)
(pcb/update-shapes selected #(upsp/convert-to-path % objects)) (pcb/update-shapes selected #(upsp/convert-to-path % objects))
(pcb/remove-objects children-ids))] (pcb/remove-objects children-ids))]
(rx/of (dch/commit-changes changes)))))) (rx/of (dch/commit-changes changes)))))))

View file

@ -91,7 +91,9 @@
input-status* (mf/use-state nil) ;; :error-url :error-manifest :success input-status* (mf/use-state nil) ;; :error-url :error-manifest :success
input-status @input-status* input-status @input-status*
error? (contains? #{:error-url :error-manifest} input-status) error-url? (= :error-url input-status)
error-manifest? (= :error-manifest input-status)
error? (or error-url? error-manifest?)
handle-close-dialog handle-close-dialog
(mf/use-callback (mf/use-callback
@ -117,17 +119,20 @@
(rx/subs! (rx/subs!
(fn [body] (fn [body]
(reset! fetching-manifest? false) (reset! fetching-manifest? false)
(let [plugin (preg/parse-manifest plugin-url body)] (if-let [plugin (preg/parse-manifest plugin-url body)]
(st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url})) (do
(modal/show! (st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
:plugin-permissions (modal/show!
{:plugin plugin :plugin-permissions
:on-accept {:plugin plugin
#(do :on-accept
(preg/install-plugin! plugin) #(do
(modal/show! :plugin-management {}))}) (preg/install-plugin! plugin)
(reset! input-status* :success) (modal/show! :plugin-management {}))})
(reset! plugin-url* ""))) (reset! input-status* :success)
(reset! plugin-url* ""))
;; Cannot get the manifest
(reset! input-status* :error-manifest)))
(fn [_] (fn [_]
(reset! fetching-manifest? false) (reset! fetching-manifest? false)
(reset! input-status* :error-url)))))) (reset! input-status* :error-url))))))
@ -170,10 +175,14 @@
:disabled @fetching-manifest? :disabled @fetching-manifest?
:on-click handle-install-click} (tr "workspace.plugins.install")]] :on-click handle-install-click} (tr "workspace.plugins.install")]]
(when error? (when error-url?
[:div {:class (stl/css-case :info true :error error?)} [:div {:class (stl/css-case :info true :error error?)}
(tr "workspace.plugins.error.url")]) (tr "workspace.plugins.error.url")])
(when error-manifest?
[:div {:class (stl/css-case :info true :error error?)}
(tr "workspace.plugins.error.manifest")])
[:> i18n/tr-html* [:> i18n/tr-html*
{:class (stl/css :discover) {:class (stl/css :discover)
:on-click #(st/emit! (ptk/event ::ev/event {::ev/name "open-plugins-list"})) :on-click #(st/emit! (ptk/event ::ev/event {::ev/name "open-plugins-list"}))

View file

@ -15,6 +15,7 @@
[app.plugins.grid :as grid] [app.plugins.grid :as grid]
[app.plugins.library :as library] [app.plugins.library :as library]
[app.plugins.public-utils] [app.plugins.public-utils]
[app.plugins.ruler-guides :as rg]
[app.plugins.shape :as shape] [app.plugins.shape :as shape]
[app.util.globals :refer [global]] [app.util.globals :refer [global]]
[app.util.object :as obj] [app.util.object :as obj]
@ -43,6 +44,8 @@
(set! flex/shape-proxy? shape/shape-proxy?) (set! flex/shape-proxy? shape/shape-proxy?)
(set! grid/shape-proxy? shape/shape-proxy?) (set! grid/shape-proxy? shape/shape-proxy?)
(set! format/shape-proxy shape/shape-proxy) (set! format/shape-proxy shape/shape-proxy)
(set! rg/shape-proxy shape/shape-proxy)
(set! rg/shape-proxy? shape/shape-proxy?)
(set! shape/lib-typography-proxy? library/lib-typography-proxy?) (set! shape/lib-typography-proxy? library/lib-typography-proxy?)
(set! shape/lib-component-proxy library/lib-component-proxy) (set! shape/lib-component-proxy library/lib-component-proxy)

View file

@ -368,7 +368,73 @@
(openPage (openPage
[_ page] [_ page]
(let [id (obj/get page "$id")] (let [id (obj/get page "$id")]
(st/emit! (dw/go-to-page id))))) (st/emit! (dw/go-to-page id))))
(alignHorizontal
[_ shapes direction]
(let [dir (case direction
"left" :hleft
"center" :hcenter
"right" :hright
nil)]
(cond
(nil? dir)
(u/display-not-valid :alignHorizontal-direction "Direction not valid")
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :alignHorizontal-shapes "Not valid shapes")
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dw/align-objects dir ids))))))
(alignVertical
[_ shapes direction]
(let [dir (case direction
"top" :vtop
"center" :vcenter
"bottom" :vbottom
nil)]
(cond
(nil? dir)
(u/display-not-valid :alignVertical-direction "Direction not valid")
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :alignVertical-shapes "Not valid shapes")
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dw/align-objects dir ids))))))
(distributeHorizontal
[_ shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :distributeHorizontal-shapes "Not valid shapes")
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dw/distribute-objects :horizontal ids)))))
(distributeVertical
[_ shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :distributeVertical-shapes "Not valid shapes")
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dw/distribute-objects :vertical ids)))))
(flatten
[_ shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :flatten-shapes "Not valid shapes")
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dw/convert-selected-to-path ids))))))
(defn create-context (defn create-context
[plugin-id] [plugin-id]

View file

@ -0,0 +1,164 @@
;; 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.comments
(:require
[app.common.geom.point :as gpt]
[app.common.record :as crc]
[app.common.spec :as us]
[app.main.data.comments :as dc]
[app.main.data.workspace.comments :as dwc]
[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.user :as user]
[app.plugins.utils :as u]
[beicon.v2.core :as rx]
[promesa.core :as p]))
(deftype CommentProxy [$plugin $file $page $thread $id]
Object
(remove [_]
(p/create
(fn [resolve reject]
(->> (rp/cmd! :delete-comment {:id $id})
(rx/tap #(st/emit! (dc/retrieve-comment-threads $file)))
(rx/subs! #(resolve) reject))))))
(defn comment-proxy? [p]
(instance? CommentProxy p))
(defn comment-proxy
[plugin-id file-id page-id thread-id users data]
(let [data* (atom data)]
(crc/add-properties!
(CommentProxy. plugin-id file-id page-id thread-id (:id data))
{: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 "$thread" :enumerable false :get (constantly thread-id)}
{:name "$id" :enumerable false :get (constantly (:id data))}
{:name "user" :get (fn [_] (user/user-proxy plugin-id (get users (:owner-id data))))}
{:name "date" :get (fn [_] (:created-at data))}
{:name "content"
:get (fn [_] (:content @data*))
:set
(fn [_ content]
(let [profile (:profile @st/state)]
(cond
(or (not (string? content)) (empty? content))
(u/display-not-valid :content "Not valid")
(not= (:id profile) (:owner-id data))
(u/display-not-valid :content "Cannot change content from another user's comments")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :content "Plugin doesn't have 'content:write' permission")
:else
(->> (rp/cmd! :update-comment {:id (:id data) :content content})
(rx/tap #(st/emit! (dc/retrieve-comment-threads file-id)))
(rx/subs! #(swap! data* assoc :content content))))))})))
(deftype CommentThreadProxy [$plugin $file $page $users $id owner]
Object
(findComments
[_]
(p/create
(fn [resolve reject]
(->> (rp/cmd! :get-comments {:thread-id $id})
(rx/subs!
(fn [comments]
(resolve
(format/format-array
#(comment-proxy $plugin $file $page $id $users %) comments)))
reject)))))
(reply
[_ content]
(cond
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :content "Plugin doesn't have 'content:write' permission")
(or (not (string? content)) (empty? content))
(u/display-not-valid :content "Not valid")
:else
(p/create
(fn [resolve reject]
(->> (rp/cmd! :create-comment {:thread-id $id :content content})
(rx/subs! #(resolve (comment-proxy $plugin $file $page $id $users %)) reject))))))
(remove [_]
(let [profile (:profile @st/state)]
(cond
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :removeCommentThread "Plugin doesn't have 'content:write' permission")
(not= (:id profile) owner)
(u/display-not-valid :content "Cannot change content from another user's comments")
:else
(p/create
(fn [resolve]
(p/create
(st/emit! (dc/delete-comment-thread-on-workspace {:id $id} #(resolve))))))))))
(defn comment-thread-proxy? [p]
(instance? CommentThreadProxy p))
(defn comment-thread-proxy
[plugin-id file-id page-id users data]
(let [data* (atom data)]
(crc/add-properties!
(CommentThreadProxy. plugin-id file-id page-id users (:id data) (:owner-id data))
{: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 data))}
{:name "$users" :enumerable false :get (constantly users)}
{:name "page" :enumerable false :get (fn [_] (u/locate-page file-id page-id))}
{:name "seqNumber" :get (fn [_] (:seqn data))}
{:name "owner" :get (fn [_] (user/user-proxy plugin-id (get users (:owner-id data))))}
{:name "board" :get (fn [_] (shape/shape-proxy plugin-id file-id page-id (:frame-id data)))}
{:name "position"
:get (fn [_] (format/format-point (:position @data*)))
:set
(fn [_ position]
(let [position (parser/parse-point position)]
(cond
(or (not (us/safe-number? (:x position))) (not (us/safe-number? (:y position))))
(u/display-not-valid :position "Not valid point")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :content "Plugin doesn't have 'content:write' permission")
:else
(do (st/emit! (dwc/update-comment-thread-position @data* [(:x position) (:y position)]))
(swap! data* assoc :position (gpt/point position))))))}
{:name "resolved"
:get (fn [_] (:is-resolved @data*))
:set
(fn [_ is-resolved]
(cond
(not (boolean? is-resolved))
(u/display-not-valid :resolved "Not a boolean type")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :resolved "Plugin doesn't have 'content:write' permission")
:else
(do (st/emit! (dc/update-comment-thread (assoc @data* :is-resolved is-resolved)))
(swap! data* assoc :is-resolved is-resolved))))})))

View file

@ -592,3 +592,9 @@
:url (:url interaction)} :url (:url interaction)}
nil)))) nil))))
(defn axis->orientation
[axis]
(case axis
:y "horizontal"
:x "vertical"))

View file

@ -5,23 +5,32 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.plugins.page (ns app.plugins.page
"RPC for plugins runtime."
(:require (:require
[app.common.colors :as cc] [app.common.colors :as cc]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.record :as crc] [app.common.record :as crc]
[app.common.spec :as us]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.comments :as dc]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.guides :as dwgu]
[app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.interactions :as dwi]
[app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.plugins.comments :as pc]
[app.plugins.format :as format] [app.plugins.format :as format]
[app.plugins.parser :as parser] [app.plugins.parser :as parser]
[app.plugins.register :as r] [app.plugins.register :as r]
[app.plugins.ruler-guides :as rg]
[app.plugins.shape :as shape] [app.plugins.shape :as shape]
[app.plugins.utils :as u] [app.plugins.utils :as u]
[app.util.object :as obj] [app.util.object :as obj]
[cuerdas.core :as str])) [beicon.v2.core :as rx]
[cuerdas.core :as str]
[promesa.core :as p]))
(deftype FlowProxy [$plugin $file $page $id] (deftype FlowProxy [$plugin $file $page $id]
Object Object
@ -52,7 +61,7 @@
:else :else
(st/emit! (dwi/update-flow page-id id #(assoc % :name value)))))} (st/emit! (dwi/update-flow page-id id #(assoc % :name value)))))}
{:name "startingFrame" {:name "startingBoard"
:get :get
(fn [self] (fn [self]
(let [frame (-> self u/proxy->flow :starting-frame)] (let [frame (-> self u/proxy->flow :starting-frame)]
@ -61,7 +70,7 @@
(fn [_ value] (fn [_ value]
(cond (cond
(not (shape/shape-proxy? value)) (not (shape/shape-proxy? value))
(u/display-not-valid :startingFrame value) (u/display-not-valid :startingBoard value)
:else :else
(st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id"))))))})) (st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id"))))))}))
@ -75,8 +84,10 @@
(u/display-not-valid :getShapeById shape-id) (u/display-not-valid :getShapeById shape-id)
:else :else
(let [shape-id (uuid/uuid shape-id)] (let [shape-id (uuid/uuid shape-id)
(shape/shape-proxy $plugin $file $id shape-id)))) shape (u/locate-shape $file $id shape-id)]
(when (some? shape)
(shape/shape-proxy $plugin $file $id shape-id)))))
(getRoot (getRoot
[_] [_]
@ -209,7 +220,123 @@
(u/display-not-valid :removeFlow-flow flow) (u/display-not-valid :removeFlow-flow flow)
:else :else
(st/emit! (dwi/remove-flow $id (obj/get flow "$id")))))) (st/emit! (dwi/remove-flow $id (obj/get flow "$id")))))
(addRulerGuide
[_ orientation value board]
(let [shape (u/proxy->shape board)]
(cond
(not (us/safe-number? value))
(u/display-not-valid :addRulerGuide "Value not a safe number")
(not (contains? #{"vertical" "horizontal"} orientation))
(u/display-not-valid :addRulerGuide "Orientation should be either 'vertical' or 'horizontal'")
(or (not (shape/shape-proxy? board))
(not (cfh/frame-shape? shape)))
(u/display-not-valid :addRulerGuide "The shape is not a board")
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :addRulerGuide "Plugin doesn't have 'content:write' permission")
:else
(let [id (uuid/next)]
(st/emit!
(dwgu/update-guides
(d/without-nils
{:id id
:axis (parser/orientation->axis orientation)
:position value
:frame-id (when board (obj/get board "$id"))})))
(rg/ruler-guide-proxy $plugin $file $id id)))))
(removeRulerGuide
[_ value]
(cond
(not (rg/ruler-guide-proxy? value))
(u/display-not-valid :removeRulerGuide "Guide not provided")
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :removeRulerGuide "Plugin doesn't have 'content:write' permission")
:else
(let [guide (u/proxy->ruler-guide value)]
(st/emit! (dwgu/remove-guide guide)))))
(addCommentThread
[_ content position board]
(let [shape (when board (u/proxy->shape board))]
(cond
(or (not (string? content)) (empty? content))
(u/display-not-valid :addCommentThread "Content not valid")
(or (not (us/safe-number? (:x position))) (not (us/safe-number? (:y position))))
(u/display-not-valid :addCommentThread "Position not valid")
(and (some? board) (or (not (shape/shape-proxy? board)) (not (cfh/frame-shape? shape))))
(u/display-not-valid :addCommentThread "Board not valid")
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :addCommentThread "Plugin doesn't have 'content:write' permission")
:else
(p/create
(fn [resolve]
(st/emit!
(dc/create-thread-on-workspace
{:file-id $file
:page-id $id
:position (gpt/point (parser/parse-point position))
:content content}
(fn [data]
(->> (rp/cmd! :get-team-users {:file-id $file})
(rx/subs!
(fn [users]
(let [users (d/index-by :id users)]
(resolve (pc/comment-thread-proxy $plugin $file $id users data)))))))
false)))))))
(removeCommentThread
[_ thread]
(cond
(not (pc/comment-thread-proxy? thread))
(u/display-not-valid :removeCommentThread "Comment thread not valid")
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :removeCommentThread "Plugin doesn't have 'content:write' permission")
:else
(p/create
(fn [resolve]
(let [thread-id (obj/get thread "$id")]
(p/create
(st/emit! (dc/delete-comment-thread-on-workspace {:id thread-id} #(resolve)))))))))
(findCommentThreads
[_ criteria]
(let [only-yours (boolean (obj/get criteria "onlyYours" false))
show-resolved (boolean (obj/get criteria "showResolved" true))
user-id (-> @st/state :profile :id)]
(p/create
(fn [resolve reject]
(->> (rx/zip (rp/cmd! :get-team-users {:file-id $file})
(rp/cmd! :get-comment-threads {:file-id $file}))
(rx/take 1)
(rx/subs!
(fn [[users comments]]
(let [users (d/index-by :id users)
comments
(cond->> comments
(not show-resolved)
(filter (comp not :is-resolved))
only-yours
(filter #(contains? (:participants %) user-id)))]
(resolve
(format/format-array
#(pc/comment-thread-proxy $plugin $file $id users %) comments))))
reject)))))))
(crc/define-properties! (crc/define-properties!
PageProxy PageProxy
@ -267,4 +394,13 @@
:get :get
(fn [self] (fn [self]
(let [flows (d/nilv (-> (u/proxy->page self) :options :flows) [])] (let [flows (d/nilv (-> (u/proxy->page self) :options :flows) [])]
(format/format-array #(flow-proxy plugin-id file-id id (:id %)) flows)))})) (format/format-array #(flow-proxy plugin-id file-id id (:id %)) flows)))}
{:name "rulerGuides"
:get
(fn [self]
(let [guides (-> (u/proxy->page self) :options :guides)]
(->> guides
(vals)
(filter #(nil? (:frame-id %)))
(format/format-array #(rg/ruler-guide-proxy plugin-id file-id id (:id %))))))}))

View file

@ -569,3 +569,9 @@
action (parse-action action)] action (parse-action action)]
(d/without-nils (d/without-nils
(d/patch-object {:event-type trigger :delay delay} action))))) (d/patch-object {:event-type trigger :delay delay} action)))))
(defn orientation->axis
[axis]
(case axis
"horizontal" :y
"vertical" :x))

View file

@ -9,6 +9,8 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.common.types.plugins :as ctp]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
@ -51,19 +53,26 @@
(and (= name (:name plugin)) (and (= name (:name plugin))
(= origin (:host plugin)))))) (= origin (:host plugin))))))
plugin-id (d/nilv (:plugin-id prev-plugin) (str (uuid/next)))] plugin-id (d/nilv (:plugin-id prev-plugin) (str (uuid/next)))
{:plugin-id plugin-id
:name name manifest
:description desc (d/without-nils
:host origin {:plugin-id plugin-id
:code code :name name
:icon icon :description desc
:permissions (into #{} (map str) permissions)})) :host origin
:code code
:icon icon
:permissions (into #{} (map str) permissions)})]
(when (sm/validate ::ctp/registry-entry manifest)
manifest)))
(defn save-to-store (defn save-to-store
[] []
(->> (rp/cmd! :update-profile-props {:props {:plugins @registry}}) ;; TODO: need this for the transition to the new schema. We can remove eventually
(rx/subs! identity))) (let [registry (update @registry :data d/update-vals d/without-nils)]
(->> (rp/cmd! :update-profile-props {:props {:plugins registry}})
(rx/subs! identity))))
(defn load-from-store (defn load-from-store
[] []

View file

@ -0,0 +1,99 @@
;; 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.ruler-guides
(:require
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.record :as crc]
[app.common.spec :as us]
[app.main.data.workspace.guides :as dwgu]
[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]))
(def shape-proxy identity)
(def shape-proxy? identity)
(deftype RulerGuideProxy [$plugin $file $page $id]
Object
(remove [self]
(let [guide (u/proxy->ruler-guide self)]
(st/emit! (dwgu/remove-guide guide)))))
(defn ruler-guide-proxy? [p]
(instance? RulerGuideProxy p))
(defn ruler-guide-proxy
[plugin-id file-id page-id id]
(crc/add-properties!
(RulerGuideProxy. 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 "board" :enumerable false
:get
(fn [self]
(let [board-id (-> self u/proxy->ruler-guide :frame-id)]
(when board-id
(shape-proxy plugin-id file-id page-id board-id))))
:set
(fn [self value]
(let [shape (u/locate-shape file-id page-id (obj/get value "$id"))]
(cond
(not (shape-proxy? value))
(u/display-not-valid :board "The board is not a shape proxy")
(not (cfh/frame-shape? shape))
(u/display-not-valid :board "The shape is not a board")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :board "Plugin doesn't have 'content:write' permission")
:else
(let [board-id (when value (obj/get value "$id"))
guide (-> self u/proxy->ruler-guide)]
(st/emit! (dwgu/update-guides (assoc guide :frame-id board-id)))))))}
{:name "orientation"
:get #(-> % u/proxy->ruler-guide :axis format/axis->orientation)}
{:name "position"
:get
(fn [self]
(let [guide (u/proxy->ruler-guide self)]
(if (:frame-id guide)
(let [objects (u/locate-objects file-id page-id)
board-pos (dm/get-in objects [(:frame-id guide) (:axis guide)])
position (:position guide)]
(- position board-pos))
;; No frame
(:position guide))))
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :position "Not valid position")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :position "Plugin doesn't have 'content:write' permission")
:else
(let [guide (u/proxy->ruler-guide self)
position
(if (:frame-id guide)
(let [objects (u/locate-objects file-id page-id)
board-pos (dm/get-in objects [(:frame-id guide) (:axis guide)])]
(+ board-pos value))
value)]
(st/emit! (dwgu/update-guides (assoc guide :position position))))))}))

View file

@ -5,7 +5,6 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.plugins.shape (ns app.plugins.shape
"RPC for plugins runtime."
(:require (:require
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
@ -33,6 +32,7 @@
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.groups :as dwg] [app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.guides :as dwgu]
[app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
@ -46,6 +46,7 @@
[app.plugins.grid :as grid] [app.plugins.grid :as grid]
[app.plugins.parser :as parser] [app.plugins.parser :as parser]
[app.plugins.register :as r] [app.plugins.register :as r]
[app.plugins.ruler-guides :as rg]
[app.plugins.text :as text] [app.plugins.text :as text]
[app.plugins.utils :as u] [app.plugins.utils :as u]
[app.util.object :as obj] [app.util.object :as obj]
@ -571,7 +572,52 @@
(u/display-not-valid :removeInteraction interaction) (u/display-not-valid :removeInteraction interaction)
:else :else
(st/emit! (dwi/remove-interaction {:id $id} (obj/get interaction "$index")))))) (st/emit! (dwi/remove-interaction {:id $id} (obj/get interaction "$index")))))
;; Ruler guides
(addRulerGuide
[self orientation value]
(let [shape (u/proxy->shape self)]
(cond
(not (us/safe-number? value))
(u/display-not-valid :addRulerGuide "Value not a safe number")
(not (contains? #{"vertical" "horizontal"} orientation))
(u/display-not-valid :addRulerGuide "Orientation should be either 'vertical' or 'horizontal'")
(not (cfh/frame-shape? shape))
(u/display-not-valid :addRulerGuide "The shape is not a board")
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :addRulerGuide "Plugin doesn't have 'content:write' permission")
:else
(let [id (uuid/next)
axis (parser/orientation->axis orientation)
objects (u/locate-objects $file $page)
frame (get objects $id)
board-pos (get frame axis)
position (+ board-pos value)]
(st/emit!
(dwgu/update-guides
{:id id
:axis axis
:position position
:frame-id $id}))
(rg/ruler-guide-proxy $plugin $file $page id)))))
(removeRulerGuide
[_ value]
(cond
(not (rg/ruler-guide-proxy? value))
(u/display-not-valid :removeRulerGuide "Guide not provided")
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :removeRulerGuide "Plugin doesn't have 'content:write' permission")
:else
(let [guide (u/proxy->ruler-guide value)]
(st/emit! (dwgu/remove-guide guide))))))
(defn shape-proxy? [p] (defn shape-proxy? [p]
(instance? ShapeProxy p)) (instance? ShapeProxy p))
@ -1202,6 +1248,15 @@
:else :else
(st/emit! (dwsh/update-shapes [id] #(assoc % :grids value))))))} (st/emit! (dwsh/update-shapes [id] #(assoc % :grids value))))))}
{:name "rulerGuides"
:get
(fn [_]
(let [guides (-> (u/locate-page file-id page-id) :options :guides)]
(->> guides
(vals)
(filter #(= id (:frame-id %)))
(format/format-array #(rg/ruler-guide-proxy plugin-id file-id page-id (:id %))))))}
{:name "horizontalSizing" {:name "horizontalSizing"
:get #(-> % u/proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name) :get #(-> % u/proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
:set :set

View file

@ -12,13 +12,13 @@
[app.plugins.utils :as u] [app.plugins.utils :as u]
[app.util.object :as obj])) [app.util.object :as obj]))
(deftype CurrentUserProxy [$plugin $session]) (deftype CurrentUserProxy [$plugin])
(deftype ActiveUserProxy [$plugin $session]) (deftype ActiveUserProxy [$plugin])
(deftype UserProxy [$plugin])
(defn add-user-properties (defn- add-session-properties
[user-proxy] [user-proxy session-id]
(let [plugin-id (obj/get user-proxy "$plugin") (let [plugin-id (obj/get user-proxy "$plugin")]
session-id (obj/get user-proxy "$session")]
(crc/add-properties! (crc/add-properties!
user-proxy user-proxy
{:name "$plugin" :enumerable false :get (constantly plugin-id)} {:name "$plugin" :enumerable false :get (constantly plugin-id)}
@ -39,21 +39,43 @@
{:name "sessionId" {:name "sessionId"
:get (fn [_] (str session-id))}))) :get (fn [_] (str session-id))})))
(defn current-user-proxy? [p] (defn current-user-proxy? [p]
(instance? CurrentUserProxy p)) (instance? CurrentUserProxy p))
(defn current-user-proxy (defn current-user-proxy
[plugin-id session-id] [plugin-id session-id]
(-> (CurrentUserProxy. plugin-id session-id) (-> (CurrentUserProxy. plugin-id)
(add-user-properties))) (add-session-properties session-id)))
(defn active-user-proxy? [p] (defn active-user-proxy? [p]
(instance? ActiveUserProxy p)) (instance? ActiveUserProxy p))
(defn active-user-proxy (defn active-user-proxy
[plugin-id session-id] [plugin-id session-id]
(-> (ActiveUserProxy. plugin-id session-id) (-> (ActiveUserProxy. plugin-id)
(add-user-properties) (add-session-properties session-id)
(crc/add-properties! (crc/add-properties!
{:name "position" :get (fn [_] (-> (u/locate-presence session-id) :point format/format-point))} {:name "position" :get (fn [_] (-> (u/locate-presence session-id) :point format/format-point))}
{:name "zoom" :get (fn [_] (-> (u/locate-presence session-id) :zoom))}))) {:name "zoom" :get (fn [_] (-> (u/locate-presence session-id) :zoom))})))
(defn- add-user-properties
[user-proxy data]
(let [plugin-id (obj/get user-proxy "$plugin")]
(crc/add-properties!
user-proxy
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "id"
:get (fn [_] (-> data :id str))}
{:name "name"
:get (fn [_] (-> data :fullname))}
{:name "avatarUrl"
:get (fn [_] (cfg/resolve-profile-photo-url data))})))
(defn user-proxy
[plugin-id data]
(-> (UserProxy. plugin-id)
(add-user-properties data)))

View file

@ -122,6 +122,15 @@
(when (some? page) (when (some? page)
(d/seek #(= (:id %) flow-id) (-> page :options :flows))))) (d/seek #(= (:id %) flow-id) (-> page :options :flows)))))
(defn proxy->ruler-guide
[proxy]
(let [file-id (obj/get proxy "$file")
page-id (obj/get proxy "$page")
ruler-id (obj/get proxy "$id")
page (locate-page file-id page-id)]
(when (some? page)
(d/seek #(= (:id %) ruler-id) (-> page :options :guides vals)))))
(defn proxy->interaction (defn proxy->interaction
[proxy] [proxy]
(let [file-id (obj/get proxy "$file") (let [file-id (obj/get proxy "$file")

View file

@ -5,7 +5,6 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.plugins.viewport (ns app.plugins.viewport
"RPC for plugins runtime."
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.record :as crc] [app.common.record :as crc]
@ -20,6 +19,12 @@
(deftype ViewportProxy [$plugin] (deftype ViewportProxy [$plugin]
Object Object
(zoomReset [_]
(st/emit! dwz/reset-zoom))
(zoomToFitAll [_]
(st/emit! dwz/zoom-to-fit-all))
(zoomIntoView [_ shapes] (zoomIntoView [_ shapes]
(let [ids (let [ids
(->> shapes (->> shapes

View file

@ -5548,10 +5548,12 @@ msgstr "Discover [more plugins](%s)"
msgid "workspace.plugins.empty-plugins" msgid "workspace.plugins.empty-plugins"
msgstr "No plugins installed yet" msgstr "No plugins installed yet"
#: src/app/main/ui/workspace/plugins.cljs:175
msgid "workspace.plugins.error.url" msgid "workspace.plugins.error.url"
msgstr "The plugin doesn't exist or the URL is not correct." msgstr "The plugin doesn't exist or the URL is not correct."
msgid "workspace.plugins.error.manifest"
msgstr "The plugin manifest is incorrect."
#: src/app/main/ui/workspace/plugins.cljs:171 #: src/app/main/ui/workspace/plugins.cljs:171
msgid "workspace.plugins.install" msgid "workspace.plugins.install"
msgstr "Install" msgstr "Install"

View file

@ -5535,10 +5535,12 @@ msgstr "Descubre [más extensiones](%s)"
msgid "workspace.plugins.empty-plugins" msgid "workspace.plugins.empty-plugins"
msgstr "No se encuentran extensiones" msgstr "No se encuentran extensiones"
#: src/app/main/ui/workspace/plugins.cljs:175
msgid "workspace.plugins.error.url" msgid "workspace.plugins.error.url"
msgstr "La extensión no existe o la url no es correcta." msgstr "La extensión no existe o la url no es correcta."
msgid "workspace.plugins.error.manifest"
msgstr "El manifiesto de la expansión es incorrecto."
#: src/app/main/ui/workspace/plugins.cljs:171 #: src/app/main/ui/workspace/plugins.cljs:171
msgid "workspace.plugins.install" msgid "workspace.plugins.install"
msgstr "Instalar" msgstr "Instalar"