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.exceptions :as ex]
[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.config :as cf]
[app.db :as db]
@ -43,7 +43,7 @@
(def schema:props
[:map {:title "ProfileProps"}
[:plugins {:optional true} schema:plugin-data]
[:plugins {:optional true} schema:plugin-registry]
[:newsletter-updates {:optional true} ::sm/boolean]
[:newsletter-news {:optional true} ::sm/boolean]
[:onboarding-team-id {:optional true} ::sm/uuid]

View file

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

View file

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

View file

@ -15,24 +15,27 @@
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn convert-selected-to-path []
(ptk/reify ::convert-selected-to-path
ptk/WatchEvent
(watch [it state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state)
selected (->> (wsh/lookup-selected state)
(remove #(ctn/has-any-copy-parent? objects (get objects %))))
(defn convert-selected-to-path
([]
(convert-selected-to-path nil))
([ids]
(ptk/reify ::convert-selected-to-path
ptk/WatchEvent
(watch [it state _]
(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
(into #{}
(mapcat #(cph/get-children-ids objects %))
selected)
children-ids
(into #{}
(mapcat #(cph/get-children-ids objects %))
selected)
changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
(pcb/update-shapes selected #(upsp/convert-to-path % objects))
(pcb/remove-objects children-ids))]
changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
(pcb/update-shapes selected #(upsp/convert-to-path % objects))
(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 @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
(mf/use-callback
@ -117,17 +119,20 @@
(rx/subs!
(fn [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
(preg/install-plugin! plugin)
(modal/show! :plugin-management {}))})
(reset! input-status* :success)
(reset! plugin-url* "")))
(if-let [plugin (preg/parse-manifest plugin-url body)]
(do
(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
(preg/install-plugin! plugin)
(modal/show! :plugin-management {}))})
(reset! input-status* :success)
(reset! plugin-url* ""))
;; Cannot get the manifest
(reset! input-status* :error-manifest)))
(fn [_]
(reset! fetching-manifest? false)
(reset! input-status* :error-url))))))
@ -170,10 +175,14 @@
:disabled @fetching-manifest?
:on-click handle-install-click} (tr "workspace.plugins.install")]]
(when error?
(when error-url?
[:div {:class (stl/css-case :info true :error error?)}
(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*
{:class (stl/css :discover)
: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.library :as library]
[app.plugins.public-utils]
[app.plugins.ruler-guides :as rg]
[app.plugins.shape :as shape]
[app.util.globals :refer [global]]
[app.util.object :as obj]
@ -43,6 +44,8 @@
(set! flex/shape-proxy? shape/shape-proxy?)
(set! grid/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-component-proxy library/lib-component-proxy)

View file

@ -368,7 +368,73 @@
(openPage
[_ page]
(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
[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)}
nil))))
(defn axis->orientation
[axis]
(case axis
:y "horizontal"
:x "vertical"))

View file

@ -5,23 +5,32 @@
;; Copyright (c) KALEIDOS INC
(ns app.plugins.page
"RPC for plugins runtime."
(:require
[app.common.colors :as cc]
[app.common.data :as d]
[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.spec :as us]
[app.common.uuid :as uuid]
[app.main.data.comments :as dc]
[app.main.data.workspace :as dw]
[app.main.data.workspace.guides :as dwgu]
[app.main.data.workspace.interactions :as dwi]
[app.main.repo :as rp]
[app.main.store :as st]
[app.plugins.comments :as pc]
[app.plugins.format :as format]
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.ruler-guides :as rg]
[app.plugins.shape :as shape]
[app.plugins.utils :as u]
[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]
Object
@ -52,7 +61,7 @@
:else
(st/emit! (dwi/update-flow page-id id #(assoc % :name value)))))}
{:name "startingFrame"
{:name "startingBoard"
:get
(fn [self]
(let [frame (-> self u/proxy->flow :starting-frame)]
@ -61,7 +70,7 @@
(fn [_ value]
(cond
(not (shape/shape-proxy? value))
(u/display-not-valid :startingFrame value)
(u/display-not-valid :startingBoard value)
:else
(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)
:else
(let [shape-id (uuid/uuid shape-id)]
(shape/shape-proxy $plugin $file $id shape-id))))
(let [shape-id (uuid/uuid shape-id)
shape (u/locate-shape $file $id shape-id)]
(when (some? shape)
(shape/shape-proxy $plugin $file $id shape-id)))))
(getRoot
[_]
@ -209,7 +220,123 @@
(u/display-not-valid :removeFlow-flow flow)
: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!
PageProxy
@ -267,4 +394,13 @@
: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)))}))
(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)]
(d/without-nils
(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
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.common.types.plugins :as ctp]
[app.common.uuid :as uuid]
[app.main.repo :as rp]
[app.main.store :as st]
@ -51,19 +53,26 @@
(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)}))
plugin-id (d/nilv (:plugin-id prev-plugin) (str (uuid/next)))
manifest
(d/without-nils
{:plugin-id plugin-id
:name name
:description desc
:host origin
:code code
:icon icon
:permissions (into #{} (map str) permissions)})]
(when (sm/validate ::ctp/registry-entry manifest)
manifest)))
(defn save-to-store
[]
(->> (rp/cmd! :update-profile-props {:props {:plugins @registry}})
(rx/subs! identity)))
;; TODO: need this for the transition to the new schema. We can remove eventually
(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
[]

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
(ns app.plugins.shape
"RPC for plugins runtime."
(:require
[app.common.colors :as clr]
[app.common.data :as d]
@ -33,6 +32,7 @@
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
[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.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
@ -46,6 +46,7 @@
[app.plugins.grid :as grid]
[app.plugins.parser :as parser]
[app.plugins.register :as r]
[app.plugins.ruler-guides :as rg]
[app.plugins.text :as text]
[app.plugins.utils :as u]
[app.util.object :as obj]
@ -571,7 +572,52 @@
(u/display-not-valid :removeInteraction interaction)
: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]
(instance? ShapeProxy p))
@ -1202,6 +1248,15 @@
:else
(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"
:get #(-> % u/proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
:set

View file

@ -12,13 +12,13 @@
[app.plugins.utils :as u]
[app.util.object :as obj]))
(deftype CurrentUserProxy [$plugin $session])
(deftype ActiveUserProxy [$plugin $session])
(deftype CurrentUserProxy [$plugin])
(deftype ActiveUserProxy [$plugin])
(deftype UserProxy [$plugin])
(defn add-user-properties
[user-proxy]
(let [plugin-id (obj/get user-proxy "$plugin")
session-id (obj/get user-proxy "$session")]
(defn- add-session-properties
[user-proxy session-id]
(let [plugin-id (obj/get user-proxy "$plugin")]
(crc/add-properties!
user-proxy
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
@ -39,21 +39,43 @@
{:name "sessionId"
:get (fn [_] (str session-id))})))
(defn current-user-proxy? [p]
(instance? CurrentUserProxy p))
(defn current-user-proxy
[plugin-id session-id]
(-> (CurrentUserProxy. plugin-id session-id)
(add-user-properties)))
(-> (CurrentUserProxy. plugin-id)
(add-session-properties session-id)))
(defn active-user-proxy? [p]
(instance? ActiveUserProxy p))
(defn active-user-proxy
[plugin-id session-id]
(-> (ActiveUserProxy. plugin-id session-id)
(add-user-properties)
(-> (ActiveUserProxy. plugin-id)
(add-session-properties session-id)
(crc/add-properties!
{:name "position" :get (fn [_] (-> (u/locate-presence session-id) :point format/format-point))}
{: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)
(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
[proxy]
(let [file-id (obj/get proxy "$file")

View file

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

View file

@ -5548,10 +5548,12 @@ msgstr "Discover [more plugins](%s)"
msgid "workspace.plugins.empty-plugins"
msgstr "No plugins installed yet"
#: src/app/main/ui/workspace/plugins.cljs:175
msgid "workspace.plugins.error.url"
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
msgid "workspace.plugins.install"
msgstr "Install"

View file

@ -5535,10 +5535,12 @@ msgstr "Descubre [más extensiones](%s)"
msgid "workspace.plugins.empty-plugins"
msgstr "No se encuentran extensiones"
#: src/app/main/ui/workspace/plugins.cljs:175
msgid "workspace.plugins.error.url"
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
msgid "workspace.plugins.install"
msgstr "Instalar"