Compare commits

..

74 Commits

Author SHA1 Message Date
Andrey Antukh
30a06249ff Merge pull request #5377 from penpot/azazeln28-fix-text-editor-issues
🐛 Fix text editor issues
2024-11-27 09:08:39 +01:00
AzazelN28
59ca09c24e 🐛 Fix text editor issues 2024-11-27 08:56:52 +01:00
Andrey Antukh
1aeafdfca7 Merge pull request #5378 from penpot/yms-fix-k8s-documentation
📚 Add links to Kubernetes documentation
2024-11-27 08:54:37 +01:00
Andrey Antukh
a714085523 Merge branch 'niwinz-plugins-reify' into staging 2024-11-27 08:32:54 +01:00
alonso.torres
eccc4226c7 Migrate proxies to new format 2024-11-27 08:32:07 +01:00
Yamila Moreno
4d6d85b3de 📚 Add links to Kubernetes documentation 2024-11-26 15:25:51 +01:00
Aitor Moreno
c607b61af6 Merge pull request #5349 from penpot/palba-add-event-for-pdf
 Add export format info to export-shapes event
2024-11-26 12:53:52 +01:00
Andrey Antukh
e16ec9c719 Add facility for create anonymous objects
Speciailly designed to be work in plugins where code
is submited to hard deep freeze on the sandboxing
process.
2024-11-25 16:35:44 +01:00
Pablo Alba
59e5656bd7 Merge pull request #5370 from penpot/niwinz-bugfix-4
🐛 Fix incorrect access to flows on add-new-interaction event
2024-11-25 15:41:13 +01:00
Andrey Antukh
723eef9565 🐛 Fix incorrect access to flows on add-new-interaction event 2024-11-25 13:32:45 +01:00
Belén Albeza
8448036d67 Merge pull request #5368 from penpot/niwinz-bugfix-3
🐛 Fix import format detection and error handling
2024-11-25 13:30:46 +01:00
Andrey Antukh
e1c9691567 Improve scss compilation error handling
Don't stop watch scss process on compilation error
2024-11-25 12:44:10 +01:00
Andrey Antukh
577b731b22 🐛 Fix import format detection and error handling 2024-11-25 12:29:59 +01:00
Andrey Antukh
ef3588d05f Merge pull request #5355 from penpot/azazeln28-fix-component-list-jumps
🐛 Fix component list jumps
2024-11-25 11:44:05 +01:00
Andrey Antukh
d4893523bc Merge pull request #5367 from penpot/niwinz-bugfix-2
🐛 Fix release 2.3 onboarding text typos
2024-11-25 11:43:47 +01:00
AzazelN28
f10792619d 🐛 Fix component list jumps 2024-11-25 11:35:33 +01:00
Alejandro
7a0702650a Merge pull request #5366 from penpot/niwinz-bugfix-1
🐛 Bugfixes
2024-11-25 11:32:28 +01:00
Elhombretecla
ee1230c488 🐛 Fix release 2.3 onboarding text typos 2024-11-25 11:30:51 +01:00
Andrey Antukh
ede1176606 Merge pull request #5340 from penpot/palba-testab-board-icon
🎉 Add A/B test of use of boards if we just change the icon for “standard” one
2024-11-25 11:24:23 +01:00
Andrey Antukh
9506606e15 Merge pull request #5359 from penpot/alotor-fixes-2
🐛 Fix problem with scroll in history versions
2024-11-25 11:23:47 +01:00
Andrey Antukh
7e5f93ca3d Merge pull request #5358 from penpot/azazeln28-fix-assets-filters
🐛 Fix assets filters
2024-11-25 11:23:14 +01:00
Andrey Antukh
6655563aba Merge pull request #5357 from penpot/superalex-fix-generate-translation-files-with-markdown-and-links
🐛 Fix generate translation files with markdown and links
2024-11-25 11:18:13 +01:00
Andrey Antukh
8ee9b45243 Merge pull request #5346 from penpot/alotor-fixes-1
Alotor fixes 1
2024-11-25 11:16:25 +01:00
Alejandro
caa6897f81 Merge pull request #5342 from penpot/niwinz-thumbnails-fix
 Make the file and shape thumbnails not dependent on PUBLIC_URI
2024-11-25 11:13:04 +01:00
Andrey Antukh
660bc1a4dd 🐛 Fix incorrect team rename operation 2024-11-25 10:01:36 +01:00
Andrey Antukh
3ddd45e99b 🐛 Fix incorrect internal form initialization 2024-11-25 10:01:36 +01:00
Andrey Antukh
9b71e04e1c 🐛 Fix exception on user-feedback rpc method
And normalizes configuration parameters
2024-11-25 10:01:36 +01:00
alonso.torres
39620fe9c4 🐛 Hover on history version entry to show options 2024-11-25 09:51:41 +01:00
alonso.torres
db7c1fc7dd 🐛 Fix problem with some texts desynchronization 2024-11-25 09:51:41 +01:00
Alejandro Alonso
c89abf56ac 🐛 Fix translate files generations with markdown and links 2024-11-25 07:20:32 +01:00
alonso.torres
d22f6e37c9 Add pin version to main menu 2024-11-22 15:47:19 +01:00
AzazelN28
19b9b3cbd9 🐛 Fix missing main menu entry version history 2024-11-22 15:35:13 +01:00
alonso.torres
c1d3e4cd6e 🐛 Fix problem with scroll in history versions 2024-11-22 15:34:32 +01:00
AzazelN28
2164593757 🐛 Fix assets filters 2024-11-22 14:47:43 +01:00
Andrey Antukh
9485ce03b5 Merge pull request #5338 from penpot/azazeln28-fix-missing-text-editor-changes
📎 Fix some text editor missing changes
2024-11-22 12:54:42 +01:00
Aitor Moreno
ba832389d1 Merge pull request #5354 from penpot/superalex-fix-text-layer-default-name-with-v2-editor
🐛 Fix text layer default name with v2 text editor
2024-11-22 12:20:59 +01:00
Alejandro Alonso
a8ee9be7b9 🐛 Fix text layer default name with v2 text editor 2024-11-22 11:14:39 +01:00
AzazelN28
c8c83c1e1d 📎 Fix some missing changes 2024-11-22 11:06:16 +01:00
Andrey Antukh
afcfbdedda Merge pull request #5341 from penpot/palba-fix-leave-team
🐛 Fix bad redirect after leaving team
2024-11-22 08:31:21 +01:00
Pablo Alba
53f55444cd Add export format info to export-shapes event 2024-11-21 16:33:06 +01:00
Pablo Alba
fa8665df88 Merge pull request #5337 from penpot/juanfran-fix-typo-keepaspectratio
🐛 Fix typo in keepAspectRatio #9336
2024-11-21 09:13:21 +01:00
Andrey Antukh
5cc678ddc3 Remove not necessary check on upgrade-version notification 2024-11-20 16:43:14 +01:00
Andrey Antukh
64c8741233 🐛 Make thumbnails independent of current public uri
Mainly always resolve the public uri at frontend, making the
PENPOT_PUBLIC_URI less necessary to be changed. This improves
the experience of on-premise configuration.

Also removes unnecesary calls for thumbnail generation
on components.
2024-11-20 16:43:14 +01:00
Pablo Alba
0cae9d6ad5 🐛 Fix bad redirect after leaving team 2024-11-20 15:37:08 +01:00
Pablo Alba
0c586551c4 🎉 Add A/B test of use of boards if we just change the icon for “standard” one 2024-11-20 15:12:54 +01:00
Juanfran
2f4cb19745 🐛 Fix typo in keepAspectRatio #9336 2024-11-20 12:43:51 +01:00
Alejandro
b80ccbec0f Merge pull request #5334 from penpot/niwinz-bug-features
🐛 Preserve frontend-only flags already present on team
2024-11-20 06:27:24 +01:00
Andrey Antukh
246415be2b Merge pull request #5306 from penpot/azazeln28-update-text-editor
⬆️ Update text editor
2024-11-19 19:20:04 +01:00
Andrey Antukh
7faa9e970e 🐛 Fix esm module incompatibilities on text-editor with node 2024-11-19 17:20:16 +01:00
Andrey Antukh
04a0d867b0 Import text-editor code into the repository 2024-11-19 17:05:30 +01:00
Andrey Antukh
a18214a1a5 🐛 Preserve frontend-only flags already present on team 2024-11-19 16:39:21 +01:00
AzazelN28
68397edd4d 🐛 Fix text editor selection 2024-11-19 14:47:38 +01:00
AzazelN28
1e2d9a15a3 🐛 Fix text editor shortcuts 2024-11-19 14:47:38 +01:00
AzazelN28
0f101fad9f ⬆️ Update text editor 2024-11-19 14:47:38 +01:00
Andrey Antukh
a91737b4d7 Merge pull request #5331 from penpot/alotor-exit-comments
🐛 Fix escape key to exit comments mode
2024-11-19 10:59:13 +01:00
alonso.torres
284d5ecb77 🐛 Fix escape key to exit comments mode 2024-11-19 10:44:54 +01:00
Alejandro
5d95d755ad Merge pull request #5315 from penpot/niwinz-team-access-request-quotes
🎉 Add quote definitions for team access requests
2024-11-19 06:52:04 +01:00
Andrey Antukh
4466abd150 Merge pull request #5320 from penpot/alotor-fix-problem-layouts
🐛 Fix problem with layout reflow
2024-11-18 18:44:42 +01:00
Andrey Antukh
27690c3da6 Add test runner for cljs on common module
In the same way as frontend tests are run and fix some
tokens related tests
2024-11-18 17:51:23 +01:00
alonso.torres
f436d72f51 Changed some events for versions 2024-11-18 16:38:38 +01:00
Alejandro
20ea188070 Merge pull request #5321 from penpot/niwinz-improvements-features
 Feature flags improvements
2024-11-18 15:52:20 +01:00
Alejandro
c4f076910b Merge pull request #5326 from penpot/alotor-fix-problem-texts
🐛 Fix problem with texts crashing
2024-11-18 12:59:40 +01:00
alonso.torres
72f2395142 🐛 Fix problem with texts crashing 2024-11-18 12:41:21 +01:00
Andrey Antukh
47d28758d7 Clean frontend and backend features on exportation 2024-11-15 15:57:25 +01:00
Andrey Antukh
b7573c0b72 Change frontend-only features automatic team assignation rules
The frontend-only features are now ignored from files and from teams
and they do not autoassigns automatically to team and file on file
creation or update operations.
2024-11-15 15:57:25 +01:00
alonso.torres
2ed743b6be 🐛 Fix problem with layout reflow 2024-11-15 15:12:52 +01:00
Andrey Antukh
036e335fc4 🎉 Add quote definitions for team access requests 2024-11-15 11:14:30 +01:00
Andrey Antukh
0e99b37c21 Merge remote-tracking branch 'origin/main' into staging 2024-11-15 10:17:18 +01:00
Praveen Juge
3cdbd7f381 📚 Fix spelling: change assents to assets 2024-11-15 09:50:44 +01:00
Andrey Antukh
76caff2b61 Merge pull request #5313 from penpot/alotor-bugfixes-enable-plugins-default
🐛 Activate plugin feature by default
2024-11-15 09:46:59 +01:00
Andrey Antukh
bb370b3e50 Merge pull request #5314 from penpot/superalex-fix-wasm-build
🐛 Fix compilation for wasm communication in release mode
2024-11-15 09:44:24 +01:00
Andrey Antukh
45d56f40e1 Merge remote-tracking branch 'origin/develop' into staging 2024-11-15 09:35:21 +01:00
Alejandro Alonso
4a1ab75d8f 🐛 Fix compilation for wasm communication in release mode 2024-11-15 09:30:59 +01:00
alonso.torres
a58ad2298a 🐛 Activate plugin feature by default 2024-11-15 09:04:59 +01:00
168 changed files with 17099 additions and 4775 deletions

View File

@@ -33,6 +33,12 @@ jobs:
command: |
clojure -M:dev:test
- run:
name: "NODE tests"
working_directory: "./common"
command: |
yarn run test
- save_cache:
paths:
- ~/.m2

View File

@@ -1,18 +1,5 @@
# CHANGELOG
## 2.5.0
### :rocket: Epics and highlights
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
### :sparkles: New features
### :bug: Bugs fixed
## 2.4.0
### :rocket: Epics and highlights
@@ -37,17 +24,20 @@
### :sparkles: New features
- Viewer role for team members [Taiga #1056 & #6590](https://tree.taiga.io/project/penpot/us/1056 & https://tree.taiga.io/project/penpot/us/6590)
- File history versions management [Taiga](https://tree.taiga.io/project/penpot/us/187?milestone=411120)
- File history versions management [Taiga #187](https://tree.taiga.io/project/penpot/us/187?milestone=411120)
- Rename selected layer via keyboard shortcut and context menu option [Taiga #8882](https://tree.taiga.io/project/penpot/us/8882)
### :bug: Bugs fixed
- Fix problem with some texts desynchronization [Taiga #9379](https://tree.taiga.io/project/penpot/issue/9379)
## 2.3.3
### :bug: Bugs fixed
- Fix problem creating manual overlay interactions [Taiga #9146](https://tree.taiga.io/project/penpot/issue/9146)
- Fix plugins list default URL
- Activate plugins feature by default
## 2.3.2

View File

@@ -134,6 +134,16 @@
(update :data feat.fdata/process-pointers deref)
(update :data feat.fdata/process-objects (partial into {}))))))))
(defn clean-file-features
[file]
(update file :features (fn [features]
(if (set? features)
(-> features
(cfeat/migrate-legacy-features)
(set/difference cfeat/frontend-only-features)
(set/difference cfeat/backend-only-features))
#{}))))
(defn get-project
[cfg project-id]
(db/get cfg :project {:id project-id}))
@@ -445,8 +455,11 @@
(fn [features]
(let [features (cfeat/check-supported-features! features)]
(-> (::features cfg #{})
(set/difference cfeat/frontend-only-features)
(set/union features))))))
(set/union features)
;; We never want to store
;; frontend-only features on file
(set/difference cfeat/frontend-only-features))))))
_ (when (contains? cf/flags :file-schema-validation)
(fval/validate-file-schema! file))

View File

@@ -508,15 +508,6 @@
(update :object-id #(str/replace-first % #"^(.*?)/" (str file-id "/")))))
thumbnails))
(defn- clean-features
[file]
(update file :features (fn [features]
(if (set? features)
(-> features
(cfeat/migrate-legacy-features)
(set/difference cfeat/backend-only-features))
#{}))))
(defmethod read-section :v1/files
[{:keys [::db/conn ::input ::project-id ::bfc/overwrite ::name] :as system}]
@@ -527,7 +518,7 @@
file-id (:id file)
file-id' (bfc/lookup-index file-id)
file (clean-features file)
file (bfc/clean-file-features file)
thumbnails (:thumbnails file)]
(when (not= file-id expected-file-id)

View File

@@ -12,6 +12,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.json :as json]
[app.common.logging :as l]
[app.common.schema :as sm]
@@ -55,7 +56,8 @@
[:map
[:id ::sm/uuid]
[:name :string]
[:project-id ::sm/uuid]]]]
[:project-id ::sm/uuid]
[:features ::cfeat/features]]]]
[:relations {:optional true}
[:vector
@@ -203,7 +205,10 @@
(dissoc :libraries))
embed-assets
(update :data #(bfc/embed-assets cfg % file-id)))))
(update :data #(bfc/embed-assets cfg % file-id))
:always
(bfc/clean-file-features))))
(defn- resolve-extension
[mtype]
@@ -259,7 +264,8 @@
(vswap! bfc/*state* update :files assoc file-id
{:id file-id
:project-id (:project-id file)
:name (:name file)})
:name (:name file)
:features (:features file)})
(let [file (cond-> (dissoc file :data)
(:options data)

View File

@@ -144,6 +144,8 @@
[:quotes-comments-per-file {:optional true} ::sm/int]
[:quotes-snapshots-per-file {:optional true} ::sm/int]
[:quotes-snapshots-per-team {:optional true} ::sm/int]
[:quotes-team-access-requests-per-team {:optional true} ::sm/int]
[:quotes-team-access-requests-per-requester {:optional true} ::sm/int]
[:auth-data-cookie-domain {:optional true} :string]
[:auth-token-cookie-name {:optional true} :string]

View File

@@ -226,8 +226,8 @@
[:priority {:optional true} [:enum :high :low]]
[:extra-data {:optional true} ::sm/text]])
(def ^:private valid-context?
(sm/validator schema:context))
(def ^:private check-context
(sm/check-fn schema:context))
(defn template-factory
[& {:keys [id schema]}]
@@ -236,10 +236,8 @@
(sm/check-fn schema)
(constantly nil))]
(fn [context]
(assert (valid-context? context) "expected a valid context")
(check-fn context)
(let [email (build-email-template id context)]
(let [context (-> context check-context check-fn)
email (build-email-template id context)]
(when-not email
(ex/raise :type :internal
:code :email-template-does-not-exists
@@ -271,7 +269,7 @@
"Schedule an already defined email to be sent using asynchronously
using worker task."
[{:keys [::conn ::factory] :as context}]
(assert (db/connection? conn) "expected a valid database connection")
(assert (db/connectable? conn) "expected a valid database connection or pool")
(let [email (if factory
(factory context)
@@ -348,7 +346,7 @@
[:subject ::sm/text]
[:content ::sm/text]])
(def feedback
(def user-feedback
"A profile feedback email."
(template-factory
:id ::feedback

View File

@@ -17,7 +17,7 @@
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]))
(declare ^:private send-feedback!)
(declare ^:private send-user-feedback!)
(def ^:private schema:send-user-feedback
[:map {:title "send-user-feedback"}
@@ -34,14 +34,16 @@
:hint "feedback not enabled"))
(let [profile (profile/get-profile pool profile-id)]
(send-feedback! pool profile params)
(send-user-feedback! pool profile params)
nil))
(defn- send-feedback!
(defn- send-user-feedback!
[pool profile params]
(let [dest (cf/get :feedback-destination)]
(let [dest (or (cf/get :user-feedback-destination)
;; LEGACY
(cf/get :feedback-destination))]
(eml/send! {::eml/conn pool
::eml/factory eml/feedback
::eml/factory eml/user-feedback
:from dest
:to dest
:profile profile

View File

@@ -118,11 +118,12 @@
;; feature on frontend and make it permanent on file
features (-> (:features params #{})
(set/intersection cfeat/no-migration-features)
(set/difference cfeat/frontend-only-features)
(set/union features))
params (-> params
(assoc :profile-id profile-id)
(assoc :features features))]
(assoc :features (set/difference features cfeat/frontend-only-features)))]
(quotes/check! cfg {::quotes/id ::quotes/files-per-project
::quotes/team-id team-id

View File

@@ -50,8 +50,7 @@
" where file_id=? and tag=? and deleted_at is null")
res (db/exec! conn [sql file-id tag])]
(->> res
(d/index-by :object-id (fn [row]
(files/resolve-public-uri (:media-id row))))
(d/index-by :object-id :media-id)
(d/without-nils))))
(defn- get-object-thumbnails
@@ -62,8 +61,7 @@
" where file_id=? and deleted_at is null")
res (db/exec! conn [sql file-id])]
(->> res
(d/index-by :object-id (fn [row]
(files/resolve-public-uri (:media-id row))))
(d/index-by :object-id :media-id)
(d/without-nils))))
([conn file-id object-ids]
@@ -75,8 +73,7 @@
res (db/exec! conn [sql file-id ids])]
(->> res
(d/index-by :object-id (fn [row]
(files/resolve-public-uri (:media-id row))))
(d/index-by :object-id :media-id)
(d/without-nils)))))
(sv/defmethod ::get-file-object-thumbnails
@@ -127,8 +124,11 @@
(if-let [frame (-> frames first)]
(let [frame-id (:id frame)
object-id (thc/fmt-object-id (:id file) page-id frame-id "frame")
frame (if-let [thumb (get thumbnails object-id)]
(assoc frame :thumbnail thumb :shapes [])
frame (if-let [media-id (get thumbnails object-id)]
(-> frame
(assoc :thumbnail-id media-id)
(assoc :shapes []))
(dissoc frame :thumbnail))
children-ids

View File

@@ -147,7 +147,7 @@
params (-> params
(assoc :profile-id profile-id)
(assoc :features features)
(assoc :features (set/difference features cfeat/frontend-only-features))
(assoc :team team)
(assoc :file file)
(assoc :changes changes))

View File

@@ -30,7 +30,8 @@
[app.storage :as sto]
[app.util.services :as sv]
[app.util.time :as dt]
[app.worker :as wrk]))
[app.worker :as wrk]
[clojure.set :as set]))
;; --- Helpers & Specs
@@ -416,6 +417,7 @@
::quotes/profile-id profile-id})
(let [features (-> (cfeat/get-enabled-features cf/flags)
(set/difference cfeat/frontend-only-features)
(cfeat/check-client-features! (:features params)))
params (-> params
(assoc :profile-id profile-id)

View File

@@ -532,17 +532,20 @@
team-owner (get-team-owner conn team-id)
file (when (some? file-id)
(get-file-for-team-access-request cfg file-id))
(get-file-for-team-access-request cfg file-id))]
request (upsert-team-access-request conn team-id profile-id)]
;; FIXME missing quotes
(-> cfg
(assoc ::quotes/profile-id profile-id)
(assoc ::quotes/team-id team-id)
(quotes/check! {::quotes/id ::quotes/team-access-requests-per-team}
{::quotes/id ::quotes/team-access-requests-per-requester}))
(teams/check-profile-muted conn requester)
(teams/check-email-bounce conn (:email team-owner) false)
(teams/check-email-spam conn (:email team-owner) true)
(let [factory (cond
(let [request (upsert-team-access-request conn team-id profile-id)
factory (cond
(and (some? file) (:is-default team) is-viewer)
eml/request-file-access-yourpenpot-view
@@ -565,9 +568,9 @@
:team-id team-id
:file-name (:name file)
:file-id file-id
:page-id (:page-id file)}))
:page-id (:page-id file)})
(with-meta {:request request}
{::audit/props {:request 1}})))
(with-meta {:request request}
{::audit/props {:request 1}}))))

View File

@@ -442,7 +442,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; QUOTE: SNAPSHOTS-PER-FILE
;; QUOTE: SNAPSHOTS-PER-TEAM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private schema:snapshots-per-team
@@ -472,6 +472,57 @@
(assoc ::count-sql [sql:get-snapshots-per-team team-id])
(generic-check!)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; QUOTE: TEAM-ACCESS-REQUESTS-PER-TEAM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private schema:team-access-requests-per-team
[:map
[::profile-id ::sm/uuid]
[::team-id ::sm/uuid]])
(def ^:private valid-team-access-requests-per-team-quote?
(sm/lazy-validator schema:team-access-requests-per-team))
(def ^:private sql:get-team-access-requests-per-team
"SELECT count(*) AS total
FROM team_access_request AS tar
WHERE tar.team_id = ?")
(defmethod check-quote ::team-access-requests-per-team
[{:keys [::profile-id ::team-id ::target] :as quote}]
(assert (valid-team-access-requests-per-team-quote? quote) "invalid quote parameters")
(-> quote
(assoc ::default (cf/get :quotes-team-access-requests-per-team Integer/MAX_VALUE))
(assoc ::quote-sql [sql:get-quotes-2 target team-id profile-id profile-id])
(assoc ::count-sql [sql:get-team-access-requests-per-team team-id])
(generic-check!)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; QUOTE: TEAM-ACCESS-REQUESTS-PER-REQUESTER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private schema:team-access-requests-per-requester
[:map
[::profile-id ::sm/uuid]])
(def ^:private valid-team-access-requests-per-requester-quote?
(sm/lazy-validator schema:team-access-requests-per-requester))
(def ^:private sql:get-team-access-requests-per-requester
"SELECT count(*) AS total
FROM team_access_request AS tar
WHERE tar.requester_id = ?")
(defmethod check-quote ::team-access-requests-per-requester
[{:keys [::profile-id ::target] :as quote}]
(assert (valid-team-access-requests-per-requester-quote? quote) "invalid quote parameters")
(-> quote
(assoc ::default (cf/get :quotes-team-access-requests-per-requester Integer/MAX_VALUE))
(assoc ::quote-sql [sql:get-quotes-1 target profile-id])
(assoc ::count-sql [sql:get-team-access-requests-per-requester profile-id])
(generic-check!)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; QUOTE: DEFAULT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -1090,8 +1090,7 @@
(t/is (contains? result :file-id))
(t/is (= (:id file) (:file-id result)))
(t/is (str/starts-with? (get-in result [:page :objects frame1-id :thumbnail])
"http://localhost:3449/assets/by-id/"))
(t/is (uuid? (get-in result [:page :objects frame1-id :thumbnail-id])))
(t/is (= [] (get-in result [:page :objects frame1-id :shapes]))))
;; Delete thumbnail data

View File

@@ -1,11 +1,11 @@
{
"name": "common",
"version": "1.0.0",
"main": "index.js",
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.3.1",
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"
@@ -15,6 +15,8 @@
"sax": "^1.4.1"
},
"devDependencies": {
"concurrently": "^9.0.1",
"nodemon": "^3.1.7",
"shadow-cljs": "2.28.18",
"source-map-support": "^0.5.21",
"ws": "^8.17.0"
@@ -23,9 +25,9 @@
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
"lint:clj": "clj-kondo --parallel=true --lint src/",
"test:watch": "clojure -M:dev:shadow-cljs watch test",
"test:compile": "clojure -M:dev:shadow-cljs compile test --config-merge '{:autorun false}'",
"test:run": "node target/test.js",
"test": "yarn run test:compile && yarn run test:run"
"lint": "yarn run lint:clj",
"watch:test": "concurrently \"clojure -M:dev:shadow-cljs watch test\" \"nodemon -C -d 2 -w target/tests/ --exec 'node target/tests/test.js'\"",
"build:test": "clojure -M:dev:shadow-cljs compile test",
"test": "yarn run build:test && node target/tests/test.js"
}
}

View File

@@ -1,19 +1,15 @@
{:deps {:aliases [:dev]}
:builds
{:test
{:target :node-test
:output-to "target/test.js"
:output-dir "target/test/"
:ns-regexp "^common-tests.*-test$"
:autorun true
{:target :esm
:output-dir "target/tests"
:runtime :node
:js-options {:js-provider :import}
:compiler-options
{:output-feature-set :es-next
:output-wrapper false
:source-map true
:source-map-include-sources-content true
:source-map-detail-level :all
:warnings {:fn-deprecated false}}}
:modules
{:test {:init-fn common-tests.runner/-main
:prepend-js "globalThis.navigator = {userAgent: \"\"}"}}}
:bench
{:target :node-script

View File

@@ -59,7 +59,8 @@
#{"fdata/shape-data-type"
"styles/v2"
"layout/grid"
"components/v2"})
"components/v2"
"plugins/runtime"})
;; A set of features which only affects on frontend and can be enabled
;; and disabled freely by the user any time. This features does not
@@ -154,6 +155,7 @@
team-features (into #{} xf-remove-ephimeral (:features team))]
(-> enabled-features
(set/intersection no-migration-features)
(set/difference frontend-only-features)
(set/union team-features))))
(defn check-client-features!

View File

@@ -409,12 +409,14 @@
;; Resize parent containers that need to
(pcb/resize-parents parents))))
(defn change-show-in-viewer [shape hide?]
(defn change-show-in-viewer
[shape hide?]
(assoc shape :hide-in-viewer hide?))
(defn add-new-interaction [shape interaction]
(-> shape
(update :interactions ctsi/add-interaction interaction)))
(defn add-new-interaction
[shape interaction]
(update shape :interactions ctsi/add-interaction interaction))
(defn show-in-viewer [shape]
(defn show-in-viewer
[shape]
(dissoc shape :hide-in-viewer))

View File

@@ -27,10 +27,22 @@
#?(:clj (Instant/now)
:cljs (.local ^js DateTime)))
#?(:clj
(defn is-after?
[one other]
(.isAfter one other)))
(defn is-after?
"Analgous to: da > db"
[da db]
(let [result (compare da db)]
(cond
(neg? result) false
(zero? result) false
:else true)))
(defn is-before?
[da db]
(let [result (compare da db)]
(cond
(neg? result) true
(zero? result) false
:else false)))
(defn instant?
[o]

View File

@@ -529,13 +529,6 @@
(or (d/not-empty? (dm/get-prop modifiers :geometry-child))
(d/not-empty? (dm/get-prop modifiers :structure-child))))
(defn only-move?
"Returns true if there are only move operations"
[modifiers]
(let [move-op? #(= :move (dm/get-prop % :type))]
(and (every? move-op? (dm/get-prop modifiers :geometry-child))
(every? move-op? (dm/get-prop modifiers :geometry-parent)))))
(defn has-geometry?
[modifiers]
(or (d/not-empty? (dm/get-prop modifiers :geometry-parent))
@@ -550,6 +543,14 @@
[modifiers]
(d/not-empty? (dm/get-prop modifiers :structure-child)))
(defn only-move?
"Returns true if there are only move operations"
[modifiers]
(let [move-op? #(= :move (dm/get-prop % :type))]
(and (not (has-structure? modifiers))
(every? move-op? (dm/get-prop modifiers :geometry-child))
(every? move-op? (dm/get-prop modifiers :geometry-parent)))))
;; Extract subsets of modifiers
(defn select-child

View File

@@ -0,0 +1,89 @@
;; 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 common-tests.runner
(:require
[clojure.test :as t]
[common-tests.colors-test]
[common-tests.data-test]
[common-tests.files-builder-test]
[common-tests.files-changes-test]
[common-tests.files-migrations-test]
[common-tests.geom-point-test]
[common-tests.geom-shapes-test]
[common-tests.geom-test]
[common-tests.logic.chained-propagation-test]
[common-tests.logic.comp-creation-test]
[common-tests.logic.comp-detach-with-nested-test]
[common-tests.logic.comp-remove-swap-slots-test]
[common-tests.logic.comp-reset-test]
[common-tests.logic.comp-sync-test]
[common-tests.logic.comp-touched-test]
[common-tests.logic.copying-and-duplicating-test]
[common-tests.logic.duplicated-pages-test]
[common-tests.logic.move-shapes-test]
[common-tests.logic.multiple-nesting-levels-test]
[common-tests.logic.swap-and-reset-test]
[common-tests.logic.swap-as-override-test]
[common-tests.pages-helpers-test]
[common-tests.record-test]
[common-tests.schema-test]
[common-tests.svg-path-test]
[common-tests.svg-test]
[common-tests.text-test]
[common-tests.time-test]
[common-tests.types-modifiers-test]
[common-tests.types-shape-interactions-test]
[common-tests.types.shape-decode-encode-test]
[common-tests.types.tokens-lib-test]
[common-tests.types.types-component-test]
[common-tests.types.types-libraries-test]))
#?(:cljs (enable-console-print!))
#?(:cljs
(defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
(if (cljs.test/successful? m)
(.exit js/process 0)
(.exit js/process 1))))
(defn -main
[& args]
(t/run-tests
'common-tests.colors-test
'common-tests.data-test
'common-tests.files-builder-test
'common-tests.files-changes-test
'common-tests.files-migrations-test
'common-tests.geom-point-test
'common-tests.geom-shapes-test
'common-tests.geom-test
'common-tests.logic.chained-propagation-test
'common-tests.logic.comp-creation-test
'common-tests.logic.comp-detach-with-nested-test
'common-tests.logic.comp-remove-swap-slots-test
'common-tests.logic.comp-reset-test
'common-tests.logic.comp-sync-test
'common-tests.logic.comp-touched-test
'common-tests.logic.copying-and-duplicating-test
'common-tests.logic.duplicated-pages-test
'common-tests.logic.move-shapes-test
'common-tests.logic.multiple-nesting-levels-test
'common-tests.logic.swap-and-reset-test
'common-tests.logic.swap-as-override-test
'common-tests.pages-helpers-test
'common-tests.record-test
'common-tests.schema-test
'common-tests.svg-path-test
'common-tests.svg-test
'common-tests.text-test
'common-tests.types-modifiers-test
'common-tests.types-shape-interactions-test
'common-tests.types.shape-decode-encode-test
'common-tests.types.types-component-test
'common-tests.types.types-libraries-test
'common-tests.types.tokens-lib-test
'common-tests.time-test))

View File

@@ -0,0 +1,16 @@
;; 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 common-tests.time-test
(:require
[app.common.time :as dt]
[clojure.test :as t]))
(t/deftest compare-time
(let [dta (dt/parse-instant 10000)
dtb (dt/parse-instant 20000)]
(t/is (false? (dt/is-after? dta dtb)))
(t/is (true? (dt/is-before? dta dtb)))))

View File

@@ -148,4 +148,4 @@
;; (app.common.pprint/pprint shape)
;; (app.common.pprint/pprint shape-3)
(= shape shape-3)))
{:num 1000})))
{:num 100})))

View File

@@ -14,8 +14,16 @@
[app.common.types.tokens-lib :as ctob]
[clojure.test :as t]))
(t/testing "token"
(t/deftest make-token
(defn setup-virtual-time
[next]
(let [current (volatile! (inst-ms (dt/now)))]
(with-redefs [dt/now #(dt/parse-instant (vswap! current inc))]
(next))))
(t/use-fixtures :once setup-virtual-time)
(t/deftest tokens
(t/testing "make-token"
(let [now (dt/now)
token1 (ctob/make-token :name "test-token-1"
:type :boolean
@@ -40,14 +48,14 @@
(t/is (= (:modified-at token2) now))
(t/is (ctob/valid-token? token2))))
(t/deftest invalid-tokens
(t/testing "invalid-tokens"
(let [args {:name 777
:type :invalid}]
(t/is (thrown-with-msg? Exception #"expected valid token"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid token"
(apply ctob/make-token args)))
(t/is (false? (ctob/valid-token? {})))))
(t/deftest find-token-value-references
(t/testing "find-token-value-references"
(t/testing "finds references inside curly braces in a string"
(t/is (= #{"foo" "bar"} (ctob/find-token-value-references "{foo} + {bar}")))
(t/testing "ignores extra text"
@@ -57,8 +65,8 @@
(t/testing "handles edge-case for extra curly braces"
(t/is (= #{"foo" "bar"} (ctob/find-token-value-references "{foo}} + {bar}"))))))
(t/testing "token-set"
(t/deftest make-token-set
(t/deftest token-set
(t/testing "make-token-set"
(let [now (dt/now)
token-set1 (ctob/make-token-set :name "test-token-set-1")
token-set2 (ctob/make-token-set :name "test-token-set-2"
@@ -76,13 +84,13 @@
(t/is (= (:modified-at token-set2) now))
(t/is (empty? (:tokens token-set2)))))
(t/deftest invalid-token-set
(t/testing "invalid-token-set"
(let [args {:name 777
:description 999}]
(t/is (thrown-with-msg? Exception #"expected valid token set"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid token set"
(apply ctob/make-token-set args)))))
(t/deftest move-token-set
(t/testing "move-token-set"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "A"))
(ctob/add-set (ctob/make-token-set :name "B"))
@@ -107,7 +115,7 @@
(t/is (= original-order (move "A" "foo/bar/baz")))
(t/is (= original-order (move "Missing" "Move"))))))
(t/deftest tokens-tree
(t/testing "tokens-tree"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "A"
:tokens {"foo.bar.baz" (ctob/make-token :name "foo.bar.baz"
@@ -125,8 +133,8 @@
(t/is (= (get-in expected ["foo" "bar" "bam" :name]) "foo.bar.bam"))
(t/is (= (get-in expected ["baz" "boo" :name]) "baz.boo")))))
(t/testing "token-theme"
(t/deftest make-token-theme
(t/deftest token-theme
(t/testing "make-token-theme"
(let [now (dt/now)
token-theme1 (ctob/make-token-theme :name "test-token-theme-1")
token-theme2 (ctob/make-token-theme :name "test-token-theme-2"
@@ -150,24 +158,24 @@
(t/is (= (:modified-at token-theme2) now))
(t/is (empty? (:sets token-theme2)))))
(t/deftest invalid-token-theme
(t/testing "invalid-token-theme"
(let [args {:name 777
:group nil
:description 999
:is-source 42}]
(t/is (thrown-with-msg? Exception #"expected valid token theme"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid token theme"
(apply ctob/make-token-theme args))))))
(t/testing "tokens-lib"
(t/deftest make-tokens-lib
(t/deftest tokens-lib
(t/testing "make-tokens-lib"
(let [tokens-lib (ctob/make-tokens-lib)]
(t/is (= (ctob/set-count tokens-lib) 0))))
(t/deftest invalid-tokens-lib
(t/testing "invalid-tokens-lib"
(let [args {:sets nil
:themes nil}]
(t/is (thrown-with-msg? Exception #"expected valid tokens lib"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid tokens lib"
(apply ctob/make-tokens-lib args))))))
@@ -263,8 +271,8 @@
(t/is (nil? token-set')))))
(t/testing "token in a lib"
(t/deftest add-token
(t/deftest token-in-a-lib
(t/testing "add-token"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
token (ctob/make-token :name "test-token"
@@ -283,7 +291,7 @@
(t/is (= (:name token') "test-token"))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/deftest update-token
(t/testing "update-token"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set"
@@ -324,7 +332,7 @@
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest rename-token
(t/testing "rename-token"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set"
@@ -356,7 +364,7 @@
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest delete-token
(t/testing "delete-token"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set"
@@ -377,7 +385,7 @@
(t/is (nil? token'))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/deftest list-active-themes-tokens-in-order
(t/testing "list-active-themes-tokens-in-order"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :name "out-of-order-theme"
;; Out of order sets in theme
@@ -405,8 +413,8 @@
(t/is (= ["set-a-token" "set-b-token"] expected-token-names)))))
(t/testing "token-theme in a lib"
(t/deftest add-token-theme
(t/deftest token-theme-in-a-lib
(t/testing "add-token-theme"
(let [tokens-lib (ctob/make-tokens-lib)
token-theme (ctob/make-token-theme :name "test-token-theme")
tokens-lib' (ctob/add-theme tokens-lib token-theme)
@@ -418,7 +426,7 @@
(t/is (= (first token-themes') token-theme))
(t/is (= token-theme' token-theme))))
(t/deftest update-token-theme
(t/testing "update-token-theme"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :name "test-token-theme")))
@@ -440,7 +448,7 @@
(t/is (= (:description token-theme') "some description"))
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme)))))
(t/deftest rename-token-theme
(t/testing "rename-token-theme"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :name "test-token-theme")))
@@ -457,7 +465,7 @@
(t/is (= (:name token-theme') "updated-name"))
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme)))))
(t/deftest delete-token-theme
(t/testing "delete-token-theme"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :name "test-token-theme")))
@@ -470,7 +478,7 @@
(t/is (= (ctob/theme-count tokens-lib') 0))
(t/is (nil? token-theme'))))
(t/deftest toggle-set-in-theme
(t/testing "toggle-set-in-theme"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "token-set-1"))
(ctob/add-set (ctob/make-token-set :name "token-set-2"))
@@ -487,8 +495,8 @@
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme))))))
(t/testing "serialization"
(t/deftest transit-serialization
(t/deftest serialization
(t/testing "transit-serialization"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set" (ctob/make-token :name "test-token"
@@ -503,23 +511,24 @@
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (ctob/theme-count tokens-lib') 1))))
(t/deftest fressian-serialization
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set" (ctob/make-token :name "test-token"
:type :boolean
:value true))
(ctob/add-theme (ctob/make-token-theme :name "test-token-theme"))
(ctob/toggle-set-in-theme "" "test-token-theme" "test-token-set"))
encoded-blob (fres/encode tokens-lib)
tokens-lib' (fres/decode encoded-blob)]
#?(:clj
(t/testing "fressian-serialization"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set" (ctob/make-token :name "test-token"
:type :boolean
:value true))
(ctob/add-theme (ctob/make-token-theme :name "test-token-theme"))
(ctob/toggle-set-in-theme "" "test-token-theme" "test-token-set"))
encoded-blob (fres/encode tokens-lib)
tokens-lib' (fres/decode encoded-blob)]
(t/is (ctob/valid-tokens-lib? tokens-lib'))
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (ctob/theme-count tokens-lib') 1)))))
(t/is (ctob/valid-tokens-lib? tokens-lib'))
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (ctob/theme-count tokens-lib') 1))))))
(t/testing "grouping"
(t/deftest split-and-join
(t/deftest grouping
(t/testing "split-and-join"
(let [name "group/subgroup/name"
path (ctob/split-path name "/")
name' (ctob/join-path path "/")]
@@ -528,14 +537,14 @@
(t/is (= (nth path 2) "name"))
(t/is (= name' name))))
(t/deftest remove-spaces
(t/testing "remove-spaces"
(let [name "group / subgroup / name"
path (ctob/split-path name "/")]
(t/is (= (first path) "group"))
(t/is (= (second path) "subgroup"))
(t/is (= (nth path 2) "name"))))
(t/deftest group-and-ungroup
(t/testing "group-and-ungroup"
(let [token-set1 (ctob/make-token-set :name "token-set1")
token-set2 (ctob/make-token-set :name "some group/token-set2")
@@ -548,7 +557,7 @@
(t/is (= (:name token-set1'') "token-set1"))
(t/is (= (:name token-set2'') "some group/token-set2"))))
(t/deftest get-groups-str
(t/testing "get-groups-str"
(let [token-set1 (ctob/make-token-set :name "token-set1")
token-set2 (ctob/make-token-set :name "some-group/token-set2")
token-set3 (ctob/make-token-set :name "some-group/some-subgroup/token-set3")]
@@ -556,7 +565,7 @@
(t/is (= (ctob/get-groups-str token-set2 "/") "some-group"))
(t/is (= (ctob/get-groups-str token-set3 "/") "some-group/some-subgroup"))))
(t/deftest get-final-name
(t/testing "get-final-name"
(let [token-set1 (ctob/make-token-set :name "token-set1")
token-set2 (ctob/make-token-set :name "some-group/token-set2")
token-set3 (ctob/make-token-set :name "some-group/some-subgroup/token-set3")]
@@ -565,7 +574,7 @@
(t/is (= (ctob/get-final-name token-set3 "/") "token-set3"))))
(t/testing "grouped tokens"
(t/deftest grouped-tokens
(t/testing "grouped-tokens"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set"
@@ -599,7 +608,7 @@
(t/is (= (:name (nth tokens-list 3)) "group1.subgroup11.token4"))
(t/is (= (:name (nth tokens-list 4)) "group2.token5"))))
(t/deftest update-token-in-groups
(t/testing "update-token-in-groups"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set"
@@ -634,7 +643,7 @@
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest rename-token-in-groups
(t/testing "rename-token-in-groups"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set"
@@ -668,7 +677,7 @@
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest move-token-of-group
(t/testing "move-token-of-group"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set"
@@ -703,7 +712,7 @@
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest delete-token-in-group
(t/testing "delete-token-in-group"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-token-in-set "test-token-set"
@@ -727,7 +736,7 @@
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))))
(t/testing "grouped sets"
(t/deftest grouped-sets
(t/testing "grouped-sets"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "token-set-1"))
(ctob/add-set (ctob/make-token-set :name "group1/token-set-2"))
@@ -786,7 +795,7 @@
(t/is (= (ctob/group? (second node-set5)) false))
(t/is (= (:name (second node-set5)) "group2/token-set-5"))))
(t/deftest update-set-in-groups
(t/testing "update-set-in-groups"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "token-set-1"))
(ctob/add-set (ctob/make-token-set :name "group1/token-set-2"))
@@ -812,7 +821,7 @@
(t/is (= (:description token-set') "some description"))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/deftest rename-set-in-groups
(t/testing "rename-set-in-groups"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "token-set-1"))
(ctob/add-set (ctob/make-token-set :name "group1/token-set-2"))
@@ -839,7 +848,7 @@
(t/is (= (:description token-set') nil))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/deftest move-set-of-group
(t/testing "move-set-of-group"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "token-set-1"))
(ctob/add-set (ctob/make-token-set :name "group1/token-set-2"))
@@ -868,7 +877,7 @@
(t/is (= (:description token-set') nil))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/deftest delete-set-in-group
(t/testing "delete-set-in-group"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "token-set-1"))
(ctob/add-set (ctob/make-token-set :name "group1/token-set-2")))
@@ -884,7 +893,7 @@
(t/is (nil? token-set')))))
(t/testing "grouped themes"
(t/deftest grouped-themes
(t/testing "grouped-themes"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :group "" :name "token-theme-1"))
(ctob/add-theme (ctob/make-token-theme :group "group1" :name "token-theme-2"))
@@ -941,7 +950,7 @@
(t/is (= (ctob/group? (second node-theme4)) false))
(t/is (= (:name (second node-theme4)) "token-theme-4"))))
(t/deftest update-theme-in-groups
(t/testing "update-theme-in-groups"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :group "" :name "token-theme-1"))
(ctob/add-theme (ctob/make-token-theme :group "group1" :name "token-theme-2"))
@@ -967,7 +976,7 @@
(t/is (= (:description token-theme') "some description"))
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme)))))
(t/deftest get-theme-groups
(t/testing "get-theme-groups"
(let [token-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :group "" :name "token-theme-1"))
(ctob/add-theme (ctob/make-token-theme :group "group1" :name "token-theme-2"))
@@ -976,13 +985,14 @@
token-groups (ctob/get-theme-groups token-lib)]
(t/is (= token-groups ["group1" "group2"]))))
(t/deftest rename-theme-in-groups
(t/testing "rename-theme-in-groups"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :group "" :name "token-theme-1"))
(ctob/add-theme (ctob/make-token-theme :group "group1" :name "token-theme-2"))
(ctob/add-theme (ctob/make-token-theme :group "group1" :name "token-theme-3"))
(ctob/add-theme (ctob/make-token-theme :group "group2" :name "token-theme-4")))
tokens-lib' (-> tokens-lib
(ctob/update-theme "group1" "token-theme-2"
(fn [token-theme]
@@ -1003,7 +1013,7 @@
(t/is (= (:description token-theme') nil))
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme)))))
(t/deftest move-theme-of-group
(t/testing "move-theme-of-group"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :group "" :name "token-theme-1"))
(ctob/add-theme (ctob/make-token-theme :group "group1" :name "token-theme-2"))
@@ -1033,7 +1043,7 @@
(t/is (= (:description token-theme') nil))
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme)))))
(t/deftest delete-theme-in-group
(t/testing "delete-theme-in-group"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :group "" :name "token-theme-1"))
(ctob/add-theme (ctob/make-token-theme :group "group1" :name "token-theme-2")))
@@ -1049,8 +1059,8 @@
(t/is (nil? token-theme'))))))
#?(:clj
(t/testing "dtcg encoding/decoding"
(t/deftest decode-dtcg-json
(t/deftest dtcg-encoding-decoding
(t/testing "decode-dtcg-json"
(let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-example.json")
(tr/decode-str))
lib (ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json)
@@ -1078,7 +1088,7 @@
(t/testing "invalid tokens got discarded"
(t/is (nil? (get-set-token "typography" "H1.Bold"))))))
(t/deftest encode-dtcg-json
(t/testing "encode-dtcg-json"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "core"
:tokens {"colors.red.600"
@@ -1111,7 +1121,7 @@
"$type" "color"}}}}}
expected))))
(t/deftest encode-decode-dtcg-json
(t/testing "encode-decode-dtcg-json"
(with-redefs [dt/now (constantly #inst "2024-10-16T12:01:20.257840055-00:00")]
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "core"

View File

@@ -88,7 +88,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-styles@npm:^4.0.0":
"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0":
version: 4.3.0
resolution: "ansi-styles@npm:4.3.0"
dependencies:
@@ -104,6 +104,16 @@ __metadata:
languageName: node
linkType: hard
"anymatch@npm:~3.1.2":
version: 3.1.3
resolution: "anymatch@npm:3.1.3"
dependencies:
normalize-path: "npm:^3.0.0"
picomatch: "npm:^2.0.4"
checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac
languageName: node
linkType: hard
"asn1.js@npm:^4.10.1":
version: 4.10.1
resolution: "asn1.js@npm:4.10.1"
@@ -139,6 +149,13 @@ __metadata:
languageName: node
linkType: hard
"binary-extensions@npm:^2.0.0":
version: 2.3.0
resolution: "binary-extensions@npm:2.3.0"
checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5
languageName: node
linkType: hard
"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9":
version: 4.12.0
resolution: "bn.js@npm:4.12.0"
@@ -153,6 +170,16 @@ __metadata:
languageName: node
linkType: hard
"brace-expansion@npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"
dependencies:
balanced-match: "npm:^1.0.0"
concat-map: "npm:0.0.1"
checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668
languageName: node
linkType: hard
"brace-expansion@npm:^2.0.1":
version: 2.0.1
resolution: "brace-expansion@npm:2.0.1"
@@ -162,6 +189,15 @@ __metadata:
languageName: node
linkType: hard
"braces@npm:~3.0.2":
version: 3.0.3
resolution: "braces@npm:3.0.3"
dependencies:
fill-range: "npm:^7.1.1"
checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04
languageName: node
linkType: hard
"brorand@npm:^1.0.1, brorand@npm:^1.1.0":
version: 1.1.0
resolution: "brorand@npm:1.1.0"
@@ -308,6 +344,35 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^4.1.2":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
dependencies:
ansi-styles: "npm:^4.1.0"
supports-color: "npm:^7.1.0"
checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880
languageName: node
linkType: hard
"chokidar@npm:^3.5.2":
version: 3.6.0
resolution: "chokidar@npm:3.6.0"
dependencies:
anymatch: "npm:~3.1.2"
braces: "npm:~3.0.2"
fsevents: "npm:~2.3.2"
glob-parent: "npm:~5.1.2"
is-binary-path: "npm:~2.1.0"
is-glob: "npm:~4.0.1"
normalize-path: "npm:~3.0.0"
readdirp: "npm:~3.6.0"
dependenciesMeta:
fsevents:
optional: true
checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462
languageName: node
linkType: hard
"chownr@npm:^2.0.0":
version: 2.0.0
resolution: "chownr@npm:2.0.0"
@@ -332,6 +397,17 @@ __metadata:
languageName: node
linkType: hard
"cliui@npm:^8.0.1":
version: 8.0.1
resolution: "cliui@npm:8.0.1"
dependencies:
string-width: "npm:^4.2.0"
strip-ansi: "npm:^6.0.1"
wrap-ansi: "npm:^7.0.0"
checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5
languageName: node
linkType: hard
"color-convert@npm:^2.0.1":
version: 2.0.1
resolution: "color-convert@npm:2.0.1"
@@ -352,14 +428,41 @@ __metadata:
version: 0.0.0-use.local
resolution: "common@workspace:."
dependencies:
concurrently: "npm:^9.0.1"
luxon: "npm:^3.4.4"
nodemon: "npm:^3.1.7"
sax: "npm:^1.4.1"
shadow-cljs: "npm:2.28.11"
shadow-cljs: "npm:2.28.18"
source-map-support: "npm:^0.5.21"
ws: "npm:^8.17.0"
languageName: unknown
linkType: soft
"concat-map@npm:0.0.1":
version: 0.0.1
resolution: "concat-map@npm:0.0.1"
checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f
languageName: node
linkType: hard
"concurrently@npm:^9.0.1":
version: 9.1.0
resolution: "concurrently@npm:9.1.0"
dependencies:
chalk: "npm:^4.1.2"
lodash: "npm:^4.17.21"
rxjs: "npm:^7.8.1"
shell-quote: "npm:^1.8.1"
supports-color: "npm:^8.1.1"
tree-kill: "npm:^1.2.2"
yargs: "npm:^17.7.2"
bin:
conc: dist/bin/concurrently.js
concurrently: dist/bin/concurrently.js
checksum: 10c0/f2f42f94dde508bfbaf47b5ac654db9e8a4bf07d3d7b6267dd058ae6f362eec677ae7c8ede398d081e5fd0d1de5811dc9a53e57d3f1f68e72ac6459db9e0896b
languageName: node
linkType: hard
"console-browserify@npm:^1.1.0":
version: 1.2.0
resolution: "console-browserify@npm:1.2.0"
@@ -460,6 +563,18 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:^4":
version: 4.3.7
resolution: "debug@npm:4.3.7"
dependencies:
ms: "npm:^2.1.3"
peerDependenciesMeta:
supports-color:
optional: true
checksum: 10c0/1471db19c3b06d485a622d62f65947a19a23fbd0dd73f7fd3eafb697eec5360cde447fb075919987899b1a2096e85d35d4eb5a4de09a57600ac9cf7e6c8e768b
languageName: node
linkType: hard
"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4":
version: 1.1.4
resolution: "define-data-property@npm:1.1.4"
@@ -585,6 +700,13 @@ __metadata:
languageName: node
linkType: hard
"escalade@npm:^3.1.1":
version: 3.2.0
resolution: "escalade@npm:3.2.0"
checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65
languageName: node
linkType: hard
"events@npm:^3.0.0":
version: 3.3.0
resolution: "events@npm:3.3.0"
@@ -610,6 +732,15 @@ __metadata:
languageName: node
linkType: hard
"fill-range@npm:^7.1.1":
version: 7.1.1
resolution: "fill-range@npm:7.1.1"
dependencies:
to-regex-range: "npm:^5.0.1"
checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018
languageName: node
linkType: hard
"foreground-child@npm:^3.1.0":
version: 3.1.1
resolution: "foreground-child@npm:3.1.1"
@@ -638,6 +769,25 @@ __metadata:
languageName: node
linkType: hard
"fsevents@npm:~2.3.2":
version: 2.3.3
resolution: "fsevents@npm:2.3.3"
dependencies:
node-gyp: "npm:latest"
checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60
conditions: os=darwin
languageName: node
linkType: hard
"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>":
version: 2.3.3
resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1"
dependencies:
node-gyp: "npm:latest"
conditions: os=darwin
languageName: node
linkType: hard
"function-bind@npm:^1.1.2":
version: 1.1.2
resolution: "function-bind@npm:1.1.2"
@@ -645,6 +795,13 @@ __metadata:
languageName: node
linkType: hard
"get-caller-file@npm:^2.0.5":
version: 2.0.5
resolution: "get-caller-file@npm:2.0.5"
checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde
languageName: node
linkType: hard
"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4":
version: 1.2.4
resolution: "get-intrinsic@npm:1.2.4"
@@ -658,6 +815,15 @@ __metadata:
languageName: node
linkType: hard
"glob-parent@npm:~5.1.2":
version: 5.1.2
resolution: "glob-parent@npm:5.1.2"
dependencies:
is-glob: "npm:^4.0.1"
checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee
languageName: node
linkType: hard
"glob@npm:^10.2.2, glob@npm:^10.3.10":
version: 10.3.16
resolution: "glob@npm:10.3.16"
@@ -689,6 +855,20 @@ __metadata:
languageName: node
linkType: hard
"has-flag@npm:^3.0.0":
version: 3.0.0
resolution: "has-flag@npm:3.0.0"
checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473
languageName: node
linkType: hard
"has-flag@npm:^4.0.0":
version: 4.0.0
resolution: "has-flag@npm:4.0.0"
checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1
languageName: node
linkType: hard
"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2":
version: 1.0.2
resolution: "has-property-descriptors@npm:1.0.2"
@@ -813,6 +993,13 @@ __metadata:
languageName: node
linkType: hard
"ignore-by-default@npm:^1.0.1":
version: 1.0.1
resolution: "ignore-by-default@npm:1.0.1"
checksum: 10c0/9ab6e70e80f7cc12735def7ecb5527cfa56ab4e1152cd64d294522827f2dcf1f6d85531241537dc3713544e88dd888f65cb3c49c7b2cddb9009087c75274e533
languageName: node
linkType: hard
"imurmurhash@npm:^0.1.4":
version: 0.1.4
resolution: "imurmurhash@npm:0.1.4"
@@ -851,6 +1038,22 @@ __metadata:
languageName: node
linkType: hard
"is-binary-path@npm:~2.1.0":
version: 2.1.0
resolution: "is-binary-path@npm:2.1.0"
dependencies:
binary-extensions: "npm:^2.0.0"
checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38
languageName: node
linkType: hard
"is-extglob@npm:^2.1.1":
version: 2.1.1
resolution: "is-extglob@npm:2.1.1"
checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^3.0.0":
version: 3.0.0
resolution: "is-fullwidth-code-point@npm:3.0.0"
@@ -858,6 +1061,15 @@ __metadata:
languageName: node
linkType: hard
"is-glob@npm:^4.0.1, is-glob@npm:~4.0.1":
version: 4.0.3
resolution: "is-glob@npm:4.0.3"
dependencies:
is-extglob: "npm:^2.1.1"
checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a
languageName: node
linkType: hard
"is-lambda@npm:^1.0.1":
version: 1.0.1
resolution: "is-lambda@npm:1.0.1"
@@ -865,6 +1077,13 @@ __metadata:
languageName: node
linkType: hard
"is-number@npm:^7.0.0":
version: 7.0.0
resolution: "is-number@npm:7.0.0"
checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811
languageName: node
linkType: hard
"isarray@npm:^1.0.0, isarray@npm:~1.0.0":
version: 1.0.0
resolution: "isarray@npm:1.0.0"
@@ -906,6 +1125,13 @@ __metadata:
languageName: node
linkType: hard
"lodash@npm:^4.17.21":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c
languageName: node
linkType: hard
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0":
version: 10.2.2
resolution: "lru-cache@npm:10.2.2"
@@ -977,6 +1203,15 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
dependencies:
brace-expansion: "npm:^1.1.7"
checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311
languageName: node
linkType: hard
"minimatch@npm:^9.0.1":
version: 9.0.4
resolution: "minimatch@npm:9.0.4"
@@ -1086,6 +1321,13 @@ __metadata:
languageName: node
linkType: hard
"ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"
checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48
languageName: node
linkType: hard
"negotiator@npm:^0.6.3":
version: 0.6.3
resolution: "negotiator@npm:0.6.3"
@@ -1144,6 +1386,26 @@ __metadata:
languageName: node
linkType: hard
"nodemon@npm:^3.1.7":
version: 3.1.7
resolution: "nodemon@npm:3.1.7"
dependencies:
chokidar: "npm:^3.5.2"
debug: "npm:^4"
ignore-by-default: "npm:^1.0.1"
minimatch: "npm:^3.1.2"
pstree.remy: "npm:^1.1.8"
semver: "npm:^7.5.3"
simple-update-notifier: "npm:^2.0.0"
supports-color: "npm:^5.5.0"
touch: "npm:^3.1.0"
undefsafe: "npm:^2.0.5"
bin:
nodemon: bin/nodemon.js
checksum: 10c0/e0b46939abdbce251b1d6281005a5763cee57db295bb00bc4a753b0f5320dac00fe53547fb6764c70a086cf6d1238875cccb800fbc71544b3ecbd3ef71183c87
languageName: node
linkType: hard
"nopt@npm:^7.0.0":
version: 7.2.1
resolution: "nopt@npm:7.2.1"
@@ -1155,6 +1417,13 @@ __metadata:
languageName: node
linkType: hard
"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0":
version: 3.0.0
resolution: "normalize-path@npm:3.0.0"
checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046
languageName: node
linkType: hard
"object-inspect@npm:^1.13.1":
version: 1.13.1
resolution: "object-inspect@npm:1.13.1"
@@ -1255,6 +1524,13 @@ __metadata:
languageName: node
linkType: hard
"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1":
version: 2.3.1
resolution: "picomatch@npm:2.3.1"
checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be
languageName: node
linkType: hard
"proc-log@npm:^3.0.0":
version: 3.0.0
resolution: "proc-log@npm:3.0.0"
@@ -1293,6 +1569,13 @@ __metadata:
languageName: node
linkType: hard
"pstree.remy@npm:^1.1.8":
version: 1.1.8
resolution: "pstree.remy@npm:1.1.8"
checksum: 10c0/30f78c88ce6393cb3f7834216cb6e282eb83c92ccb227430d4590298ab2811bc4a4745f850a27c5178e79a8f3e316591de0fec87abc19da648c2b3c6eb766d14
languageName: node
linkType: hard
"public-encrypt@npm:^4.0.0":
version: 4.0.3
resolution: "public-encrypt@npm:4.0.3"
@@ -1375,6 +1658,15 @@ __metadata:
languageName: node
linkType: hard
"readdirp@npm:~3.6.0":
version: 3.6.0
resolution: "readdirp@npm:3.6.0"
dependencies:
picomatch: "npm:^2.2.1"
checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b
languageName: node
linkType: hard
"readline-sync@npm:^1.4.7":
version: 1.4.10
resolution: "readline-sync@npm:1.4.10"
@@ -1382,6 +1674,13 @@ __metadata:
languageName: node
linkType: hard
"require-directory@npm:^2.1.1":
version: 2.1.1
resolution: "require-directory@npm:2.1.1"
checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99
languageName: node
linkType: hard
"retry@npm:^0.12.0":
version: 0.12.0
resolution: "retry@npm:0.12.0"
@@ -1399,6 +1698,15 @@ __metadata:
languageName: node
linkType: hard
"rxjs@npm:^7.8.1":
version: 7.8.1
resolution: "rxjs@npm:7.8.1"
dependencies:
tslib: "npm:^2.1.0"
checksum: 10c0/3c49c1ecd66170b175c9cacf5cef67f8914dcbc7cd0162855538d365c83fea631167cacb644b3ce533b2ea0e9a4d0b12175186985f89d75abe73dbd8f7f06f68
languageName: node
linkType: hard
"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
version: 5.2.1
resolution: "safe-buffer@npm:5.2.1"
@@ -1436,6 +1744,15 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^7.5.3":
version: 7.6.3
resolution: "semver@npm:7.6.3"
bin:
semver: bin/semver.js
checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf
languageName: node
linkType: hard
"set-function-length@npm:^1.2.1":
version: 1.2.2
resolution: "set-function-length@npm:1.2.2"
@@ -1476,9 +1793,9 @@ __metadata:
languageName: node
linkType: hard
"shadow-cljs@npm:2.28.11":
version: 2.28.11
resolution: "shadow-cljs@npm:2.28.11"
"shadow-cljs@npm:2.28.18":
version: 2.28.18
resolution: "shadow-cljs@npm:2.28.18"
dependencies:
node-libs-browser: "npm:^2.2.1"
readline-sync: "npm:^1.4.7"
@@ -1488,7 +1805,7 @@ __metadata:
ws: "npm:^7.4.6"
bin:
shadow-cljs: cli/runner.js
checksum: 10c0/c5c77d524ee8f44e4ae2ddc196af170d02405cc8731ea71f852c7b220fc1ba8aaf5cf33753fd8a7566c8749bb75d360f903dfb0d131bcdc6c2c33f44404bd6a3
checksum: 10c0/4732cd11a5722644a0a91ae5560a55f62432ae5317bd2d1fd5bf12af8354c81776f4fcfce5c477b43af1ac2ecd4a216887337e1b92cca37a1b8cb9c157a393c1
languageName: node
linkType: hard
@@ -1508,6 +1825,13 @@ __metadata:
languageName: node
linkType: hard
"shell-quote@npm:^1.8.1":
version: 1.8.1
resolution: "shell-quote@npm:1.8.1"
checksum: 10c0/8cec6fd827bad74d0a49347057d40dfea1e01f12a6123bf82c4649f3ef152fc2bc6d6176e6376bffcd205d9d0ccb4f1f9acae889384d20baff92186f01ea455a
languageName: node
linkType: hard
"side-channel@npm:^1.0.6":
version: 1.0.6
resolution: "side-channel@npm:1.0.6"
@@ -1527,6 +1851,15 @@ __metadata:
languageName: node
linkType: hard
"simple-update-notifier@npm:^2.0.0":
version: 2.0.0
resolution: "simple-update-notifier@npm:2.0.0"
dependencies:
semver: "npm:^7.5.3"
checksum: 10c0/2a00bd03bfbcbf8a737c47ab230d7920f8bfb92d1159d421bdd194479f6d01ebc995d13fbe13d45dace23066a78a3dc6642999b4e3b38b847e6664191575b20c
languageName: node
linkType: hard
"smart-buffer@npm:^4.2.0":
version: 4.2.0
resolution: "smart-buffer@npm:4.2.0"
@@ -1627,7 +1960,7 @@ __metadata:
languageName: node
linkType: hard
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0":
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3":
version: 4.2.3
resolution: "string-width@npm:4.2.3"
dependencies:
@@ -1685,6 +2018,33 @@ __metadata:
languageName: node
linkType: hard
"supports-color@npm:^5.5.0":
version: 5.5.0
resolution: "supports-color@npm:5.5.0"
dependencies:
has-flag: "npm:^3.0.0"
checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05
languageName: node
linkType: hard
"supports-color@npm:^7.1.0":
version: 7.2.0
resolution: "supports-color@npm:7.2.0"
dependencies:
has-flag: "npm:^4.0.0"
checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124
languageName: node
linkType: hard
"supports-color@npm:^8.1.1":
version: 8.1.1
resolution: "supports-color@npm:8.1.1"
dependencies:
has-flag: "npm:^4.0.0"
checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89
languageName: node
linkType: hard
"tar@npm:^6.1.11, tar@npm:^6.1.2":
version: 6.2.1
resolution: "tar@npm:6.2.1"
@@ -1715,6 +2075,40 @@ __metadata:
languageName: node
linkType: hard
"to-regex-range@npm:^5.0.1":
version: 5.0.1
resolution: "to-regex-range@npm:5.0.1"
dependencies:
is-number: "npm:^7.0.0"
checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892
languageName: node
linkType: hard
"touch@npm:^3.1.0":
version: 3.1.1
resolution: "touch@npm:3.1.1"
bin:
nodetouch: bin/nodetouch.js
checksum: 10c0/d2e4d269a42c846a22a29065b9af0b263de58effc85a1764bb7a2e8fc4b47700e9e2fcbd7eb1f5bffbb7c73d860f93600cef282b93ddac8f0b62321cb498b36e
languageName: node
linkType: hard
"tree-kill@npm:^1.2.2":
version: 1.2.2
resolution: "tree-kill@npm:1.2.2"
bin:
tree-kill: cli.js
checksum: 10c0/7b1b7c7f17608a8f8d20a162e7957ac1ef6cd1636db1aba92f4e072dc31818c2ff0efac1e3d91064ede67ed5dc57c565420531a8134090a12ac10cf792ab14d2
languageName: node
linkType: hard
"tslib@npm:^2.1.0":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
languageName: node
linkType: hard
"tty-browserify@npm:0.0.0":
version: 0.0.0
resolution: "tty-browserify@npm:0.0.0"
@@ -1722,6 +2116,13 @@ __metadata:
languageName: node
linkType: hard
"undefsafe@npm:^2.0.5":
version: 2.0.5
resolution: "undefsafe@npm:2.0.5"
checksum: 10c0/96c0466a5fbf395917974a921d5d4eee67bca4b30d3a31ce7e621e0228c479cf893e783a109af6e14329b52fe2f0cb4108665fad2b87b0018c0df6ac771261d5
languageName: node
linkType: hard
"unique-filename@npm:^3.0.0":
version: 3.0.0
resolution: "unique-filename@npm:3.0.0"
@@ -1815,7 +2216,7 @@ __metadata:
languageName: node
linkType: hard
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0":
version: 7.0.0
resolution: "wrap-ansi@npm:7.0.0"
dependencies:
@@ -1874,9 +2275,38 @@ __metadata:
languageName: node
linkType: hard
"y18n@npm:^5.0.5":
version: 5.0.8
resolution: "y18n@npm:5.0.8"
checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249
languageName: node
linkType: hard
"yallist@npm:^4.0.0":
version: 4.0.0
resolution: "yallist@npm:4.0.0"
checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a
languageName: node
linkType: hard
"yargs-parser@npm:^21.1.1":
version: 21.1.1
resolution: "yargs-parser@npm:21.1.1"
checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2
languageName: node
linkType: hard
"yargs@npm:^17.7.2":
version: 17.7.2
resolution: "yargs@npm:17.7.2"
dependencies:
cliui: "npm:^8.0.1"
escalade: "npm:^3.1.1"
get-caller-file: "npm:^2.0.5"
require-directory: "npm:^2.1.1"
string-width: "npm:^4.2.3"
y18n: "npm:^5.0.5"
yargs-parser: "npm:^21.1.1"
checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05
languageName: node
linkType: hard

View File

@@ -65,7 +65,7 @@ You will be able to share your plugin with the <a target="_blank" href="https://
### My plugin works on my local machine, but I couldnt install it on Penpot. What could be the problem?
The url you that you need to provide in the plugin manager should look <a target="_blank" href="/plugins/create-a-plugin/#2.6.-step-6.-configure-the-manifest-file">like this</a>: <code class="language-bash">https:\/\/yourdomain.com/assents/manifest.json</code>
The url you that you need to provide in the plugin manager should look <a target="_blank" href="/plugins/create-a-plugin/#2.6.-step-6.-configure-the-manifest-file">like this</a>: <code class="language-bash">https:\/\/yourdomain.com/assets/manifest.json</code>
### Where can I get support if I find a bug or an unexpected behavior?

View File

@@ -4,7 +4,8 @@ title: 1. Self-hosting Guide
# Self-hosting Guide
This guide explains how to get your own Penpot instance, running on a machine you control, to test it, use it by you or your team, or even customize and extend it any way you like.
This guide explains how to get your own Penpot instance, running on a machine you control,
to test it, use it by you or your team, or even customize and extend it any way you like.
If you need more context you can look at the <a
href="https://community.penpot.app/t/self-hosting-penpot-i/2336" target="_blank">post
@@ -14,13 +15,16 @@ about self-hosting</a> in Penpot community.
href="https://design.penpot.app">our SaaS offer</a> for Penpot and your
self-hosted Penpot platform!**
There are two main options for creating a Penpot instance:
There are three main options for creating a Penpot instance:
1. Using the platform of our partner <a href="https://elest.io/open-source/penpot" target="_blank">Elestio</a>.
2. Using <a href="https://docker.com" target="_blank">Docker</a> tool.
3. Using <a href="https://kubernetes.io/" target="_blank">Kubernetes</a>.
<p class="advice">
The recommended way is to use Elestio, since it's simpler, fully automatic and still greatly flexible. Use Docker if you already know the tool, if need full control of the process or have extra requirements and do not want to depend on any external provider, or need to do any special customization.
The recommended way is to use Elestio, since it's simpler, fully automatic and still greatly flexible.
Use Docker if you already know the tool, if need full control of the process or have extra requirements
and do not want to depend on any external provider, or need to do any special customization.
</p>
Or you can try <a href="#unofficial-self-host-options">other options</a>,
@@ -261,7 +265,7 @@ itself.
This section details everything you need to know to get Penpot up and running in
production environments using a Kubernetes cluster of your choice. To do this, we have
created a <a href="https://helm.sh/" target="_blank">Helm<a> repository with everything
created a <a href="https://helm.sh/" target="_blank">Helm</a> repository with everything
you need.
Therefore, your prerequisite will be to have a Kubernetes cluster on which we can install
@@ -287,7 +291,7 @@ in turn have its own release name.
With these concepts in mind, we can now explain Helm like this:
> Helm installs charts into Kubernetes clusters, creating a new release for each
> installation. And to find new charts, you can search Helm chart repositories.
> installation. To find new charts, you can search Helm chart repositories.
### Install Helm

View File

@@ -20,6 +20,8 @@ machine.
* In the [Install with Docker][2] section, you can find the official Docker installation guide.
* In the [Install with Kubernetes][7] section, you can find the official Kubernetes installation guide.
* In the [Configuration][3] section, you can find all the customization options you can set up after installing.
* Or you can try other, not supported by Penpot, [Unofficial options][4].
@@ -28,9 +30,11 @@ machine.
The [Integration Guide][5] explains how to connect Penpot with external apps, so they get notified
when certain events occur and may create your own interconnections and collaboration features.
## Developing Penpot
Also, if you are a developer, you can get into the code, to explore it, learn how it is made, or extend it and contribute with new functionality. For this, we have a different Docker installation.
Also, if you are a developer, you can get into the code, to explore it, learn how it is made,
or extend it and contribute with new functionality. For this, we have a different Docker installation.
In the [Developer Guide][6] you can find how to setup a development environment and many other dev-oriented documentation.
[1]: /technical-guide/getting-started/#install-with-elestio
@@ -39,3 +43,4 @@ In the [Developer Guide][6] you can find how to setup a development environment
[4]: /technical-guide/getting-started/#unofficial-self-host-options
[5]: /technical-guide/integration/
[6]: /technical-guide/developer/
[7]: /technical-guide/getting-started/#install-with-kubernetes

View File

@@ -43,7 +43,9 @@
{:extra-paths ["dev"]
:extra-deps
{thheller/shadow-cljs {:mvn/version "2.28.18"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "0.48.0"}}}
:shadow-cljs

34
frontend/dev/user.clj Normal file
View File

@@ -0,0 +1,34 @@
;; 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 user
(:require
[app.common.data :as d]
[app.common.pprint :as pp]
[clojure.java.io :as io]
[clojure.tools.namespace.repl :as repl]
[clojure.pprint :refer [pprint print-table]]
[clojure.repl :refer :all]
[clojure.walk :refer [macroexpand-all]]
[criterium.core :as crit]))
;; --- Benchmarking Tools
(defmacro run-quick-bench
[& exprs]
`(crit/with-progress-reporting (crit/quick-bench (do ~@exprs) :verbose)))
(defmacro run-quick-bench'
[& exprs]
`(crit/quick-bench (do ~@exprs)))
(defmacro run-bench
[& exprs]
`(crit/with-progress-reporting (crit/bench (do ~@exprs) :verbose)))
(defmacro run-bench'
[& exprs]
`(crit/bench (do ~@exprs)))

View File

@@ -35,7 +35,7 @@
"lint:scss:fix": "yarn run prettier -c resources/styles -c src/**/*.scss -w",
"build:test": "clojure -M:dev:shadow-cljs compile test",
"test": "yarn run build:test && node target/tests/test.js",
"watch:test": "concurrently \"clojure -M:dev:shadow-cljs watch test\" \"nodemon -w target/tests/test.js --exec 'sleep 2 && node target/tests/test.js'\"",
"watch:test": "mkdir -p target/tests && concurrently \"clojure -M:dev:shadow-cljs watch test\" \"nodemon -C -d 2 -w target/tests --exec 'node target/tests/test.js'\"",
"test:e2e": "playwright test --project default",
"translations": "node ./scripts/translations.js",
"watch:app:assets": "node ./scripts/watch.js",
@@ -100,7 +100,7 @@
"@penpot/hljs": "portal:./vendor/hljs",
"@penpot/mousetrap": "portal:./vendor/mousetrap",
"@penpot/svgo": "penpot/svgo#c6fba7a4dcfbc27b643e7fc0c94fc98cf680b77b",
"@penpot/text-editor": "penpot/penpot-text-editor#a100aad8d0efcbb070bed9144dbd2782547e78ba",
"@penpot/text-editor": "portal:./text-editor",
"@tokens-studio/sd-transforms": "^0.16.1",
"compression": "^1.7.4",
"date-fns": "^4.1.0",

View File

@@ -58,10 +58,11 @@ test("Save and restore version", async ({ page }) => {
await page.getByRole("textbox").press("Enter");
await page
.locator("li")
.filter({ hasText: "INIT" })
.getByRole("button")
.click();
.getByLabel("History", { exact: true })
.locator("div")
.nth(3)
.hover();
await page.getByRole("button", { name: "Open version menu" }).click();
await page.getByRole("button", { name: "Restore" }).click();
await workspacePage.mockRPC(

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M3.5 3.5h-2m2 0v-2m0 2h9m-9 0v9m9-9v-2m0 2h2m-2 0v9m0 0h2m-2 0v2m0-2h-9m0 0v2m0-2h-2"/>
</svg>

After

Width:  |  Height:  |  Size: 214 B

View File

@@ -237,13 +237,18 @@ async function renderTemplate(path, context = {}, partials = {}) {
return mustache.render(content, context, partials);
}
const renderer = {
link(href, title, text) {
return `<a href="${href}" target="_blank">${text}</a>`;
const extension = {
useNewRenderer: true,
renderer: {
link(token) {
const href = token.href;
const text = token.text;
return `<a href="${href}" target="_blank">${text}</a>`;
},
},
};
marked.use({ renderer });
marked.use(extension);
async function readTranslations() {
const langs = [
@@ -503,6 +508,7 @@ export async function compileStyles() {
const start = process.hrtime();
log.info("init: compile styles");
let result = await compileSassAll(worker);
result = concatSass(result);

6
frontend/scripts/repl Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
export OPTIONS="-A:dev -J-XX:-OmitStackTraceInFastThrow";
set -ex
exec clojure $OPTIONS -M -m rebel-readline.main

View File

@@ -24,15 +24,22 @@ async function compileSassAll() {
async function compileSass(path) {
const start = process.hrtime();
log.info("changed:", path);
const result = await h.compileSass(worker, path, { modules: true });
sass.index[result.outputPath] = result.css;
const output = h.concatSass(sass);
try {
const result = await h.compileSass(worker, path, { modules: true });
sass.index[result.outputPath] = result.css;
await fs.writeFile("./resources/public/css/main.css", output);
const output = h.concatSass(sass);
const end = process.hrtime(start);
log.info("done:", `(${ppt(end)})`);
await fs.writeFile("./resources/public/css/main.css", output);
const end = process.hrtime(start);
log.info("done:", `(${ppt(end)})`);
} catch (cause) {
console.error(cause);
const end = process.hrtime(start);
log.error("error:", `(${ppt(end)})`);
}
}
await fs.mkdir("./resources/public/css/", { recursive: true });

View File

@@ -90,7 +90,6 @@
"unknown"
date)))
;; --- Global Config Vars
(def default-theme "default")

View File

@@ -30,6 +30,7 @@
[app.util.i18n :as i18n]
[app.util.theme :as theme]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[debug]
[features]
[potok.v2.core :as ptk]
@@ -38,11 +39,11 @@
(log/setup! {:app :info})
(when (= :browser cf/target)
(log/info :message "Welcome to penpot"
:version (:full cf/version)
(log/info :version (:full cf/version)
:asserts *assert*
:build-date cf/build-date
:public-uri (dm/str cf/public-uri)))
:public-uri (dm/str cf/public-uri))
(log/info :flags (str/join "," (map name cf/flags))))
(declare reinit)

View File

@@ -12,7 +12,6 @@
[app.common.schema :as sm]
[app.common.types.components-list :as ctkl]
[app.common.types.team :as ctt]
[app.config :as cf]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
[app.main.features :as features]
@@ -75,15 +74,13 @@
(watch [_ _ _]
(case code
:upgrade-version
(when (or (not= (:version params) (:full cf/version))
(true? (:force params)))
(rx/of (ntf/dialog
:content (tr "notifications.by-code.upgrade-version")
:controls :inline-actions
:type :inline
:level level
:actions [{:label "Refresh" :callback force-reload!}]
:tag :notification)))
(rx/of (ntf/dialog
:content (tr "notifications.by-code.upgrade-version")
:controls :inline-actions
:type :inline
:level level
:actions [{:label "Refresh" :callback force-reload!}]
:tag :notification))
:maintenance
(rx/of (ntf/dialog

View File

@@ -450,7 +450,9 @@
(ptk/reify ::update-team
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:teams id :name] name))
(-> state
(assoc-in [:teams id :name] name)
(assoc-in [:team :name] name)))
ptk/WatchEvent
(watch [_ _ _]
@@ -1118,6 +1120,9 @@
(rx/of (rt/nav :dashboard-projects {:team-id team-id}))))))
([team-id]
(ptk/reify ::go-to-projects-1
ptk/UpdateEvent
(update [_ state]
(assoc state :current-team-id team-id))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (rt/nav :dashboard-projects {:team-id team-id}))))))

View File

@@ -7,6 +7,7 @@
(ns app.main.data.exports.assets
(:require
[app.common.uuid :as uuid]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.data.persistence :as dwp]
[app.main.data.workspace.state-helpers :as wsh]
@@ -247,6 +248,12 @@
(rx/map #(clear-export-state @resource-id))
(rx/take-until (rx/delay 6000 stopper))))))))
(defn request-export
[{:keys [exports] :as params}]
(if (= 1 (count exports))
(request-simple-export (assoc params :export (first exports)))
(request-multiple-export params)))
(defn retry-last-export
[]
(ptk/reify ::retry-last-export
@@ -256,3 +263,16 @@
(when (seq params)
(rx/of (request-multiple-export params)))))))
(defn export-shapes-event
[exports origin]
(let [types (reduce (fn [counts {:keys [type]}]
(if (#{:png :pdf :svg :jpeg} type)
(update counts type inc)
counts))
{:png 0, :pdf 0, :svg 0, :jpeg 0}
exports)]
(ptk/event
::ev/event (merge types
{::ev/name "export-shapes"
::ev/origin origin
:num-shapes (count exports)}))))

View File

@@ -199,18 +199,21 @@
;; Load libraries
(->> (rp/cmd! :get-file-libraries {:file-id file-id})
(rx/mapcat identity)
(rx/merge-map
(fn [{:keys [id synced-at]}]
(->> (rp/cmd! :get-file {:id id :features features})
(rx/map #(assoc % :synced-at synced-at)))))
(rx/merge-map fpmap/resolve-file)
(rx/merge-map
(fn [{:keys [id] :as file}]
(->> (rp/cmd! :get-file-object-thumbnails {:file-id id :tag "component"})
(rx/map #(assoc file :thumbnails %)))))
(rx/reduce conj [])
(rx/map libraries-fetched)))
(rx/mapcat (fn [libraries]
(rx/merge
(->> (rx/from libraries)
(rx/merge-map
(fn [{:keys [id synced-at]}]
(->> (rp/cmd! :get-file {:id id :features features})
(rx/map #(assoc % :synced-at synced-at)))))
(rx/merge-map fpmap/resolve-file)
(rx/reduce conj [])
(rx/map libraries-fetched))
(->> (rx/from libraries)
(rx/map :id)
(rx/mapcat (fn [file-id]
(rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"})))
(rx/map dwl/library-thumbnails-fetched)))))))
(rx/of (with-meta (workspace-initialized)
{:file-id file-id})))

View File

@@ -15,6 +15,7 @@
[app.main.data.changes :as dch]
[app.main.data.comments :as dcm]
[app.main.data.events :as ev]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwco]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.state-helpers :as wsh]
@@ -60,7 +61,11 @@
(let [local (:comments-local state)]
(cond
(:draft local) (rx/of (dcm/close-thread))
(:open local) (rx/of (dcm/close-thread)))))))
(:open local) (rx/of (dcm/close-thread))
:else
(rx/of (dw/clear-edition-mode)
(dw/deselect-all true)))))))
;; Event responsible of the what should be executed when user clicked
;; on the comments layer. An option can be create a new draft thread,

View File

@@ -168,7 +168,7 @@
objects (get page :objects)
frame (cfh/get-root-frame objects (:id shape))
flows (get page :objects)
flows (get page :flows)
flow (ctp/get-frame-flow flows (:id frame))]
(rx/concat
(rx/of (dwsh/update-shapes
@@ -177,14 +177,14 @@
(let [new-interaction (-> ctsi/default-interaction
(ctsi/set-destination destination)
(assoc :position-relative-to (:id shape)))]
(cls/add-new-interaction shape new-interaction))))
(cls/add-new-interaction shape new-interaction)))))
(when destination
(dwsh/update-shapes [destination] cls/show-in-viewer))
(when destination
(rx/of (dwsh/update-shapes [destination] cls/show-in-viewer)))
(when (and (not (connected-frame? objects (:id frame)))
(nil? flow))
(add-flow (:id frame))))))))))
(when (and (not (connected-frame? objects (:id frame)))
(nil? flow))
(rx/of (add-flow (:id frame))))))))))
(defn remove-interaction
([shape index]

View File

@@ -881,11 +881,9 @@
(rx/of
(dwu/start-undo-transaction undo-id)
(update-component shape-id undo-group)
(sync-file current-file-id file-id :components (:component-id shape) undo-group)
(update-component-thumbnail-sync state component-id file-id "frame")
(update-component-thumbnail-sync state component-id file-id "component")
(sync-file current-file-id file-id :components component-id undo-group)
(when (not current-file?)
(sync-file file-id file-id :components (:component-id shape) undo-group))
(sync-file file-id file-id :components component-id undo-group))
(dwu/commit-undo-transaction undo-id)))))))
(defn launch-component-sync
@@ -937,9 +935,9 @@
;; in the grid creating new rows/columns to make space
(let [file (wsh/get-file state file-id)
libraries (wsh/get-libraries state)
page (wsh/lookup-page state)
objects (wsh/lookup-page-objects state)
parent (get objects (:parent-id shape))
page (wsh/lookup-page state)
objects (wsh/lookup-page-objects state)
parent (get objects (:parent-id shape))
;; If the target parent is a grid layout we need to pass the target cell
target-cell (when (ctl/grid-layout? parent)

View File

@@ -12,6 +12,7 @@
[app.main.data.shortcuts :as ds]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.fonts :as fonts]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -113,18 +114,24 @@
(defn calculate-text-values
[shape]
(let [state-map (deref refs/workspace-editor-state)
editor-state (get state-map (:id shape))]
(let [state-map (if (features/active-feature? @st/state "text-editor/v2")
(deref refs/workspace-v2-editor-state)
(deref refs/workspace-editor-state))
editor-state (get state-map (:id shape))
editor-instance (when (features/active-feature? @st/state "text-editor/v2")
(deref refs/workspace-editor))]
(d/merge
(dwt/current-root-values
{:shape shape
:attrs txt/root-attrs})
(dwt/current-paragraph-values
{:editor-state editor-state
:editor-instance editor-instance
:shape shape
:attrs txt/paragraph-attrs})
(dwt/current-text-values
{:editor-state editor-state
:editor-instance editor-instance
:shape shape
:attrs txt/text-node-attrs}))))

View File

@@ -168,7 +168,7 @@
(.error js/console cause)
(rx/empty)))
(rx/tap #(l/trc :hint "thumbnail updated" :elapsed (dm/str (tp) "ms")))
(rx/tap #(l/dbg :hint "thumbnail updated" :elapsed (dm/str (tp) "ms")))
;; We cancel all the stream if user starts editing while
;; thumbnail is generating

View File

@@ -117,7 +117,7 @@
(rx/of (ptk/event ::ev/event {::ev/name "rename-version"}))))))
(defn restore-version
[project-id file-id id]
[project-id file-id id origin]
(dm/assert! (uuid? project-id))
(dm/assert! (uuid? file-id))
(dm/assert! (uuid? id))
@@ -132,7 +132,17 @@
(rx/take 1)
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
(rx/map #(dw/initialize-file project-id file-id)))
(rx/of (ptk/event ::ev/event {::ev/name "restore-version"}))))))
(case origin
:version
(rx/of (ptk/event ::ev/event {::ev/name "restore-pin-version"}))
:snapshot
(rx/of (ptk/event ::ev/event {::ev/name "restore-autosave"}))
:plugin
(rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"}))
(rx/empty))))))
(defn delete-version
[file-id id]

View File

@@ -33,10 +33,13 @@
(defn get-team-enabled-features
[state]
(-> global-enabled-features
(set/union (:features-runtime state #{}))
(set/intersection cfeat/no-migration-features)
(set/union (:features-team state #{}))))
(let [runtime-features (:features-runtime state #{})
team-features (->> (:features-team state #{})
(into #{} cfeat/xf-remove-ephimeral))]
(-> global-enabled-features
(set/union runtime-features)
(set/intersection cfeat/no-migration-features)
(set/union team-features))))
(def features-ref
(l/derived get-team-enabled-features st/state =))
@@ -124,9 +127,9 @@
(let [features (get-team-enabled-features state)]
(if (contains? features "render-wasm/v1")
(render.wasm/initialize true)
(render.wasm/initialize false)))
(render.wasm/initialize false))
(log/trc :hint "initialized features"
:team (str/join "," (:features-team state))
:runtime (str/join "," (:features-runtime state)))))))
(log/inf :hint "initialized"
:enabled (str/join "," features)
:runtime (str/join "," (:features-runtime state))))))))

View File

@@ -13,6 +13,7 @@
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
[app.common.types.tokens-lib :as ctob]
[app.config :as cf]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.store :as st]
[app.main.ui.workspace.tokens.token-set :as wtts]
@@ -582,7 +583,8 @@
[object-id]
(l/derived
(fn [state]
(dm/get-in state [:workspace-thumbnails object-id]))
(some-> (dm/get-in state [:workspace-thumbnails object-id])
(cf/resolve-media)))
st/state))
(def workspace-text-modifier

View File

@@ -338,7 +338,7 @@
;; used to render thumbnails on assets panel.
(mf/defc component-svg
{::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
[{:keys [objects root-shape show-grids? zoom class] :or {zoom 1} :as props}]
[{:keys [objects root-shape show-grids? is-hidden zoom class] :or {zoom 1} :as props}]
(when root-shape
(let [root-shape-id (:id root-shape)
include-metadata (mf/use-ctx export/include-metadata-ctx)
@@ -381,13 +381,14 @@
:xmlns:penpot (when include-metadata "https://penpot.app/xmlns")
:fill "none"}
[:*
[:> shape-container {:shape root-shape'}
[:& (mf/provider muc/is-component?) {:value true}
[:& root-shape-wrapper {:shape root-shape' :view-box vbox}]]]
(when-not is-hidden
[:*
[:> shape-container {:shape root-shape'}
[:& (mf/provider muc/is-component?) {:value true}
[:& root-shape-wrapper {:shape root-shape' :view-box vbox}]]]
(when show-grids?
[:& empty-grids {:root-shape-id root-shape-id :objects objects}])]])))
(when show-grids?
[:& empty-grids {:root-shape-id root-shape-id :objects objects}])])])))
(mf/defc component-svg-thumbnail
{::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}

View File

@@ -9,6 +9,7 @@
[app.common.types.component :as ctk]
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[app.config :as cf]
[app.main.ui.icons :as i]
[rumext.v2 :as mf]))
@@ -31,7 +32,7 @@
i/flex-grid
:else
i/board)
(if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board))
;; TODO -> THUMBNAIL ICON
:image i/img
:line (if (cts/has-images? shape) i/img i/path)
@@ -56,7 +57,7 @@
(if main-instance?
i/component
(case type
:frame i/board
:frame (if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board)
:image i/img
:shape i/path
:text i/text

View File

@@ -64,6 +64,7 @@
}
.file-entry {
display: flex;
.file-name {
@include flexRow;
.file-icon {
@@ -114,6 +115,8 @@
}
.error-message,
.progress-message {
display: flex;
align-items: center;
height: $s-32;
color: var(--modal-text-foreground-color);
}

View File

@@ -73,7 +73,9 @@
{::mf/register modal/components
::mf/register-as :team-form}
[{:keys [team] :as props}]
(let [initial (mf/use-memo (fn [] (or team {})))
(let [initial (mf/use-memo (fn []
(or (some-> team (select-keys [:name :id]))
{})))
form (fm/use-form :schema schema:team-form
:initial initial)
handle-keydown

View File

@@ -12,7 +12,6 @@
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.data.events :as ev]
[app.main.data.exports.assets :as de]
[app.main.data.modal :as modal]
[app.main.refs :as refs]
@@ -23,7 +22,6 @@
[app.util.i18n :as i18n :refer [tr c]]
[app.util.strings :as ust]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def ^:private neutral-icon
@@ -59,13 +57,8 @@
(fn [event]
(dom/prevent-default event)
(st/emit! (modal/hide)
(de/request-multiple-export
{:exports enabled-exports
:cmd cmd})
(ptk/event
::ev/event {::ev/name "export-shapes"
::ev/origin origin
:num-shapes (count enabled-exports)})))
(de/request-multiple-export {:exports enabled-exports :cmd cmd})
(de/export-shapes-event enabled-exports origin)))
on-toggle-enabled
(mf/use-fn

View File

@@ -63,6 +63,7 @@
(def ^:icon arrow (icon-xref :arrow))
(def ^:icon asc-sort (icon-xref :asc-sort))
(def ^:icon board (icon-xref :board))
(def ^:icon board-2 (icon-xref :board-2))
(def ^:icon boards-thumbnail (icon-xref :boards-thumbnail))
(def ^:icon boolean-difference (icon-xref :boolean-difference))
(def ^:icon boolean-exclude (icon-xref :boolean-exclude))

View File

@@ -41,7 +41,7 @@
"The introduction of our brand new Plugin system allows you to access even richer ecosystem of capabilities."]
[:p {:class (stl/css :feature-content)}
"We are beyond excitement about how this will further involve the Penpot community in building the best design and prototyping platform."]
"We are beyond excited about how this will further involve the Penpot community in building the best design and prototyping platform."]
[:p {:class (stl/css :feature-content)}
"Lets dive in!"]]
@@ -69,7 +69,7 @@
"Penpot Plugins encourage developers to easily customize and expand the platform using standard web technologies like JavaScript, CSS, and HTML."]
[:p {:class (stl/css :feature-content)}
"Find everything you need in ouor full comprehensive documentation to start building your plugins now!"]]
"Find everything you need in our full comprehensive documentation to start building your plugins now!"]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
@@ -101,7 +101,7 @@
"Be sure to keep an eye on our evolving " [:a {:href "https://penpot.app/penpothub" :target "_blank"} "Penpot Hub"] " to pick the ones that are best suited to enhance your workflow."]
[:p {:class (stl/css :feature-content)}
"This is just the beginning of a myriad of possibilities. Lets build this community together <3."]]
"This is just the beginning of a myriad of possibilities. Lets build this community together ❤️."]]
[:div {:class (stl/css :navigation)}

View File

@@ -135,7 +135,7 @@
bounds (mf/with-memo [bounds points]
(or bounds (gsb/get-frame-bounds shape)))
thumb (:thumbnail shape)
thumb (cf/resolve-media (:thumbnail-id shape))
debug? (dbg/enabled? :thumbnails)
safari? (cf/check-browser? :safari)
@@ -171,7 +171,7 @@
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")]
(when ^boolean (:thumbnail shape)
(when ^boolean (:thumbnail-id shape)
[:> frame-container props
[:> frame-thumbnail-image props]])))

View File

@@ -49,11 +49,16 @@
(defn generate-paragraph-styles
[_shape data]
(let [line-height (:line-height data 1.2)
(let [line-height (:line-height data)
line-height
(if (and (some? line-height) (not= "" line-height))
line-height
(:line-height txt/default-text-attrs))
text-align (:text-align data "start")
base #js {;; Fix a problem when exporting HTML
:fontSize 0 ;;(str (:font-size data (:font-size txt/default-text-attrs)) "px")
:lineHeight (:line-height data (:line-height txt/default-text-attrs))
:lineHeight line-height
:margin 0}]
(cond-> base

View File

@@ -8,7 +8,6 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.data.events :as ev]
[app.main.data.exports.assets :as de]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -18,7 +17,6 @@
[app.util.dom :as dom]
[app.util.i18n :refer [tr c]]
[app.util.keyboard :as kbd]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(mf/defc exports
@@ -63,15 +61,9 @@
:object-id (-> shapes first :id)}
(cond-> share-id (assoc :share-id share-id)))
exports (mapv #(merge % defaults) @exports)]
(if (= 1 (count exports))
(st/emit!
(de/request-simple-export {:export (first exports)})
(ptk/event
::ev/event {::ev/name "export-shapes" ::ev/origin "viewer" :num-shapes 1}))
(st/emit!
(de/request-multiple-export {:exports exports})
(ptk/event
::ev/event {::ev/name "export-shapes" ::ev/origin "viewer" :num-shapes (count exports)}))))))
(st/emit!
(de/request-export {:exports exports})
(de/export-shapes-event exports "viewer")))))
add-export
(mf/use-callback

View File

@@ -24,6 +24,7 @@
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.versions :as dwv]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -524,6 +525,32 @@
(when (kbd/enter? event)
(on-add-shared event))))
on-show-version-history
(mf/use-fn
(mf/deps file-id)
(fn [_]
(st/emit! (dw/toggle-layout-flag :document-history))))
on-show-version-history-key-down
(mf/use-fn
(mf/deps on-show-version-history)
(fn [event]
(when (kbd/enter? event)
(on-show-version-history event))))
on-pin-version
(mf/use-fn
(mf/deps file-id)
(fn [_]
(st/emit! (dwv/create-version file-id))))
on-pin-version-key-down
(mf/use-fn
(mf/deps on-pin-version)
(fn [event]
(when (kbd/enter? event)
(on-pin-version event))))
on-export-shapes
(mf/use-fn #(st/emit! (de/show-workspace-export-dialog {:origin "workspace:menu"})))
@@ -575,14 +602,34 @@
:on-click on-remove-shared
:on-key-down on-remove-shared-key-down
:id "file-menu-remove-shared"}
[:span {:class (stl/css :item-name)} (tr "dashboard.unpublish-shared")]])
[:span {:class (stl/css :item-name)}
(tr "dashboard.unpublish-shared")]])
(when can-edit
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-add-shared
:on-key-down on-add-shared-key-down
:id "file-menu-add-shared"}
[:span {:class (stl/css :item-name)} (tr "dashboard.add-shared")]]))
[:span {:class (stl/css :item-name)}
(tr "dashboard.add-shared")]]))
[:div {:class (stl/css :separator)}]
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-pin-version
:on-key-down on-pin-version-key-down
:id "file-menu-show-version-history"}
[:span {:class (stl/css :item-name)}
(tr "dashboard.create-version-menu")]]
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-show-version-history
:on-key-down on-show-version-history-key-down
:id "file-menu-show-version-history"}
[:span {:class (stl/css :item-name)}
(tr "dashboard.show-version-history")]]
[:div {:class (stl/css :separator)}]
[:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-export-shapes

View File

@@ -43,9 +43,12 @@
}
.separator {
margin-top: $s-8;
border-top: $s-1 solid var(--color-background-quaternary);
height: $s-4;
border-top: $s-1 solid var(--color-background-secondary);
left: calc(-1 * $s-4);
margin-top: $s-8;
position: relative;
width: calc(100% + $s-8);
}
.shortcut {

View File

@@ -27,6 +27,13 @@
[app.util.text.content.styles :as styles]
[rumext.v2 :as mf]))
(defn- gen-name
[editor]
(when (some? editor)
(let [editor-root (.-root editor)
result (.-textContent editor-root)]
(when (not= result "") result))))
(defn- initialize-event-handlers
"Internal editor events handler initializer/destructor"
[shape-id content selection-ref editor-ref container-ref]
@@ -51,6 +58,8 @@
instance
(dwt/create-editor editor-node options)
update-name? (nil? content)
on-key-up
(fn [event]
(dom/stop-propagation event)
@@ -60,7 +69,7 @@
on-blur
(fn []
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
(st/emit! (dwt/v2-update-text-shape-content shape-id content true)))
(st/emit! (dwt/v2-update-text-shape-content shape-id content update-name? (gen-name instance))))
(let [container-node (mf/ref-val container-ref)]
(dom/set-style! container-node "opacity" 0)))

View File

@@ -15,6 +15,7 @@
[app.common.math :as mth]
[app.common.text :as txt]
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]
[app.main.data.workspace.modifiers :as mdwm]
[app.main.data.workspace.texts :as dwt]
[app.main.fonts :as fonts]
@@ -184,7 +185,7 @@
(mf/use-fn
(fn [shape node]
;; Unique to indentify the pending state
(let [uid (js/Symbol)]
(let [uid (uuid/next)]
(swap! pending-update* assoc uid (:id shape))
(p/then
(update-text-shape shape node)

View File

@@ -237,15 +237,11 @@
[:& comments-sidebar]
(true? is-history?)
[:> tab-switcher* {:tabs #js [#js {:label "History" :id "history" :content versions-tab}
#js {:label "Actions" :id "actions" :content history-tab}]
:default-selected "history"
;;:selected (name section)
;;:on-change-tab on-tab-change
:class (stl/css :left-sidebar-tabs)
;;:action-button-position "start"
;;:action-button (mf/html [:& collapse-button {:on-click handle-collapse}])
}]
[:> tab-switcher*
{:tabs #js [#js {:label (tr "workspace.versions.tab.history") :id "history" :content versions-tab}
#js {:label (tr "workspace.versions.tab.actions") :id "actions" :content history-tab}]
:default-selected "history"
:class (stl/css :left-sidebar-tabs)}]
:else
[:> options-toolbox props])]]]))

View File

@@ -113,4 +113,5 @@ $width-settings-bar-max: $s-500;
.versions-tab {
width: 100%;
overflow: hidden;
height: calc(100vh - $s-88);
}

View File

@@ -133,23 +133,23 @@
options
[{:name (tr "workspace.assets.box-filter-all")
:id "section-all"
:id "all"
:handler on-section-filter-change}
{:name (tr "workspace.assets.components")
:id "section-components"
:id "components"
:handler on-section-filter-change}
(when (not components-v2)
{:name (tr "workspace.assets.graphics")
:id "section-graphics"
:id "graphics"
:handler on-section-filter-change})
{:name (tr "workspace.assets.colors")
:id "section-colors"
:id "colors"
:handler on-section-filter-change}
{:name (tr "workspace.assets.typography")
:id "section-typographies"
:id "typographies"
:handler on-section-filter-change}]]
[:article {:class (stl/css :assets-bar)}

View File

@@ -269,17 +269,19 @@
(mf/defc component-item-thumbnail
"Component that renders the thumbnail image or the original SVG."
{::mf/wrap-props false}
[{:keys [file-id root-shape component container class]}]
(let [page-id (:main-instance-page component)
root-id (:main-instance-id component)
{::mf/props :obj}
[{:keys [file-id root-shape component container class is-hidden]}]
(let [page-id (:main-instance-page component)
root-id (:main-instance-id component)
retry (mf/use-state 0)
retry (mf/use-state 0)
thumbnail-uri*
(mf/with-memo [file-id page-id root-id]
(let [object-id (thc/fmt-object-id file-id page-id root-id "component")]
(refs/workspace-thumbnail-by-id object-id)))
thumbnail-uri* (mf/with-memo [file-id page-id root-id]
(let [object-id (thc/fmt-object-id file-id page-id root-id "component")]
(refs/workspace-thumbnail-by-id object-id)))
thumbnail-uri (mf/deref thumbnail-uri*)
thumbnail-uri
(mf/deref thumbnail-uri*)
on-error
(mf/use-fn
@@ -288,7 +290,8 @@
(when (< @retry 3)
(inc retry))))]
(if (and (some? thumbnail-uri) (contains? cf/flags :component-thumbnails))
(if (and (some? thumbnail-uri)
(contains? cf/flags :component-thumbnails))
[:& component-svg-thumbnail
{:thumbnail-uri thumbnail-uri
:class class
@@ -301,7 +304,8 @@
{:root-shape root-shape
:class class
:objects (:objects container)
:show-grids? true}])))
:show-grids? true
:is-hidden is-hidden}])))
(defn generate-components-menu-entries
[shapes components-v2]

View File

@@ -57,15 +57,6 @@
component)]
[root-shape container])))
;; NOTE: We don't schedule the thumbnail generation on idle right now
;; until we can queue and handle thumbnail batching properly.
#_(mf/with-effect []
(when-not (some? thumbnail-uri)
(tm/schedule-on-idle
#(st/emit! (dwl/update-component-thumbnail (:id component) file-id)))))
(mf/defc components-item
{::mf/wrap-props false}
[{:keys [component renaming listing-thumbs? selected
@@ -180,13 +171,13 @@
(when ^boolean dragging?
[:div {:class (stl/css :dragging)}])]
(when visible?
[:& cmm/component-item-thumbnail {:file-id file-id
:class (stl/css-case :thumbnail true
:asset-list-thumbnail (not listing-thumbs?))
:root-shape root-shape
:component component
:container container}])])]))
[:& cmm/component-item-thumbnail {:file-id file-id
:class (stl/css-case :thumbnail true
:asset-list-thumbnail (not listing-thumbs?))
:root-shape root-shape
:component component
:container container
:is-hidden (not visible?)}]])]))
(mf/defc components-group
{::mf/wrap-props false}

View File

@@ -74,14 +74,13 @@
(mf/defc file-library-content
{::mf/wrap-props false}
[{:keys [file local? open-status-ref on-clear-selection]}]
[{:keys [file local? open-status-ref on-clear-selection filters]}]
(let [components-v2 (mf/use-ctx ctx/components-v2)
open-status (mf/deref open-status-ref)
file-id (:id file)
project-id (:project-id file)
filters (mf/use-ctx cmm/assets-filters)
filters-section (:section filters)
filters-term (:term filters)

View File

@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.config :as cf]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -154,7 +155,7 @@
:circle i/elipse
:text i/text
:path i/path
:frame i/board
:frame (if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board)
:group i/group
:color i/drop-icon
:typography i/text-palette

View File

@@ -12,6 +12,7 @@
[app.common.files.helpers :as cfh]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -335,7 +336,7 @@
:on-click add-filter}
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
[:span {:class (stl/css :filter-menu-item-icon)}
i/board]
(if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board)]
[:span {:class (stl/css :filter-menu-item-name)}
(tr "workspace.sidebar.layers.frames")]]

View File

@@ -8,7 +8,6 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.data.events :as ev]
[app.main.data.exports.assets :as de]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
@@ -21,7 +20,6 @@
[app.util.dom :as dom]
[app.util.i18n :refer [tr c]]
[app.util.keyboard :as kbd]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def exports-attrs
@@ -66,21 +64,20 @@
;; I can select multiple shapes all of them with no export settings and one of them with only one
;; In that situation we must export it directly
(if (and (= 1 (count shapes-with-exports)) (= 1 (-> shapes-with-exports first :exports count)))
(let [shape (-> shapes-with-exports first)
export (-> shape :exports first)
sname (:name shape)
suffix (:suffix export)
defaults {:page-id page-id
:file-id file-id
:name sname
:object-id (:id (first shapes-with-exports))}]
(cond-> sname
(some? suffix)
(str suffix))
(let [shape (-> shapes-with-exports first)
export (-> shape :exports first)
suffix (:suffix export)
sname (cond-> (:name shape)
(some? suffix)
(str suffix))
defaults {:page-id page-id
:file-id file-id
:name sname
:object-id (:id (first shapes-with-exports))}
full-export (merge export defaults)]
(st/emit!
(de/request-simple-export {:export (merge export defaults)})
(ptk/event
::ev/event {::ev/name "export-shapes" ::ev/origin "workspace:sidebar" :num-shapes 1})))
(de/request-simple-export {:export full-export})
(de/export-shapes-event [full-export] "workspace:sidebar")))
(st/emit!
(de/show-workspace-export-dialog {:selected (reverse ids) :origin "workspace:sidebar"})))
@@ -92,16 +89,11 @@
:name sname
:object-id (first ids)}
exports (mapv #(merge % defaults) exports)]
(if (= 1 (count exports))
(let [export (first exports)]
(st/emit!
(de/request-simple-export {:export export})
(ptk/event
::ev/event {::ev/name "export-shapes" ::ev/origin "workspace:sidebar" :num-shapes 1})))
(st/emit!
(de/request-multiple-export {:exports exports})
(ptk/event
::ev/event {::ev/name "export-shapes" ::ev/origin "workspace:sidebar" :num-shapes (count exports)})))))))
(st/emit!
(de/request-export {:exports exports})
(de/export-shapes-event exports "workspace:sidebar"))))))
;; TODO: maybe move to specific events for avoid to have this logic here?
add-export

View File

@@ -134,7 +134,8 @@
time (dt/timeago (:created-at entry) {:locale locale})]
[:span {:class (stl/css :date)} time])]]
[:> icon-button* {:variant "ghost"
[:> icon-button* {:class (stl/css :version-entry-options)
:variant "ghost"
:aria-label (tr "workspace.versions.version-menu")
:on-click handle-open-menu
:icon "menu"}]]
@@ -274,10 +275,11 @@
(fn [id label]
(st/emit! (dwv/rename-version file-id id label))))
handle-restore-version
(mf/use-fn
(mf/deps project-id file-id)
(fn [id]
(fn [origin id]
(st/emit!
(ntf/dialog
:content (tr "workspace.versions.restore-warning")
@@ -287,9 +289,21 @@
:callback #(st/emit! (ntf/hide))}
{:label (tr "labels.restore")
:type :primary
:callback #(st/emit! (dwv/restore-version project-id file-id id))}]
:callback #(st/emit! (dwv/restore-version project-id file-id id origin))}]
:tag :restore-dialog))))
handle-restore-version-pinned
(mf/use-fn
(mf/deps handle-restore-version)
(fn [id]
(handle-restore-version :version id)))
handle-restore-version-snapshot
(mf/use-fn
(mf/deps handle-restore-version)
(fn [id]
(handle-restore-version :snapshot id)))
handle-delete-version
(mf/use-fn
(mf/deps file-id)
@@ -362,7 +376,7 @@
:editing? (= (:id entry) editing)
:profile (get users (:profile-id entry))
:on-rename-version handle-rename-version
:on-restore-version handle-restore-version
:on-restore-version handle-restore-version-pinned
:on-delete-version handle-delete-version}]
:snapshot
@@ -371,7 +385,7 @@
:entry entry
:is-expanded (contains? @expanded idx-entry)
:on-toggle-expand handle-toggle-expand
:on-restore-snapshot handle-restore-version
:on-restore-snapshot handle-restore-version-snapshot
:on-pin-snapshot handle-pin-version}]
nil))])])]))

View File

@@ -8,6 +8,10 @@
.version-toolbox {
padding: $s-8;
height: 100%;
display: grid;
overflow: hidden;
grid-template-rows: auto auto 1fr;
}
.versions-entry-empty {
@@ -49,6 +53,8 @@
display: flex;
flex-direction: column;
gap: $s-6;
overflow: auto;
margin: 0;
}
.version-entry {

View File

@@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.media :as cm]
[app.config :as cf]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
@@ -146,7 +147,7 @@
:on-click select-drawtool
:data-tool "frame"
:data-testid "artboard-btn"}
i/board]]
(if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board)]]
[:li
[:button
{:title (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))

View File

@@ -171,6 +171,7 @@
(and (some? drawing-obj) (= :path (:type drawing-obj))))
node-editing? (and edition (= :path (get-in base-objects [edition :type])))
text-editing? (and edition (= :text (get-in base-objects [edition :type])))
grid-editing? (and edition (ctl/grid-layout? base-objects edition))
mode-inspect? (= options-mode :inspect)

View File

@@ -12,7 +12,6 @@
[app.common.files.changes-builder :as cb]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.record :as cr]
[app.common.schema :as sm]
[app.common.text :as txt]
[app.common.types.color :as ctc]
@@ -59,406 +58,432 @@
(st/emit! (ch/commit-changes changes))
(shape/shape-proxy plugin-id (:id shape))))
(deftype PenpotContext [$plugin]
Object
(addListener
[_ type callback props]
(events/add-listener type $plugin callback props))
(defn create-context
[plugin-id]
(obj/reify {:name "PenpotContext"}
;; Private properties
:$plugin {:enumerable false :get (fn [] plugin-id)}
(removeListener
[_ listener-id]
(events/remove-listener listener-id))
;; Public properties
:root
{:this true
:get #(.getRoot ^js %)}
(getViewport
[_]
(viewport/viewport-proxy $plugin))
:currentFile
{:this true
:get #(.getFile ^js %)}
(getFile
[_]
(when (some? (:current-file-id @st/state))
(file/file-proxy $plugin (:current-file-id @st/state))))
:currentPage
{:this true
:get #(.getPage ^js %)}
(getPage
[_]
(let [file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)]
(when (and (some? file-id) (some? page-id))
(page/page-proxy $plugin file-id page-id))))
:theme
{:this true
:get #(.getTheme ^js %)}
(getSelectedShapes
[_]
(let [selection (get-in @st/state [:workspace-local :selected])]
(apply array (sequence (map (partial shape/shape-proxy $plugin)) selection))))
:selection
{:this true
:get #(.getSelectedShapes ^js %)
:set
(fn [_ shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :selection shapes)
(shapesColors
[_ shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :shapesColors-shapes shapes)
:else
(let [ids (into (d/ordered-set) (map #(obj/get % "$id")) shapes)]
(st/emit! (dws/select-shapes ids)))))}
:else
(let [objects (u/locate-objects)
shapes (->> shapes
(map #(obj/get % "$id"))
(mapcat #(cfh/get-children-with-self objects %)))
file-id (:current-file-id @st/state)
shared-libs (:workspace-libraries @st/state)]
:viewport
{:this true
:get #(.getViewport ^js %)}
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs)
(format/format-array format/format-color-result)))))
:currentUser
{:this true
:get #(.getCurrentUser ^js %)}
(replaceColor
[_ shapes old-color new-color]
:activeUsers
{:this true
:get #(.getActiveUsers ^js %)}
(let [old-color (parser/parse-color old-color)
new-color (parser/parse-color new-color)]
:fonts
{:get (fn [] (fonts/fonts-subcontext plugin-id))}
:library
{:get (fn [] (library/library-subcontext plugin-id))}
:history
{:get (fn [] (history/history-subcontext plugin-id))}
;; Methods
:addListener
(fn [type callback props]
(events/add-listener type plugin-id callback props))
:removeListener
(fn [listener-id]
(events/remove-listener listener-id))
:getViewport
(fn []
(viewport/viewport-proxy plugin-id))
:getFile
(fn []
(when (some? (:current-file-id @st/state))
(file/file-proxy plugin-id (:current-file-id @st/state))))
:getPage
(fn []
(let [file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)]
(when (and (some? file-id) (some? page-id))
(page/page-proxy plugin-id file-id page-id))))
:getSelectedShapes
(fn []
(let [selection (get-in @st/state [:workspace-local :selected])]
(apply array (sequence (map (partial shape/shape-proxy plugin-id)) selection))))
:shapesColors
(fn [shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :replaceColor-shapes shapes)
(not (sm/validate ::ctc/color old-color))
(u/display-not-valid :replaceColor-oldColor old-color)
(not (sm/validate ::ctc/color new-color))
(u/display-not-valid :replaceColor-newColor new-color)
(u/display-not-valid :shapesColors-shapes shapes)
:else
(let [file-id (:current-file-id @st/state)
shared-libs (:workspace-libraries @st/state)
objects (u/locate-objects)
shapes
(->> shapes
(map #(obj/get % "$id"))
(mapcat #(cfh/get-children-with-self objects %)))
(let [objects (u/locate-objects)
shapes (->> shapes
(map #(obj/get % "$id"))
(mapcat #(cfh/get-children-with-self objects %)))
file-id (:current-file-id @st/state)
shared-libs (:workspace-libraries @st/state)]
shapes-by-color
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs))]
(st/emit! (dwc/change-color-in-selected new-color (get shapes-by-color old-color) old-color))))))
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs)
(format/format-array format/format-color-result)))))
(getRoot
[_]
(when (and (some? (:current-file-id @st/state))
(some? (:current-page-id @st/state)))
(shape/shape-proxy $plugin uuid/zero)))
:replaceColor
(fn [shapes old-color new-color]
(let [old-color (parser/parse-color old-color)
new-color (parser/parse-color new-color)]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :replaceColor-shapes shapes)
(getTheme
[_]
(let [theme (get-in @st/state [:profile :theme])]
(if (or (not theme) (= theme "default"))
"dark"
(get-in @st/state [:profile :theme]))))
(not (sm/validate ::ctc/color old-color))
(u/display-not-valid :replaceColor-oldColor old-color)
(getCurrentUser
[_]
(user/current-user-proxy $plugin (:session-id @st/state)))
(not (sm/validate ::ctc/color new-color))
(u/display-not-valid :replaceColor-newColor new-color)
(getActiveUsers
[_]
(apply array
(->> (:workspace-presence @st/state)
(vals)
(remove #(= (:id %) (:session-id @st/state)))
(map #(user/active-user-proxy $plugin (:id %))))))
:else
(let [file-id (:current-file-id @st/state)
shared-libs (:workspace-libraries @st/state)
objects (u/locate-objects)
shapes
(->> shapes
(map #(obj/get % "$id"))
(mapcat #(cfh/get-children-with-self objects %)))
(uploadMediaUrl
[_ name url]
(cond
(not (string? name))
(u/display-not-valid :uploadMedia-name name)
shapes-by-color
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs))]
(st/emit! (dwc/change-color-in-selected new-color (get shapes-by-color old-color) old-color))))))
(not (string? url))
(u/display-not-valid :uploadMedia-url url)
:getRoot
(fn []
(when (and (some? (:current-file-id @st/state))
(some? (:current-page-id @st/state)))
(shape/shape-proxy plugin-id uuid/zero)))
:else
:getTheme
(fn []
(let [theme (get-in @st/state [:profile :theme])]
(if (or (not theme) (= theme "default"))
"dark"
(get-in @st/state [:profile :theme]))))
:getCurrentUser
(fn []
(user/current-user-proxy plugin-id (:session-id @st/state)))
:getActiveUsers
(fn []
(apply array
(->> (:workspace-presence @st/state)
(vals)
(remove #(= (:id %) (:session-id @st/state)))
(map #(user/active-user-proxy plugin-id (:id %))))))
:uploadMediaUrl
(fn [name url]
(cond
(not (string? name))
(u/display-not-valid :uploadMedia-name name)
(not (string? url))
(u/display-not-valid :uploadMedia-url url)
:else
(let [file-id (:current-file-id @st/state)]
(js/Promise.
(fn [resolve reject]
(->> (dwm/upload-media-url name file-id url)
(rx/take 1)
(rx/map format/format-image)
(rx/subs! resolve reject)))))))
:uploadMediaData
(fn [name data mime-type]
(let [file-id (:current-file-id @st/state)]
(js/Promise.
(fn [resolve reject]
(->> (dwm/upload-media-url name file-id url)
(->> (dwm/process-blobs
{:file-id file-id
:local? false
:name name
:blobs [(js/Blob. #js [data] #js {:type mime-type})]
:on-image identity
:on-svg identity})
(rx/take 1)
(rx/map format/format-image)
(rx/subs! resolve reject)))))))
(rx/subs! resolve reject))))))
(uploadMediaData
[_ name data mime-type]
(let [file-id (:current-file-id @st/state)]
(js/Promise.
(fn [resolve reject]
(->> (dwm/process-blobs
{:file-id file-id
:local? false
:name name
:blobs [(js/Blob. #js [data] #js {:type mime-type})]
:on-image identity
:on-svg identity})
(rx/take 1)
(rx/map format/format-image)
(rx/subs! resolve reject))))))
:group
(fn [shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :group-shapes shapes)
(group
[_ shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :group-shapes shapes)
:else
(let [file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)
id (uuid/next)
ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwg/group-shapes id ids))
(shape/shape-proxy plugin-id file-id page-id id))))
:else
(let [file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)
id (uuid/next)
ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwg/group-shapes id ids))
(shape/shape-proxy $plugin file-id page-id id))))
:ungroup
(fn [group & rest]
(cond
(not (shape/shape-proxy? group))
(u/display-not-valid :ungroup group)
(ungroup
[_ group & rest]
(and (some? rest) (not (every? shape/shape-proxy? rest)))
(u/display-not-valid :ungroup rest)
(cond
(not (shape/shape-proxy? group))
(u/display-not-valid :ungroup group)
:else
(let [shapes (concat [group] rest)
ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwg/ungroup-shapes ids)))))
(and (some? rest) (not (every? shape/shape-proxy? rest)))
(u/display-not-valid :ungroup rest)
:createBoard
(fn []
(create-shape plugin-id :frame))
:else
(let [shapes (concat [group] rest)
ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwg/ungroup-shapes ids)))))
:createRectangle
(fn []
(create-shape plugin-id :rect))
(createBoard
[_]
(create-shape $plugin :frame))
:createEllipse
(fn []
(create-shape plugin-id :circle))
(createRectangle
[_]
(create-shape $plugin :rect))
(createEllipse
[_]
(create-shape $plugin :circle))
(createPath
[_]
(let [page-id (:current-page-id @st/state)
page (dm/get-in @st/state [:workspace-data :pages-index page-id])
shape (cts/setup-shape
{:type :path
:content [{:command :move-to :params {:x 0 :y 0}}
{:command :line-to :params {:x 100 :y 100}}]})
changes
(-> (cb/empty-changes)
(cb/with-page page)
(cb/with-objects (:objects page))
(cb/add-object shape))]
(st/emit! (ch/commit-changes changes))
(shape/shape-proxy $plugin (:id shape))))
(createText
[_ text]
(cond
(or (not (string? text)) (empty? text))
(u/display-not-valid :createText text)
:else
(let [file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)
:createPath
(fn []
(let [page-id (:current-page-id @st/state)
page (dm/get-in @st/state [:workspace-data :pages-index page-id])
shape (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
(txt/change-text text)
(assoc :position-data nil))
shape (cts/setup-shape
{:type :path
:content [{:command :move-to :params {:x 0 :y 0}}
{:command :line-to :params {:x 100 :y 100}}]})
changes
(-> (cb/empty-changes)
(cb/with-page page)
(cb/with-objects (:objects page))
(cb/add-object shape))]
(st/emit! (ch/commit-changes changes))
(shape/shape-proxy $plugin file-id page-id (:id shape)))))
(shape/shape-proxy plugin-id (:id shape))))
(createShapeFromSvg
[_ svg-string]
(cond
(or (not (string? svg-string)) (empty? svg-string))
(u/display-not-valid :createShapeFromSvg svg-string)
:else
(let [id (uuid/next)
file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)]
(st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0)))
(shape/shape-proxy $plugin file-id page-id id))))
(createBoolean [_ bool-type shapes]
(let [bool-type (keyword bool-type)]
:createText
(fn [text]
(cond
(not (contains? cts/bool-types bool-type))
(u/display-not-valid :createBoolean-boolType bool-type)
(or (not (array? shapes)) (empty? shapes) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :createBoolean-shapes shapes)
(or (not (string? text)) (empty? text))
(u/display-not-valid :createText text)
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)
id-ret (atom nil)]
(st/emit! (dwb/create-bool bool-type ids {:id-ret id-ret}))
(shape/shape-proxy $plugin @id-ret)))))
(let [file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)
page (dm/get-in @st/state [:workspace-data :pages-index page-id])
shape (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
(txt/change-text text)
(assoc :position-data nil))
changes
(-> (cb/empty-changes)
(cb/with-page page)
(cb/with-objects (:objects page))
(cb/add-object shape))]
(st/emit! (ch/commit-changes changes))
(shape/shape-proxy plugin-id file-id page-id (:id shape)))))
(generateMarkup
[_ shapes options]
(let [type (d/nilv (obj/get options "type") "html")]
:createShapeFromSvg
(fn [svg-string]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :generateMarkup-shapes shapes)
(and (some? type) (not (contains? #{"html" "svg"} type)))
(u/display-not-valid :generateMarkup-type type)
(or (not (string? svg-string)) (empty? svg-string))
(u/display-not-valid :createShapeFromSvg svg-string)
:else
(let [objects (u/locate-objects)
shapes (into [] (map u/proxy->shape) shapes)]
(cg/generate-markup-code objects type shapes)))))
(let [id (uuid/next)
file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)]
(st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0)))
(shape/shape-proxy plugin-id file-id page-id id))))
(generateStyle
[_ shapes options]
(let [type (d/nilv (obj/get options "type") "css")
prelude? (d/nilv (obj/get options "withPrelude") false)
children? (d/nilv (obj/get options "includeChildren") true)]
:createBoolean
(fn [bool-type shapes]
(let [bool-type (keyword bool-type)]
(cond
(not (contains? cts/bool-types bool-type))
(u/display-not-valid :createBoolean-boolType bool-type)
(or (not (array? shapes)) (empty? shapes) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :createBoolean-shapes shapes)
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)
id-ret (atom nil)]
(st/emit! (dwb/create-bool bool-type ids {:id-ret id-ret}))
(shape/shape-proxy plugin-id @id-ret)))))
:generateMarkup
(fn [shapes options]
(let [type (d/nilv (obj/get options "type") "html")]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :generateMarkup-shapes shapes)
(and (some? type) (not (contains? #{"html" "svg"} type)))
(u/display-not-valid :generateMarkup-type type)
:else
(let [objects (u/locate-objects)
shapes (into [] (map u/proxy->shape) shapes)]
(cg/generate-markup-code objects type shapes)))))
:generateStyle
(fn [shapes options]
(let [type (d/nilv (obj/get options "type") "css")
prelude? (d/nilv (obj/get options "withPrelude") false)
children? (d/nilv (obj/get options "includeChildren") true)]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :generateStyle-shapes shapes)
(and (some? type) (not (contains? #{"css"} type)))
(u/display-not-valid :generateStyle-type type)
(and (some? prelude?) (not (boolean? prelude?)))
(u/display-not-valid :generateStyle-withPrelude prelude?)
(and (some? children?) (not (boolean? children?)))
(u/display-not-valid :generateStyle-includeChildren children?)
:else
(let [objects (u/locate-objects)
shapes
(->> (into #{} (map u/proxy->shape) shapes)
(cfh/clean-loops objects))
shapes-with-children
(if children?
(->> shapes
(mapcat #(cfh/get-children-with-self objects (:id %))))
shapes)]
(cg/generate-style-code
objects type shapes shapes-with-children {:with-prelude? prelude?})))))
:openViewer
(fn []
(let [params {:page-id (:current-page-id @st/state)
:file-id (:current-file-id @st/state)
:section "interactions"}]
(st/emit! (dw/go-to-viewer params))))
:createPage
(fn []
(let [file-id (:current-file-id @st/state)
id (uuid/next)]
(st/emit! (dw/create-page {:page-id id :file-id file-id}))
(page/page-proxy plugin-id file-id id)))
:openPage
(fn [page]
(let [id (obj/get page "$id")]
(st/emit! (dw/go-to-page id))))
:alignHorizontal
(fn [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
(fn [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
(fn [shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :generateStyle-shapes shapes)
(and (some? type) (not (contains? #{"css"} type)))
(u/display-not-valid :generateStyle-type type)
(and (some? prelude?) (not (boolean? prelude?)))
(u/display-not-valid :generateStyle-withPrelude prelude?)
(and (some? children?) (not (boolean? children?)))
(u/display-not-valid :generateStyle-includeChildren children?)
:else
(let [objects (u/locate-objects)
shapes
(->> (into #{} (map u/proxy->shape) shapes)
(cfh/clean-loops objects))
shapes-with-children
(if children?
(->> shapes
(mapcat #(cfh/get-children-with-self objects (:id %))))
shapes)]
(cg/generate-style-code
objects type shapes shapes-with-children {:with-prelude? prelude?})))))
(openViewer
[_]
(let [params {:page-id (:current-page-id @st/state)
:file-id (:current-file-id @st/state)
:section "interactions"}]
(st/emit! (dw/go-to-viewer params))))
(createPage
[_]
(let [file-id (:current-file-id @st/state)
id (uuid/next)]
(st/emit! (dw/create-page {:page-id id :file-id file-id}))
(page/page-proxy $plugin file-id id)))
(openPage
[_ page]
(let [id (obj/get page "$id")]
(st/emit! (dw/go-to-page id))))
(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")
(u/display-not-valid :distributeHorizontal-shapes "Not valid shapes")
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dw/align-objects dir ids))))))
(st/emit! (dw/distribute-objects :horizontal ids)))))
(alignVertical
[_ shapes direction]
(let [dir (case direction
"top" :vtop
"center" :vcenter
"bottom" :vbottom
nil)]
:distributeVertical
(fn [shapes]
(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")
(u/display-not-valid :distributeVertical-shapes "Not valid shapes")
:else
(let [ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dw/align-objects dir ids))))))
(st/emit! (dw/distribute-objects :vertical 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]
(cr/add-properties!
(PenpotContext. plugin-id)
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "root" :get #(.getRoot ^js %)}
{:name "currentFile" :get #(.getFile ^js %)}
{:name "currentPage" :get #(.getPage ^js %)}
{:name "theme" :get #(.getTheme ^js %)}
{:name "selection"
:get #(.getSelectedShapes ^js %)
:set
(fn [_ shapes]
:flatten
(fn [shapes]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :selection shapes)
(u/display-not-valid :flatten-shapes "Not valid shapes")
:else
(let [ids (into (d/ordered-set) (map #(obj/get % "$id")) shapes)]
(st/emit! (dws/select-shapes ids)))))}
{:name "viewport" :get #(.getViewport ^js %)}
{:name "currentUser" :get #(.getCurrentUser ^js %)}
{:name "activeUsers" :get #(.getActiveUsers ^js %)}
{:name "fonts" :get (fn [_] (fonts/fonts-subcontext plugin-id))}
{:name "library" :get (fn [_] (library/library-subcontext plugin-id))}
{:name "history" :get (fn [_] (history/history-subcontext plugin-id))}))
(let [ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dw/convert-selected-to-path ids)))))))

View File

@@ -7,7 +7,6 @@
(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]
@@ -19,159 +18,170 @@
[app.plugins.shape :as shape]
[app.plugins.user :as user]
[app.plugins.utils :as u]
[app.util.object :as obj]
[beicon.v2.core :as rx]))
(deftype CommentProxy [$plugin $file $page $thread $id]
Object
(remove [_]
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission $plugin "comment:write"))
(do
(u/display-not-valid :remove "Plugin doesn't have 'comment:write' permission")
(reject "Plugin doesn't have 'comment:write' permission"))
:else
(->> (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))
(obj/type-of? p "CommentProxy"))
(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))}
(obj/reify {:name "CommentProxy"}
;; Private properties
:$plugin {:enumerable false :get (fn [] plugin-id)}
:$file {:enumerable false :get (fn [] file-id)}
:$page {:enumerable false :get (fn [] page-id)}
:$thread {:enumerable false :get (fn [] thread-id)}
:$id {:enumerable false :get (fn [] (:id data))}
{:name "user" :get (fn [_] (user/user-proxy plugin-id (get users (:owner-id data))))}
{:name "date" :get (fn [_] (:created-at data))}
;; Public properties
:user
{:get
(fn [] (user/user-proxy plugin-id (get users (:owner-id 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")
:date
{:get
(fn [] (:created-at data))}
(not= (:id profile) (:owner-id data))
(u/display-not-valid :content "Cannot change content from another user's comments")
:content
{:get
(fn [] (:content @data*))
(not (r/check-permission plugin-id "comment:write"))
(u/display-not-valid :content "Plugin doesn't have 'comment:write' permission")
:set
(fn [content]
(let [profile (:profile @st/state)]
(cond
(or (not (string? content)) (empty? content))
(u/display-not-valid :content "Not valid")
: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))))))})))
(not= (:id profile) (:owner-id data))
(u/display-not-valid :content "Cannot change content from another user's comments")
(deftype CommentThreadProxy [$plugin $file $page $users $id owner]
Object
(findComments
[_]
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission $plugin "comment:read"))
(do
(u/display-not-valid :findComments "Plugin doesn't have 'comment:read' permission")
(reject "Plugin doesn't have 'comment:read' permission"))
(not (r/check-permission plugin-id "comment:write"))
(u/display-not-valid :content "Plugin doesn't have 'comment:write' permission")
:else
(->> (rp/cmd! :get-comments {:thread-id $id})
(rx/subs!
(fn [comments]
(resolve
(format/format-array
#(comment-proxy $plugin $file $page $id $users %) comments)))
reject))))))
: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))))))}
(reply
[_ content]
(cond
(not (r/check-permission $plugin "comment:write"))
(u/display-not-valid :reply "Plugin doesn't have 'comment:write' permission")
(or (not (string? content)) (empty? content))
(u/display-not-valid :reply "Not valid")
:else
(js/Promise.
(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 "comment:write"))
(u/display-not-valid :remove "Plugin doesn't have 'comment:write' permission")
(not= (:id profile) owner)
(u/display-not-valid :remove "Cannot change content from another user's comments")
:else
;; Public methods
:remove
(fn []
(js/Promise.
(fn [resolve]
(js/Promise.
(st/emit! (dc/delete-comment-thread-on-workspace {:id $id} #(resolve))))))))))
(fn [resolve reject]
(cond
(not (r/check-permission plugin-id "comment:write"))
(do
(u/display-not-valid :remove "Plugin doesn't have 'comment:write' permission")
(reject "Plugin doesn't have 'comment:write' permission"))
:else
(->> (rp/cmd! :delete-comment {:id (:id data)})
(rx/tap #(st/emit! (dc/retrieve-comment-threads file-id)))
(rx/subs! #(resolve) reject)))))))))
(defn comment-thread-proxy? [p]
(instance? CommentThreadProxy p))
(obj/type-of? p "CommentThreadProxy"))
(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))}
(obj/reify {:name "CommentThreadProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:$file {:enumerable false :get (fn [] file-id)}
:$page {:enumerable false :get (fn [] page-id)}
:$id {:enumerable false :get (fn [] (:id data))}
:$users {:enumerable false :get (fn [] users)}
{: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)))}
:page {:enumerable false :get #(u/locate-page file-id page-id)}
:seqNumber {:get #(:seqn data)}
:owner {:get #(user/user-proxy plugin-id (get users (:owner-id data)))}
:board {:get #(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")
:position
{:get
(fn []
(format/format-point (:position @data*)))
(not (r/check-permission plugin-id "comment:write"))
(u/display-not-valid :position "Plugin doesn't have 'comment:write' permission")
: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")
:else
(do (st/emit! (dwc/update-comment-thread-position @data* [(:x position) (:y position)]))
(swap! data* assoc :position (gpt/point position))))))}
(not (r/check-permission plugin-id "comment:write"))
(u/display-not-valid :position "Plugin doesn't have 'comment:write' permission")
{:name "resolved"
:get (fn [_] (:is-resolved @data*))
:set
(fn [_ is-resolved]
:else
(do (st/emit! (dwc/update-comment-thread-position @data* [(:x position) (:y position)]))
(swap! data* assoc :position (gpt/point position))))))}
: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 "comment:write"))
(u/display-not-valid :resolved "Plugin doesn't have 'comment:write' permission")
:else
(do (st/emit! (dc/update-comment-thread (assoc @data* :is-resolved is-resolved)))
(swap! data* assoc :is-resolved is-resolved))))}
:findComments
(fn []
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission plugin-id "comment:read"))
(do
(u/display-not-valid :findComments "Plugin doesn't have 'comment:read' permission")
(reject "Plugin doesn't have 'comment:read' permission"))
:else
(->> (rp/cmd! :get-comments {:thread-id (:id data)})
(rx/subs!
(fn [comments]
(resolve
(format/format-array
#(comment-proxy plugin-id file-id page-id (:id data) users %) comments)))
reject))))))
:reply
(fn [content]
(cond
(not (boolean? is-resolved))
(u/display-not-valid :resolved "Not a boolean type")
(not (r/check-permission plugin-id "comment:write"))
(u/display-not-valid :resolved "Plugin doesn't have 'comment:write' permission")
(u/display-not-valid :reply "Plugin doesn't have 'comment:write' permission")
(or (not (string? content)) (empty? content))
(u/display-not-valid :reply "Not valid")
:else
(do (st/emit! (dc/update-comment-thread (assoc @data* :is-resolved is-resolved)))
(swap! data* assoc :is-resolved is-resolved))))})))
(js/Promise.
(fn [resolve reject]
(->> (rp/cmd! :create-comment {:thread-id (:id data) :content content})
(rx/subs! #(resolve (comment-proxy plugin-id file-id page-id (:id data) users %)) reject))))))
:remove
(fn []
(let [profile (:profile @st/state)
owner (get users (:owner-id data))]
(cond
(not (r/check-permission plugin-id "comment:write"))
(u/display-not-valid :remove "Plugin doesn't have 'comment:write' permission")
(not= (:id profile) owner)
(u/display-not-valid :remove "Cannot change content from another user's comments")
:else
(js/Promise.
(fn [resolve]
(st/emit! (dc/delete-comment-thread-on-workspace {:id (:id data)} #(resolve)))))))))))

View File

@@ -62,15 +62,16 @@
(defmethod handle-state-change "shapechange"
[_ plugin-id old-val new-val props]
(let [shape-id (-> (obj/get props "shapeId") parser/parse-id)
old-shape (wsh/lookup-shape old-val shape-id)
new-shape (wsh/lookup-shape new-val shape-id)
(if-let [shape-id (-> (obj/get props "shapeId") parser/parse-id)]
(let [old-shape (wsh/lookup-shape old-val shape-id)
new-shape (wsh/lookup-shape new-val shape-id)
file-id (:current-file-id new-val)
page-id (:current-page-id new-val)]
(if (and (identical? old-shape new-shape) (some? plugin-id) (some? file-id) (some? page-id) (some? shape-id))
::not-changed
(shape/shape-proxy plugin-id file-id page-id shape-id))))
file-id (:current-file-id new-val)
page-id (:current-page-id new-val)]
(if (and (identical? old-shape new-shape) (some? plugin-id) (some? file-id) (some? page-id) (some? shape-id))
::not-changed
(shape/shape-proxy plugin-id file-id page-id shape-id)))
::not-changed))
(defmethod handle-state-change "contentsave"
[_ _ old-val new-val _]

View File

@@ -8,7 +8,6 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.record :as crc]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.exports.files :as exports.files]
@@ -18,6 +17,7 @@
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.worker :as uw]
[app.plugins.format :as format]
[app.plugins.page :as page]
[app.plugins.parser :as parser]
[app.plugins.register :as r]
@@ -28,300 +28,294 @@
[app.util.time :as dt]
[beicon.v2.core :as rx]))
(declare file-version-proxy)
(deftype FileVersionProxy [$plugin $file $version $data]
Object
(restore
[_]
(cond
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :restore "Plugin doesn't have 'content:write' permission")
:else
(let [project-id (:current-project-id @st/state)]
(st/emit! (dwv/restore-version project-id $file $version)))))
(remove
[_]
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission $plugin "content:write"))
(u/reject-not-valid reject :remove "Plugin doesn't have 'content:write' permission")
:else
(->> (rp/cmd! :delete-file-snapshot {:id $version})
(rx/subs! #(resolve) reject))))))
(pin
[_]
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission $plugin "content:write"))
(u/reject-not-valid reject :pin "Plugin doesn't have 'content:write' permission")
(not= "system" (:created-by $data))
(u/reject-not-valid reject :pin "Only auto-saved versions can be pinned")
:else
(let [params {:id $version
:label (dt/format (:created-at $data) :date-full)}]
(->> (rx/zip (rp/cmd! :get-team-users {:file-id $file})
(rp/cmd! :update-file-snapshot params))
(rx/subs! (fn [[users data]]
(let [users (d/index-by :id users)]
(resolve (file-version-proxy $plugin $file users data))))
reject))))))))
(defn file-version-proxy?
[proxy]
(obj/type-of? proxy "FileVersionProxy"))
(defn file-version-proxy
[plugin-id file-id users data]
(let [data (atom data)]
(crc/add-properties!
(FileVersionProxy. plugin-id file-id (:id @data) data)
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$version" :enumerable false :get (constantly (:id @data))}
{:name "$data" :enumerable false :get (constantly @data)}
(obj/reify {:name "FileVersionProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:$file {:enumerable false :get (fn [] file-id)}
{:name "label"
:get (fn [_] (:label @data))
:set
(fn [_ value]
(cond
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :label "Plugin doesn't have 'content:write' permission")
(or (not (string? value)) (empty? value))
(u/display-not-valid :label value)
:else
(do (swap! data assoc :label value :created-by "user")
(->> (rp/cmd! :update-file-snapshot {:id (:id @data) :label value})
(rx/take 1)
(rx/subs! identity)))))}
{:name "createdBy"
:get (fn [_]
(when-let [user-data (get users (:profile-id @data))]
(user/user-proxy plugin-id user-data)))}
{:name "createdAt"
:get (fn [_]
(.toJSDate ^js (:created-at @data)))}
{:name "isAutosave"
:get (fn [_]
(= "system" (:created-by @data)))})))
(deftype FileProxy [$plugin $id]
Object
(getPages [_]
(let [file (u/locate-file $id)]
(apply array (sequence (map #(page/page-proxy $plugin $id %)) (dm/get-in file [:data :pages])))))
;; Plugin data
(getPluginData
[self key]
(cond
(not (string? key))
(u/display-not-valid :getPluginData-key key)
:else
(let [file (u/proxy->file self)]
(dm/get-in file [:data :plugin-data (keyword "plugin" (str $plugin)) key]))))
(setPluginData
[_ key value]
(cond
(or (not (string? key)) (empty? key))
(u/display-not-valid :setPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data $id :file (keyword "plugin" (str $plugin)) key value))))
(getPluginDataKeys
[self]
(let [file (u/proxy->file self)]
(apply array (keys (dm/get-in file [:data :plugin-data (keyword "plugin" (str $plugin))])))))
(getSharedPluginData
[self namespace key]
(cond
(not (string? namespace))
(u/display-not-valid :getSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :getSharedPluginData-key key)
:else
(let [file (u/proxy->file self)]
(dm/get-in file [:data :plugin-data (keyword "shared" namespace) key]))))
(setSharedPluginData
[_ namespace key value]
(cond
(or (not (string? namespace)) (empty? namespace))
(u/display-not-valid :setSharedPluginData-namespace namespace)
(or (not (string? key)) (empty? key))
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data $id :file (keyword "shared" namespace) key value))))
(getSharedPluginDataKeys
[self namespace]
(cond
(not (string? namespace))
(u/display-not-valid :getSharedPluginDataKeys namespace)
:else
(let [file (u/proxy->file self)]
(apply array (keys (dm/get-in file [:data :plugin-data (keyword "shared" namespace)]))))))
(createPage
[_]
(cond
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :createPage "Plugin doesn't have 'content:write' permission")
:else
(let [page-id (uuid/next)]
(st/emit! (dw/create-page {:page-id page-id :file-id $id}))
(page/page-proxy $plugin $id page-id))))
(export
[self format type]
(let [type (or (parser/parse-keyword type) :all)]
(cond
(not (contains? #{"penpot" "zip"} format))
(u/display-not-valid :format type)
(not (contains? (set exports.files/valid-types) type))
(u/display-not-valid :type type)
:else
(let [file (u/proxy->file self)
features (features/get-team-enabled-features @st/state)
team-id (:current-team-id @st/state)
format (case format
"penpot" (if (contains? cf/flags :export-file-v3)
:binfile-v3
:binfile-v1)
"zip" :legacy-zip)]
(js/Promise.
(fn [resolve reject]
(->> (uw/ask-many!
{:cmd :export-files
:format format
:type type
:team-id team-id
:features features
:files [file]})
(rx/mapcat
(fn [msg]
(case (:type msg)
:error
(rx/throw (ex-info "cannot export file" {:type :export-file}))
:progress
(rx/empty)
:finish
(http/send! {:method :get
:uri (:uri msg)
:mode :no-cors
:response-type :buffer}))))
(rx/take 1)
(rx/map (fn [data] (js/Uint8Array. data)))
(rx/subs! resolve reject))))))))
(findVersions
[_ criteria]
(let [user (obj/get criteria "createdBy" nil)]
(js/Promise.
(fn [resolve reject]
:label
{:get #(:label @data)
:set
(fn [value]
(cond
(not (r/check-permission $plugin "content:read"))
(u/reject-not-valid reject :findVersions "Plugin doesn't have 'content:read' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :label "Plugin doesn't have 'content:write' permission")
(and (not user) (not (user/user-proxy? user)))
(u/reject-not-valid reject :findVersions-user "Created by user is not a valid user object")
(or (not (string? value)) (empty? value))
(u/display-not-valid :label value)
:else
(->> (rx/zip (rp/cmd! :get-team-users {:file-id $id})
(rp/cmd! :get-file-snapshots {:file-id $id}))
(rx/take 1)
(rx/subs!
(fn [[users snapshots]]
(let [users (d/index-by :id users)]
(->> snapshots
(filter #(= (dm/str (:profile-id %)) (obj/get user "id")))
(map #(file-version-proxy $plugin $id users %))
(sequence)
(apply array)
(resolve))))
reject)))))))
(do (swap! data assoc :label value :created-by "user")
(->> (rp/cmd! :update-file-snapshot {:id (:id @data) :label value})
(rx/take 1)
(rx/subs! identity)))))}
(saveVersion
[_ label]
(let [users-promise
(js/Promise.
(fn [resolve reject]
(->> (rp/cmd! :get-team-users {:file-id $id})
(rx/subs! resolve reject))))
:createdBy
{:get
(fn []
(when-let [user-data (get users (:profile-id @data))]
(user/user-proxy plugin-id user-data)))}
create-version-promise
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission $plugin "content:write"))
(u/reject-not-valid reject :findVersions "Plugin doesn't have 'content:write' permission")
:createdAt
{:get #(.toJSDate ^js (:created-at @data))}
:else
(st/emit! (dwv/create-version-from-plugins $id label resolve reject)))))]
(-> (js/Promise.all #js [users-promise create-version-promise])
(.then
(fn [[users data]]
(let [users (d/index-by :id users)]
(file-version-proxy $plugin $id users data))))))))
:isAutosave
{:get #(= "system" (:created-by @data))}
(crc/define-properties!
FileProxy
{:name js/Symbol.toStringTag
:get (fn [] (str "FileProxy"))})
:restore
(fn []
(cond
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :restore "Plugin doesn't have 'content:write' permission")
:else
(let [project-id (:current-project-id @st/state)
version-id (get @data :id)]
(st/emit! (dwv/restore-version project-id file-id version-id :plugin)))))
:remove
(fn []
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission plugin-id "content:write"))
(u/reject-not-valid reject :remove "Plugin doesn't have 'content:write' permission")
:else
(let [version-id (:id @data)]
(->> (rp/cmd! :delete-file-snapshot {:id version-id})
(rx/subs! #(resolve) reject)))))))
:pin
(fn []
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission plugin-id "content:write"))
(u/reject-not-valid reject :pin "Plugin doesn't have 'content:write' permission")
(not= "system" (:created-by @data))
(u/reject-not-valid reject :pin "Only auto-saved versions can be pinned")
:else
(let [params {:id (:id @data)
:label (dt/format (:created-at @data) :date-full)}]
(->> (rx/zip (rp/cmd! :get-team-users {:file-id file-id})
(rp/cmd! :update-file-snapshot params))
(rx/subs! (fn [[users data]]
(let [users (d/index-by :id users)]
(resolve (file-version-proxy plugin-id file-id users @data))))
reject))))))))))
(defn file-proxy? [p]
(instance? FileProxy p))
(obj/type-of? p "FileProxy"))
(defn file-proxy
[plugin-id id]
(crc/add-properties!
(FileProxy. plugin-id id)
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "id"
:get #(dm/str (obj/get % "$id"))}
(obj/reify {:name "FileProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:$id {:enumerable false :get (fn [] id)}
{:name "name"
:get #(-> % u/proxy->file :name)}
:id
{:get #(format/format-id id)}
{:name "pages"
:get #(.getPages ^js %)}))
:name
{:get #(-> (u/locate-file id) :name)}
:pages
{:this true
:get #(.getPages ^js %)}
:getPages
(fn []
(let [file (u/locate-file id)]
(apply array (sequence (map #(page/page-proxy plugin-id id %)) (dm/get-in file [:data :pages])))))
;; Plugin data
:getPluginData
(fn [key]
(cond
(not (string? key))
(u/display-not-valid :getPluginData-key key)
:else
(let [file (u/locate-file id)]
(dm/get-in file [:data :plugin-data (keyword "plugin" (str plugin-id)) key]))))
:setPluginData
(fn [key value]
(cond
(or (not (string? key)) (empty? key))
(u/display-not-valid :setPluginData-key key)
(not (string? value))
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data id :file (keyword "plugin" (str plugin-id)) key value))))
:getPluginDataKeys
(fn []
(let [file (u/locate-file id)]
(apply array (keys (dm/get-in file [:data :plugin-data (keyword "plugin" (dm/str plugin-id))])))))
:getSharedPluginData
(fn [namespace key]
(cond
(not (string? namespace))
(u/display-not-valid :getSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :getSharedPluginData-key key)
:else
(let [file (u/locate-file id)]
(dm/get-in file [:data :plugin-data (keyword "shared" namespace) key]))))
:setSharedPluginData
(fn [namespace key value]
(cond
(or (not (string? namespace)) (empty? namespace))
(u/display-not-valid :setSharedPluginData-namespace namespace)
(or (not (string? key)) (empty? key))
(u/display-not-valid :setSharedPluginData-key key)
(not (string? value))
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data id :file (keyword "shared" namespace) key value))))
:getSharedPluginDataKeys
(fn [namespace]
(cond
(not (string? namespace))
(u/display-not-valid :getSharedPluginDataKeys namespace)
:else
(let [file (u/locate-file id)]
(apply array (keys (dm/get-in file [:data :plugin-data (keyword "shared" namespace)]))))))
:createPage
(fn []
(cond
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :createPage "Plugin doesn't have 'content:write' permission")
:else
(let [page-id (uuid/next)]
(st/emit! (dw/create-page {:page-id page-id :file-id id}))
(page/page-proxy plugin-id id page-id))))
:export
(fn [format type]
(js/Promise.
(fn [resolve reject]
(let [type (or (parser/parse-keyword type) :all)]
(cond
(and (some? format) (not (contains? #{"penpot" "zip"} format)))
(u/reject-not-valid reject :format (dm/str "Invalid format: " format))
(not (contains? (set exports.files/valid-types) type))
(u/reject-not-valid reject :format (dm/str "Invalid type: " type))
:else
(let [file (u/locate-file id)
features (features/get-team-enabled-features @st/state)
team-id (:current-team-id @st/state)
format (case format
"zip" :legacy-zip
(if (contains? cf/flags :export-file-v3)
:binfile-v3
:binfile-v1))]
(->> (uw/ask-many!
{:cmd :export-files
:format format
:type type
:team-id team-id
:features features
:files [file]})
(rx/mapcat
(fn [msg]
(.log js/console msg)
(case (:type msg)
:error
(rx/throw (ex-info "cannot export file" {:type :export-file}))
:progress
(rx/empty)
:finish
(http/send! {:method :get
:uri (:uri msg)
:mode :no-cors
:response-type :buffer}))))
(rx/take 1)
(rx/map #(js/Uint8Array. (:body %)))
(rx/subs! resolve reject))))))))
:findVersions
(fn [criteria]
(let [user (obj/get criteria "createdBy" nil)]
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission plugin-id "content:read"))
(u/reject-not-valid reject :findVersions "Plugin doesn't have 'content:read' permission")
(and (some? user) (not (user/user-proxy? user)))
(u/reject-not-valid reject :findVersions-user "Created by user is not a valid user object")
:else
(->> (rx/zip (rp/cmd! :get-team-users {:file-id id})
(rp/cmd! :get-file-snapshots {:file-id id}))
(rx/take 1)
(rx/subs!
(fn [[users snapshots]]
(let [users (d/index-by :id users)]
(->> snapshots
(filter #(or (not (obj/get user "id"))
(= (dm/str (:profile-id %))
(obj/get user "id"))))
(map #(file-version-proxy plugin-id id users %))
(sequence)
(apply array)
(resolve))))
reject)))))))
:saveVersion
(fn [label]
(let [users-promise
(js/Promise.
(fn [resolve reject]
(->> (rp/cmd! :get-team-users {:file-id id})
(rx/subs! resolve reject))))
create-version-promise
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission plugin-id "content:write"))
(u/reject-not-valid reject :findVersions "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwv/create-version-from-plugins id label resolve reject)))))]
(-> (js/Promise.all #js [users-promise create-version-promise])
(.then
(fn [[users data]]
(let [users (d/index-by :id users)]
(file-version-proxy plugin-id id users data)))))))))

View File

@@ -7,7 +7,6 @@
(ns app.plugins.flex
(:require
[app.common.data :as d]
[app.common.record :as crc]
[app.common.spec :as us]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.shape-layout :as dwsl]
@@ -21,491 +20,485 @@
;; Define in `app.plugins.shape` we do this way to prevent circular dependency
(def shape-proxy? nil)
(deftype FlexLayout [$plugin $file $page $id]
Object
(remove
[_]
(st/emit! (dwsl/remove-layout #{$id})))
(appendChild
[_ child]
(cond
(not (shape-proxy? child))
(u/display-not-valid :appendChild child)
:else
(let [child-id (obj/get child "$id")]
(st/emit! (dwt/move-shapes-to-frame #{child-id} $id nil nil)
(ptk/data-event :layout/update {:ids [$id]}))))))
(defn flex-layout-proxy? [p]
(instance? FlexLayout p))
(obj/type-of? p "FlexLayoutProxy"))
(defn flex-layout-proxy
[plugin-id file-id page-id id]
(-> (FlexLayout. plugin-id file-id page-id id)
(crc/add-properties!
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
(obj/reify {:name "FlexLayoutProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:$id {:enumerable false :get (fn [] id)}
:$file {:enumerable false :get (fn [] file-id)}
:$page {:enumerable false :get (fn [] page-id)}
{:name "dir"
:get #(-> % u/proxy->shape :layout-flex-dir d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/flex-direction-types value))
(u/display-not-valid :dir value)
:dir
{:this true
:get #(-> % u/proxy->shape :layout-flex-dir d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/flex-direction-types value))
(u/display-not-valid :dir value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :dir "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :dir "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-flex-dir value}))))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-flex-dir value})))))}
{:name "wrap"
:get #(-> % u/proxy->shape :layout-wrap-type d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/wrap-types value))
(u/display-not-valid :wrap value)
:wrap
{:this true
:get #(-> % u/proxy->shape :layout-wrap-type d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/wrap-types value))
(u/display-not-valid :wrap value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :wrap "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :wrap "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-wrap-type value}))))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-wrap-type value})))))}
{:name "alignItems"
:get #(-> % u/proxy->shape :layout-align-items d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/align-items-types value))
(u/display-not-valid :alignItems value)
:alignItems
{:this true
:get #(-> % u/proxy->shape :layout-align-items d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/align-items-types value))
(u/display-not-valid :alignItems value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignItems "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignItems "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-align-items value}))))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))}
{:name "alignContent"
:get #(-> % u/proxy->shape :layout-align-content d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/align-content-types value))
(u/display-not-valid :alignContent value)
:alignContent
{:this true
:get #(-> % u/proxy->shape :layout-align-content d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/align-content-types value))
(u/display-not-valid :alignContent value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignContent "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignContent "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-align-content value}))))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))}
{:name "justifyItems"
:get #(-> % u/proxy->shape :layout-justify-items d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/justify-items-types value))
(u/display-not-valid :justifyItems value)
:justifyItems
{:this true
:get #(-> % u/proxy->shape :layout-justify-items d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/justify-items-types value))
(u/display-not-valid :justifyItems value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifyItems "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifyItems "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-justify-items value}))))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))}
{:name "justifyContent"
:get #(-> % u/proxy->shape :layout-justify-content d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/justify-content-types value))
(u/display-not-valid :justifyContent value)
:justifyContent
{:this true
:get #(-> % u/proxy->shape :layout-justify-content d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/justify-content-types value))
(u/display-not-valid :justifyContent value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifyContent "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :justifyContent "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-justify-content value}))))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))}
{:name "rowGap"
:get #(-> % u/proxy->shape :layout-gap :row-gap (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :rowGap value)
:rowGap
{:this true
:get #(-> % u/proxy->shape :layout-gap :row-gap (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :rowGap value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rowGap "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rowGap "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}}))))}
{:name "columnGap"
:get #(-> % u/proxy->shape :layout-gap :column-gap (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :columnGap value)
:columnGap
{:this true
:get #(-> % u/proxy->shape :layout-gap :column-gap (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :columnGap value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :columnGap "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :columnGap "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}}))))}
{:name "verticalPadding"
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :verticalPadding value)
:verticalPadding
{:this true
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :verticalPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalPadding "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}}))))}
{:name "horizontalPadding"
:get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :horizontalPadding value)
:horizontalPadding
{:this true
:get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalPadding "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}}))))}
{:name "topPadding"
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :topPadding value)
:topPadding
{:this true
:get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :topPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :topPadding "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :topPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}})))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}}))))}
{:name "rightPadding"
:get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :rightPadding value)
:rightPadding
{:this true
:get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :rightPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rightPadding "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rightPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}})))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}}))))}
{:name "bottomPadding"
:get #(-> % u/proxy->shape :layout-padding :p3 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :bottomPadding value)
:bottomPadding
{:this true
:get #(-> % u/proxy->shape :layout-padding :p3 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :bottomPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :bottomPadding "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :bottomPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}})))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}}))))}
{:name "leftPadding"
:get #(-> % u/proxy->shape :layout-padding :p4 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :leftPadding value)
:leftPadding
{:this true
:get #(-> % u/proxy->shape :layout-padding :p4 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-int? value))
(u/display-not-valid :leftPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :leftPadding "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :leftPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}})))))})))
:else
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}}))))}
:remove
(fn []
(st/emit! (dwsl/remove-layout #{id})))
:appendChild
(fn [child]
(cond
(not (shape-proxy? child))
(u/display-not-valid :appendChild child)
:else
(let [child-id (obj/get child "$id")]
(st/emit! (dwt/move-shapes-to-frame #{child-id} id nil nil)
(ptk/data-event :layout/update {:ids [id]})))))))
(deftype LayoutChildProxy [$plugin $file $page $id])
(defn layout-child-proxy? [p]
(instance? LayoutChildProxy p))
(obj/type-of? p "LayoutChildProxy"))
(defn layout-child-proxy
[plugin-id file-id page-id id]
(-> (LayoutChildProxy. plugin-id file-id page-id id)
(crc/add-properties!
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
(obj/reify {:name "LayoutChildProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:$id {:enumerable false :get (fn [] id)}
:$file {:enumerable false :get (fn [] file-id)}
:$page {:enumerable false :get (fn [] page-id)}
{:name "absolute"
:get #(-> % u/proxy->shape :layout-item-absolute boolean)
:set
(fn [self value]
(cond
(not (boolean? value))
(u/display-not-valid :absolute value)
:absolute
{:this true
:get #(-> % u/proxy->shape :layout-item-absolute boolean)
:set
(fn [_ value]
(cond
(not (boolean? value))
(u/display-not-valid :absolute value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :absolute "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :absolute "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout #{id} {:layout-item-absolute value})))))}
:else
(st/emit! (dwsl/update-layout #{id} {:layout-item-absolute value}))))}
{:name "zIndex"
:get #(-> % u/proxy->shape :layout-item-z-index (d/nilv 0))
:set
(fn [self value]
(cond
(us/safe-int? value)
(u/display-not-valid :zIndex value)
:zIndex
{:this true
:get #(-> % u/proxy->shape :layout-item-z-index (d/nilv 0))
:set
(fn [_ value]
(cond
(us/safe-int? value)
(u/display-not-valid :zIndex value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :zIndex "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :zIndex "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-z-index value})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-z-index value}))))}
{:name "horizontalSizing"
:get #(-> % u/proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/item-h-sizing-types value))
(u/display-not-valid :horizontalPadding value)
:horizontalSizing
{:this true
:get #(-> % u/proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/item-h-sizing-types value))
(u/display-not-valid :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalPadding "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalPadding "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-h-sizing value}))))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-h-sizing value})))))}
{:name "verticalSizing"
:get #(-> % u/proxy->shape :layout-item-v-sizing (d/nilv :fix) d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/item-v-sizing-types value))
(u/display-not-valid :verticalSizing value)
:verticalSizing
{:this true
:get #(-> % u/proxy->shape :layout-item-v-sizing (d/nilv :fix) d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/item-v-sizing-types value))
(u/display-not-valid :verticalSizing value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalSizing "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalSizing "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-v-sizing value}))))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-v-sizing value})))))}
{:name "alignSelf"
:get #(-> % u/proxy->shape :layout-item-align-self (d/nilv :auto) d/name)
:set
(fn [self value]
(let [value (keyword value)]
(cond
(not (contains? ctl/item-align-self-types value))
(u/display-not-valid :alignSelf value)
:alignSelf
{:this true
:get #(-> % u/proxy->shape :layout-item-align-self (d/nilv :auto) d/name)
:set
(fn [_ value]
(let [value (keyword value)]
(cond
(not (contains? ctl/item-align-self-types value))
(u/display-not-valid :alignSelf value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignSelf "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :alignSelf "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-align-self value}))))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-align-self value})))))}
{:name "verticalMargin"
:get #(-> % u/proxy->shape :layout-item-margin :m1 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :verticalMargin value)
:verticalMargin
{:this true
:get #(-> % u/proxy->shape :layout-item-margin :m1 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :verticalMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalMargin "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :verticalMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value :m3 value}})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value :m3 value}}))))}
{:name "horizontalMargin"
:get #(-> % u/proxy->shape :layout-item-margin :m2 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :horizontalMargin value)
:horizontalMargin
{:this true
:get #(-> % u/proxy->shape :layout-item-margin :m2 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :horizontalMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalMargin "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :horizontalMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value :m4 value}})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value :m4 value}}))))}
{:name "topMargin"
:get #(-> % u/proxy->shape :layout-item-margin :m1 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :topMargin value)
:topMargin
{:this true
:get #(-> % u/proxy->shape :layout-item-margin :m1 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :topMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :topMargin "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :topMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value}})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value}}))))}
{:name "rightMargin"
:get #(-> % u/proxy->shape :layout-item-margin :m2 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :rightMargin value)
:rightMargin
{:this true
:get #(-> % u/proxy->shape :layout-item-margin :m2 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :rightMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rightMargin "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :rightMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value}})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value}}))))}
{:name "bottomMargin"
:get #(-> % u/proxy->shape :layout-item-margin :m3 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :bottomMargin value)
:bottomMargin
{:this true
:get #(-> % u/proxy->shape :layout-item-margin :m3 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :bottomMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :bottomMargin "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :bottomMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m3 value}})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m3 value}}))))}
{:name "leftMargin"
:get #(-> % u/proxy->shape :layout-item-margin :m4 (d/nilv 0))
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :leftMargin value)
:leftMargin
{:this true
:get #(-> % u/proxy->shape :layout-item-margin :m4 (d/nilv 0))
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :leftMargin value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :leftMargin "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :leftMargin "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m4 value}})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m4 value}}))))}
{:name "maxWidth"
:get #(-> % u/proxy->shape :layout-item-max-w)
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :maxWidth value)
:maxWidth
{:this true
:get #(-> % u/proxy->shape :layout-item-max-w)
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :maxWidth value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :maxWidth "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :maxWidth "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-w value})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-w value}))))}
{:name "minWidth"
:get #(-> % u/proxy->shape :layout-item-min-w)
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :minWidth value)
:minWidth
{:this true
:get #(-> % u/proxy->shape :layout-item-min-w)
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :minWidth value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :minWidth "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :minWidth "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-w value})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-w value}))))}
{:name "maxHeight"
:get #(-> % u/proxy->shape :layout-item-max-h)
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :maxHeight value)
:maxHeight
{:this true
:get #(-> % u/proxy->shape :layout-item-max-h)
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :maxHeight value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :maxHeight "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :maxHeight "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-h value})))))}
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-h value}))))}
{:name "minHeight"
:get #(-> % u/proxy->shape :layout-item-min-h)
:set
(fn [self value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :minHeight value)
:minHeight
{:this true
:get #(-> % u/proxy->shape :layout-item-min-h)
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :minHeight value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :minHeight "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :minHeight "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get self "$id")]
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-h value})))))})))
:else
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-h value}))))}))

View File

@@ -7,10 +7,10 @@
(ns app.plugins.fonts
(:require
[app.common.data :as d]
[app.common.record :as cr]
[app.main.data.workspace.texts :as dwt]
[app.main.fonts :as fonts]
[app.main.store :as st]
[app.plugins.format :as format]
[app.plugins.register :as r]
[app.plugins.shape :as shape]
[app.plugins.text :as text]
@@ -18,117 +18,133 @@
[app.util.object :as obj]
[cuerdas.core :as str]))
(deftype PenpotFontVariant [name fontVariantId fontWeight fontStyle])
(defn font-variant-proxy? [p]
(obj/type-of? p "FontVariantProxy"))
(defn variant-proxy? [p]
(instance? PenpotFontVariant p))
(deftype PenpotFont [name fontId fontFamily fontStyle fontVariantId fontWeight variants]
Object
(applyToText [_ text variant]
(cond
(not (shape/shape-proxy? text))
(u/display-not-valid :applyToText text)
(not (r/check-permission (obj/get text "$plugin") "content:write"))
(u/display-not-valid :applyToText "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get text "$id")
values {:font-id fontId
:font-family fontFamily
:font-style (d/nilv (obj/get variant "fontStyle") fontStyle)
:font-variant-id (d/nilv (obj/get variant "fontVariantId") fontVariantId)
:font-weight (d/nilv (obj/get variant "fontWeight") fontWeight)}]
(st/emit! (dwt/update-attrs id values)))))
(applyToRange [_ range variant]
(cond
(not (text/text-range? range))
(u/display-not-valid :applyToRange range)
(not (r/check-permission (obj/get range "$plugin") "content:write"))
(u/display-not-valid :applyToRange "Plugin doesn't have 'content:write' permission")
:else
(let [id (obj/get range "$id")
start (obj/get range "start")
end (obj/get range "end")
values {:font-id fontId
:font-family fontFamily
:font-style (d/nilv (obj/get variant "fontStyle") fontStyle)
:font-variant-id (d/nilv (obj/get variant "fontVariantId") fontVariantId)
:font-weight (d/nilv (obj/get variant "fontWeight") fontWeight)}]
(st/emit! (dwt/update-text-range id start end values))))))
(defn font-variant-proxy [name id weight style]
(obj/reify {:name "FontVariantProxy"}
:name {:get (fn [] name)}
:fontVariantId {:get (fn [] id)}
:fontWeight {:get (fn [] weight)}
:fontStyle {:get (fn [] style)}))
(defn font-proxy? [p]
(instance? PenpotFont p))
(obj/type-of? p "FontProxy"))
(defn font-proxy
[{:keys [id family name variants] :as font}]
(when (some? font)
(let [default-variant (fonts/get-default-variant font)]
(PenpotFont.
name
id
family
(:style default-variant)
(:id default-variant)
(:weight default-variant)
(apply
array
(->> variants
(map (fn [{:keys [id name style weight]}]
(PenpotFontVariant. name id weight style)))))))))
(obj/reify {:name "FontProxy"}
:name {:get (fn [] name)}
:fontId {:get (fn [] id)}
:fontFamily {:get (fn [] family)}
:fontStyle {:get (fn [] (:style default-variant))}
:fontVariantId {:get (fn [] (:id default-variant))}
:fontWeight {:get (fn [] (:weight default-variant))}
(deftype PenpotFontsSubcontext [$plugin]
Object
(findById
[_ id]
(cond
(not (string? id))
(u/display-not-valid :findbyId id)
:variants
{:get
(fn []
(format/format-array
(fn [{:keys [id name style weight]}]
(font-variant-proxy name id weight style))
variants))}
:else
(font-proxy (d/seek #(str/includes? (str/lower (:id %)) (str/lower id)) (vals @fonts/fontsdb)))))
:applyToText
(fn [text variant]
(cond
(not (shape/shape-proxy? text))
(u/display-not-valid :applyToText text)
(findByName
[_ name]
(cond
(not (string? name))
(u/display-not-valid :findByName name)
(not (r/check-permission (obj/get text "$plugin") "content:write"))
(u/display-not-valid :applyToText "Plugin doesn't have 'content:write' permission")
:else
(font-proxy (d/seek #(str/includes? (str/lower (:name %)) (str/lower name)) (vals @fonts/fontsdb)))))
:else
(let [id (obj/get text "$id")
values {:font-id id
:font-family family
:font-style (d/nilv (obj/get variant "fontStyle") (:style default-variant))
:font-variant-id (d/nilv (obj/get variant "fontVariantId") (:id default-variant))
:font-weight (d/nilv (obj/get variant "fontWeight") (:wegith default-variant))}]
(st/emit! (dwt/update-attrs id values)))))
(findAllById
[_ id]
(cond
(not (string? id))
(u/display-not-valid :findAllById name)
:applyToRange
(fn [range variant]
(cond
(not (text/text-range-proxy? range))
(u/display-not-valid :applyToRange range)
:else
(apply array (->> (vals @fonts/fontsdb)
(filter #(str/includes? (str/lower (:id %)) (str/lower id)))
(map font-proxy)))))
(not (r/check-permission (obj/get range "$plugin") "content:write"))
(u/display-not-valid :applyToRange "Plugin doesn't have 'content:write' permission")
(findAllByName
[_ name]
(cond
(not (string? name))
(u/display-not-valid :findAllByName name)
:else
(apply array (->> (vals @fonts/fontsdb)
(filter #(str/includes? (str/lower (:name %)) (str/lower name)))
(map font-proxy))))))
:else
(let [id (obj/get range "$id")
start (obj/get range "start")
end (obj/get range "end")
values {:font-id id
:font-family family
:font-style (d/nilv (obj/get variant "fontStyle") (:style default-variant))
:font-variant-id (d/nilv (obj/get variant "fontVariantId") (:id default-variant))
:font-weight (d/nilv (obj/get variant "fontWeight") (:weight default-variant))}]
(st/emit! (dwt/update-text-range id start end values)))))))))
(defn fonts-subcontext
[plugin-id]
(cr/add-properties!
(PenpotFontsSubcontext. plugin-id)
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "all" :get
(fn [_]
(apply array (->> @fonts/fontsdb vals (map font-proxy))))}))
(obj/reify {:name "PenpotFontsSubcontext"}
:$plugin {:name "" :enumerable false :get (constantly plugin-id)}
:all
{:get
(fn []
(format/format-array
font-proxy
(vals @fonts/fontsdb)))}
:findById
(fn [id]
(cond
(not (string? id))
(u/display-not-valid :findbyId id)
:else
(->> (vals @fonts/fontsdb)
(d/seek #(str/includes? (str/lower (:id %)) (str/lower id)))
(font-proxy))))
:findByName
(fn [name]
(cond
(not (string? name))
(u/display-not-valid :findByName name)
:else
(->> (vals @fonts/fontsdb)
(d/seek #(str/includes? (str/lower (:name %)) (str/lower name)))
(font-proxy))))
:findAllById
(fn [id]
(cond
(not (string? id))
(u/display-not-valid :findAllById name)
:else
(format/format-array
(fn [font]
(when (str/includes? (str/lower (:id font)) (str/lower id))
(font-proxy font)))
(vals @fonts/fontsdb))))
:findAllByName
(fn [name]
(cond
(not (string? name))
(u/display-not-valid :findAllByName name)
:else
(format/format-array
(fn [font]
(when (str/includes? (str/lower (:name font)) (str/lower name))
(font-proxy font)))
(vals @fonts/fontsdb))))))

View File

@@ -103,7 +103,7 @@
;; height: number;
;; mtype?: string;
;; id: string;
;; keepApectRatio?: boolean;
;; keepAspectRatio?: boolean;
;; };
(defn format-image
[{:keys [name width height mtype id keep-aspect-ratio] :as image}]

View File

File diff suppressed because it is too large Load Diff

View File

@@ -6,47 +6,41 @@
(ns app.plugins.history
(:require
[app.common.record :as crc]
[app.main.data.workspace.undo :as dwu]
[app.main.store :as st]
[app.plugins.register :as r]
[app.plugins.utils :as u]))
(deftype HistorySubcontext [$plugin]
Object
(undoBlockBegin
[_]
(cond
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :resize "Plugin doesn't have 'content:write' permission")
:else
(let [id (js/Symbol)]
(st/emit! (dwu/start-undo-transaction id))
id)))
(undoBlockFinish
[_ block-id]
(cond
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :resize "Plugin doesn't have 'content:write' permission")
(not block-id)
(u/display-not-valid :undoBlockFinish block-id)
:else
(st/emit! (dwu/commit-undo-transaction block-id)))))
(crc/define-properties!
HistorySubcontext
{:name js/Symbol.toStringTag
:get (fn [] (str "HistorySubcontext"))})
[app.plugins.utils :as u]
[app.util.object :as obj]))
(defn history-subcontext? [p]
(instance? HistorySubcontext p))
(obj/type-of? p "HistorySubcontext"))
(defn history-subcontext
[plugin-id]
(HistorySubcontext. plugin-id))
(obj/reify {:name "HistorySubcontext"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:undoBlockBegin
(fn []
(cond
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :resize "Plugin doesn't have 'content:write' permission")
:else
(let [id (js/Symbol)]
(st/emit! (dwu/start-undo-transaction id))
id)))
:undoBlockFinish
(fn [block-id]
(cond
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :resize "Plugin doesn't have 'content:write' permission")
(not block-id)
(u/display-not-valid :undoBlockFinish block-id)
:else
(st/emit! (dwu/commit-undo-transaction block-id))))))

View File

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,6 @@
[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]
@@ -31,390 +30,394 @@
[beicon.v2.core :as rx]
[cuerdas.core :as str]))
(deftype FlowProxy [$plugin $file $page $id]
Object
(remove [_]
(st/emit! (dwi/remove-flow $page $id))))
(declare page-proxy)
(defn flow-proxy? [p]
(instance? FlowProxy p))
(obj/type-of? p "FlowProxy"))
(defn flow-proxy
[plugin-id file-id page-id id]
(crc/add-properties!
(FlowProxy. plugin-id file-id page-id id)
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "page" :enumerable false :get (fn [_] (u/locate-page file-id page-id))}
(obj/reify {:name "FlowProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:$file {:enumerable false :get (fn [] file-id)}
:$page {:enumerable false :get (fn [] page-id)}
:$id {:enumerable false :get (fn [] id)}
{:name "name"
:get #(-> % u/proxy->flow :name)
:set
(fn [_ value]
(cond
(or (not (string? value)) (empty? value))
(u/display-not-valid :name value)
:page
{:enumerable false
:get
(fn []
(page-proxy plugin-id file-id page-id))}
:else
(st/emit! (dwi/update-flow page-id id #(assoc % :name value)))))}
:name
{:this true
:get #(-> % u/proxy->flow :name)
:set
(fn [_ value]
(cond
(or (not (string? value)) (empty? value))
(u/display-not-valid :name value)
{:name "startingBoard"
:get
(fn [self]
(let [frame (-> self u/proxy->flow :starting-frame)]
(u/locate-shape file-id page-id frame)))
:set
(fn [_ value]
(cond
(not (shape/shape-proxy? value))
(u/display-not-valid :startingBoard value)
:else
(st/emit! (dwi/update-flow page-id id #(assoc % :name value)))))}
:else
(st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id"))))))}))
:startingBoard
{:this true
:get
(fn [self]
(when-let [frame (-> self u/proxy->flow :starting-frame)]
(shape/shape-proxy file-id page-id frame)))
:set
(fn [_ value]
(cond
(not (shape/shape-proxy? value))
(u/display-not-valid :startingBoard value)
(deftype PageProxy [$plugin $file $id]
Object
(getShapeById
[_ shape-id]
(cond
(not (string? shape-id))
(u/display-not-valid :getShapeById shape-id)
:else
(st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id"))))))}
:else
(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)))))
:remove
(fn []
(st/emit! (dwi/remove-flow page-id id)))))
(getRoot
[_]
(shape/shape-proxy $plugin $file $id uuid/zero))
(findShapes
[_ criteria]
;; Returns a lazy (iterable) of all available shapes
(let [criteria (parser/parse-criteria criteria)
match-criteria?
(if (some? criteria)
(fn [[_ shape]]
(and
(or (not (:name criteria))
(= (str/lower (:name criteria)) (str/lower (:name shape))))
(or (not (:name-like criteria))
(str/includes? (str/lower (:name shape)) (str/lower (:name-like criteria))))
(or (not (:type criteria))
(= (:type criteria) (:type shape)))))
identity)]
(when (and (some? $file) (some? $id))
(let [page (u/locate-page $file $id)
xf (comp
(filter match-criteria?)
(map #(shape/shape-proxy $plugin $file $id (first %))))]
(apply array (sequence xf (:objects page)))))))
;; Plugin data
(getPluginData
[self key]
(cond
(not (string? key))
(u/display-not-valid :page-plugin-data-key key)
:else
(let [page (u/proxy->page self)]
(dm/get-in page [:plugin-data (keyword "plugin" (str $plugin)) key]))))
(setPluginData
[_ key value]
(cond
(not (string? key))
(u/display-not-valid :setPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :page $id (keyword "plugin" (str $plugin)) key value))))
(getPluginDataKeys
[self]
(let [page (u/proxy->page self)]
(apply array (keys (dm/get-in page [:plugin-data (keyword "plugin" (str $plugin))])))))
(getSharedPluginData
[self namespace key]
(cond
(not (string? namespace))
(u/display-not-valid :page-plugin-data-namespace namespace)
(not (string? key))
(u/display-not-valid :page-plugin-data-key key)
:else
(let [page (u/proxy->page self)]
(dm/get-in page [:plugin-data (keyword "shared" namespace) key]))))
(setSharedPluginData
[_ namespace key value]
(cond
(not (string? namespace))
(u/display-not-valid :setSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission $plugin "content:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data $file :page $id (keyword "shared" namespace) key value))))
(getSharedPluginDataKeys
[self namespace]
(cond
(not (string? namespace))
(u/display-not-valid :page-plugin-data-namespace namespace)
:else
(let [page (u/proxy->page self)]
(apply array (keys (dm/get-in page [:plugin-data (keyword "shared" namespace)]))))))
(openPage
[_]
(cond
(not (r/check-permission $plugin "content:read"))
(u/display-not-valid :openPage "Plugin doesn't have 'content:read' permission")
:else
(st/emit! (dw/go-to-page $id))))
(createFlow
[_ name frame]
(cond
(or (not (string? name)) (empty? name))
(u/display-not-valid :createFlow-name name)
(not (shape/shape-proxy? frame))
(u/display-not-valid :createFlow-frame frame)
:else
(let [flow-id (uuid/next)]
(st/emit! (dwi/add-flow flow-id $id name (obj/get frame "$id")))
(flow-proxy $plugin $file $id flow-id))))
(removeFlow
[_ flow]
(cond
(not (flow-proxy? flow))
(u/display-not-valid :removeFlow-flow flow)
:else
(st/emit! (dwi/remove-flow $id (obj/get flow "$id")))))
(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'")
(and (some? shape)
(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 'comment: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))
position (parser/parse-point position)]
(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 "comment:write"))
(u/display-not-valid :addCommentThread "Plugin doesn't have 'comment:write' permission")
:else
(let [position
(cond-> position
(some? board)
(-> (update :x - (:x board))
(update :y - (:y board))))]
(js/Promise.
(fn [resolve]
(st/emit!
(dc/create-thread-on-workspace
{:file-id $file
:page-id $id
:position (gpt/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 "comment:write"))
(u/display-not-valid :removeCommentThread "Plugin doesn't have 'content:write' permission")
:else
(js/Promise.
(fn [resolve]
(let [thread-id (obj/get thread "$id")]
(js/Promise.
(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)]
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission $plugin "comment:read"))
(do
(u/display-not-valid :findCommentThreads "Plugin doesn't have 'comment:read' permission")
(reject "Plugin doesn't have 'comment:read' permission"))
:else
(->> (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
{:name js/Symbol.toStringTag
:get (fn [] (str "PageProxy"))})
(defn page-proxy? [p]
(instance? PageProxy p))
(defn page-proxy? [proxy]
(obj/type-of? proxy "PageProxy"))
(defn page-proxy
[plugin-id file-id id]
(crc/add-properties!
(PageProxy. plugin-id file-id id)
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
(obj/reify {:name "PageProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:$file {:enumerable false :get (fn [] file-id)}
:$id {:enumerable false :get (fn [] id)}
{:name "id"
:get #(dm/str (obj/get % "$id"))}
:id
{:get #(dm/str id)}
{:name "name"
:get #(-> % u/proxy->page :name)
:set
(fn [_ value]
:name
{:this true
:get #(-> % u/proxy->page :name)
:set
(fn [_ value]
(cond
(not (string? value))
(u/display-not-valid :name value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :name "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/rename-page id value))))}
:getRoot
(fn []
(shape/shape-proxy plugin-id file-id id uuid/zero))
:root
{:this true
:enumerable false
:get #(.getRoot ^js %)}
:background
{:this true
:get #(or (-> % u/proxy->page :background) cc/canvas)
:set
(fn [_ value]
(cond
(or (not (string? value)) (not (cc/valid-hex-color? value)))
(u/display-not-valid :background value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :background "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/change-canvas-color id {:color value}))))}
:flows
{:this true
:get
(fn [self]
(let [flows (d/nilv (-> (u/proxy->page self) :flows) [])]
(->> (vals flows)
(format/format-array #(flow-proxy plugin-id file-id id (:id %))))))}
:rulerGuides
{:this true
:get
(fn [self]
(let [guides (-> (u/proxy->page self) :guides)]
(->> guides
(vals)
(filter #(nil? (:frame-id %)))
(format/format-array #(rg/ruler-guide-proxy plugin-id file-id id (:id %))))))}
:getShapeById
(fn [shape-id]
(cond
(not (string? value))
(u/display-not-valid :name value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :name "Plugin doesn't have 'content:write' permission")
(not (string? shape-id))
(u/display-not-valid :getShapeById shape-id)
:else
(st/emit! (dw/rename-page id value))))}
(let [shape-id (uuid/uuid shape-id)
shape (u/locate-shape file-id id shape-id)]
(when (some? shape)
(shape/shape-proxy plugin-id file-id id shape-id)))))
{:name "root"
:enumerable false
:get #(.getRoot ^js %)}
:findShapes
(fn [criteria]
;; Returns a lazy (iterable) of all available shapes
(let [criteria (parser/parse-criteria criteria)
match-criteria?
(if (some? criteria)
(fn [[_ shape]]
(and
(or (not (:name criteria))
(= (str/lower (:name criteria)) (str/lower (:name shape))))
{:name "background"
:enumerable false
:get #(or (-> % u/proxy->page :background) cc/canvas)
:set
(fn [_ value]
(or (not (:name-like criteria))
(str/includes? (str/lower (:name shape)) (str/lower (:name-like criteria))))
(or (not (:type criteria))
(= (:type criteria) (:type shape)))))
identity)]
(when (and (some? file-id) (some? id))
(let [page (u/locate-page file-id id)
xf (comp
(filter match-criteria?)
(map #(shape/shape-proxy plugin-id file-id id (first %))))]
(apply array (sequence xf (:objects page)))))))
;; Plugin data
:getPluginData
(fn [key]
(cond
(or (not (string? value)) (not (cc/valid-hex-color? value)))
(u/display-not-valid :background value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :background "Plugin doesn't have 'content:write' permission")
(not (string? key))
(u/display-not-valid :page-plugin-data-key key)
:else
(st/emit! (dw/change-canvas-color id {:color value}))))}
(let [page (u/locate-page file-id id)]
(dm/get-in page [:plugin-data (keyword "plugin" (str plugin-id)) key]))))
{:name "flows"
:get
(fn [self]
(let [flows (d/nilv (-> (u/proxy->page self) :flows) [])]
(format/format-array #(flow-proxy plugin-id file-id id (:id %)) flows)))}
:setPluginData
(fn [key value]
(cond
(not (string? key))
(u/display-not-valid :setPluginData-key key)
{:name "rulerGuides"
:get
(fn [self]
(let [guides (-> (u/proxy->page self) :guides)]
(->> guides
(vals)
(filter #(nil? (:frame-id %)))
(format/format-array #(rg/ruler-guide-proxy plugin-id file-id id (:id %))))))}))
(and (some? value) (not (string? value)))
(u/display-not-valid :setPluginData-value value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :setPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data file-id :page id (keyword "plugin" (str plugin-id)) key value))))
:getPluginDataKeys
(fn []
(let [page (u/locate-page file-id id)]
(apply array (keys (dm/get-in page [:plugin-data (keyword "plugin" (str plugin-id))])))))
:getSharedPluginData
(fn [namespace key]
(cond
(not (string? namespace))
(u/display-not-valid :page-plugin-data-namespace namespace)
(not (string? key))
(u/display-not-valid :page-plugin-data-key key)
:else
(let [page (u/locate-page file-id id)]
(dm/get-in page [:plugin-data (keyword "shared" namespace) key]))))
:setSharedPluginData
(fn [namespace key value]
(cond
(not (string? namespace))
(u/display-not-valid :setSharedPluginData-namespace namespace)
(not (string? key))
(u/display-not-valid :setSharedPluginData-key key)
(and (some? value) (not (string? value)))
(u/display-not-valid :setSharedPluginData-value value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :setSharedPluginData "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dw/set-plugin-data file-id :page id (keyword "shared" namespace) key value))))
:getSharedPluginDataKeys
(fn [self namespace]
(cond
(not (string? namespace))
(u/display-not-valid :page-plugin-data-namespace namespace)
:else
(let [page (u/proxy->page self)]
(apply array (keys (dm/get-in page [:plugin-data (keyword "shared" namespace)]))))))
:openPage
(fn []
(cond
(not (r/check-permission plugin-id "content:read"))
(u/display-not-valid :openPage "Plugin doesn't have 'content:read' permission")
:else
(st/emit! (dw/go-to-page id))))
:createFlow
(fn [name frame]
(cond
(or (not (string? name)) (empty? name))
(u/display-not-valid :createFlow-name name)
(not (shape/shape-proxy? frame))
(u/display-not-valid :createFlow-frame frame)
:else
(let [flow-id (uuid/next)]
(st/emit! (dwi/add-flow flow-id id name (obj/get frame "$id")))
(flow-proxy plugin-id file-id id flow-id))))
:removeFlow
(fn [flow]
(cond
(not (flow-proxy? flow))
(u/display-not-valid :removeFlow-flow flow)
:else
(st/emit! (dwi/remove-flow id (obj/get flow "$id")))))
:addRulerGuide
(fn [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'")
(and (some? shape)
(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-id "content:write"))
(u/display-not-valid :addRulerGuide "Plugin doesn't have 'content:write' permission")
:else
(let [ruler-id (uuid/next)]
(st/emit!
(dwgu/update-guides
(d/without-nils
{:id ruler-id
:axis (parser/orientation->axis orientation)
:position value
:frame-id (when board (obj/get board "$id"))})))
(rg/ruler-guide-proxy plugin-id file-id id ruler-id)))))
:removeRulerGuide
(fn [value]
(cond
(not (rg/ruler-guide-proxy? value))
(u/display-not-valid :removeRulerGuide "Guide not provided")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :removeRulerGuide "Plugin doesn't have 'comment:write' permission")
:else
(let [guide (u/proxy->ruler-guide value)]
(st/emit! (dwgu/remove-guide guide)))))
:addCommentThread
(fn [content position board]
(let [shape (when board (u/proxy->shape board))
position (parser/parse-point position)]
(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-id "comment:write"))
(u/display-not-valid :addCommentThread "Plugin doesn't have 'comment:write' permission")
:else
(let [position
(cond-> position
(some? board)
(-> (update :x - (:x board))
(update :y - (:y board))))]
(js/Promise.
(fn [resolve]
(st/emit!
(dc/create-thread-on-workspace
{:file-id file-id
:page-id id
:position (gpt/point position)
:content content}
(fn [data]
(->> (rp/cmd! :get-team-users {:file-id file-id})
(rx/subs!
(fn [users]
(let [users (d/index-by :id users)]
(resolve (pc/comment-thread-proxy plugin-id file-id id users data)))))))
false))))))))
:removeCommentThread
(fn [thread]
(cond
(not (pc/comment-thread-proxy? thread))
(u/display-not-valid :removeCommentThread "Comment thread not valid")
(not (r/check-permission plugin-id "comment:write"))
(u/display-not-valid :removeCommentThread "Plugin doesn't have 'content:write' permission")
:else
(js/Promise.
(fn [resolve]
(let [thread-id (obj/get thread "$id")]
(js/Promise.
(st/emit! (dc/delete-comment-thread-on-workspace {:id thread-id} #(resolve)))))))))
:findCommentThreads
(fn [criteria]
(let [only-yours (boolean (obj/get criteria "onlyYours" false))
show-resolved (boolean (obj/get criteria "showResolved" true))
user-id (-> @st/state :profile :id)]
(js/Promise.
(fn [resolve reject]
(cond
(not (r/check-permission plugin-id "comment:read"))
(do
(u/display-not-valid :findCommentThreads "Plugin doesn't have 'comment:read' permission")
(reject "Plugin doesn't have 'comment:read' permission"))
:else
(->> (rx/zip (rp/cmd! :get-team-users {:file-id file-id})
(rp/cmd! :get-comment-threads {:file-id file-id}))
(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-id file-id id users %) comments))))
reject)))))))))

View File

@@ -66,7 +66,7 @@
;; height: number;
;; mtype?: string;
;; id: string;
;; keepApectRatio?: boolean;
;; keepAspectRatio?: boolean;
;;}
(defn parse-image-data
[^js image-data]
@@ -77,7 +77,7 @@
:width (obj/get image-data "width")
:height (obj/get image-data "height")
:mtype (obj/get image-data "mtype")
:keep-aspect-ratio (obj/get image-data "keepApectRatio")})))
:keep-aspect-ratio (obj/get image-data "keepAspectRatio")})))
;; export type Gradient = {
;; type: 'linear' | 'radial';

View File

@@ -45,7 +45,7 @@
(conj "content:read")
(contains? permissions "library:write")
(conj "content:write")
(conj "library:read")
(contains? permissions "comment:write")
(conj "comment:read"))

View File

@@ -8,7 +8,6 @@
(: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]
@@ -20,80 +19,82 @@
(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))
(obj/type-of? p "RulerGuideProxy"))
(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)}
(obj/reify {:name "RuleGuideProxy"}
:$plugin {:enumerable false :get (constantly plugin-id)}
:$file {:enumerable false :get (constantly file-id)}
:$page {:enumerable false :get (constantly page-id)}
:$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))))
:board
{:this true
: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")
: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 (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")
(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)))))))}
: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)}
:orientation
{:this true
: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))
:position
{:this true
: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")
;; 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")
(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))
: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))))))}))
value)]
(st/emit! (dwgu/update-guides (assoc guide :position position))))))}
:remove
(fn []
(let [guide (u/locate-ruler-guide file-id page-id id)]
(st/emit! (dwgu/remove-guide guide))))))

View File

File diff suppressed because it is too large Load Diff

View File

@@ -27,21 +27,16 @@
;; This regex seems duplicated but probably in the future when we support diferent units
;; this will need to reflect changes for each property
(def font-size-re #"^\d*\.?\d*$")
(def line-height-re #"^\d*\.?\d*$")
(def letter-spacing-re #"^\d*\.?\d*$")
(def text-transform-re #"uppercase|capitalize|lowercase|none")
(def text-decoration-re #"underline|line-through|none")
(def text-direction-re #"ltr|rtl")
(def text-align-re #"left|center|right|justify")
(def vertical-align-re #"top|center|bottom")
(def ^:private font-size-re #"^\d*\.?\d*$")
(def ^:private line-height-re #"^\d*\.?\d*$")
(def ^:private letter-spacing-re #"^\d*\.?\d*$")
(def ^:private text-transform-re #"uppercase|capitalize|lowercase|none")
(def ^:private text-decoration-re #"underline|line-through|none")
(def ^:private text-direction-re #"ltr|rtl")
(def ^:private text-align-re #"left|center|right|justify")
(def ^:private vertical-align-re #"top|center|bottom")
(defn mixed-value
[values]
(let [s (set values)]
(if (= (count s) 1) (first s) "mixed")))
(defn font-data
(defn- font-data
[font variant]
(d/without-nils
{:font-id (:id font)
@@ -50,284 +45,326 @@
:font-style (:style variant)
:font-weight (:weight variant)}))
(defn variant-data
(defn- variant-data
[variant]
(d/without-nils
{:font-variant-id (:id variant)
:font-style (:style variant)
:font-weight (:weight variant)}))
(deftype TextRange [$plugin $file $page $id start end]
Object
(applyTypography [_ typography]
(let [typography (u/proxy->library-typography typography)
attrs (-> typography
(assoc :typography-ref-file $file)
(assoc :typography-ref-id (:id typography))
(dissoc :id :name))]
(st/emit! (dwt/update-text-range $id start end attrs)))))
(defn text-range?
[range]
(instance? TextRange range))
(defn text-props
(defn- text-props
[shape]
(d/merge
(dwt/current-root-values {:shape shape :attrs txt/root-attrs})
(dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs})
(dwt/current-text-values {:shape shape :attrs txt/text-node-attrs})))
(defn text-range
(defn text-range-proxy?
[range]
(obj/type-of? range "TextRange"))
(defn text-range-proxy
[plugin-id file-id page-id id start end]
(-> (TextRange. plugin-id file-id page-id id start end)
(crc/add-properties!
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
(obj/reify {:name "TextRange"}
:$plugin {:enumerable false :get (constantly plugin-id)}
:$id {:enumerable false :get (constantly id)}
:$file {:enumerable false :get (constantly file-id)}
:$page {:enumerable false :get (constantly page-id)}
{:name "shape"
:get #(-> % u/proxy->shape)}
:shape
{:this true
:get #(-> % u/proxy->shape)}
{:name "characters"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text) (str/join "")))}
:characters
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text) (str/join ""))))}
{:name "fontId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-id) mixed-value))
:fontId
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-id) u/mixed-value)))
:set
(fn [_ value]
(let [font (when (string? value) (fonts/get-font-data value))
variant (fonts/get-default-variant font)]
(cond
(not font)
(u/display-not-valid :fontId value)
:set
(fn [_ value]
(let [font (when (string? value) (fonts/get-font-data value))
variant (fonts/get-default-variant font)]
(cond
(not font)
(u/display-not-valid :fontId value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontId "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontId "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end (font-data font variant))))))}
:else
(st/emit! (dwt/update-text-range id start end (font-data font variant))))))}
{:name "fontFamily"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-family) mixed-value))
:fontFamily
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-family) u/mixed-value)))
:set
(fn [_ value]
(let [font (fonts/find-font-data {:family value})
variant (fonts/get-default-variant font)]
(cond
(not (string? value))
(u/display-not-valid :fontFamily value)
:set
(fn [_ value]
(let [font (fonts/find-font-data {:family value})
variant (fonts/get-default-variant font)]
(cond
(not (string? value))
(u/display-not-valid :fontFamily value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontFamily "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontFamily "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end (font-data font variant))))))}
:else
(st/emit! (dwt/update-text-range id start end (font-data font variant))))))}
{:name "fontVariantId"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-variant-id) mixed-value))
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
variant (fonts/get-variant font value)]
(cond
(not (string? value))
(u/display-not-valid :fontVariantId value)
:fontVariantId
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-variant-id) u/mixed-value)))
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
variant (fonts/get-variant font value)]
(cond
(not (string? value))
(u/display-not-valid :fontVariantId value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontVariantId "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontVariantId "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
{:name "fontSize"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-size) mixed-value))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (not (re-matches font-size-re value)))
(u/display-not-valid :fontSize value)
:fontSize
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-size) u/mixed-value)))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (not (re-matches font-size-re value)))
(u/display-not-valid :fontSize value)
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontSize "Plugin doesn't have 'content:write' permission")
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontSize "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:font-size value})))))}
:else
(st/emit! (dwt/update-text-range id start end {:font-size value})))))}
{:name "fontWeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-weight) mixed-value))
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
weight (dm/str value)
style (obj/get self "fontStyle")
variant
(or
(fonts/find-variant font {:style style :weight weight})
(fonts/find-variant font {:weight weight}))]
(cond
(nil? variant)
(u/display-not-valid :fontWeight (dm/str "Font weight '" value "' not supported for the current font"))
:fontWeight
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-weight) u/mixed-value)))
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontWeight "Plugin doesn't have 'content:write' permission")
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
weight (dm/str value)
style (obj/get self "fontStyle")
variant
(or
(fonts/find-variant font {:style style :weight weight})
(fonts/find-variant font {:weight weight}))]
(cond
(nil? variant)
(u/display-not-valid :fontWeight (dm/str "Font weight '" value "' not supported for the current font"))
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontWeight "Plugin doesn't have 'content:write' permission")
{:name "fontStyle"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-style) mixed-value))
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
style (dm/str value)
weight (obj/get self "fontWeight")
variant
(or
(fonts/find-variant font {:weight weight :style style})
(fonts/find-variant font {:style style}))]
(cond
(nil? variant)
(u/display-not-valid :fontStyle (dm/str "Font style '" value "' not supported for the current font"))
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontStyle "Plugin doesn't have 'content:write' permission")
:fontStyle
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :font-style) u/mixed-value)))
:set
(fn [self value]
(let [font (fonts/get-font-data (obj/get self "fontId"))
style (dm/str value)
weight (obj/get self "fontWeight")
variant
(or
(fonts/find-variant font {:weight weight :style style})
(fonts/find-variant font {:style style}))]
(cond
(nil? variant)
(u/display-not-valid :fontStyle (dm/str "Font style '" value "' not supported for the current font"))
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fontStyle "Plugin doesn't have 'content:write' permission")
{:name "lineHeight"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :line-height) mixed-value))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (not (re-matches line-height-re value)))
(u/display-not-valid :lineHeight value)
:else
(st/emit! (dwt/update-text-range id start end (variant-data variant))))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :lineHeight "Plugin doesn't have 'content:write' permission")
:lineHeight
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :line-height) u/mixed-value)))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (not (re-matches line-height-re value)))
(u/display-not-valid :lineHeight value)
:else
(st/emit! (dwt/update-text-range id start end {:line-height value})))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :lineHeight "Plugin doesn't have 'content:write' permission")
{:name "letterSpacing"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :letter-spacing) mixed-value))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (re-matches letter-spacing-re value))
(u/display-not-valid :letterSpacing value)
:else
(st/emit! (dwt/update-text-range id start end {:line-height value})))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :letterSpacing "Plugin doesn't have 'content:write' permission")
:letterSpacing
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :letter-spacing) u/mixed-value)))
:set
(fn [_ value]
(let [value (str/trim (dm/str value))]
(cond
(or (empty? value) (re-matches letter-spacing-re value))
(u/display-not-valid :letterSpacing value)
:else
(st/emit! (dwt/update-text-range id start end {:letter-spacing value})))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :letterSpacing "Plugin doesn't have 'content:write' permission")
{:name "textTransform"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-transform) mixed-value))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-transform-re value))
(u/display-not-valid :textTransform value)
:else
(st/emit! (dwt/update-text-range id start end {:letter-spacing value})))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textTransform "Plugin doesn't have 'content:write' permission")
:textTransform
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-transform) u/mixed-value)))
:set
(fn [_ value]
(cond
(and (string? value) (not (re-matches text-transform-re value)))
(u/display-not-valid :textTransform value)
:else
(st/emit! (dwt/update-text-range id start end {:text-transform value}))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textTransform "Plugin doesn't have 'content:write' permission")
{:name "textDecoration"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-decoration) mixed-value))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-decoration-re value))
(u/display-not-valid :textDecoration value)
:else
(st/emit! (dwt/update-text-range id start end {:text-transform value}))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textDecoration "Plugin doesn't have 'content:write' permission")
:textDecoration
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-decoration) u/mixed-value)))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-decoration-re value))
(u/display-not-valid :textDecoration value)
:else
(st/emit! (dwt/update-text-range id start end {:text-decoration value}))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :textDecoration "Plugin doesn't have 'content:write' permission")
{:name "direction"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :direction) mixed-value))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-direction-re value))
(u/display-not-valid :direction value)
:else
(st/emit! (dwt/update-text-range id start end {:text-decoration value}))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :direction "Plugin doesn't have 'content:write' permission")
:direction
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :direction) u/mixed-value)))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-direction-re value))
(u/display-not-valid :direction value)
:else
(st/emit! (dwt/update-text-range id start end {:direction value}))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :direction "Plugin doesn't have 'content:write' permission")
{:name "align"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-align) mixed-value))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-align-re value))
(u/display-not-valid :align value)
:else
(st/emit! (dwt/update-text-range id start end {:direction value}))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :align "Plugin doesn't have 'content:write' permission")
:align
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :text-align) u/mixed-value)))
:set
(fn [_ value]
(cond
(and (string? value) (re-matches text-align-re value))
(u/display-not-valid :align value)
:else
(st/emit! (dwt/update-text-range id start end {:text-align value}))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :align "Plugin doesn't have 'content:write' permission")
{:name "fills"
:get #(let [range-data
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :fills) mixed-value format/format-fills))
:set
(fn [_ value]
(let [value (parser/parse-fills value)]
(cond
(not (sm/validate [:vector ::cts/fill] value))
(u/display-not-valid :fills value)
:else
(st/emit! (dwt/update-text-range id start end {:text-align value}))))}
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fills "Plugin doesn't have 'content:write' permission")
:fills
{:this true
:get
(fn [self]
(let [range-data
(-> self u/proxy->shape :content (txt/content-range->text+styles start end))]
(->> range-data (map :fills) u/mixed-value format/format-fills)))
:set
(fn [_ value]
(let [value (parser/parse-fills value)]
(cond
(not (sm/validate [:vector ::cts/fill] value))
(u/display-not-valid :fills value)
:else
(st/emit! (dwt/update-text-range id start end {:fills value})))))})))
(not (r/check-permission plugin-id "content:write"))
(u/display-not-valid :fills "Plugin doesn't have 'content:write' permission")
:else
(st/emit! (dwt/update-text-range id start end {:fills value})))))}
:applyTypography
(fn [typography]
(let [typography (u/proxy->library-typography typography)
attrs (-> typography
(assoc :typography-ref-file file-id)
(assoc :typography-ref-id (:id typography))
(dissoc :id :name))]
(st/emit! (dwt/update-text-range id start end attrs))))))
(defn add-text-props
[shape-proxy plugin-id]

View File

@@ -41,23 +41,28 @@
(defn current-user-proxy? [p]
(instance? CurrentUserProxy p))
(obj/type-of? p "CurrentUserProxy"))
(defn current-user-proxy
[plugin-id session-id]
(-> (CurrentUserProxy. plugin-id)
(-> (obj/reify {:name "CurrentUserProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)})
(add-session-properties session-id)))
(defn active-user-proxy? [p]
(instance? ActiveUserProxy p))
(obj/type-of? p "ActiveUserProxy"))
(defn active-user-proxy
[plugin-id session-id]
(-> (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))})))
(-> (obj/reify {:name "ActiveUserProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:position
{:get (fn [] (-> (u/locate-presence session-id) :point format/format-point))}
:zoom
{:get (fn [] (-> (u/locate-presence session-id) :zoom))})
(add-session-properties session-id)))
(defn- add-user-properties
[user-proxy data]
@@ -75,13 +80,14 @@
{:name "avatarUrl"
:get (fn [_] (cfg/resolve-profile-photo-url data))})))
(defn user-proxy?
[p]
(or (instance? UserProxy p)
(current-user-proxy? p)
(active-user-proxy? p)))
(defn user-proxy
[plugin-id data]
(-> (UserProxy. plugin-id)
(-> (obj/reify {:name "UserProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)})
(add-user-properties data)))
(defn user-proxy?
[p]
(or (obj/type-of? p "UserProxy")
(current-user-proxy? p)
(active-user-proxy? p)))

View File

@@ -119,26 +119,33 @@
flow-id (obj/get proxy "$id")
page (locate-page file-id page-id)]
(when (some? page)
(d/seek #(= (:id %) flow-id) (:flows page)))))
(get (:flows page) flow-id))))
(defn locate-ruler-guide
[file-id page-id ruler-id]
(let [page (locate-page file-id page-id)]
(when (some? page)
(d/seek #(= (:id %) ruler-id) (-> page :guides vals)))))
(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 :guides vals)))))
ruler-id (obj/get proxy "$id")]
(locate-ruler-guide file-id page-id ruler-id)))
(defn locate-interaction
[file-id page-id shape-id index]
(when-let [shape (locate-shape file-id page-id shape-id)]
(get-in shape [:interactions index])))
(defn proxy->interaction
[proxy]
(let [file-id (obj/get proxy "$file")
page-id (obj/get proxy "$page")
shape-id (obj/get proxy "$shape")
index (obj/get proxy "$index")
shape (locate-shape file-id page-id shape-id)]
(when (some? shape)
(get-in shape [:interactions index]))))
index (obj/get proxy "$index")]
(locate-interaction file-id page-id shape-id index)))
(defn get-data
([self attr]
@@ -193,3 +200,8 @@
(let [msg (dm/str "[PENPOT PLUGIN] Value not valid: " value ". Code: " code)]
(.error js/console msg)
(reject msg)))
(defn mixed-value
[values]
(let [s (set values)]
(if (= (count s) 1) (first s) "mixed")))

View File

@@ -7,7 +7,6 @@
(ns app.plugins.viewport
(:require
[app.common.data.macros :as dm]
[app.common.record :as crc]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.data.workspace.viewport :as dwv]
@@ -17,83 +16,81 @@
[app.plugins.utils :as u]
[app.util.object :as obj]))
(deftype ViewportProxy [$plugin]
Object
(zoomReset [_]
(st/emit! dwz/reset-zoom))
(zoomToFitAll [_]
(st/emit! dwz/zoom-to-fit-all))
(zoomIntoView [_ shapes]
(let [ids
(->> shapes
(map (fn [v]
(if (string? v)
(uuid/uuid v)
(uuid/uuid (obj/get v "x"))))))]
(st/emit! (dwz/fit-to-shapes ids)))))
(crc/define-properties!
ViewportProxy
{:name js/Symbol.toStringTag
:get (fn [] (str "ViewportProxy"))})
(defn viewport-proxy? [p]
(instance? ViewportProxy p))
(obj/type-of? p "ViewportProxy"))
(defn viewport-proxy
[plugin-id]
(crc/add-properties!
(ViewportProxy. plugin-id)
{:name "center"
:get
(fn [_]
(let [vp (dm/get-in @st/state [:workspace-local :vbox])
x (+ (:x vp) (/ (:width vp) 2))
y (+ (:y vp) (/ (:height vp) 2))]
(.freeze js/Object #js {:x x :y y})))
(obj/reify {:name "ViewportProxy"}
:$plugin {:enumerable false :get (fn [] plugin-id)}
:set
(fn [_ value]
(let [new-x (obj/get value "x")
new-y (obj/get value "y")]
(cond
(not (us/safe-number? new-x))
(u/display-not-valid :center-x new-x)
:center
{:get
(fn []
(let [vp (dm/get-in @st/state [:workspace-local :vbox])
x (+ (:x vp) (/ (:width vp) 2))
y (+ (:y vp) (/ (:height vp) 2))]
(.freeze js/Object #js {:x x :y y})))
(not (us/safe-number? new-y))
(u/display-not-valid :center-y new-y)
:set
(fn [value]
(let [new-x (obj/get value "x")
new-y (obj/get value "y")]
(cond
(not (us/safe-number? new-x))
(u/display-not-valid :center-x new-x)
:else
(let [vb (dm/get-in @st/state [:workspace-local :vbox])
old-x (+ (:x vb) (/ (:width vb) 2))
old-y (+ (:y vb) (/ (:height vb) 2))
delta-x (- new-x old-x)
delta-y (- new-y old-y)
to-position
{:x #(+ % delta-x)
:y #(+ % delta-y)}]
(st/emit! (dwv/update-viewport-position to-position))))))}
(not (us/safe-number? new-y))
(u/display-not-valid :center-y new-y)
{:name "zoom"
:get
(fn [_]
(dm/get-in @st/state [:workspace-local :zoom]))
:set
(fn [_ value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :zoom value)
:else
(let [vb (dm/get-in @st/state [:workspace-local :vbox])
old-x (+ (:x vb) (/ (:width vb) 2))
old-y (+ (:y vb) (/ (:height vb) 2))
delta-x (- new-x old-x)
delta-y (- new-y old-y)
to-position
{:x #(+ % delta-x)
:y #(+ % delta-y)}]
(st/emit! (dwv/update-viewport-position to-position))))))}
:else
(let [z (dm/get-in @st/state [:workspace-local :zoom])]
(st/emit! (dwz/set-zoom (/ value z))))))}
:zoom
{:get
(fn []
(dm/get-in @st/state [:workspace-local :zoom]))
{:name "bounds"
:get
(fn [_]
(let [vbox (dm/get-in @st/state [:workspace-local :vbox])]
(.freeze js/Object (format/format-bounds vbox))))}))
:set
(fn [value]
(cond
(not (us/safe-number? value))
(u/display-not-valid :zoom value)
:else
(let [z (dm/get-in @st/state [:workspace-local :zoom])]
(st/emit! (dwz/set-zoom (/ value z))))))}
:bounds
{:get
(fn []
(let [vbox (dm/get-in @st/state [:workspace-local :vbox])]
(.freeze js/Object (format/format-bounds vbox))))}
:zoomReset
(fn []
(st/emit! dwz/reset-zoom))
:zoomToFitAll
(fn []
(st/emit! dwz/zoom-to-fit-all))
:zoomIntoView
(fn [shapes]
(let [ids
(->> shapes
(map (fn [v]
(if (string? v)
(uuid/uuid v)
(uuid/uuid (obj/get v "x"))))))]
(st/emit! (dwz/fit-to-shapes ids))))))

View File

@@ -12,7 +12,6 @@
[app.common.types.shape.impl :as ctsi]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.util.object :as obj]
[promesa.core :as p]))
(defn initialize
@@ -28,21 +27,24 @@
(defn create-shape
[id]
(let [buffer (uuid/uuid->u32 id)]
(._create_shape ^js internal-module (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
(let [buffer (uuid/uuid->u32 id)
create-shape (unchecked-get internal-module "_create_shape")]
(^function create-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
(defn use-shape
[id]
(let [buffer (uuid/uuid->u32 id)]
(._use_shape ^js internal-module (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
(let [buffer (uuid/uuid->u32 id)
use-shape (unchecked-get internal-module "_use_shape")]
(^function use-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
(defn set-shape-selrect
[selrect]
(let [x1 (:x1 selrect)
y1 (:y1 selrect)
x2 (:x2 selrect)
y2 (:y2 selrect)]
(._set_shape_selrect ^js internal-module x1 y1 x2 y2)))
y2 (:y2 selrect)
set-shape-selrect (unchecked-get internal-module "_set_shape_selrect")]
(^function set-shape-selrect x1 y1 x2 y2)))
(defn set-shape-transform
[transform]
@@ -51,27 +53,33 @@
c (:c transform)
d (:d transform)
e (:e transform)
f (:f transform)]
(._set_shape_transform ^js internal-module a b c d e f)))
f (:f transform)
set-shape-transform (unchecked-get internal-module "_set_shape_transform")]
(^function set-shape-transform a b c d e f)))
(defn set-shape-rotation
[rotation]
(._set_shape_rotation ^js internal-module rotation))
(let [set-shape-rotation (unchecked-get internal-module "_set_shape_rotation")]
(^function set-shape-rotation rotation)))
(defn set-shape-children
[shape_ids]
(._clear_shape_children ^js internal-module)
(doseq [id shape_ids]
(let [buffer (uuid/uuid->u32 id)]
(._add_shape_child ^js internal-module (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3)))))
(let [clear-shape-children (unchecked-get internal-module "_clear_shape_children")
add-shape-child (unchecked-get internal-module "_add_shape_child")]
(^function clear-shape-children)
(doseq [id shape_ids]
(let [buffer (uuid/uuid->u32 id)]
(^function add-shape-child (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))))
(defn set-shape-fills
[fills]
(._clear_shape_fills ^js internal-module)
(doseq [fill (filter #(contains? % :fill-color) fills)]
(let [a (:fill-opacity fill)
[r g b] (cc/hex->rgb (:fill-color fill))]
(._add_shape_solid_fill ^js internal-module r g b a))))
(let [clear-shape-fills (unchecked-get internal-module "_clear_shape_fills")
add-shape-fill (unchecked-get internal-module "_add_shape_solid_fill")]
(^function clear-shape-fills)
(doseq [fill (filter #(contains? % :fill-color) fills)]
(let [a (:fill-opacity fill)
[r g b] (cc/hex->rgb (:fill-color fill))]
(^function add-shape-fill r g b a)))))
(defn set-objects
[objects]
@@ -98,9 +106,10 @@
[zoom vbox]
(js/requestAnimationFrame
(fn []
(let [pan-x (- (dm/get-prop vbox :x))
pan-y (- (dm/get-prop vbox :y))]
(._draw_all_shapes ^js internal-module zoom pan-x pan-y)))))
(let [pan-x (- (dm/get-prop vbox :x))
pan-y (- (dm/get-prop vbox :y))
draw-all-shapes (unchecked-get internal-module "_draw_all_shapes")]
(^function draw-all-shapes zoom pan-x pan-y)))))
(defn cancel-draw
[frame-id]
@@ -129,12 +138,10 @@
handle (.registerContext ^js gl context #js {"majorVersion" 2})]
(.makeContextCurrent ^js gl handle)
;; Initialize Skia
(init-fn (.-width ^js canvas)
(.-height ^js canvas))
(^function init-fn (.-width ^js canvas)
(.-height ^js canvas))
(set! (.-width canvas) (.-clientWidth ^js canvas))
(set! (.-height canvas) (.-clientHeight ^js canvas))
(obj/set! js/window "shape_list" (fn [] ((unchecked-get internal-module "_shape_list"))))))
(set! (.-height canvas) (.-clientHeight ^js canvas))))
(defonce module
(if (exists? js/dynamicImport)

View File

@@ -148,9 +148,7 @@
(mf/set-ref-val! internal-state initial))
(mf/with-effect [initial]
(if (fn? initial)
(swap! form-mutator update :data merge (initial))
(swap! form-mutator update :data merge initial)))
(swap! form-mutator d/deep-merge initial))
form-mutator))

View File

@@ -0,0 +1,247 @@
;; 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.util.object
"A collection of helpers for work with javascript objects."
(:refer-clojure :exclude [set! new get merge clone contains? array? into-array reify])
#?(:cljs (:require-macros [app.util.object]))
(:require
[clojure.core :as c]))
#?(:cljs
(defn array?
[o]
(.isArray js/Array o)))
#?(:cljs
(defn into-array
[o]
(js/Array.from o)))
#?(:cljs
(defn create [] #js {}))
#?(:cljs
(defn get
([obj k]
(when (some? obj)
(unchecked-get obj k)))
([obj k default]
(let [result (get obj k)]
(if (undefined? result) default result)))))
#?(:cljs
(defn contains?
[obj k]
(when (some? obj)
(js/Object.hasOwn obj k))))
#?(:cljs
(defn clone
[a]
(js/Object.assign #js {} a)))
#?(:cljs
(defn merge!
([a b]
(js/Object.assign a b))
([a b & more]
(reduce merge! (merge! a b) more))))
#?(:cljs
(defn merge
([a b]
(js/Object.assign #js {} a b))
([a b & more]
(reduce merge! (merge a b) more))))
#?(:cljs
(defn set!
[obj key value]
(unchecked-set obj key value)
obj))
#?(:cljs
(defn unset!
[obj key]
(js-delete obj key)
obj))
#?(:cljs
(def ^:private not-found-sym
(js/Symbol "not-found")))
#?(:cljs
(defn update!
[obj key f & args]
(let [found (c/get obj key not-found-sym)]
(when-not ^boolean (identical? found not-found-sym)
(unchecked-set obj key (apply f found args)))
obj)))
#?(:cljs
(defn ^boolean in?
[obj prop]
(js* "~{} in ~{}" prop obj)))
#?(:cljs
(defn without-empty
[^js obj]
(when (some? obj)
(js* "Object.entries(~{}).reduce((a, [k,v]) => (v == null ? a : (a[k]=v, a)), {}) " obj))))
(defmacro add-properties!
"Adds properties to an object using `.defineProperty`"
[rsym & properties]
(let [rsym (with-meta rsym {:tag 'js})
getf-sym (with-meta (gensym (str rsym "-get-fn-")) {:tag 'js})
setf-sym (with-meta (gensym (str rsym "-set-fn-")) {:tag 'js})
this-sym (with-meta (gensym (str rsym "-this-")) {:tag 'js})
target-sym (with-meta (gensym (str rsym "-target-")) {:tag 'js})]
`(let [~target-sym ~rsym]
;; Creates the `.defineProperty` per property
~@(for [params properties
:let [pname (c/get params :name)
get-expr (c/get params :get)
set-expr (c/get params :set)
this? (c/get params :this true)
enum? (c/get params :enumerable true)
conf? (c/get params :configurable)
writ? (c/get params :writable)]]
`(let [~@(concat
(when get-expr
[getf-sym get-expr])
(when set-expr
[setf-sym set-expr]))]
(.defineProperty
js/Object
~target-sym
~pname
(cljs.core/js-obj
~@(concat
["enumerable" (boolean enum?)]
(when conf?
["configurable" true])
(when (some? writ?)
["writable" true])
(when get-expr
(if this?
["get" `(fn [] (cljs.core/this-as ~this-sym (~getf-sym ~this-sym)))]
["get" getf-sym]))
(when set-expr
(if this?
["set" `(fn [v#] (cljs.core/this-as ~this-sym (~setf-sym ~this-sym v#)))]
["set" setf-sym])))))))
;; Returns the object
~target-sym)))
(defn- collect-properties
[params]
(let [[tmeta params] (if (map? (first params))
[(first params) (rest params)]
[{} params])]
(loop [params (seq params)
props []
defs {}
curr :start
ckey nil]
(cond
(= curr :start)
(let [candidate (first params)]
(cond
(keyword? candidate)
(recur (rest params) props defs :property candidate)
(nil? candidate)
(recur (rest params) props defs :end nil)
:else
(recur (rest params) props defs :definition candidate)))
(= :end curr)
[tmeta props defs]
(= :property curr)
(let [definition (first params)]
(if (some? definition)
(let [definition (if (map? definition)
(c/merge {:this false} (assoc definition :name (name ckey)))
(-> {:enumerable false}
(c/merge (meta definition))
(assoc :name (name ckey))
(assoc :this false)
(assoc :get `(fn [] ~definition))))]
(recur (rest params)
(conj props definition)
defs
:start
nil))
(let [hint (str "expected property definition for: " curr)]
(throw (ex-info hint {:key curr})))))
(= :definition curr)
(let [[params props defs curr ckey]
(loop [params params
defs (update defs ckey #(or % []))]
(let [candidate (first params)
params (rest params)]
(cond
(nil? candidate)
[params props defs :end]
(keyword? candidate)
[params props defs :property candidate]
(symbol? candidate)
[params props defs :definition candidate]
:else
(recur params (update defs ckey conj candidate)))))]
(recur params props defs curr ckey))
:else
(throw (ex-info "invalid params" {}))))))
#?(:cljs
(def type-symbol
(js/Symbol.for "penpot.reify:type")))
#?(:cljs
(defn type-of?
[o t]
(let [o (get o type-symbol)]
(= o t))))
(defmacro reify
"A domain specific variation of reify that creates anonymous objects
on demand with the ability to assign protocol implementations and
custom properties"
[& params]
(let [[tmeta properties definitions] (collect-properties params)
obj-sym (gensym "obj-")]
`(let [~obj-sym (cljs.core/js-obj)]
(add-properties! ~obj-sym
~@(when-let [tname (:name tmeta)]
[`{:name ~'js/Symbol.toStringTag
:this false
:enumerable false
:get (fn [] ~tname)}
`{:name type-symbol
:this false
:enumerable false
:get (fn [] ~tname)}])
~@properties)
(let [~obj-sym ~(if-let [definitions (seq definitions)]
`(cljs.core/specify! ~obj-sym
~@(mapcat (fn [[k v]] (cons k v)) definitions))
obj-sym)]
(cljs.core/specify! ~obj-sym)))))

Some files were not shown because too many files have changed in this diff Show More