Compare commits

..

1 Commits

Author SHA1 Message Date
Andrey Antukh
c2b13a6d5d 📚 Update changelog 2025-04-29 14:46:15 +02:00
324 changed files with 18973 additions and 34058 deletions

View File

@@ -1,49 +1,5 @@
# CHANGELOG
## 2.7.0 (Unreleased)
### :rocket: Epics and highlights
### :boom: Breaking changes & Deprecations
### :heart: Community contributions (Thank you!)
- Design improvements to the Invitations page with an empty state [GitHub #2608](https://github.com/penpot/penpot/issues/2608) by [@iprithvitharun](https://github.com/iprithvitharun)
### :sparkles: New features
- Update board presets with a newer devices [Taiga #10610](https://tree.taiga.io/project/penpot/us/10610)
- Propagate "sharing a prototype" to editors and viewers [Taiga #8853](https://tree.taiga.io/project/penpot/us/8853)
- Design improvements to the Invitations page with an empty state [Taiga #4554](https://tree.taiga.io/project/penpot/us/4554)
- Duplicate token sets [Taiga #10694](https://tree.taiga.io/project/penpot/issue/10694)
- Add set selection in create Token themes flow [Taiga #10746](https://tree.taiga.io/project/penpot/issue/10746)
- Display indicator on not active sets [Taiga #10668](https://tree.taiga.io/project/penpot/issue/10668)
### :bug: Bugs fixed
- Fix problem in viewer with the back button [Taiga #10907](https://tree.taiga.io/project/penpot/issue/10907)
- Fix resize bar background on tokens panel [Taiga #10811](https://tree.taiga.io/project/penpot/issue/10811)
- Fix shortcut for history version panel [Taiga #11006](https://tree.taiga.io/project/penpot/issue/11006)
- Fix positioning of comment drafts when near the right / bottom edges of viewport [Taiga #10534](https://tree.taiga.io/project/penpot/issue/10534)
- Fix path having a wrong selrect [Taiga #10257](https://tree.taiga.io/project/penpot/issue/10257)
- Fix SVG `stroke-linecap` property when importing SVGs [Taiga #9489](https://tree.taiga.io/project/penpot/issue/9489)
- Fix position problems cutting-pasting a component [Taiga #10677](https://tree.taiga.io/project/penpot/issue/10677)
- Fix design tab has a horizontal scroll [Taiga #10660](https://tree.taiga.io/project/penpot/issue/10660)
- Fix long file names being clipped when longer than allowed length [Taiga #10662](https://tree.taiga.io/project/penpot/issue/10662)
- Fix problem with error detail in toast [Taiga #10519](https://tree.taiga.io/project/penpot/issue/10519)
- Fix view mode error when an external user tries to export something from a prototype using a shared link [Taiga #10251](https://tree.taiga.io/project/penpot/issue/10251)
- Fix merge path nodes with only one node selected [Taiga #9626](https://tree.taiga.io/project/penpot/issue/9626)
- Fix problem with import errors [Taiga #10040](https://tree.taiga.io/project/penpot/issue/10040)
- Fix color gradient on texts [Taiga Issue #7488](https://tree.taiga.io/project/penpot/issue/7488)
- Add support for self mentions [Taiga #10809](https://tree.taiga.io/project/penpot/issue/10809)
- Fix team info settings alignment [Taiga #10869](https://tree.taiga.io/project/penpot/issue/10869)
- Fix left sidebar horizontal scroll on nested layers [Taiga #10791](https://tree.taiga.io/project/penpot/issue/10791)
- Improve error message details importing tokens [Taiga Issue #10772](https://tree.taiga.io/project/penpot/issue/10772)
- Fix no selected set after Drag & Drop [Github #71](https://github.com/tokens-studio/penpot/issues/71)
- Styledictionary v5 Update [Github #6283](https://github.com/penpot/penpot/pull/6283)
- Fix Rename a set throws an internal error [Github #78](https://github.com/tokens-studio/penpot/issues/78)
- Fix Out of Sync Token Value & Color Picker [Github #102](https://github.com/tokens-studio/penpot/issues/102)
- Fix Color should preserve color space [Github #69](https://github.com/tokens-studio/penpot/issues/69)
- Fix cannot rename Design Token Sets when group of same name exists [Taiga Issue #10773](https://tree.taiga.io/project/penpot/issue/10773)
- Fix problem when duplicating grid layout [Github #6391](https://github.com/penpot/penpot/issues/6391)
## 2.6.2
### :bug: Bugs fixed

View File

@@ -16,18 +16,18 @@
</p>
<p align="center">
<a href="https://penpot.app/"><b>Website</b></a> •
<a href="https://help.penpot.app/user-guide/"><b>User Guide</b></a> •
<a href="https://penpot.app/learning-center"><b>Learning Center</b></a> •
<a href="https://penpot.app/"><b>Website</b></a> •
<a href="https://help.penpot.app/technical-guide/getting-started/"><b>Getting Started</b></a> •
<a href="https://help.penpot.app/user-guide/"><b>User Guide</b></a> •
<a href="https://help.penpot.app/user-guide/introduction/info/"><b>Tutorials & Info</b></a> •
<a href="https://community.penpot.app/"><b>Community</b></a>
</p>
<p align="center">
<a href="https://www.youtube.com/@Penpot"><b>Youtube</b></a> •
<a href="https://peertube.kaleidos.net/a/penpot_app/video-channels"><b>Peertube</b></a> •
<a href="https://www.linkedin.com/company/penpot/"><b>Linkedin</b></a> •
<a href="https://instagram.com/penpot.app"><b>Instagram</b></a> •
<a href="https://fosstodon.org/@penpot/"><b>Mastodon</b></a> •
<a href="https://bsky.app/profile/penpot.app"><b>Bluesky</b></a> •
<a href="https://www.youtube.com/@Penpot"><b>Youtube</b></a> •
<a href="https://peertube.kaleidos.net/a/penpot_app/video-channels"><b>Peertube</b></a> •
<a href="https://www.linkedin.com/company/penpot/"><b>Linkedin</b></a> •
<a href="https://instagram.com/penpot.app"><b>Instagram</b></a> •
<a href="https://fosstodon.org/@penpot/"><b>Mastodon</b></a> •
<a href="https://twitter.com/penpotapp"><b>X</b></a>
</p>
@@ -40,13 +40,12 @@
Penpot is the first **open-source** design tool for design and code collaboration. Designers can create stunning designs, interactive prototypes, design systems at scale, while developers enjoy ready-to-use code and make their workflow easy and fast. And all of this with no handoff drama.
Available on browser or self-hosted, Penpot works with open standards like SVG, CSS, HTML and JSON, and its free!
Penpot is available on browser and [self host](https://penpot.app/self-host). Its web-based and works with open standards (SVG, CSS and HTML). And last but not least, its free!
The latest updates take Penpot even further. Its the first design tool to integrate native [design tokens](https://penpot.dev/collaboration/design-tokens)—a single source of truth to improve efficiency and collaboration between product design and development.
With the [huge 2.0 release](https://penpot.app/dev-diaries), Penpot took the platform to a whole new level. This update introduces the ground-breaking [CSS Grid Layout feature](https://penpot.app/penpot-2.0), a complete UI redesign, a new Components system, and much more.
For organizations that need extra service for its teams, [get in touch](https://cal.com/team/penpot/talk-to-us)
Penpots latest [huge release 2.0](https://penpot.app/dev-diaries), takes the platform to a whole new level. This update introduces the ground-breaking [CSS Grid Layout feature](https://penpot.app/penpot-2.0), a complete UI redesign, a new Components system, and much more. Plus, it's faster and more accessible.
🎇 Design, code, and Open Source meet at [Penpot Fest](https://penpot.app/penpotfest)! Be part of the 2025 edition in Madrid, Spain, on October 9-10.
🎇 **Penpot Fest** is our design, code & Open Source event. Check out the highlights from [Penpot Fest 2023 edition](https://www.youtube.com/watch?v=sOpLZaK5mDc)!
## Table of contents ##
@@ -62,7 +61,7 @@ For organizations that need extra service for its teams, [get in touch](https://
Penpot expresses designs as code. Designers can do their best work and see it will be beautifully implemented by developers in a two-way collaboration.
### Plugin system ###
[Penpot plugins](https://penpot.app/penpothub/plugins) let you expand the platform's capabilities, give you the flexibility to integrate it with other apps, and design custom solutions.
[Penpot plugins](https://penpot.app/penpothub/plugins) let you expand the platform's capabilities, give you the flexibility to integrate it with other apps, and design custom solutions.
### Designed for developers ###
Penpot was built to serve both designers and developers and create a fluid design-code process. You have the choice to enjoy real-time collaboration or play "solo".
@@ -79,10 +78,6 @@ Penpot offers integration into the development toolchain, thanks to its support
### Whats great for design ###
With Penpot you can design libraries to share and reuse; turn design elements into components and tokens to allow reusability and scalability; and build realistic user flows and interactions.
### Design Tokens ###
With Penpots standardized [design tokens](https://penpot.dev/collaboration/design-tokens) format, you can easily reuse and sync tokens across different platforms, workflows, and disciplines.
<br />
<p align="center">
@@ -130,13 +125,13 @@ You will find the following categories:
## Contributing ##
Any contribution will make a difference to improve Penpot. How can you get involved?
Any contribution will make a difference to improve Penpot. How can you get involved?
Choose your way:
Choose your way:
- Create and [share Libraries & Templates](https://penpot.app/libraries-templates.html) that will be helpful for the community
- Invite your [team to join](https://design.penpot.app/#/auth/register)
- Give this repo a star and follow us on Social Media: [Mastodon](https://fosstodon.org/@penpot/), [Youtube](https://www.youtube.com/c/Penpot), [Instagram](https://instagram.com/penpot.app), [Linkedin](https://www.linkedin.com/company/penpotdesign), [Peertube](https://peertube.kaleidos.net/a/penpot_app), [X](https://twitter.com/penpotapp) and [BlueSky](https://bsky.app/profile/penpot.app)
- Star this repo and follow us on Social Media: [Mastodon](https://fosstodon.org/@penpot/), [Youtube](https://www.youtube.com/c/Penpot), [Instagram](https://instagram.com/penpot.app), [Linkedin](https://www.linkedin.com/company/penpotdesign), [Peertube](https://peertube.kaleidos.net/a/penpot_app) and [X](https://twitter.com/penpotapp).
- Participate in the [Community](https://community.penpot.app/) space by asking and answering questions; reacting to others articles; opening your own conversations and following along on decisions affecting the project.
- Report bugs with our easy [guide for bugs hunting](https://help.penpot.app/contributing-guide/reporting-bugs/) or [GitHub issues](https://github.com/penpot/penpot/issues)
- Become a [translator](https://help.penpot.app/contributing-guide/translations)

View File

@@ -31,8 +31,7 @@ export PENPOT_FLAGS="\
enable-tiered-file-data-storage \
enable-file-validation \
enable-file-schema-validation \
enable-subscriptons \
enable-subscriptons-old";
enable-subscriptions-old";
# Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h"
@@ -72,18 +71,15 @@ export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
export PENPOT_OBJECTS_STORAGE_FS_DIRECTORY="assets"
export JAVA_OPTS="\
export JAVA_OPTS="--enable-preview \
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-Djdk.attach.allowAttachSelf \
-Dlog4j2.configurationFile=log4j2-devenv-repl.xml \
-Djdk.tracePinnedThreads=full \
-XX:+EnableDynamicAgentLoading \
-XX:-OmitStackTraceInFastThrow \
-XX:-OmitStackTraceInFastThrow \
-XX:+UnlockDiagnosticVMOptions \
-XX:+DebugNonSafepoints \
--sun-misc-unsafe-memory-access=allow \
--enable-preview \
--enable-native-access=ALL-UNNAMED";
-XX:+DebugNonSafepoints";
export OPTIONS="-A:jmx-remote -A:dev"

View File

@@ -18,7 +18,7 @@ if [ -f ./environ ]; then
source ./environ
fi
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --enable-native-access=ALL-UNNAMED --enable-preview $JVM_OPTS"
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow --enable-preview $JVM_OPTS"
ENTRYPOINT=${1:-app.main};

View File

@@ -24,8 +24,18 @@ export PENPOT_FLAGS="\
enable-tiered-file-data-storage \
enable-file-validation \
enable-file-schema-validation \
enable-subscriptons \
enable-subscriptons-old ";
enable-subscriptions-old";
export OPTIONS="
-A:jmx-remote -A:dev \
-J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-J-Djdk.attach.allowAttachSelf \
-J-Dpolyglot.engine.WarnInterpreterOnly=false \
-J-Dlog4j2.configurationFile=log4j2-devenv.xml \
-J-XX:+EnableDynamicAgentLoading \
-J-XX:-OmitStackTraceInFastThrow \
-J-XX:+UnlockDiagnosticVMOptions \
-J-XX:+DebugNonSafepoints"
# Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h"
@@ -56,20 +66,6 @@ export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
entrypoint=${1:-app.main};
export JAVA_OPTS="\
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-Djdk.attach.allowAttachSelf \
-Dlog4j2.configurationFile=log4j2-devenv.xml \
-Djdk.tracePinnedThreads=full \
-XX:+EnableDynamicAgentLoading \
-XX:-OmitStackTraceInFastThrow \
-XX:+UnlockDiagnosticVMOptions \
-XX:+DebugNonSafepoints \
--sun-misc-unsafe-memory-access=allow \
--enable-preview \
--enable-native-access=ALL-UNNAMED";
export OPTIONS="-A:jmx-remote -A:dev"
set -ex
clojure $OPTIONS -M -m $entrypoint;
clojure $OPTIONS -A:dev -M -m $entrypoint;

View File

@@ -11,25 +11,6 @@
[app.common.data :as d]
[app.common.uuid :as uuid]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PRE DECODE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn clean-shape-pre-decode
"Applies a pre-decode phase migration to the shape"
[shape]
(if (= "bool" (:type shape))
(if-let [content (get shape :bool-content)]
(-> shape
(assoc :content content)
(dissoc :bool-content))
shape)
shape))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; POST DECODE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- fix-shape-shadow-color
"Some shapes can come with invalid `id` property on shadow colors
caused by incorrect uuid parsing bug that should be already fixed;

View File

@@ -603,20 +603,10 @@
(reduce-kv (fn [objects id shape]
(assoc objects id (bfl/clean-shape-post-decode shape)))
objects
objects))))
clean-component-pre-decode
(fn [component]
(d/update-when component :objects
(fn [objects]
(reduce-kv (fn [objects id shape]
(assoc objects id (bfl/clean-shape-pre-decode shape)))
objects
objects))))]
(->> (keep (match-component-entry-fn file-id) entries)
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
(clean-component-pre-decode)
(decode-component)
(clean-component-post-decode)
(validate-component))]
@@ -651,10 +641,10 @@
(->> (keep (match-shape-entry-fn file-id page-id) entries)
(reduce (fn [result {:keys [id entry]}]
(let [object (->> (read-entry input entry)
(bfl/clean-shape-pre-decode)
(decode-shape)
(bfl/clean-shape-post-decode)
(validate-shape))]
(if (= id (:id object))
(assoc result id object)
result)))
@@ -765,6 +755,7 @@
;; only the applied
(vary-meta dissoc ::fmg/migrated))]
(bfm/register-pending-migrations! cfg file)
(bfc/save-file! cfg file ::db/return-keys false)

View File

@@ -1462,6 +1462,8 @@
(:objects page)
(:id page)
file-id
true
nil
cfsh/prepare-create-artboard-from-selection)]
(shape-cb shape)

View File

@@ -108,7 +108,6 @@
[::ip-addr {:optional true} ::sm/text]
[::props {:optional true} [:map-of :keyword :any]]
[::context {:optional true} [:map-of :keyword :any]]
[::tracked-at {:optional true} ::sm/inst]
[::webhooks/event? {:optional true} ::sm/boolean]
[::webhooks/batch-timeout {:optional true} ::dt/duration]
[::webhooks/batch-key {:optional true}
@@ -119,12 +118,12 @@
(defn prepare-event
[cfg mdata params result]
(let [resultm (meta result)
request (-> params meta ::http/request)
profile-id (or (::profile-id resultm)
(:profile-id result)
(::rpc/profile-id params)
uuid/zero)
(let [resultm (meta result)
request (-> params meta ::http/request)
profile-id (or (::profile-id resultm)
(:profile-id result)
(::rpc/profile-id params)
uuid/zero)
session-id (get params ::rpc/external-session-id)
event-origin (get params ::rpc/external-event-origin)
@@ -136,14 +135,14 @@
(clean-props))
token-id (::actoken/id request)
context (-> (::context resultm)
(assoc :external-session-id session-id)
(assoc :external-event-origin event-origin)
(assoc :access-token-id (some-> token-id str))
(d/without-nils))
token-id (::actoken/id request)
context (-> (::context resultm)
(assoc :external-session-id session-id)
(assoc :external-event-origin event-origin)
(assoc :access-token-id (some-> token-id str))
(d/without-nils))
ip-addr (inet/parse-request request)]
ip-addr (inet/parse-request request)]
{::type (or (::type resultm)
(::rpc/type cfg))

View File

@@ -15,7 +15,6 @@
[app.config :as cf]
[app.db :as db]
[app.http.client :as http]
[app.loggers.audit :as audit]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.data.json :as json]
@@ -68,27 +67,18 @@
(defmethod ig/init-key ::process-event-handler
[_ cfg]
(fn [{:keys [props] :as task}]
(l/dbg :hint "process webhook event" :name (:name props))
(let [items (lookup-webhooks cfg props)
event {::audit/profile-id (:profile-id props)
::audit/name "webhook"
::audit/type "trigger"
::audit/props {:name (get props :name)
:event-id (get props :id)
:total-affected (count items)}}]
(audit/insert! cfg event)
(when items
(l/trc :hint "webhooks found for event" :total (count items))
(db/tx-run! cfg (fn [cfg]
(doseq [item items]
(wrk/submit! (-> cfg
(assoc ::wrk/task :run-webhook)
(assoc ::wrk/queue :webhooks)
(assoc ::wrk/max-retries 3)
(assoc ::wrk/params {:event props
:config item}))))))))))
(when-let [items (lookup-webhooks cfg props)]
(l/trc :hint "webhooks found for event" :total (count items))
(db/tx-run! cfg (fn [cfg]
(doseq [item items]
(wrk/submit! (-> cfg
(assoc ::wrk/task :run-webhook)
(assoc ::wrk/queue :webhooks)
(assoc ::wrk/max-retries 3)
(assoc ::wrk/params {:event props
:config item})))))))))
;; --- RUN
(declare interpret-exception)

View File

@@ -208,7 +208,7 @@
[:project-id {:optional true} ::sm/uuid]])
(defn- migrate-file
[{:keys [::db/conn] :as cfg} {:keys [id] :as file} {:keys [read-only?]}]
[{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)
pmap/*tracked* (pmap/create-tracked)]
(let [;; For avoid unnecesary overhead of creating multiple pointers and
@@ -219,45 +219,43 @@
file (-> file
(update :data feat.fdata/process-pointers deref)
(update :data feat.fdata/process-objects (partial into {}))
(fmg/migrate-file))]
(fmg/migrate-file))
(if (or read-only? (db/read-only? conn))
file
(let [;; When file is migrated, we break the rule of no perform
;; mutations on get operations and update the file with all
;; migrations applied
file (if (contains? (:features file) "fdata/objects-map")
(feat.fdata/enable-objects-map file)
file)
file (if (contains? (:features file) "fdata/pointer-map")
(feat.fdata/enable-pointer-map file)
file)]
;; When file is migrated, we break the rule of no perform
;; mutations on get operations and update the file with all
;; migrations applied
;;
;; WARN: he following code will not work on read-only mode,
;; it is a known issue; we keep is not implemented until we
;; really need this.
file (if (contains? (:features file) "fdata/objects-map")
(feat.fdata/enable-objects-map file)
file)
file (if (contains? (:features file) "fdata/pointer-map")
(feat.fdata/enable-pointer-map file)
file)]
(db/update! conn :file
{:data (blob/encode (:data file))
:version (:version file)
:features (db/create-array conn "text" (:features file))}
{:id id}
{::db/return-keys false})
(db/update! conn :file
{:data (blob/encode (:data file))
:version (:version file)
:features (db/create-array conn "text" (:features file))}
{:id id})
(when (contains? (:features file) "fdata/pointer-map")
(feat.fdata/persist-pointers! cfg id))
(when (contains? (:features file) "fdata/pointer-map")
(feat.fdata/persist-pointers! cfg id))
(feat.fmigr/upsert-migrations! conn file)
(feat.fmigr/resolve-applied-migrations cfg file))))))
(feat.fmigr/upsert-migrations! conn file)
(feat.fmigr/resolve-applied-migrations cfg file))))
(defn get-file
[{:keys [::db/conn ::wrk/executor] :as cfg} id
& {:keys [project-id
migrate?
include-deleted?
lock-for-update?
preload-pointers?]
lock-for-update?]
:or {include-deleted? false
lock-for-update? false
migrate? true
preload-pointers? false}
:as options}]
migrate? true}}]
(assert (db/connection? conn) "expected cfg with valid connection")
@@ -275,16 +273,10 @@
;; because it has heavy and synchronous operations for
;; decoding file body that are not very friendly with virtual
;; threads.
file (px/invoke! executor #(decode-row file))
file (if (and migrate? (fmg/need-migration? file))
(migrate-file cfg file options)
file)]
(if preload-pointers?
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
(update file :data feat.fdata/process-pointers deref))
file (px/invoke! executor #(decode-row file))]
(if (and migrate? (fmg/need-migration? file))
(migrate-file cfg file)
file)))
(defn get-minimal-file
@@ -482,7 +474,7 @@
(update page :objects update-vals #(dissoc % :thumbnail)))
(defn get-page
[{:keys [::db/conn] :as cfg} {:keys [profile-id file-id page-id object-id share-id] :as params}]
[{:keys [::db/conn] :as cfg} {:keys [profile-id file-id page-id object-id] :as params}]
(when (and (uuid? object-id)
(not (uuid? page-id)))
@@ -490,30 +482,22 @@
:code :params-validation
:hint "page-id is required when object-id is provided"))
(let [perms (get-permissions conn profile-id file-id share-id)
(let [team (teams/get-team conn
:profile-id profile-id
:file-id file-id)
file (get-file cfg file-id :read-only? true)
file (get-file cfg file-id)
proj (db/get conn :project {:id (:project-id file)})
_ (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file)))
team (-> (db/get conn :team {:id (:team-id proj)})
(teams/decode-row))
_ (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file)))
page (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
(let [page-id (or page-id (-> file :data :pages first))
page (dm/get-in file [:data :pages-index page-id])]
(if (pmap/pointer-map? page)
(deref page)
page)))]
(when-not perms
(ex/raise :type :not-found
:code :object-not-found
:hint "object not found"))
page (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
(let [page-id (or page-id (-> file :data :pages first))
page (dm/get-in file [:data :pages-index page-id])]
(if (pmap/pointer-map? page)
(deref page)
page)))]
(cond-> (prune-thumbnails page)
(some? object-id)
@@ -749,9 +733,7 @@
:project-id project-id
:file-id id)
file (get-file cfg id
:project-id project-id
:read-only? true)]
file (get-file cfg id :project-id project-id)]
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))

View File

@@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.features :as cfeat]
[app.common.files.helpers :as cfh]
[app.common.files.migrations :as fmg]
[app.common.geom.shapes :as gsh]
[app.common.schema :as sm]
[app.common.thumbnails :as thc]
@@ -17,6 +18,7 @@
[app.config :as cf]
[app.db :as db]
[app.db.sql :as-alias sql]
[app.features.fdata :as feat.fdata]
[app.loggers.audit :as-alias audit]
[app.loggers.webhooks :as-alias webhooks]
[app.media :as media]
@@ -198,13 +200,14 @@
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
(files/check-read-permissions! conn profile-id file-id)
(let [team (teams/get-team conn
:profile-id profile-id
:file-id file-id)
(let [team (teams/get-team conn
:profile-id profile-id
:file-id file-id)
file (files/get-file cfg file-id
:preload-pointers? true
:read-only? true)]
file (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
(-> (files/get-file cfg file-id :migrate? false)
(update :data feat.fdata/process-pointers deref)
(fmg/migrate-file)))]
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-file-features! (:features file)))

View File

@@ -179,7 +179,7 @@
component-child (first component-children)]
(if (or (nil? child) (nil? component-child))
container
(let [container (if (and (not (ctk/is-main-of? component-child child))
(let [container (if (and (not (ctk/is-main-of? component-child child true))
(nil? (ctk/get-swap-slot child))
(ctk/instance-head? child))
(let [slot (guess-swap-slot component-child component-container)]

View File

@@ -6,7 +6,7 @@
(ns app.common.data.macros
"Data retrieval & manipulation specific macros."
(:refer-clojure :exclude [get-in select-keys str with-open max])
(:refer-clojure :exclude [get-in select-keys str with-open min max])
#?(:cljs (:require-macros [app.common.data.macros]))
(:require
#?(:clj [clojure.core :as c]
@@ -144,8 +144,3 @@
(str "expr assert: " (pr-str expr)))]
(when *assert*
`(runtime-assert ~hint (fn [] ~expr))))))
(defn truncate
"Truncates a string to a certain length"
[s max-length]
(subs s 0 (min max-length (count s))))

View File

@@ -103,7 +103,9 @@
"Translate a flag to a feature name"
[flag]
(case flag
:feature-components-v2 "components/v2"
:feature-styles-v2 "styles/v2"
:feature-grid-layout "layout/grid"
:feature-fdata-objects-map "fdata/objects-map"
:feature-fdata-pointer-map "fdata/pointer-map"
:feature-plugins "plugins/runtime"
@@ -214,12 +216,6 @@
(check-supported-features! file-features)
;; Components v1 is deprecated
(when-not (contains? file-features "components/v2")
(ex/raise :type :restriction
:code :file-in-components-v1
:hint "components v1 is deprecated"))
(let [not-supported (-> file-features
(set/difference enabled-features)
(set/difference backend-only-features)

View File

@@ -11,6 +11,7 @@
[app.common.exceptions :as ex]
[app.common.files.changes :as ch]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh]
[app.common.pprint :as pp]
[app.common.schema :as sm]
@@ -37,12 +38,20 @@
fail-on-spec?]
:or {add-container? false
fail-on-spec? false}}]
(let [change (cond-> change
add-container?
(let [components-v2 (dm/get-in file [:data :options :components-v2])
component-id (:current-component-id file)
change (cond-> change
(and add-container? (some? component-id) (not components-v2))
(-> (assoc :component-id component-id)
(cond-> (some? (:current-frame-id file))
(assoc :frame-id (:current-frame-id file))))
(and add-container? (or (nil? component-id) components-v2))
(assoc :page-id (:current-page-id file)
:frame-id (:current-frame-id file)))
valid? (or (and (nil? (:component-id change))
valid? (or (and components-v2
(nil? (:component-id change))
(nil? (:page-id change)))
(ch/valid-change? change))]
@@ -57,11 +66,11 @@
(cond-> file
(and valid? (or (not add-container?) (some? (:component-id change)) (some? (:page-id change))))
(-> (update :changes conjv change)
(update :data ch/process-changes [change] false))
(-> (update :changes conjv change) ;; In components-v2 we do not add shapes
(update :data ch/process-changes [change] false)) ;; inside a component
(not valid?)
(update :errors conjv change)))))
(update :errors conjv change)))));)
(defn- lookup-objects
([file]
@@ -176,10 +185,12 @@
(update :parent-stack conjv (:id obj)))))
(defn close-artboard [file]
(let [parent-id (-> file :parent-stack peek)
(let [components-v2 (dm/get-in file [:data :options :components-v2])
parent-id (-> file :parent-stack peek)
parent (lookup-shape file parent-id)
current-frame-id (or (:frame-id parent)
root-id)]
(when (or (nil? (:current-component-id file)) components-v2)
root-id))]
(-> file
(assoc :current-frame-id current-frame-id)
(update :parent-stack pop))))
@@ -272,14 +283,14 @@
:else
(let [objects (lookup-objects file)
content (gsh/calc-bool-content bool objects)
bool' (gsh/update-bool-selrect bool children objects)]
bool-content (gsh/calc-bool-content bool objects)
bool' (gsh/update-bool-selrect bool children objects)]
(commit-change
file
{:type :mod-obj
:id bool-id
:operations
[{:type :set :attr :content :val content :ignore-touched true}
[{:type :set :attr :bool-content :val bool-content :ignore-touched true}
{:type :set :attr :selrect :val (:selrect bool') :ignore-touched true}
{:type :set :attr :points :val (:points bool') :ignore-touched true}
{:type :set :attr :x :val (-> bool' :selrect :x) :ignore-touched true}
@@ -503,29 +514,58 @@
(defn start-component
([file data]
(start-component file data :frame))
(let [components-v2 (dm/get-in file [:data :options :components-v2])
root-type (if components-v2 :frame :group)]
(start-component file data root-type)))
([file data root-type]
(let [name (:name data)
;; FIXME: data probably can be a shape instance, then we can use gsh/shape->rect
(let [components-v2 (dm/get-in file [:data :options :components-v2])
selrect (or (grc/make-rect (:x data) (:y data) (:width data) (:height data))
grc/empty-rect)
name (:name data)
path (:path data)
main-instance-id (:main-instance-id data)
main-instance-page (:main-instance-page data)
obj-id (or (:id data) (uuid/next))]
;; In components v1 we must create the root shape and set it inside
;; the :objects attribute of the component. When in components-v2,
;; this will be ignored as the root shape has already been created
;; in its page, by the normal page import.
attrs (-> data
(assoc :type root-type)
(assoc :x (:x selrect))
(assoc :y (:y selrect))
(assoc :width (:width selrect))
(assoc :height (:height selrect))
(assoc :selrect selrect)
(dissoc :path)
(dissoc :main-instance-id)
(dissoc :main-instance-page)
(dissoc :main-instance-x)
(dissoc :main-instance-y))
obj (-> (cts/setup-shape attrs)
(check-name file root-type)
;; Components need to have nil values for frame and parent
(assoc :frame-id nil)
(assoc :parent-id nil))]
(-> file
(commit-change
{:type :add-component
:id obj-id
:name name
:path path
:main-instance-id main-instance-id
:main-instance-page main-instance-page})
(cond-> {:type :add-component
:id (:id obj)
:name name
:path path
:main-instance-id main-instance-id
:main-instance-page main-instance-page}
(not components-v2)
(assoc :shapes [obj])))
(assoc :last-id obj-id)
(assoc :parent-stack [obj-id])
(assoc :current-component-id obj-id)
(assoc :current-frame-id (if (= root-type :frame) obj-id uuid/zero))))))
(assoc :last-id (:id obj))
(assoc :parent-stack [(:id obj)])
(assoc :current-component-id (:id obj))
(assoc :current-frame-id (if (= (:type obj) :frame) (:id obj) uuid/zero))))))
(defn start-deleted-component
[file data]
@@ -560,7 +600,8 @@
file
(cond
component-data
;; In components-v2 components haven't any shape inside them.
(and component-data (:main-instance-id component-data))
(update file :data
(fn [data]
(ctkl/update-component data component-id dissoc :objects)))
@@ -636,12 +677,17 @@
page (ctpl/get-page (:data file) page-id)
component (ctkl/get-component (:data file) component-id)
components-v2 (dm/get-in file [:options :components-v2])
[shape shapes]
(ctn/make-component-instance page
component
(:id file)
(gpt/point x
y))]
y)
components-v2
#_{:main-instance true
:force-id main-instance-id})]
(as-> file $
(reduce #(commit-change %1

View File

@@ -26,10 +26,11 @@
[app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.token :as cto]
[app.common.types.token-theme :as ctot]
[app.common.types.tokens-lib :as ctob]
[app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as ctt]
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
[clojure.set :as set]))
@@ -335,17 +336,13 @@
[:type [:= :mod-component]]
[:id ::sm/uuid]
[:shapes {:optional true} [:vector {:gen/max 3} :any]]
[:name {:optional true} :string]
[:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector ::ctv/variant-property]]]]
[:name {:optional true} :string]]]
[:del-component
[:map {:title "DelComponentChange"}
[:type [:= :del-component]]
[:id ::sm/uuid]
;; when it is an undo of a cut-paste, we need to undo the movement
;; of the shapes so we need to move them delta
[:delta {:optional true} ::gpt/point]
[:main-instance {:optional true} :any]
[:skip-undelete? {:optional true} :boolean]]]
[:restore-component
@@ -406,7 +403,7 @@
[:type [:= :set-token-theme]]
[:theme-name :string]
[:group :string]
[:theme [:maybe ctob/schema:token-theme-attrs]]]]
[:theme [:maybe ::ctot/token-theme]]]]
[:set-tokens-lib
[:map {:title "SetTokensLib"}
@@ -418,14 +415,14 @@
[:type [:= :set-token-set]]
[:set-name :string]
[:group? :boolean]
[:token-set [:maybe ctob/schema:token-set-attrs]]]]
[:token-set [:maybe ::ctot/token-set]]]]
[:set-token
[:map {:title "SetTokenChange"}
[:type [:= :set-token]]
[:set-name :string]
[:token-name :string]
[:token [:maybe ctob/schema:token-attrs]]]]]])
[:token [:maybe ::cto/token]]]]]])
(def schema:changes
[:sequential {:gen/max 5 :gen/min 1} schema:change])
@@ -959,8 +956,8 @@
(ctkl/mod-component data params))
(defmethod process-change :del-component
[data {:keys [id skip-undelete? delta]}]
(ctf/delete-component data id skip-undelete? delta))
[data {:keys [id skip-undelete? main-instance]}]
(ctf/delete-component data id skip-undelete? main-instance))
(defmethod process-change :restore-component
[data {:keys [id page-id]}]

View File

@@ -921,11 +921,11 @@
(apply-changes-local))))
(defn add-component
([changes id path name updated-shapes main-instance-id main-instance-page]
(add-component changes id path name updated-shapes main-instance-id main-instance-page nil nil nil))
([changes id path name updated-shapes main-instance-id main-instance-page annotation]
(add-component changes id path name updated-shapes main-instance-id main-instance-page annotation nil nil))
([changes id path name updated-shapes main-instance-id main-instance-page annotation variant-id variant-properties & {:keys [apply-changes-local-library?]}]
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page]
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page nil nil nil))
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation]
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation nil nil))
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation variant-id variant-properties & {:keys [apply-changes-local-library?]}]
(assert-page-id! changes)
(assert-objects! changes)
(let [page-id (::page-id (meta changes))
@@ -964,11 +964,11 @@
:name name
:main-instance-id main-instance-id
:main-instance-page main-instance-page
:annotation annotation}
(some? variant-id)
(assoc :variant-id variant-id)
(seq variant-properties)
(assoc :variant-properties variant-properties)))
:annotation annotation
:variant-id variant-id
:variant-properties variant-properties}
(some? new-shapes) ;; this will be null in components-v2
(assoc :shapes (vec new-shapes))))
(into (map mk-change) updated-shapes))))
(update :undo-changes
(fn [undo-changes]
@@ -991,39 +991,27 @@
new-component (update-fn prev-component)]
(if prev-component
(-> changes
(update :redo-changes conj (cond-> {:type :mod-component
:id id
:name (:name new-component)
:path (:path new-component)
:main-instance-id (:main-instance-id new-component)
:main-instance-page (:main-instance-page new-component)
:annotation (:annotation new-component)
:objects (:objects new-component) ;; for deleted components
:modified-at (:modified-at new-component)}
(some? (:variant-id new-component))
(assoc :variant-id (:variant-id new-component))
(nil? (:variant-id new-component))
(dissoc :variant-id)
(seq (:variant-properties new-component))
(assoc :variant-properties (:variant-properties new-component))
(not (seq (:variant-properties new-component)))
(dissoc :variant-properties)))
(update :undo-changes conj (cond-> {:type :mod-component
:id id
:name (:name prev-component)
:path (:path prev-component)
:main-instance-id (:main-instance-id prev-component)
:main-instance-page (:main-instance-page prev-component)
:annotation (:annotation prev-component)
:objects (:objects prev-component)}
(some? (:variant-id prev-component))
(assoc :variant-id (:variant-id prev-component))
(nil? (:variant-id prev-component))
(dissoc :variant-id)
(seq (:variant-properties prev-component))
(assoc :variant-properties (:variant-properties prev-component))
(not (seq (:variant-properties prev-component)))
(dissoc :variant-properties)))
(update :redo-changes conj {:type :mod-component
:id id
:name (:name new-component)
:path (:path new-component)
:main-instance-id (:main-instance-id new-component)
:main-instance-page (:main-instance-page new-component)
:annotation (:annotation new-component)
:variant-id (:variant-id new-component)
:variant-properties (:variant-properties new-component)
:objects (:objects new-component) ;; this won't exist in components-v2 (except for deleted components)
:modified-at (:modified-at new-component)})
(update :undo-changes conj {:type :mod-component
:id id
:name (:name prev-component)
:path (:path prev-component)
:main-instance-id (:main-instance-id prev-component)
:main-instance-page (:main-instance-page prev-component)
:annotation (:annotation prev-component)
:variant-id (:variant-id prev-component)
:variant-properties (:variant-properties prev-component)
:objects (:objects prev-component)})
(cond-> apply-changes-local-library?
(apply-changes-local {:apply-to-library? true})))
changes)))
@@ -1039,7 +1027,7 @@
:page-id page-id})))
(defn restore-component
[changes id page-id delta]
[changes id page-id main-instance]
(assert-library! changes)
(-> changes
(update :redo-changes conj {:type :restore-component
@@ -1047,34 +1035,7 @@
:page-id page-id})
(update :undo-changes conj {:type :del-component
:id id
:delta delta})))
(defn reorder-children
[changes id children]
(assert-page-id! changes)
(assert-objects! changes)
(let [page-id (::page-id (meta changes))
objects (lookup-objects changes)
shape (get objects id)
old-children (:shapes shape)
redo-change
{:type :reorder-children
:parent-id (:id shape)
:page-id page-id
:shapes children}
undo-change
{:type :reorder-children
:parent-id (:id shape)
:page-id page-id
:shapes old-children}]
(-> changes
(update :redo-changes conj redo-change)
(update :undo-changes conj undo-change)
(apply-changes-local))))
:main-instance main-instance})))
(defn reorder-grid-children
[changes ids]
@@ -1122,11 +1083,3 @@
(defn get-objects
[changes]
(dm/get-in (::file-data (meta changes)) [:pages-index uuid/zero :objects]))
(defn get-page
[changes]
(::page (meta changes)))
(defn get-page-id
[changes]
(::page-id (meta changes)))

View File

@@ -427,6 +427,11 @@
(map #(str/concat base-name (suffix-fn %))
(iterate inc 1))))
(defn ^:private get-suffix
"Default suffix impelemtation"
[copy-count]
(str/concat " " copy-count))
(defn generate-unique-name
"Generates a unique name by selecting the first available name from a generated sequence.
The sequence consists of `base-name` and its variants, avoiding conflicts with `existing-names`.
@@ -440,7 +445,8 @@
Returns:
- A unique name not present in `existing-names`."
[base-name existing-names & {:keys [suffix-fn immediate-suffix? suffix]}]
[base-name existing-names & {:keys [suffix-fn immediate-suffix?]
:or {suffix-fn get-suffix}}]
(dm/assert!
"expected a set of strings"
(coll? existing-names))
@@ -448,21 +454,9 @@
(dm/assert!
"expected a string for `basename`."
(string? base-name))
(let [suffix-fn (if suffix-fn
suffix-fn
(if suffix
(fn [copy-count]
(str/concat "-"
suffix
(when (> copy-count 1)
(str "-" copy-count))))
(fn [copy-count]
(str/concat " " copy-count))))
existing-name-set (cond-> (set existing-names)
(let [existing-name-set (cond-> (set existing-names)
immediate-suffix? (conj base-name))
names (name-seq base-name suffix-fn)]
(->> names
(remove #(contains? existing-name-set %))
first)))

View File

@@ -1264,26 +1264,6 @@
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0002-normalize-bool-content"
[data _]
(letfn [(update-object [object]
;; NOTE: we still preserve the previous value for possible
;; rollback, we still need to perform an other migration
;; for properly delete the bool-content prop from shapes
;; once the know the migration was OK
(if (cfh/bool-shape? object)
(if-let [content (:bool-content object)]
(assoc object :content content)
object)
(dissoc object :bool-content :bool-type)))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(defmethod migrate-data "0003-fix-root-shape"
[data _]
(letfn [(update-object [shape]
@@ -1361,6 +1341,5 @@
"legacy-66"
"legacy-67"
"0001-remove-tokens-from-groups"
"0002-normalize-bool-content"
"0002-clean-shape-interactions"
"0003-fix-root-shape"]))

View File

@@ -572,51 +572,6 @@
(pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape))))
(defmethod repair-error :not-a-variant
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :invalid-variant-id
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :invalid-variant-properties
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :variant-not-main
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :parent-not-variant
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :variant-bad-name
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :variant-no-properties
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :variant-bad-variant-name
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :variant-component-bad-name
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
(defmethod repair-error :default
[_ error file _]
(log/error :hint "Unknown error code, don't know how to repair" :code (:code error))

View File

@@ -62,10 +62,6 @@
changes id parent-id objects selected index frame-name without-fill? nil))
([changes id parent-id objects selected index frame-name without-fill? target-cell-id]
(prepare-create-artboard-from-selection
changes id parent-id objects selected index frame-name without-fill? target-cell-id nil))
([changes id parent-id objects selected index frame-name without-fill? target-cell-id delta]
(when-let [selected-objs (->> selected
(map (d/getf objects))
(not-empty))]
@@ -103,11 +99,10 @@
:id))
target-cell-id)
attrs
{:type :frame
:x (cond-> (:x srect) delta (+ (:x delta)))
:y (cond-> (:y srect) delta (+ (:y delta)))
:x (:x srect)
:y (:y srect)
:width (:width srect)
:height (:height srect)}

View File

@@ -10,15 +10,12 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv]
[app.common.schema :as sm]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst]
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
@@ -59,17 +56,7 @@
:instance-head-not-frame
:misplaced-slot
:missing-slot
:shape-ref-cycle
:not-a-variant
:invalid-variant-id
:invalid-variant-properties
:variant-not-main
:parent-not-variant
:variant-bad-name
:variant-bad-variant-name
:variant-component-bad-name
:variant-no-properties
:variant-component-bad-id})
:shape-ref-cycle})
(def ^:private schema:error
[:map {:title "ValidationError"}
@@ -414,68 +401,6 @@
(check-empty-swap-slot shape file page)
(run! #(check-shape % file page libraries :context :not-component) (:shapes shape)))
(defn- check-variant-container
"Shape is a variant container, so:
-all its children should be variants with variant-id equals to the shape-id
-all the components should have the same properties
"
[shape file page]
(let [shape-id (:id shape)
shapes (:shapes shape)
children (map #(ctst/get-shape page %) shapes)
prop-names (cfv/extract-properties-names (first children) (:data file))]
(doseq [child children]
(if (not (ctk/is-variant? child))
(report-error :not-a-variant
(str/ffmt "Shape % should be a variant" (:id child))
child file page)
(do
(when (not= (:variant-id child) shape-id)
(report-error :invalid-variant-id
(str/ffmt "Variant % has invalid variant-id %" (:id child) (:variant-id child))
child file page))
(when (not= prop-names (cfv/extract-properties-names child (:data file)))
(report-error :invalid-variant-properties
(str/ffmt "Variant % has invalid properties %" (:id child) (vec prop-names))
child file page)))))))
(defn- check-variant
"Shape is a variant, so
-it should be a main component
-its parent should be a variant-container
-its variant-name is derived from the properties
-its name should be tha same as its parent's
"
[shape file page]
(let [parent (ctst/get-shape page (:parent-id shape))
component (ctkl/get-component (:data file) (:component-id shape) true)
name (ctv/properties-to-name (:variant-properties component))]
(when-not (ctk/main-instance? shape)
(report-error :variant-not-main
(str/ffmt "Variant % is not a main instance" (:id shape))
shape file page))
(when-not (ctk/is-variant-container? parent)
(report-error :parent-not-variant
(str/ffmt "Variant % has an invalid parent" (:id shape))
shape file page))
(when-not (= name (:variant-name shape))
(report-error :variant-bad-variant-name
(str/ffmt "Variant % has an invalid variant-name" (:id shape))
shape file page))
(when-not (= (:name parent) (:name shape))
(report-error :variant-bad-name
(str/ffmt "Variant % has an invalid name" (:id shape))
shape file page))
(when-not (= (:name parent) (cfh/merge-path-item (:path component) (:name component)))
(report-error :variant-component-bad-name
(str/ffmt "Component % has an invalid name" (:id shape))
shape file page))
(when-not (= (:variant-id component) (:variant-id shape))
(report-error :variant-component-bad-id
(str/ffmt "Variant % has adifferent variant-id than its component" (:id shape))
shape file page))))
(defn- check-shape
"Validate referential integrity and semantic coherence of
a shape and all its children. Report all errors found.
@@ -496,12 +421,6 @@
(check-parent-children shape file page)
(check-frame shape file page)
(when (ctk/is-variant-container? shape)
(check-variant-container shape file page))
(when (ctk/is-variant? shape)
(check-variant shape file page))
(if (ctk/instance-head? shape)
(if (not= :frame (:type shape))
(report-error :instance-head-not-frame
@@ -577,24 +496,6 @@
"This deleted component has shapes with shape-ref pointing to self"
component file nil :cycles-ids cycles-ids))))
(defn- check-variant-component
"Component is a variant, so:
-Its main should be a variant
-It should have at least one variant property"
[component file]
(let [component-page (ctf/get-component-page (:data file) component)
main-component (if (:deleted component)
(dm/get-in component [:objects (:main-instance-id component)])
(ctst/get-shape component-page (:main-instance-id component)))]
(when-not (ctk/is-variant? main-component)
(report-error :not-a-variant
(str/ffmt "Shape % should be a variant" (:id main-component))
main-component file component-page))
(when (< (count (:variant-properties component)) 1)
(report-error :variant-no-properties
(str/ffmt "Component variant % should have properties" (:id main-component))
main-component file nil))))
(defn- check-component
"Validate semantic coherence of a component. Report all errors found."
[component file]
@@ -604,10 +505,7 @@
component file nil))
(when (:deleted component)
(check-component-duplicate-swap-slot component file)
(check-ref-cycles component file))
(when (ctk/is-variant? component)
(check-variant-component component file)))
(check-ref-cycles component file)))
(defn- get-orphan-shapes
[{:keys [objects] :as page}]

View File

@@ -1,84 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.files.variant
(:require
[app.common.data.macros :as dm]
[app.common.types.component :as ctc]
[app.common.types.components-list :as ctcl]
[app.common.types.variant :as ctv]
[cuerdas.core :as str]))
(defn find-variant-components
"Find a list of the components thet belongs to this variant-id"
[data objects variant-id]
;; We can't simply filter components, because we need to maintain the order
(->> (dm/get-in objects [variant-id :shapes])
(map #(dm/get-in objects [% :component-id]))
(map #(ctcl/get-component data % true))
reverse))
(defn- dashes-to-end
[property-values]
(let [dashes (if (some #(= % "--") property-values) ["--"] [])]
(concat (remove #(= % "--") property-values) dashes)))
(defn extract-properties-names
[shape data]
(->> shape
(#(ctcl/get-component data (:component-id %) true))
:variant-properties
(map :name)))
(defn extract-properties-values
[data objects variant-id]
(->> (find-variant-components data objects variant-id)
(mapcat :variant-properties)
(group-by :name)
(map (fn [[k v]]
{:name k
:value (->> v
(map #(if (str/empty? (:value %)) "--" (:value %)))
distinct
dashes-to-end)}))))
(defn get-variant-mains
[component data]
(assert (ctv/valid-variant-component? component) "expected valid component variant")
(when-let [variant-id (:variant-id component)]
(let [page-id (:main-instance-page component)
objects (-> (dm/get-in data [:pages-index page-id])
(get :objects))]
(dm/get-in objects [variant-id :shapes]))))
(defn is-secondary-variant?
[component data]
(let [shapes (get-variant-mains component data)]
(and (seq shapes)
(not= (:main-instance-id component) (last shapes)))))
(defn get-primary-variant
[data component]
(let [page-id (:main-instance-page component)
objects (-> (dm/get-in data [:pages-index page-id])
(get :objects))
variant-id (:variant-id component)]
(->> (dm/get-in objects [variant-id :shapes])
peek
(get objects))))
(defn get-primary-component
[data component-id]
(when-let [component (ctcl/get-component data component-id)]
(if (ctc/is-variant? component)
(->> component
(get-primary-variant data)
:component-id
(ctcl/get-component data))
component)))

View File

@@ -79,7 +79,7 @@
:file-schema-validation
;; Reports the schema validation errors internally.
:soft-file-schema-validation
;; Activates the referential integrity validation during update file.
;; Activates the referential integrity validation during update file; related to components-v2.
:file-validation
;; Reports the referential integrity validation errors internally.
:soft-file-validation
@@ -125,7 +125,6 @@
:export-file-v3
:render-wasm-dpr
:hide-release-modal
:subscriptions
:subscriptions-old})
(def all-flags

View File

@@ -39,7 +39,7 @@
;;
;; 5. If any track still has an infinite growth limit set its growth limit to its base size.
;; - Distribute extra space accross spaned tracks
;; - Distribute extra space accross spaned tracks
;; - Maximize tracks
;;
;; - Expand flexible tracks
@@ -198,7 +198,7 @@
track-list))
(defn stretch-tracks
(defn add-auto-size
[track-list add-size]
(->> track-list
(mapv (fn [{:keys [type size max-size] :as track}]
@@ -357,8 +357,7 @@
to-idx (+ (dec (get cell prop)) (get cell prop-span))
indexed-tracks (subvec (d/enumerate track-list) from-idx to-idx)
to-allocate
(size-to-allocate type parent (get children-map shape-id) cell bounds objects)
to-allocate (size-to-allocate type parent (get children-map shape-id) cell bounds objects)
;; Remove the size and the tracks that are not allocated
[to-allocate total-frs indexed-tracks]
@@ -494,11 +493,11 @@
column-tracks (cond-> column-tracks
(= :stretch (:layout-justify-content parent))
(stretch-tracks column-add-auto))
(add-auto-size column-add-auto))
row-tracks (cond-> row-tracks
(= :stretch (:layout-align-content parent))
(stretch-tracks row-add-auto))
(add-auto-size row-add-auto))
column-total-size (tracks-total-size column-tracks)
row-total-size (tracks-total-size row-tracks)

View File

@@ -347,11 +347,7 @@
move-p nil
content (seq content)]
(if content
(let [last-p (last content)
content (if (= :move-to (:command last-p))
(butlast content)
content)
command (first content)
(let [command (first content)
to-p (command->point command)
[from-p move-p command-pts]

View File

@@ -95,8 +95,8 @@
(d/update-when :x d/safe+ dx)
(d/update-when :y d/safe+ dy)
(d/update-when :position-data move-position-data mvec)
(cond-> (or (= :bool type) (= :path type))
(update :content gpa/move-content mvec)))))
(cond-> (= :bool type) (update :bool-content gpa/move-content mvec))
(cond-> (= :path type) (update :content gpa/move-content mvec)))))
;; --- Absolute Movement
@@ -317,10 +317,13 @@
points (gco/transform-points (dm/get-prop shape :points) transform-mtx)
selrect (gco/transform-selrect (dm/get-prop shape :selrect) transform-mtx)
shape (if (= type :bool)
(update shape :bool-content gpa/transform-content transform-mtx)
shape)
shape (if (= type :text)
(update shape :position-data transform-position-data transform-mtx)
shape)
shape (if (or (= type :path) (= type :bool))
shape (if (= type :path)
(update shape :content gpa/transform-content transform-mtx)
(assoc shape
:x (dm/get-prop selrect :x)
@@ -352,8 +355,11 @@
rotation (mod (+ (d/nilv (:rotation shape) 0)
(d/nilv (dm/get-in shape [:modifiers :rotation]) 0))
360)
shape (if (= type :bool)
(update shape :bool-content gpa/transform-content transform-mtx)
shape)
shape (if (or (= type :path) (= type :bool))
shape (if (= type :path)
(update shape :content gpa/transform-content transform-mtx)
(assoc shape
:x (dm/get-prop selrect :x)
@@ -448,14 +454,9 @@
"Calculates the selrect+points for the boolean shape"
[shape children objects]
(let [content
(gshb/calc-bool-content shape objects)
shape
(assoc shape :content content)
[points selrect]
(gpa/content->points+selrect shape content)]
(let [bool-content (gshb/calc-bool-content shape objects)
shape (assoc shape :bool-content bool-content)
[points selrect] (gpa/content->points+selrect shape bool-content)]
(if (and (some? selrect) (d/not-empty? points))
(-> shape

View File

@@ -15,7 +15,6 @@
[app.common.geom.shapes :as gsh]
[app.common.logging :as log]
[app.common.logic.shapes :as cls]
[app.common.logic.variant-properties :as clvp]
[app.common.spec :as us]
[app.common.text :as txt]
[app.common.types.color :as ctc]
@@ -30,7 +29,6 @@
[app.common.types.shape.layout :as ctl]
[app.common.types.token :as cto]
[app.common.types.typography :as cty]
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
[clojure.set :as set]
[clojure.spec.alpha :as s]))
@@ -107,99 +105,113 @@
(defn- duplicate-component
"Clone the root shape of the component and all children. Generate new
ids from all of them."
[component new-component-id library-data force-id delta variant-id]
(let [main-instance-page (ctf/get-component-page library-data component)
main-instance-shape (ctf/get-component-root library-data component)
delta (or delta (gpt/point (+ (:width main-instance-shape) 50) 0))
[component new-component-id library-data force-id]
(let [components-v2 (dm/get-in library-data [:options :components-v2])]
(if components-v2
(let [main-instance-page (ctf/get-component-page library-data component)
main-instance-shape (ctf/get-component-root library-data component)
delta (gpt/point (+ (:width main-instance-shape) 50) 0)
ids-map (volatile! {})
inverted-ids-map (volatile! {})
nested-main-heads (volatile! #{})
ids-map (volatile! {})
inverted-ids-map (volatile! {})
nested-main-heads (volatile! #{})
update-original-shape
(fn [original-shape new-shape]
update-original-shape
(fn [original-shape new-shape]
; Save some ids for later
(vswap! ids-map assoc (:id original-shape) (:id new-shape))
(vswap! inverted-ids-map assoc (:id new-shape) (:id original-shape))
(when (and (ctk/main-instance? original-shape)
(not= (:component-id original-shape) (:id component)))
(vswap! nested-main-heads conj (:id original-shape)))
original-shape)
(vswap! ids-map assoc (:id original-shape) (:id new-shape))
(vswap! inverted-ids-map assoc (:id new-shape) (:id original-shape))
(when (and (ctk/main-instance? original-shape)
(not= (:component-id original-shape) (:id component)))
(vswap! nested-main-heads conj (:id original-shape)))
original-shape)
update-new-shape
(fn [new-shape _]
(cond-> new-shape
; Link the new main to the new component
(= (:component-id new-shape) (:id component))
(assoc :component-id new-component-id)
update-new-shape
(fn [new-shape _]
(cond-> new-shape
; Link the new main to the new component
(= (:component-id new-shape) (:id component))
(assoc :component-id new-component-id)
; If it is the instance root, add it the variant-id
(and (ctk/instance-root? new-shape) (some? variant-id))
(assoc :variant-id variant-id)
:always
(gsh/move delta)))
:always
(gsh/move delta)))
[new-instance-shape new-instance-shapes _]
(ctst/clone-shape main-instance-shape
(:parent-id main-instance-shape)
(:objects main-instance-page)
:update-new-shape update-new-shape
:update-original-shape update-original-shape
:force-id force-id)
[new-instance-shape new-instance-shapes _]
(ctst/clone-shape main-instance-shape
(:parent-id main-instance-shape)
(:objects main-instance-page)
:update-new-shape update-new-shape
:update-original-shape update-original-shape
:force-id force-id)
remap-frame
(fn [shape]
remap-frame
(fn [shape]
; Remap all frame-ids internal to the component to the new shapes
(update shape :frame-id
#(get @ids-map % (:frame-id shape))))
(update shape :frame-id
#(get @ids-map % (:frame-id shape))))
convert-nested-main
(fn [shape]
convert-nested-main
(fn [shape]
; If there is some nested main instance, convert it into a copy of
; main nested in the original component.
(let [origin-shape-id (get @inverted-ids-map (:id shape))
objects (:objects main-instance-page)
parent-ids (cfh/get-parent-ids-seq-with-self objects origin-shape-id)]
(cond-> shape
(@nested-main-heads origin-shape-id)
(dissoc :main-instance)
(let [origin-shape-id (get @inverted-ids-map (:id shape))
objects (:objects main-instance-page)
parent-ids (cfh/get-parent-ids-seq-with-self objects origin-shape-id)]
(cond-> shape
(@nested-main-heads origin-shape-id)
(dissoc :main-instance)
(some @nested-main-heads parent-ids)
(assoc :shape-ref origin-shape-id))))
(some @nested-main-heads parent-ids)
(assoc :shape-ref origin-shape-id))))
xf-shape (comp (map remap-frame)
(map convert-nested-main))
xf-shape (comp (map remap-frame)
(map convert-nested-main))
new-instance-shapes (into [] xf-shape new-instance-shapes)]
new-instance-shapes (into [] xf-shape new-instance-shapes)]
[new-instance-shape new-instance-shapes]))
[nil nil new-instance-shape new-instance-shapes])
(let [component-root (d/seek #(nil? (:parent-id %)) (vals (:objects component)))
[new-component-shape new-component-shapes _]
(ctst/clone-shape component-root
nil
(get component :objects))]
[new-component-shape new-component-shapes nil nil]))))
(defn generate-duplicate-component
"Create a new component copied from the one with the given id."
[changes library component-id new-component-id & {:keys [new-shape-id apply-changes-local-library? delta new-variant-id page-id]}]
[changes library component-id new-component-id components-v2 & {:keys [new-shape-id apply-changes-local-library?]}]
(let [component (ctkl/get-component (:data library) component-id)
new-name (:name component)
main-instance-page (ctf/get-component-page (:data library) component)
;; Since variants, we can duplicate a component into another page
target-page-id (or page-id (:id main-instance-page))
main-instance-page (when components-v2
(ctf/get-component-page (:data library) component))
[new-main-instance-shape new-main-instance-shapes]
(duplicate-component component new-component-id (:data library) new-shape-id delta new-variant-id)]
new-component-id (when components-v2
new-component-id)
[new-component-shape new-component-shapes ; <- null in components-v2
new-main-instance-shape new-main-instance-shapes]
(duplicate-component component new-component-id (:data library) new-shape-id)]
[new-main-instance-shape
(-> changes
(pcb/with-page-id target-page-id)
(pcb/with-page main-instance-page)
(pcb/with-objects (:objects main-instance-page))
(pcb/add-objects new-main-instance-shapes {:ignore-touched true})
(pcb/add-component new-component-id
(pcb/add-component (if components-v2
new-component-id
(:id new-component-shape))
(:path component)
new-name
new-component-shapes
[]
(:id new-main-instance-shape)
target-page-id
(:id main-instance-page)
(:annotation component)
(or new-variant-id (:variant-id component))
(:variant-id component)
(:variant-properties component)
{:apply-changes-local-library? apply-changes-local-library?})
;; Update grid layout if the new main instance is inside
@@ -216,34 +228,23 @@
"Generate changes to create a new instance from a component."
([changes objects file-id component-id position page libraries]
(generate-instantiate-component changes objects file-id component-id position page libraries nil nil nil {}))
([changes objects file-id component-id position page libraries old-id parent-id frame-id params]
(generate-instantiate-component changes objects file-id component-id position page libraries old-id parent-id frame-id {} params))
([changes objects file-id component-id position page libraries old-id parent-id frame-id ids-map
([changes objects file-id component-id position page libraries old-id parent-id frame-id
{:keys [force-frame?]
:or {force-frame? false}}]
(let [component (ctf/get-component libraries file-id component-id)
library (get libraries file-id)
parent (when parent-id (get objects parent-id))
library (get libraries file-id)
;; When we are intanciating a variant, it can't be on a variant-container
parent (when parent
(if (and (ctk/is-variant? component)
(ctk/is-variant-container? parent))
(get objects (:parent-id parent))
parent))
parent-id (d/nilv (:id parent) parent-id)
frame-id (d/nilv (:frame-id parent) frame-id)
components-v2 (dm/get-in library [:data :options :components-v2])
[new-shape new-shapes]
(ctn/make-component-instance page
component
(:data library)
position
components-v2
(cond-> {}
(contains? ids-map old-id)
(assoc :force-id (get ids-map old-id))
force-frame?
(assoc :force-frame-id frame-id)))
@@ -265,11 +266,8 @@
(cond-> (pcb/add-object changes first-shape {:ignore-touched true})
(some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id)))
duplicated-parent?
(->> ids-map vals (some #(= % (:parent-id first-shape))))
changes
(if (and (ctl/grid-layout? objects (:parent-id first-shape)) (not duplicated-parent?))
(if (ctl/grid-layout? objects (:parent-id first-shape))
(let [target-cell (-> position meta :cell)
[row column]
@@ -396,28 +394,17 @@
(when (some #(= (:id current-page) %) (:pages library-data)) ;; If the page doesn't belong to the library, it's not valid
current-page)
(ctpl/get-last-page library-data))]
(prepare-restore-component changes library-data component-id page nil nil nil nil)))
(prepare-restore-component changes library-data component-id page (gpt/point 0 0) nil nil nil)))
([changes library-data component-id page position old-id parent-id frame-id]
(let [library-data (or (pcb/get-library-data changes) library-data)
component (ctkl/get-deleted-component library-data component-id)
objects (or (pcb/get-objects changes) (:objects page))
parent (get objects parent-id)
([changes library-data component-id page delta old-id parent-id frame-id]
(let [component (ctkl/get-deleted-component library-data component-id)
parent (get-in page [:objects parent-id])
main-inst (get-in component [:objects (:main-instance-id component)])
inside-component? (some? (ctn/get-instance-root (:objects page) parent))
shapes (cfh/get-children-with-self (:objects component) (:main-instance-id component))
shapes (map #(gsh/move % delta) shapes)
is-variant? (ctk/is-variant? component)
orig-pos (gpt/point (:x main-inst) (:y main-inst))
delta (if position
(gpt/subtract position orig-pos)
(gpt/point 0 0))
minusdelta (gpt/point (- (:x delta)) (- (:y delta)))
moved-shapes (map #(gsh/move % delta) shapes)
first-shape (cond-> (first moved-shapes)
first-shape (cond-> (first shapes)
(not (nil? parent-id))
(assoc :parent-id parent-id)
(not (nil? frame-id))
@@ -439,16 +426,9 @@
(some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) ; on copy/paste old id is used later to reorder the paster layers
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
changes
(rest moved-shapes))
changes (cond-> changes
;; Remove variant info when restoring into a parent that is not a variant-container
(and is-variant? parent (not (ctk/is-variant-container? parent)))
(clvp/generate-make-shapes-no-variant [first-shape])
;; Add variant info and rename when restoring into a variant-container
(ctk/is-variant-container? parent)
(clvp/generate-make-shapes-variant [first-shape] parent))]
{:changes (pcb/restore-component changes component-id (:id page) minusdelta)
:shape (first moved-shapes)})))
(rest shapes))]
{:changes (pcb/restore-component changes component-id (:id page) main-inst)
:shape (first shapes)})))
;; ---- General library synchronization functions ----
@@ -471,7 +451,8 @@
:file (pretty-file file-id libraries current-file-id)
:library (pretty-file library-id libraries current-file-id))
(let [file (get-in libraries [file-id :data])]
(let [file (get-in libraries [file-id :data])
components-v2 (get-in file [:options :components-v2])]
(loop [containers (ctf/object-containers-seq file)
changes changes]
(if-let [container (first containers)]
@@ -484,6 +465,7 @@
asset-id
library-id
container
components-v2
libraries
current-file-id))))
changes))))
@@ -508,7 +490,8 @@
:file (pretty-file file-id libraries current-file-id)
:library (pretty-file library-id libraries current-file-id))
(let [file (get-in libraries [file-id :data])]
(let [file (get-in libraries [file-id :data])
components-v2 (get-in file [:options :components-v2])]
(loop [local-components (ctkl/components-seq file)
changes changes]
(if-let [local-component (first local-components)]
@@ -520,6 +503,7 @@
asset-id
library-id
(cfh/make-container local-component :component)
components-v2
libraries
current-file-id)))
changes))))
@@ -527,7 +511,7 @@
(defn- generate-sync-container
"Generate changes to synchronize all shapes in a particular container (a page
or a component) that use assets of the given type in the given library."
[changes asset-type asset-id library-id container libraries current-file-id]
[changes asset-type asset-id library-id container components-v2 libraries current-file-id]
(if (cfh/page? container)
(container-log :debug (:id container) :msg "Sync page in local file" :page-id (:id container))
@@ -546,6 +530,7 @@
library-id
container
shape
components-v2
libraries
current-file-id))
changes))))
@@ -575,13 +560,13 @@
(defmulti generate-sync-shape
"Generate changes to synchronize one shape from all assets of the given type
that is using, in the given library."
(fn [asset-type _changes _library-id _container _shape _libraries _current-file-id] asset-type))
(fn [asset-type _changes _library-id _container _shape _components-v2 _libraries _current-file-id] asset-type))
(defmethod generate-sync-shape :components
[_ changes _library-id container shape libraries current-file-id]
[_ changes _library-id container shape components-v2 libraries current-file-id]
(let [shape-id (:id shape)
file (get current-file-id libraries)]
(generate-sync-shape-direct changes file libraries container shape-id false)))
(generate-sync-shape-direct changes file libraries container shape-id false components-v2)))
(defmethod generate-sync-shape :colors
[_ changes library-id _ shape _ libraries _]
@@ -757,7 +742,7 @@
(defn generate-sync-shape-direct
"Generate changes to synchronize one shape that is the root of a component
instance, and all its children, from the given component."
[changes file libraries container shape-id reset?]
[changes file libraries container shape-id reset? components-v2]
(shape-log :debug shape-id container
:msg "Sync shape direct" :shape-inst (str shape-id) :reset? reset?)
(let [shape-inst (ctn/get-shape container shape-id)
@@ -768,12 +753,12 @@
(let [redirect-shaperef (partial redirect-shaperef container libraries)
shape-main (when component
(if reset?
(if (and reset? components-v2)
;; the reset is against the ref-shape, not against the original shape of the component
(ctf/find-ref-shape file container libraries shape-inst)
(ctf/get-ref-shape library component shape-inst)))
shape-inst (if reset?
shape-inst (if (and reset? components-v2)
(redirect-shaperef shape-inst shape-main)
shape-inst)
@@ -795,10 +780,13 @@
root-main
reset?
initial-root?
redirect-shaperef)
redirect-shaperef
components-v2)
;; If the component is not found, because the master component has been
;; deleted or the library unlinked, do nothing.
changes))
;; deleted or the library unlinked, do nothing in v2 or detach in v1.
(if components-v2
changes
(generate-detach-instance changes libraries container shape-id))))
changes)))
(defn- find-main-container
@@ -819,7 +807,7 @@
nil))))))
(defn- generate-sync-shape-direct-recursive
[changes container shape-inst component library file libraries shape-main root-inst root-main reset? initial-root? redirect-shaperef]
[changes container shape-inst component library file libraries shape-main root-inst root-main reset? initial-root? redirect-shaperef components-v2]
(shape-log :debug (:id shape-inst) container
:msg "Sync shape direct recursive"
:shape-inst (str (:name shape-inst) " " (pretty-uuid (:id shape-inst)))
@@ -827,7 +815,9 @@
(if (nil? shape-main)
;; This should not occur, but protect against it in any case
changes
(if components-v2
changes
(generate-detach-instance changes container {(:id library) library} (:id shape-inst)))
(let [omit-touched? (not reset?)
clear-remote-synced? (and initial-root? reset?)
set-remote-synced? (and (not initial-root?) reset?)
@@ -866,7 +856,7 @@
children-inst (vec (ctn/get-direct-children container shape-inst))
children-main (vec (ctn/get-direct-children component-container shape-main))
children-inst (if reset?
children-inst (if (and reset? components-v2)
(map #(redirect-shaperef %) children-inst) children-inst)
only-inst (fn [changes child-inst]
@@ -898,7 +888,8 @@
root-inst
root-main
omit-touched?
set-remote-synced?)
set-remote-synced?
components-v2)
changes))
both (fn [changes child-inst child-main]
@@ -918,7 +909,8 @@
root-main
reset?
initial-root?
redirect-shaperef))
redirect-shaperef
components-v2))
swapped (fn [changes child-inst child-main]
(shape-log :trace (:id child-inst) container
@@ -956,7 +948,8 @@
swapped
moved
false
reset?)
reset?
components-v2)
changes
(cond-> changes
@@ -972,26 +965,41 @@
(defn generate-rename-component
"Generate the changes for rename the component with the given id, in the current file library."
[changes id new-name library-data]
(let [[path name] (cfh/parse-path-name new-name)]
[changes id new-name library-data components-v2]
(let [[path name] (cfh/parse-path-name new-name)
update-fn
(fn [component]
(cond-> component
:always
(assoc :path path
:name name)
(not components-v2)
(update :objects
;; Give the same name to the root shape
#(assoc-in % [id :name] name))))]
(-> changes
(pcb/with-library-data library-data)
(pcb/update-component id #(assoc % :path path :name name)))))
(pcb/update-component id update-fn))))
(defn generate-sync-shape-inverse
"Generate changes to update the component a shape is linked to, from
the values in the shape and all its children."
[changes file libraries container shape-id]
[changes file libraries container shape-id components-v2]
(shape-log :debug shape-id container :msg "Sync shape inverse" :shape (str shape-id))
(let [redirect-shaperef (partial redirect-shaperef container libraries)
shape-inst (ctn/get-shape container shape-id)
library (dm/get-in libraries [(:component-file shape-inst) :data])
component (ctkl/get-component library (:component-id shape-inst))
shape-main (when component
(ctf/find-remote-shape container libraries shape-inst))
shape-main (when component
(if components-v2
(ctf/find-remote-shape container libraries shape-inst)
(ctf/get-ref-shape library component shape-inst)))
shape-inst (redirect-shaperef shape-inst shape-main)
shape-inst (if components-v2
(redirect-shaperef shape-inst shape-main)
shape-inst)
initial-root? (:component-root shape-inst)
@@ -1000,7 +1008,7 @@
changes (cond-> changes
(and component (contains? (:touched shape-inst) :name-group))
(generate-rename-component (:component-id shape-inst) (:name shape-inst) library))]
(generate-rename-component (:component-id shape-inst) (:name shape-inst) library components-v2))]
(if component
(generate-sync-shape-inverse-recursive changes
@@ -1014,11 +1022,12 @@
root-inst
root-main
initial-root?
redirect-shaperef)
redirect-shaperef
components-v2)
changes)))
(defn- generate-sync-shape-inverse-recursive
[changes container shape-inst component library file libraries shape-main root-inst root-main initial-root? redirect-shaperef]
[changes container shape-inst component library file libraries shape-main root-inst root-main initial-root? redirect-shaperef components-v2]
(shape-log :trace (:id shape-inst) container
:msg "Sync shape inverse recursive"
:shape (str (:name shape-inst))
@@ -1075,7 +1084,9 @@
children-main (mapv #(ctn/get-shape component-container %)
(:shapes shape-main))
children-inst (map #(redirect-shaperef %) children-inst)
children-inst (if components-v2
(map #(redirect-shaperef %) children-inst)
children-inst)
only-inst (fn [changes child-inst]
(add-shape-to-main changes
@@ -1086,7 +1097,8 @@
component-container
container
root-inst
root-main))
root-main
components-v2))
only-main (fn [changes child-main]
(remove-shape changes
@@ -1106,7 +1118,8 @@
root-inst
root-main
initial-root?
redirect-shaperef))
redirect-shaperef
components-v2))
swapped (fn [changes child-inst child-main]
(shape-log :trace (:id child-inst) container
@@ -1140,7 +1153,8 @@
swapped
moved
true
true)
true
components-v2)
;; The inverse sync may be made on a component that is inside a
;; remote library. We need to separate changes that are from
@@ -1158,7 +1172,7 @@
;; ---- Operation generation helpers ----
(defn- compare-children
[changes shape-inst children-inst children-main container-inst container-main file libraries only-inst-cb only-main-cb both-cb swapped-cb moved-cb inverse? reset?]
[changes shape-inst children-inst children-main container-inst container-main file libraries only-inst-cb only-main-cb both-cb swapped-cb moved-cb inverse? reset? components-v2]
(shape-log :trace (:id shape-inst) container-inst :msg "Compare children")
(loop [children-inst (seq (or children-inst []))
children-main (seq (or children-main []))
@@ -1179,18 +1193,18 @@
(reduce only-inst-cb changes children-inst)
:else
(if (or (ctk/is-main-of? child-main child-inst)
(if (or (ctk/is-main-of? child-main child-inst components-v2)
(and (ctf/match-swap-slot? child-main child-inst container-inst container-main file libraries) (not reset?)))
(recur (next children-inst)
(next children-main)
(if (ctk/is-main-of? child-main child-inst)
(if (ctk/is-main-of? child-main child-inst components-v2)
(both-cb changes child-inst child-main)
(swapped-cb changes child-inst child-main)))
(let [child-inst' (d/seek #(or (ctk/is-main-of? child-main %)
(let [child-inst' (d/seek #(or (ctk/is-main-of? child-main % components-v2)
(and (ctf/match-swap-slot? child-main % container-inst container-main file libraries) (not reset?)))
children-inst)
child-main' (d/seek #(or (ctk/is-main-of? % child-inst)
child-main' (d/seek #(or (ctk/is-main-of? % child-inst components-v2)
(and (ctf/match-swap-slot? % child-inst container-inst container-main file libraries) (not reset?)))
children-main)]
(cond
@@ -1206,7 +1220,7 @@
:else
(if inverse?
(let [is-main? (ctk/is-main-of? child-inst child-main')]
(let [is-main? (ctk/is-main-of? child-inst child-main' components-v2)]
(recur (next children-inst)
(remove #(= (:id %) (:id child-main')) children-main)
(cond-> changes
@@ -1216,7 +1230,7 @@
(swapped-cb child-inst child-main')
:always
(moved-cb child-inst child-main'))))
(let [is-main? (ctk/is-main-of? child-inst' child-main)]
(let [is-main? (ctk/is-main-of? child-inst' child-main components-v2)]
(recur (remove #(= (:id %) (:id child-inst')) children-inst)
(next children-main)
(cond-> changes
@@ -1228,14 +1242,14 @@
(moved-cb child-inst' child-main))))))))))))
(defn- add-shape-to-instance
[changes component-shape index component-page container root-instance root-main omit-touched? set-remote-synced?]
[changes component-shape index component-page container root-instance root-main omit-touched? set-remote-synced? components-v2]
(shape-log :info (:id component-shape) component-page
:msg (str "ADD [P " (pretty-uuid (:id container)) "] "
(:name component-shape)
" "
(pretty-uuid (:id component-shape))))
(let [component-parent-shape (ctn/get-shape component-page (:parent-id component-shape))
parent-shape (d/seek #(ctk/is-main-of? component-parent-shape %)
parent-shape (d/seek #(ctk/is-main-of? component-parent-shape % components-v2)
(cfh/get-children-with-self (:objects container)
(:id root-instance)))
all-parents (into [(:id parent-shape)]
@@ -1304,14 +1318,14 @@
changes')))
(defn- add-shape-to-main
[changes shape index component component-container page root-instance root-main]
[changes shape index component component-container page root-instance root-main components-v2]
(shape-log :info (:id shape) page
:msg (str "ADD [C " (pretty-uuid (:id component-container)) "] "
(:name shape)
" "
(pretty-uuid (:id shape))))
(let [parent-shape (ctn/get-shape page (:parent-id shape))
component-parent-shape (d/seek #(ctk/is-main-of? % parent-shape)
component-parent-shape (d/seek #(ctk/is-main-of? % parent-shape components-v2)
(cfh/get-children-with-self (:objects component-container)
(:id root-main)))
all-parents (into [(:id component-parent-shape)]
@@ -1896,33 +1910,30 @@
(assoc change :component-id (:id container))))
(defn generate-add-component-changes
[changes root objects file-id page-id variant-props]
[changes root objects file-id page-id components-v2]
(let [name (:name root)
variant-id (when (ctk/is-variant? root) (:parent-id root))
props (when (ctk/is-variant? root) (get variant-props (:component-id root)))
[path name] (cfh/parse-path-name name)
[root-shape updated-shapes]
(ctn/convert-shape-in-component root objects file-id)
[root-shape new-shapes updated-shapes]
(if-not components-v2
(ctn/make-component-shape root objects file-id components-v2)
(ctn/convert-shape-in-component root objects file-id))
changes (-> changes
(pcb/add-component (:id root-shape)
path
name
updated-shapes
(:id root)
page-id
nil
variant-id
props))]
changes (-> changes
(pcb/add-component (:id root-shape)
path
name
new-shapes
updated-shapes
(:id root)
page-id))]
[root-shape changes]))
(defn generate-add-component
"If there is exactly one id, and it's a frame, and not already a component,
use it as root. Otherwise, create a frame that contains all ids. Then, make a
"If there is exactly one id, and it's a frame (or a group in v1), and not already a component,
use it as root. Otherwise, create a frame (v2) or group (v1) that contains all ids. Then, make a
component with it, and link all shapes to their corresponding one in the component."
[changes shapes objects page-id file-id prepare-create-board]
[changes shapes objects page-id file-id components-v2 prepare-create-group prepare-create-board]
(let [changes (pcb/with-page-id changes page-id)
shapes-count (count shapes)
@@ -1934,7 +1945,9 @@
[root changes old-root-ids]
(if (and (= shapes-count 1)
(cfh/frame-shape? first-shape)
(or (and (cfh/group-shape? first-shape)
(not components-v2))
(cfh/frame-shape? first-shape))
(not (ctk/instance-head? first-shape)))
[first-shape
(-> changes
@@ -1949,14 +1962,21 @@
shape-ids (into (d/ordered-set) (map :id) shapes)
[root changes]
(prepare-create-board changes
(uuid/next)
(:parent-id first-shape)
objects
shape-ids
nil
root-name
true)]
(if-not components-v2
(prepare-create-group changes ; These functions needs to be passed as argument
objects ; to avoid a circular dependence
page-id
shapes
root-name
(not (ctk/instance-head? first-shape)))
(prepare-create-board changes
(uuid/next)
(:parent-id first-shape)
objects
shape-ids
nil
root-name
true))]
[root changes shape-ids]))
@@ -1970,7 +1990,7 @@
objects' (assoc objects (:id root) root)
[root-shape changes] (generate-add-component-changes changes root objects' file-id page-id nil)
[root-shape changes] (generate-add-component-changes changes root objects' file-id page-id components-v2)
changes (pcb/update-shapes changes
old-root-ids
@@ -1984,26 +2004,17 @@
[changes library-data component-id library-id current-page objects]
(let [{:keys [changes shape]} (prepare-restore-component changes library-data component-id current-page)
parent-id (:parent-id shape)
insert-before?
(and (ctl/flex-layout? objects parent-id)
(not (ctl/reverse? objects parent-id)))
objects
(-> objects
(assoc (:id shape) shape)
(cond-> (and (some? parent-id) insert-before?)
(update-in [parent-id :shapes] #(d/concat-vec [(:id shape)] %)))
(cond-> (and (some? parent-id) (not insert-before?))
(update-in [parent-id :shapes] conj (:id shape))))
objects (cond-> (assoc objects (:id shape) shape)
(not (nil? parent-id))
(update-in [parent-id :shapes]
#(conj % (:id shape))))
;; Adds a resize-parents operation so the groups are updated. We add all the new objects
new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id))
changes (-> changes
(pcb/with-objects objects)
(pcb/resize-parents new-objects-ids)
;; Fix the order of the children inside the parent
(pcb/reorder-children parent-id (get-in objects [parent-id :shapes])))]
(pcb/resize-parents new-objects-ids))]
(assoc changes :file-id library-id)))
(defn generate-detach-component
@@ -2065,7 +2076,8 @@
[changes objects shape file page libraries id-new-component index target-cell keep-props-values]
(let [[all-parents changes]
(-> changes
(cls/generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:component-swap true}))
(cls/generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:components-v2 true
:component-swap true}))
[new-shape changes]
(-> changes
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
@@ -2096,7 +2108,7 @@
(generate-sync-file file-id :typographies asset-id library-id libraries current-file-id))))
(defn generate-sync-head
[changes file-full libraries container id reset?]
[changes file-full libraries container id components-v2 reset?]
(let [shape-inst (ctn/get-shape container id)
objects (:objects container)
parent (get objects (:parent-id shape-inst))
@@ -2105,11 +2117,11 @@
(-> changes
(pcb/with-container container)
(pcb/with-objects (:objects container))
(generate-sync-shape-direct file-full libraries container (:id head) reset?))]
(generate-sync-shape-direct file-full libraries container (:id head) reset? components-v2))]
changes))
(defn generate-reset-component
[changes file-full libraries container id]
[changes file-full libraries container id components-v2]
(let [objects (:objects container)
swap-slot (-> (ctn/get-shape container id)
(ctk/get-swap-slot))
@@ -2117,11 +2129,11 @@
(-> changes
(pcb/with-container container)
(pcb/with-objects objects)
(generate-sync-shape-direct file-full libraries container id true))]
(generate-sync-shape-direct file-full libraries container id true components-v2))]
(cond-> changes
(some? swap-slot)
(generate-sync-head file-full libraries container id true))))
(generate-sync-head file-full libraries container id components-v2 true))))
(defn generate-duplicate-flows
[changes shapes page ids-map]
@@ -2175,90 +2187,52 @@
(pcb/with-page changes page)
frames)))
(defn- duplicate-variant
[changes library component base-pos parent-id page-id]
(let [component-page (ctpl/get-page (:data library) (:main-instance-page component))
component-shape (dm/get-in component-page [:objects (:main-instance-id component)])
orig-pos (gpt/point (:x component-shape) (:y component-shape))
delta (gpt/subtract base-pos orig-pos)
new-component-id (uuid/next)
[shape changes] (generate-duplicate-component changes
library
(:id component)
new-component-id
{:apply-changes-local-library? true
:delta delta
:new-variant-id parent-id
:page-id page-id})]
[shape
(-> changes
(pcb/change-parent parent-id [shape]))]))
(defn generate-duplicate-component-change
[changes objects page main parent-id frame-id delta libraries library-data ids-map]
(let [main-id (:id main)
component-id (:component-id main)
file-id (:component-file main)
component (ctf/get-component libraries file-id component-id)
pos (as-> (gsh/move main delta) $
(gpt/point (:x $) (:y $)))
;; When we duplicate a variant alone, we will instanciate it
;; When we duplicate a variant along with its variant-container, we will duplicate it
in-variant-container? (contains? ids-map (:variant-id main))
[changes objects page component-root parent-id frame-id delta libraries library-data]
(let [component-id (:component-id component-root)
file-id (:component-file component-root)
main-component (ctf/get-component libraries file-id component-id)
moved-component (gsh/move component-root delta)
pos (gpt/point (:x moved-component) (:y moved-component))
origin-frame (get-in page [:objects frame-id])
delta (cond-> delta
(some? origin-frame)
(gpt/subtract (-> origin-frame :selrect gpt/point)))
instantiate-component
#(generate-instantiate-component changes
objects
file-id
(:component-id component-root)
pos
page
libraries
(:id component-root)
parent-id
frame-id
{})
restore-component
#(let [{:keys [shape changes]}
(prepare-restore-component changes
library-data
component-id
page
pos
main-id
parent-id
frame-id)]
[shape changes])
#(let [restore (prepare-restore-component changes library-data (:component-id component-root) page delta (:id component-root) parent-id frame-id)]
[(:shape restore) (:changes restore)])
[_shape changes]
(if (nil? component)
(if (nil? main-component)
(restore-component)
(if (and (ctk/is-variant? main) in-variant-container?)
(duplicate-variant changes
(get libraries file-id)
component
pos
parent-id
(:id page))
(generate-instantiate-component changes
objects
file-id
component-id
pos
page
libraries
main-id
parent-id
frame-id
ids-map
{})))]
(instantiate-component))]
changes))
(defn generate-duplicate-shape-change
([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id]
(generate-duplicate-shape-change changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id (:frame-id obj) (:parent-id obj) false false true nil))
([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id variant-props]
(generate-duplicate-shape-change changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id (:frame-id obj) (:parent-id obj) false false true variant-props))
([changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data file-id]
(generate-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data file-id (:frame-id obj) (:parent-id obj) false false true))
([changes objects page unames update-unames! ids ids-map obj delta level-delta libraries library-data file-id frame-id parent-id duplicating-component? child? remove-swap-slot? variant-props]
([changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data file-id frame-id parent-id duplicating-component? child? remove-swap-slot?]
(cond
(nil? obj)
changes
(ctf/is-main-of-known-component? obj libraries)
(generate-duplicate-component-change changes objects page obj parent-id frame-id delta libraries library-data ids-map)
(generate-duplicate-component-change changes objects page obj parent-id frame-id delta libraries library-data)
:else
(let [frame? (cfh/frame-shape? obj)
@@ -2288,7 +2262,8 @@
regenerate-component
(fn [changes shape]
(let [[_ changes] (generate-add-component-changes changes shape objects file-id (:id page) variant-props)]
(let [components-v2 (dm/get-in library-data [:options :components-v2])
[_ changes] (generate-add-component-changes changes shape objects file-id (:id page) components-v2)]
changes))
new-obj
@@ -2327,14 +2302,7 @@
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects))
(cond-> (ctl/grid-layout? obj)
(ctl/remap-grid-cells ids-map))
(cond-> (ctk/is-variant-container? parent)
(assoc :variant-id parent-id))
(cond-> (and (ctk/is-variant? obj) (not (ctk/is-variant-container? parent)))
(-> (assoc :name (ctv/variant-name-to-name obj))
(dissoc :variant-id))))
(ctl/remap-grid-cells ids-map)))
new-obj (cond-> new-obj
(not duplicating-component?)
@@ -2366,7 +2334,6 @@
page
unames
update-unames!
ids
ids-map
child
delta
@@ -2382,22 +2349,21 @@
;; only remove swap slot of children when the current shape
;; is not a subinstance head nor a instance root
(not subinstance-head?)
(not instance-root?))
variant-props))
(not instance-root?))))
changes
(map (d/getf objects) (:shapes obj)))))))
(defn generate-duplicate-changes
"Prepare objects to duplicate: generate new id, give them unique names,
move to the desired position, and recalculate parents and frames as needed."
[changes all-objects page ids delta libraries library-data file-id & {:keys [variant-props]}]
[changes all-objects page ids delta libraries library-data file-id]
(let [shapes (map (d/getf all-objects) ids)
unames (volatile! (cfh/get-used-names (:objects page)))
update-unames! (fn [new-name] (vswap! unames conj new-name))
all-ids (reduce #(into %1 (cons %2 (cfh/get-children-ids all-objects %2))) (d/ordered-set) ids)
;; We need ids-map for remapping the grid layout. But when duplicating the guides
;; we calculate a new one because the components will have created new shapes.
;; We need ids-map for remapping the grid layout. But when duplicating the guides
;; we calculate a new one because the components will have created new shapes.
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
changes (-> changes
@@ -2410,15 +2376,13 @@
page
unames
update-unames!
ids
ids-map
%2
delta
nil
libraries
library-data
file-id
variant-props)
file-id)
changes))
;; We need to check the changes to get the ids-map

View File

@@ -10,14 +10,14 @@
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
[app.common.logic.variant-properties :as clvp]
[app.common.logic.variants :as clv]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.types.token :as cto]
[app.common.uuid :as uuid]))
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
(defn- generate-unapply-tokens
"When updating attributes that have a token applied, we must unapply it, because the value
@@ -81,167 +81,163 @@
(pcb/update-shapes ids update-fn {:attrs #{:blocked :hidden}}))))
(defn generate-delete-shapes
([changes file page objects ids options]
(generate-delete-shapes (-> changes
(pcb/with-page page)
(pcb/with-objects objects)
(pcb/with-library-data file))
ids
options))
([changes ids {:keys [ignore-touched component-swap]}]
(let [objects (pcb/get-objects changes)
data (pcb/get-library-data changes)
page-id (pcb/get-page-id changes)
page (or (pcb/get-page changes)
(ctpl/get-page data page-id))
[changes file page objects ids {:keys [components-v2 ignore-touched component-swap]}]
(let [ids (cfh/clean-loops objects ids)
ids (cfh/clean-loops objects ids)
in-component-copy?
(fn [shape-id]
in-component-copy?
(fn [shape-id]
;; Look for shapes that are inside a component copy, but are
;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily).
;; Unless we are doing a component swap, in which case we want
;; to delete the old shape
(let [shape (get objects shape-id)]
(and (ctn/has-any-copy-parent? objects shape)
(not component-swap))))
(let [shape (get objects shape-id)]
(and (ctn/has-any-copy-parent? objects shape)
(not component-swap))))
[ids-to-delete ids-to-hide]
(loop [ids-seq (seq ids)
ids-to-delete []
ids-to-hide []]
(let [id (first ids-seq)]
(if (nil? id)
[ids-to-delete ids-to-hide]
(if (in-component-copy? id)
(recur (rest ids-seq)
ids-to-delete
(conj ids-to-hide id))
(recur (rest ids-seq)
(conj ids-to-delete id)
ids-to-hide)))))
[ids-to-delete ids-to-hide]
(if components-v2
(loop [ids-seq (seq ids)
ids-to-delete []
ids-to-hide []]
(let [id (first ids-seq)]
(if (nil? id)
[ids-to-delete ids-to-hide]
(if (in-component-copy? id)
(recur (rest ids-seq)
ids-to-delete
(conj ids-to-hide id))
(recur (rest ids-seq)
(conj ids-to-delete id)
ids-to-hide)))))
[ids []])
lookup (d/getf objects)
changes (-> changes
(pcb/with-page page)
(pcb/with-objects objects)
(pcb/with-library-data file))
groups-to-unmask
(reduce (fn [group-ids id]
lookup (d/getf objects)
groups-to-unmask
(reduce (fn [group-ids id]
;; When the shape to delete is the mask of a masked group,
;; the mask condition must be removed, and it must be
;; converted to a normal group.
(let [obj (lookup id)
parent (lookup (:parent-id obj))]
(if (and (:masked-group parent)
(= id (first (:shapes parent))))
(conj group-ids (:id parent))
group-ids)))
#{}
ids-to-delete)
(let [obj (lookup id)
parent (lookup (:parent-id obj))]
(if (and (:masked-group parent)
(= id (first (:shapes parent))))
(conj group-ids (:id parent))
group-ids)))
#{}
ids-to-delete)
interacting-shapes
(filter (fn [shape]
interacting-shapes
(filter (fn [shape]
;; If any of the deleted shapes is the destination of
;; some interaction, this must be deleted, too.
(let [interactions (:interactions shape)]
(some #(and (ctsi/has-destination %)
(contains? ids-to-delete (:destination %)))
interactions)))
(vals objects))
(let [interactions (:interactions shape)]
(some #(and (ctsi/has-destination %)
(contains? ids-to-delete (:destination %)))
interactions)))
(vals objects))
changes
(reduce (fn [changes {:keys [id] :as flow}]
(if (contains? ids-to-delete (:starting-frame flow))
(pcb/set-flow changes id nil)
changes))
changes
(:flows page))
changes
(reduce (fn [changes {:keys [id] :as flow}]
(if (contains? ids-to-delete (:starting-frame flow))
(pcb/set-flow changes id nil)
changes))
changes
(:flows page))
all-parents
(reduce (fn [res id]
all-parents
(reduce (fn [res id]
;; All parents of any deleted shape must be resized.
(into res (cfh/get-parent-ids objects id)))
(d/ordered-set)
(concat ids-to-delete ids-to-hide))
(into res (cfh/get-parent-ids objects id)))
(d/ordered-set)
(concat ids-to-delete ids-to-hide))
all-children
(->> ids-to-delete ;; Children of deleted shapes must be also deleted.
(reduce (fn [res id]
(into res (cfh/get-children-ids objects id)))
[])
(reverse)
(into (d/ordered-set)))
all-children
(->> ids-to-delete ;; Children of deleted shapes must be also deleted.
(reduce (fn [res id]
(into res (cfh/get-children-ids objects id)))
[])
(reverse)
(into (d/ordered-set)))
find-all-empty-parents
(fn recursive-find-empty-parents [empty-parents]
(let [all-ids (into empty-parents ids-to-delete)
contains? (partial contains? all-ids)
xform (comp (map lookup)
(filter #(or (cfh/group-shape? %) (cfh/bool-shape? %) (ctk/is-variant-container? %)))
(remove #(->> (:shapes %) (remove contains?) seq))
(map :id))
parents (into #{} xform all-parents)]
(if (= empty-parents parents)
empty-parents
(recursive-find-empty-parents parents))))
find-all-empty-parents
(fn recursive-find-empty-parents [empty-parents]
(let [all-ids (into empty-parents ids-to-delete)
contains? (partial contains? all-ids)
xform (comp (map lookup)
(filter #(or (cfh/group-shape? %) (cfh/bool-shape? %)))
(remove #(->> (:shapes %) (remove contains?) seq))
(map :id))
parents (into #{} xform all-parents)]
(if (= empty-parents parents)
empty-parents
(recursive-find-empty-parents parents))))
empty-parents
empty-parents
;; Any parent whose children are all deleted, must be deleted too.
;; Unless we are during a component swap: in this case we are replacing a shape by
;; other one, so must not delete empty parents.
(if-not component-swap
(into (d/ordered-set) (find-all-empty-parents #{}))
#{})
(if-not component-swap
(into (d/ordered-set) (find-all-empty-parents #{}))
#{})
components-to-delete
(reduce (fn [components id]
(let [shape (get objects id)]
(if (and (= (:component-file shape) (:id data)) ;; Main instances should exist only in local file
(:main-instance shape)) ;; but check anyway
(conj components (:component-id shape))
components)))
[]
(into ids-to-delete all-children))
components-to-delete
(if components-v2
(reduce (fn [components id]
(let [shape (get objects id)]
(if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file
(:main-instance shape)) ;; but check anyway
(conj components (:component-id shape))
components)))
[]
(into ids-to-delete all-children))
[])
ids-set (set ids-to-delete)
ids-set (set ids-to-delete)
guides-to-delete
(->> (:guides page)
(vals)
(filter #(contains? ids-set (:frame-id %)))
(map :id))
guides-to-delete
(->> (:guides page)
(vals)
(filter #(contains? ids-set (:frame-id %)))
(map :id))
changes (reduce (fn [changes guide-id]
(pcb/set-flow changes guide-id nil))
changes
guides-to-delete)
changes (reduce (fn [changes guide-id]
(pcb/set-flow changes guide-id nil))
changes
guides-to-delete)
changes (reduce (fn [changes component-id]
changes (reduce (fn [changes component-id]
;; It's important to delete the component before the main instance, because we
;; need to store the instance position if we want to restore it later.
(pcb/delete-component changes component-id (:id page)))
changes
components-to-delete)
(pcb/delete-component changes component-id (:id page)))
changes
components-to-delete)
changes (-> changes
(generate-update-shape-flags ids-to-hide objects {:hidden true})
(pcb/remove-objects all-children {:ignore-touched true})
(pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched})
(pcb/remove-objects empty-parents)
(pcb/resize-parents all-parents)
(pcb/update-shapes groups-to-unmask
(fn [shape]
(assoc shape :masked-group false)))
(pcb/update-shapes (map :id interacting-shapes)
(fn [shape]
(d/update-when shape :interactions
(fn [interactions]
(into []
(remove #(and (ctsi/has-destination %)
(contains? ids-to-delete (:destination %))))
interactions))))))]
[all-parents changes])))
changes (-> changes
(generate-update-shape-flags ids-to-hide objects {:hidden true})
(pcb/remove-objects all-children {:ignore-touched true})
(pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched})
(pcb/remove-objects empty-parents)
(pcb/resize-parents all-parents)
(pcb/update-shapes groups-to-unmask
(fn [shape]
(assoc shape :masked-group false)))
(pcb/update-shapes (map :id interacting-shapes)
(fn [shape]
(d/update-when shape :interactions
(fn [interactions]
(into []
(remove #(and (ctsi/has-destination %)
(contains? ids-to-delete (:destination %))))
interactions))))))]
[all-parents changes]))
(defn generate-relocate
@@ -259,7 +255,7 @@
child-heads-ids (map :id child-heads)
variant-shapes (filter ctk/is-variant? shapes)
variant-heads (filter ctk/is-variant? child-heads)
component-main-parent
(ctn/find-component-main objects parent false)
@@ -343,19 +339,7 @@
(map :id)))
index-cell-data (when to-index (ctl/get-cell-by-index parent to-index))
cell (or cell (and index-cell-data [(:row index-cell-data) (:column index-cell-data)]))
;; Parents that are a variant-container that becomes empty
empty-variant-cont (reduce
(fn [to-delete parent-id]
(let [parent (get objects parent-id)]
(if (and (ctk/is-variant-container? parent)
(empty? (remove (set ids) (:shapes parent))))
(conj to-delete (:id parent))
to-delete)))
#{}
(remove #(= % parent-id) all-parents))]
cell (or cell (and index-cell-data [(:row index-cell-data) (:column index-cell-data)]))]
(-> changes
;; Remove layout-item properties when moving a shape outside a layout
@@ -384,11 +368,82 @@
;; Remove variant info and rename when moving outside a variant-container
(cond-> (not (ctk/is-variant-container? parent))
(clvp/generate-make-shapes-no-variant variant-shapes))
((fn [changes]
(reduce
(fn [changes shape]
(let [new-name (str/replace (:variant-name shape) #", " " / ")
[cpath cname] (cfh/parse-path-name new-name)]
(-> changes
(pcb/update-component (:component-id shape)
#(-> (dissoc % :variant-id :variant-properties)
(assoc :name cname
:path cpath))
{:apply-changes-local-library? true})
(pcb/update-shapes [(:id shape)]
#(-> (dissoc % :variant-id :variant-name)
(assoc :name new-name))))))
changes
variant-heads))))
;; Add variant info and rename when moving into a different variant-container
(cond-> (ctk/is-variant-container? parent)
(clvp/generate-make-shapes-variant child-heads parent))
((fn [changes]
(let [get-base-name #(if (some? (:variant-name %))
(str/replace (:variant-name %) #", " " / ")
(:name %))
calc-num-props #(-> %
get-base-name
cfh/split-path
count)
max-path-items (apply max (map calc-num-props child-heads))
first-comp-id (->> parent
:shapes
first
(get objects)
:component-id)
data (pcb/get-library-data changes)
variant-properties (get-in data [:components first-comp-id :variant-properties])
num-props (count variant-properties)
num-new-props (if (< max-path-items num-props)
0
(- max-path-items num-props))
changes (nth
(iterate #(clv/generate-add-new-property % (:id parent)) changes)
num-new-props)]
(reduce
(fn [changes shape]
(if (= (:id parent) (:variant-id shape))
changes ;; do nothing if we aren't changing the parent
(let [base-name (get-base-name shape)
;; we need to get the updated library data to have access to the current properties
data (pcb/get-library-data changes)
props (clv/path-to-properties
base-name
(get-in data [:components first-comp-id :variant-properties]))
variant-name (clv/properties-to-name props)
[cpath cname] (cfh/parse-path-name (:name parent))]
(-> (pcb/update-component changes
(:component-id shape)
#(assoc % :variant-id (:id parent)
:variant-properties props
:name cname
:path cpath)
{:apply-changes-local-library? true})
(pcb/update-shapes [(:id shape)]
#(assoc % :variant-id (:id parent)
:variant-name variant-name
:name (:name parent)))))))
changes
child-heads)))))
;; Move the shapes
(pcb/change-parent parent-id
@@ -463,11 +518,7 @@
(pcb/update-shapes ids #(assoc % :blocked true)))
;; Resize parent containers that need to
(pcb/resize-parents parents)
;; Remove parents when are a variant-container that becomes empty
(cond-> (seq empty-variant-cont)
(#(second (generate-delete-shapes % empty-variant-cont {})))))))
(pcb/resize-parents parents))))
(defn change-show-in-viewer
[shape hide?]

View File

@@ -1,198 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.logic.variant-properties
(:require
[app.common.data :as d]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctcl]
[app.common.types.variant :as ctv]
[cuerdas.core :as str]))
(defn generate-update-property-name
[changes variant-id pos new-name]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)]
(reduce (fn [changes component]
(pcb/update-component
changes (:id component)
#(assoc-in % [:variant-properties pos :name] new-name)
{:apply-changes-local-library? true}))
changes
related-components)))
(defn generate-remove-property
[changes variant-id pos]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)]
(reduce (fn [changes component]
(let [props (:variant-properties component)
props (d/remove-at-index props pos)
main-id (:main-instance-id component)
name (ctv/properties-to-name props)]
(-> changes
(pcb/update-component (:id component) #(assoc % :variant-properties props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
changes
related-components)))
(defn generate-update-property-value
[changes component-id pos value]
(let [data (pcb/get-library-data changes)
component (ctcl/get-component data component-id true)
main-id (:main-instance-id component)
name (-> (:variant-properties component)
(update pos assoc :value value)
ctv/properties-to-name)]
(-> changes
(pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
(defn generate-add-new-property
[changes variant-id & {:keys [fill-values? property-name]}]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)
props (-> related-components last :variant-properties)
next-prop-num (ctv/next-property-number props)
property-name (or property-name (str ctv/property-prefix next-prop-num))
[_ changes]
(reduce (fn [[num changes] component]
(let [main-id (:main-instance-id component)
update-props #(-> (d/nilv % [])
(conj {:name property-name
:value (if fill-values? (str ctv/value-prefix num) "")}))
update-name #(if fill-values?
(if (str/empty? %)
(str ctv/value-prefix num)
(str % ", " ctv/value-prefix num))
%)]
[(inc num)
(-> changes
(pcb/update-component (:id component)
#(update % :variant-properties update-props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(update % :variant-name update-name)))]))
[1 changes]
related-components)]
changes))
(defn- generate-make-shape-no-variant
[changes shape]
(let [new-name (ctv/variant-name-to-name shape)
[cpath cname] (cfh/parse-path-name new-name)]
(-> changes
(pcb/update-component (:component-id shape)
#(-> (dissoc % :variant-id :variant-properties)
(assoc :name cname
:path cpath))
{:apply-changes-local-library? true})
(pcb/update-shapes [(:id shape)]
#(-> (dissoc % :variant-id :variant-name)
(assoc :name new-name))))))
(defn generate-make-shapes-no-variant
[changes shapes]
(reduce generate-make-shape-no-variant changes shapes))
(defn- generate-new-properties-from-variant
[shape min-props data container-name base-properties]
(let [component (ctcl/get-component data (:component-id shape) true)
add-name? (not= (:name component) container-name)
props (ctv/merge-properties base-properties
(:variant-properties component))
new-props (- min-props
(+ (count props)
(if add-name? 1 0)))
props (ctv/add-new-props props (repeat new-props ""))]
(if add-name?
(ctv/add-new-prop props (:name component))
props)))
(defn- generate-new-properties-from-non-variant
[shape min-props container-name base-properties]
(let [;; Remove container name from shape name if present
shape-name (ctv/remove-prefix (:name shape) container-name)]
(ctv/path-to-properties shape-name base-properties min-props)))
(defn generate-make-shapes-variant
[changes shapes variant-container]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
variant-id (:id variant-container)
;; If we are cut-pasting a variant-container, this will be null
;; because it hasn't any shapes yet
first-comp-id (->> variant-container
:shapes
first
(get objects)
:component-id)
base-props (->> (get-in data [:components first-comp-id :variant-properties])
(map #(assoc % :value "")))
num-base-props (count base-props)
[cpath cname] (cfh/parse-path-name (:name variant-container))
container-name (:name variant-container)
generate-new-properties
(fn [shape min-props]
(if (ctk/is-variant? shape)
(generate-new-properties-from-variant shape min-props data container-name base-props)
(generate-new-properties-from-non-variant shape min-props container-name base-props)))
total-props (reduce (fn [m shape]
(max m (count (generate-new-properties shape num-base-props))))
0
shapes)
num-new-props (if (or (zero? num-base-props)
(< total-props num-base-props))
0
(- total-props num-base-props))
changes (nth
(iterate #(generate-add-new-property % variant-id) changes)
num-new-props)
changes (pcb/update-shapes changes (map :id shapes)
#(assoc % :variant-id variant-id
:name (:name variant-container)))]
(reduce
(fn [changes shape]
(if (or (zero? num-base-props)
(= variant-id (:variant-id shape)))
changes ;; do nothing more if we aren't changing the parent or there are no base props
(let [props (generate-new-properties shape total-props)
variant-name (ctv/properties-to-name props)]
(-> (pcb/update-component changes
(:component-id shape)
#(assoc % :variant-id variant-id
:variant-properties props
:name cname
:path cpath)
{:apply-changes-local-library? true})
(pcb/update-shapes [(:id shape)]
#(assoc % :variant-name variant-name))))))
changes
shapes)))

View File

@@ -1,30 +1,160 @@
;; 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.common.logic.variants
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.variant :as cfv]
[app.common.logic.libraries :as cll]
[app.common.logic.variant-properties :as clvp]
[app.common.types.variant :as ctv]))
[app.common.files.helpers :as cfh]
[app.common.types.components-list :as ctcl]
[cuerdas.core :as str]))
(defn generate-add-new-variant
[changes shape variant-id new-component-id new-shape-id prop-num]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
component-id (:component-id shape)
value (str ctv/value-prefix
(-> (cfv/extract-properties-values data objects variant-id)
last
:value
count
inc))
[new-shape changes] (-> changes
(cll/generate-duplicate-component
{:data data}
component-id
new-component-id
{:new-shape-id new-shape-id :apply-changes-local-library? true}))]
(def property-prefix "Property")
(def property-regex (re-pattern (str property-prefix "(\\d+)")))
(def value-prefix "Value")
(defn find-related-components
"Find a list of the components thet belongs to this variant-id"
[data objects variant-id]
(->> (dm/get-in objects [variant-id :shapes])
(map #(dm/get-in objects [% :component-id]))
(map #(ctcl/get-component data % true))
reverse))
(defn properties-to-name
"Transform the properties into a name, with the values separated by comma"
[properties]
(->> properties
(map :value)
(remove str/empty?)
(str/join ", ")))
(defn next-property-number
"Returns the next property number, to avoid duplicates on the property names"
[properties]
(let [numbers (keep
#(some->> (:name %) (re-find property-regex) second d/parse-integer)
properties)
max-num (if (seq numbers)
(apply max numbers)
0)]
(inc (max max-num (count properties)))))
(defn path-to-properties
"From a list of properties and a name with path, assign each token of the
path as value of a different property"
[path properties]
(let [next-prop-num (next-property-number properties)
cpath (cfh/split-path path)
assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range))
remaining (drop (count properties) cpath)
new-properties (map-indexed (fn [i v] {:name (str property-prefix (+ next-prop-num i))
:value v}) remaining)]
(into assigned new-properties)))
(defn- dashes-to-end
[property-values]
(let [dashes (if (some #(= % "--") property-values) ["--"] [])]
(concat (remove #(= % "--") property-values) dashes)))
(defn extract-properties-values
[data objects variant-id]
(->> (find-related-components data objects variant-id)
(mapcat :variant-properties)
(group-by :name)
(map (fn [[k v]]
{:name k
:value (->> v
(map #(if (str/empty? (:value %)) "--" (:value %)))
distinct
dashes-to-end)}))))
(defn generate-update-property-name
[changes variant-id pos new-name]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)]
(reduce (fn [changes component]
(pcb/update-component
changes (:id component)
#(assoc-in % [:variant-properties pos :name] new-name)
{:apply-changes-local-library? true}))
changes
related-components)))
(defn generate-remove-property
[changes variant-id pos]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)]
(reduce (fn [changes component]
(let [props (:variant-properties component)
props (d/remove-at-index props pos)
main-id (:main-instance-id component)
name (properties-to-name props)]
(-> changes
(pcb/update-component (:id component) #(assoc % :variant-properties props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
changes
related-components)))
(defn generate-update-property-value
[changes component-id pos value]
(let [data (pcb/get-library-data changes)
component (ctcl/get-component data component-id true)
main-id (:main-instance-id component)
name (-> (:variant-properties component)
(update pos assoc :value value)
properties-to-name)]
(-> changes
(clvp/generate-update-property-value new-component-id prop-num value)
(pcb/change-parent (:parent-id shape) [new-shape] 0))))
(pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
(defn generate-add-new-property
[changes variant-id & {:keys [fill-values?]}]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)
props (-> related-components first :variant-properties)
next-prop-num (next-property-number props)
property-name (str property-prefix next-prop-num)
[_ changes]
(reduce (fn [[num changes] component]
(let [main-id (:main-instance-id component)
update-props #(-> (d/nilv % [])
(conj {:name property-name
:value (if fill-values? (str value-prefix num) "")}))
update-name #(if fill-values?
(if (str/empty? %)
(str value-prefix num)
(str % ", " value-prefix num))
%)]
[(inc num)
(-> changes
(pcb/update-component (:id component)
#(update % :variant-properties update-props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(update % :variant-name update-name)))]))
[1 changes]
related-components)]
changes))

View File

@@ -113,10 +113,6 @@
[schema]
(mu/optional-keys schema default-options))
(defn required-keys
[schema]
(mu/required-keys schema default-options))
(defn transformer
[& transformers]
(apply mt/transformer transformers))
@@ -149,30 +145,11 @@
;; :else
;; o))
(defn -transform-map-keys
([f]
(let [xform (map (fn [[k v]] [(f k) v]))]
#(cond->> % (map? %) (into (empty %) xform))))
([ks f]
(let [xform (map (fn [[k v]] [(cond-> k (contains? ks k) f) v]))]
#(cond->> % (map? %) (into (empty %) xform)))))
(defn json-transformer
[]
(let [map-of-key-decoders (mt/-string-decoders)]
(mt/transformer
{:name :json
:decoders (-> (mt/-json-decoders)
(assoc :map-of {:compile (fn [schema _]
(let [key-schema (some-> schema (m/children) (first))]
(or (some-> key-schema (m/type) map-of-key-decoders
(mt/-interceptor schema {}) (m/-intercepting)
(m/-comp m/-keyword->string)
(mt/-transform-if-valid key-schema)
(-transform-map-keys))
(-transform-map-keys m/-keyword->string))))}))
:encoders (mt/-json-encoders)}
(mt/collection-transformer))))
(mt/transformer
(mt/json-transformer)
(mt/collection-transformer)))
(defn string-transformer
[]
@@ -897,7 +874,7 @@
{:title "inst"
:description "Satisfies Inst protocol"
:error/message "should be an instant"
:gen/gen (->> (sg/small-int :min 0 :max 100000)
:gen/gen (->> (sg/small-int)
(sg/fmap (fn [v] (tm/parse-instant v))))
:decode/string tm/parse-instant

View File

@@ -40,3 +40,76 @@
(map (fn [segment]
(.toPersistentMap ^js segment)))
(parser/parse path-str)))))
#?(:cljs
(defn content->buffer
"Converts the path content into binary format."
[content]
(let [total (count content)
ssize 28
buffer (new js/ArrayBuffer (* total ssize))
dview (new js/DataView buffer)]
(loop [index 0]
(when (< index total)
(let [segment (nth content index)
offset (* index ssize)]
(case (:command segment)
:move-to
(let [{:keys [x y]} (:params segment)]
(.setInt16 dview (+ offset 0) 1)
(.setFloat32 dview (+ offset 20) x)
(.setFloat32 dview (+ offset 24) y))
:line-to
(let [{:keys [x y]} (:params segment)]
(.setInt16 dview (+ offset 0) 2)
(.setFloat32 dview (+ offset 20) x)
(.setFloat32 dview (+ offset 24) y))
:curve-to
(let [{:keys [c1x c1y c2x c2y x y]} (:params segment)]
(.setInt16 dview (+ offset 0) 3)
(.setFloat32 dview (+ offset 4) c1x)
(.setFloat32 dview (+ offset 8) c1y)
(.setFloat32 dview (+ offset 12) c2x)
(.setFloat32 dview (+ offset 16) c2y)
(.setFloat32 dview (+ offset 20) x)
(.setFloat32 dview (+ offset 24) y))
:close-path
(.setInt16 dview (+ offset 0) 4))
(recur (inc index)))))
buffer)))
#?(:cljs
(defn buffer->content
"Converts the a buffer to a path content vector"
[buffer]
(assert (instance? js/ArrayBuffer buffer) "expected ArrayBuffer instance")
(let [ssize 28
total (/ (.-byteLength buffer) ssize)
dview (new js/DataView buffer)]
(loop [index 0
result []]
(if (< index total)
(let [offset (* index ssize)
type (.getInt16 dview (+ offset 0))
command (case type
1 :move-to
2 :line-to
3 :curve-to
4 :close-path)
params (case type
1 {:x (.getFloat32 dview (+ offset 20))
:y (.getFloat32 dview (+ offset 24))}
2 {:x (.getFloat32 dview (+ offset 20))
:y (.getFloat32 dview (+ offset 24))}
3 {:c1x (.getFloat32 dview (+ offset 4))
:c1y (.getFloat32 dview (+ offset 8))
:c2x (.getFloat32 dview (+ offset 12))
:c2y (.getFloat32 dview (+ offset 16))
:x (.getFloat32 dview (+ offset 20))
:y (.getFloat32 dview (+ offset 24))}
4 {})]
(recur (inc index)
(conj result {:command command
:params params})))
result)))))

View File

@@ -10,7 +10,6 @@
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
[app.common.geom.shapes.path :as gsp]
[app.common.math :as mth]
[app.common.svg.path.command :as upc]
[app.common.svg.path.subpath :as ups]))
@@ -234,45 +233,6 @@
(gsp/command->point current)
(conj result (dissoc current :prev)))))))
(defn remove-duplicated-segments
"Remove from the content segments"
[content]
(letfn [;; This is a comparator for float points with a precission
;; used to remove already existing segments
(comparator [[fx1 fy1 tx1 ty1 :as v1] [fx2 fy2 tx2 ty2 :as v2]]
(if (and (mth/close? tx1 tx2)
(mth/close? ty1 ty2)
(mth/close? fx1 fx2)
(mth/close? fy1 fy2))
0 ;; equal
(compare v1 v2)))]
(loop [current (first content)
content (rest content)
segments (sorted-set-by comparator)
result []]
(if (nil? current)
result
(let [fx (-> current :prev :x)
fy (-> current :prev :y)
tx (-> current :params :x)
ty (-> current :params :y)
result
(cond-> result
(and (not (contains? segments [fx fy tx ty]))
(not (contains? segments [tx ty fx fy])))
(conj current))
segments (conj segments [fx fy tx ty])]
(recur (first content)
(rest content)
segments
result))))))
(defn create-union [content-a content-a-split content-b content-b-split sr-a sr-b]
;; Pick all segments in content-a that are not inside content-b
;; Pick all segments in content-b that are not inside content-a
@@ -352,17 +312,15 @@
content-a-split (->> content-a-split add-previous (filter is-segment?))
content-b-split (->> content-b-split add-previous (filter is-segment?))
content
bool-content
(case bool-type
:union (create-union content-a content-a-split content-b content-b-split sr-a sr-b)
:difference (create-difference content-a content-a-split content-b content-b-split sr-a sr-b)
:intersection (create-intersection content-a content-a-split content-b content-b-split sr-a sr-b)
:exclude (create-exclusion content-a-split content-b-split))]
(-> content
remove-duplicated-segments
fix-move-to
ups/close-subpaths)))
(->> (fix-move-to bool-content)
(ups/close-subpaths))))
(defn content-bool
[bool-type contents]
@@ -373,3 +331,4 @@
(reduce (partial content-bool-pair bool-type))
(into []))
[]))

View File

@@ -126,7 +126,7 @@
(pt= (:from subpath) (:to subpath)))
(defn close-subpaths
"Searches a path for possible subpaths that can create closed loops and merge them"
"Searches a path for possible supaths that can create closed loops and merge them"
[content]
(let [subpaths (get-subpaths content)
closed-subpaths

View File

@@ -435,12 +435,16 @@
attrs
(-> attrs
(cond-> linecap
(dissoc :strokeLinecap))
(cond-> (some? color)
(dissoc :stroke :strokeWidth :strokeOpacity))
(update
:style
(fn [style]
(-> style
(cond-> linecap
(dissoc :strokeLinecap))
(cond-> (some? color)
(dissoc :stroke :strokeWidth :strokeOpacity)))))
(d/without-nils))]
@@ -457,14 +461,12 @@
(and (some? linecap) (cfh/path-shape? shape)
(or (= linecap :round) (= linecap :square)))
(assoc :stroke-cap-start linecap
:stroke-cap-end linecap
:stroke-linecap linecap)
:stroke-cap-end linecap)
(d/any-key? (dm/get-in shape [:strokes 0])
:strokeColor :strokeOpacity :strokeWidth
:strokeLinecap :strokeCapStart :strokeCapEnd)
:strokeCapStart :strokeCapEnd)
(assoc-in [:strokes 0 :stroke-style] :svg))))
(defn setup-opacity [shape]

View File

@@ -31,7 +31,7 @@
"Need that root is already a frame"
(cfh/frame-shape? root))
(let [[_new-root updated-shapes]
(let [[_new-root _new-shapes updated-shapes]
(ctn/convert-shape-in-component root (:objects page) (:id file))
updated-root (first updated-shapes) ; Can't use new-root because it has a new id
@@ -54,7 +54,8 @@
:name name
:path path
:main-instance-id (:id updated-root)
:main-instance-page (:id page)))))))))
:main-instance-page (:id page)
:shapes updated-shapes))))))))
(defn update-component
[file component-label & {:keys [] :as params}]
@@ -97,6 +98,7 @@
component
(:data library)
(gpt/point 100 100)
true
{:force-id (thi/new-id! copy-root-label)
:force-frame-id frame-id})

View File

@@ -85,7 +85,7 @@
& {:keys [component-params root-params child-params]}]
;; Generated shape tree:
;; {:root-label} [:name Frame1] # [Component :component-label]
;; :child-label [:name Rect1]
;; :child-label [:name Rect1]
(-> file
(add-frame-with-child root-label child-label :frame-params root-params :child-params child-params)
(thc/make-component component-label root-label component-params)))
@@ -95,7 +95,7 @@
& {:keys [component-params main-root-params main-child-params copy-root-params]}]
;; Generated shape tree:
;; {:main-root-label} [:name Frame1] # [Component :component-label]
;; :main-child-label [:name Rect1]
;; :main-child-label [:name Rect1]
;;
;; :copy-root-label [:name Frame1] #--> [Component :component-label] :main-root-label
;; <no-label> [:name Rect1] ---> :main-child-label
@@ -113,9 +113,9 @@
& {:keys [component-params root-params child-params-list]}]
;; Generated shape tree:
;; {:root-label} [:name Frame1] # [Component :component-label]
;; :child1-label [:name Rect1]
;; :child2-label [:name Rect2]
;; :child3-label [:name Rect3]
;; :child1-label [:name Rect1]
;; :child2-label [:name Rect2]
;; :child3-label [:name Rect3]
(as-> file $
(add-frame $ root-label root-params)
(reduce (fn [file [index [label params]]]
@@ -134,9 +134,9 @@
& {:keys [component-params main-root-params main-child-params-list copy-root-params]}]
;; Generated shape tree:
;; {:root-label} [:name Frame1] # [Component :component-label]
;; :child1-label [:name Rect1]
;; :child2-label [:name Rect2]
;; :child3-label [:name Rect3]
;; :child1-label [:name Rect1]
;; :child2-label [:name Rect2]
;; :child3-label [:name Rect3]
;;
;; :copy-root-label [:name Frame1] #--> [Component :component-label] :root-label
;; <no-label> [:name Rect1] ---> :child1-label
@@ -156,7 +156,7 @@
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params]}]
;; Generated shape tree:
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
;; :main1-child-label [:name Rect1]
;; :main1-child-label [:name Rect1]
;;
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
;; :nested-head-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
@@ -183,7 +183,7 @@
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params copy2-root-params]}]
;; Generated shape tree:
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
;; :main1-child-label [:name Rect1]
;; :main1-child-label [:name Rect1]
;;
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
;; :nested-head-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
@@ -336,7 +336,8 @@
file
{file-id file}
(ctn/make-container container :page)
(:id shape)))
(:id shape)
true))
file' (thf/apply-changes file changes)]
(if propagate-fn
(propagate-fn file')
@@ -360,7 +361,7 @@
(:objects page)
#{(-> (ths/get-shape file shape-tag :page-label page-label)
:id)}
{})
{:components-v2 true})
file' (thf/apply-changes file changes)]
(if propagate-fn
(propagate-fn file')
@@ -379,7 +380,7 @@
(gpt/point 0 0) ;; delta
{(:id file) file} ;; libraries
(:data file) ;; library-data
(:id file)) ;; file-id
(:id file)) ;; file-id
(cll/generate-duplicate-changes-update-indices (:objects page) ;; objects
#{(:id shape)}))
file' (thf/apply-changes file changes)]

View File

@@ -22,18 +22,4 @@
(thc/make-component component1-label root1-label)
(thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]})
(thc/make-component component2-label root2-label)
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value2"}]}))))
(defn add-variant-two-properties
[file variant-label component1-label root1-label component2-label root2-label
& {:keys []}]
(let [file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true)
variant-id (thi/id variant-label)]
(-> file
(ths/add-sample-shape root2-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "p1v2, p2v2")
(ths/add-sample-shape root1-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "p1v1, p2v1")
(thc/make-component component1-label root1-label)
(thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "p1v1"} {:name "Property2" :value "p2v1"}]})
(thc/make-component component2-label root2-label)
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "p1v2"} {:name "Property2" :value "p2v2"}]}))))
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]}))))

View File

@@ -10,7 +10,6 @@
[app.common.schema :as sm]
[app.common.types.page :as ctp]
[app.common.types.plugins :as ctpg]
[app.common.types.variant :as ctv]
[cuerdas.core :as str]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -18,17 +17,15 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def schema:component
[:merge
[:map
[:id ::sm/uuid]
[:name :string]
[:path {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::sm/inst]
[:objects {:gen/max 10 :optional true} ::ctp/objects]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]
[:plugin-data {:optional true} ::ctpg/plugin-data]]
::ctv/variant-component])
[:map
[:id ::sm/uuid]
[:name :string]
[:path {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::sm/inst]
[:objects {:gen/max 10 :optional true} ::ctp/objects]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
(sm/register! ::component schema:component)
@@ -93,8 +90,8 @@
:constraints-h :constraints-group
:constraints-v :constraints-group
:fixed-scroll :constraints-group
:bool-type :content-group
:bool-content :content-group
:bool-type :bool-group
:bool-content :bool-group
:exports :exports-group
:grids :grids-group
@@ -182,8 +179,10 @@
(= (:component-file shape) file-id)))
(defn is-main-of?
[shape-main shape-inst]
(= (:shape-ref shape-inst) (:id shape-main)))
[shape-main shape-inst components-v2]
(or (= (:shape-ref shape-inst) (:id shape-main))
(and (= (:shape-ref shape-inst) (:shape-ref shape-main))
(not components-v2))))
(defn main-instance?
"Check if this shape is the root of the main instance of some
@@ -334,6 +333,8 @@
(let [parent (get objects (:parent-id shape))]
;; We don't want to change the structure of component copies
(and (not (in-component-copy-not-head? shape))
;; We don't want to duplicate variants
(not (is-variant? shape))
;; Non instance, non copy. We allow
(or (not (instance-head? shape))
(not (in-component-copy? parent))))))

View File

@@ -34,12 +34,20 @@
(assoc component :modified-at (dt/now)))
(defn add-component
[fdata {:keys [id name path main-instance-id main-instance-page annotation variant-id variant-properties]}]
(let [fdata (update fdata :components assoc id (touch {:id id :name name :path path}))]
(cond-> (update-in fdata [:components id] assoc :main-instance-id main-instance-id :main-instance-page main-instance-page)
annotation (update-in [:components id] assoc :annotation annotation)
variant-id (update-in [:components id] assoc :variant-id variant-id)
variant-properties (update-in [:components id] assoc :variant-properties variant-properties))))
[fdata {:keys [id name path main-instance-id main-instance-page shapes annotation variant-id variant-properties]}]
(let [components-v2 (dm/get-in fdata [:options :components-v2])
fdata (update fdata :components assoc id (touch {:id id :name name :path path}))]
(if components-v2
(cond-> (update-in fdata [:components id] assoc :main-instance-id main-instance-id :main-instance-page main-instance-page)
annotation (update-in [:components id] assoc :annotation annotation)
variant-id (update-in [:components id] assoc :variant-id variant-id)
variant-properties (update-in [:components id] assoc :variant-properties variant-properties))
(let [wrap-object-fn cfeat/*wrap-with-objects-map-fn*]
(assoc-in fdata [:components id :objects]
(->> shapes
(d/index-by :id)
(wrap-object-fn)))))))
(defn mod-component
[file-data {:keys [id name path main-instance-id main-instance-page objects annotation variant-id variant-properties modified-at]}]
@@ -111,6 +119,7 @@
[file-data component-id f & args]
(d/update-in-when file-data [:components component-id] #(-> (apply f % args)
(touch))))
(defn set-component-modified
[file-data component-id]
(update-component file-data component-id identity))

View File

@@ -267,8 +267,67 @@
new-children (->> (cfh/get-children objects (:id root))
(map #(dissoc % :component-root)))]
[(assoc new-root :id new-id)
nil
(into [new-root] new-children)]))
(defn make-component-shape ;; Only used for components v1
"Clone the shape and all children. Generate new ids and detach
from parent and frame. Update the original shapes to have links
to the new ones."
[shape objects file-id components-v2]
(assert (nil? (:component-id shape)))
(assert (nil? (:component-file shape)))
(assert (nil? (:shape-ref shape)))
(let [frame-ids-map (volatile! {})
;; Ensure that the component root is not an instance
update-new-shape (fn [new-shape original-shape]
(when (= (:type original-shape) :frame)
(vswap! frame-ids-map assoc (:id original-shape) (:id new-shape)))
(cond-> new-shape
true
(dissoc :component-root)
(nil? (:parent-id new-shape))
(dissoc :component-id
:component-file
:shape-ref)))
;; Make the original shape an instance of the new component.
;; If one of the original shape children already was a component
;; instance, maintain this instanceness untouched.
update-original-shape (fn [original-shape new-shape]
(cond-> original-shape
(nil? (:shape-ref original-shape))
(-> (assoc :shape-ref (:id new-shape))
(dissoc :touched))
(nil? (:parent-id new-shape))
(assoc :component-id (:id new-shape)
:component-file file-id
:component-root true)
(and (nil? (:parent-id new-shape)) components-v2)
(assoc :main-instance true)
(some? (:parent-id new-shape))
(dissoc :component-root)))
[new-root-shape new-shapes updated-shapes]
(ctst/clone-shape shape
nil
objects
:update-new-shape update-new-shape
:update-original-shape update-original-shape)
;; If frame-id points to a shape inside the component, remap it to the
;; corresponding new frame shape. If not, set it to nil.
remap-frame-id (fn [shape]
(update shape :frame-id #(get @frame-ids-map % nil)))]
[new-root-shape (map remap-frame-id new-shapes) updated-shapes]))
(defn remove-swap-keep-attrs
"Remove flex children properties except the fit-content for flex layouts. These are properties
that we don't have to propagate to copies but will be respected when swapping components"
@@ -293,18 +352,20 @@
WARNING: This process does not remap media references (on fills, strokes, ...); that is
delegated to an async process on the backend side that checks unreferenced shapes and
automatically creates correct references."
([page component library-data position]
(make-component-instance page component library-data position {}))
([page component library-data position
([page component library-data position components-v2]
(make-component-instance page component library-data position components-v2 {}))
([page component library-data position components-v2
{:keys [main-instance? force-id force-frame-id keep-ids?]
:or {main-instance? false force-id nil force-frame-id nil keep-ids? false}}]
(let [component-page (ctpl/get-page library-data (:main-instance-page component))
component-shape (-> (get-shape component-page (:main-instance-id component))
(assoc :parent-id nil) ;; On v2 we force parent-id to nil in order to behave like v1
(assoc :frame-id uuid/zero)
(remove-swap-keep-attrs))
(let [component-page (when components-v2
(ctpl/get-page library-data (:main-instance-page component)))
component-shape (if components-v2
(-> (get-shape component-page (:main-instance-id component))
(assoc :parent-id nil) ;; On v2 we force parent-id to nil in order to behave like v1
(assoc :frame-id uuid/zero)
(remove-swap-keep-attrs))
(get-shape component (:id component)))
orig-pos (gpt/point (:x component-shape) (:y component-shape))
delta (gpt/subtract position orig-pos)
@@ -334,7 +395,8 @@
update-new-shape
(fn [new-shape original-shape]
(let [new-name (:name new-shape)
root? (ctk/instance-root? original-shape)]
root? (or (ctk/instance-root? original-shape) ; If shape is inside a component (not components-v2)
(nil? (:parent-id original-shape)))] ; we detect it by having no parent)
(when root?
(vswap! unames conj new-name))
@@ -355,8 +417,10 @@
main-instance?
(dissoc :shape-ref)
(not main-instance?)
(assoc :shape-ref (:id original-shape)) ; shape-ref points to the near instance
(and (not main-instance?)
(or components-v2 ; In v1, shape-ref points to the remote instance
(nil? (:shape-ref original-shape)))) ; in v2, shape-ref points to the near instance
(assoc :shape-ref (:id original-shape))
(nil? (:parent-id original-shape))
(assoc :component-id (:id component)
@@ -364,14 +428,14 @@
:component-root true
:name new-name)
(or (some? (:parent-id original-shape)) ; On v2 we have removed the parent-id for component roots
(or (some? (:parent-id original-shape)) ; On v2 we have removed the parent-id for component roots (see above)
(some? component-frame))
(dissoc :component-root))))
[new-shape new-shapes _]
(ctst/clone-shape component-shape
frame-id
(:objects component-page)
(if components-v2 (:objects component-page) (:objects component))
:update-new-shape update-new-shape
:force-id force-id
:keep-ids? keep-ids?
@@ -481,39 +545,21 @@
no-changes?
(and (every? #(= parent-id (:parent-id %)) children)
(not pasting?))
;; When pasting frames, children have the frames and their children
;; We need to check only the top shapes
children-ids (set (map :id children))
top-children (remove #(contains? children-ids (:parent-id %)) children)
;; Are all the top-children a main-instance of a component?
all-main?
(every? ctk/main-instance? top-children)
(every? ctk/main-instance? children)
any-main-descendant
(some
(fn [shape]
(some ctk/main-instance? (cfh/get-children-with-self objects (:id shape))))
children)
children)]
;; Are all the top-children a main-instance of a cutted component?
all-comp-cut?
(when all-main?
(->> top-children
(map #(ctkl/get-component (dm/get-in libraries [(:component-file %) :data])
(:component-id %)
true))
(every? :deleted)))]
(if (or no-changes?
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
;; If we are moving into a main component, no descendant can be main
(or (nil? any-main-descendant) (not (ctk/main-instance? parent)))
;; If we are moving into a variant-container, all the items should be main
;; so if we are pasting, only allow main instances that are cut-and-pasted
(or (not (ctk/is-variant-container? parent))
(and (not pasting?) all-main?)
all-comp-cut?)))
(or all-main? (not (ctk/is-variant-container? parent)))
;; If we are moving into a main component, no descendant can be main
(or (nil? any-main-descendant) (not (ctk/main-instance? parent)))))
[parent-id (get-frame parent-id)]
(recur (:parent-id parent) objects children pasting? libraries))))))
@@ -556,7 +602,8 @@
;; TODO: the check of :width and :height probably may be
;; removed after the check added in
;; data/workspace/modifiers/check-delta function.
;; data/workspace/modifiers/check-delta function. Better check
;; it and test toroughly when activating components-v2 mode.
in-copy?
(ctk/in-component-copy? shape)

View File

@@ -130,7 +130,7 @@
(some? page-id)
(ctpl/add-page page)
:always
(contains? cfeat/*current* "components/v2")
(assoc-in [:options :components-v2] true)))))
(defn make-file
@@ -221,45 +221,48 @@
(ctpl/get-page file-data (:main-instance-page component)))
(defn get-component-container
"Retrieve the container that holds the component shapes (the page
or the component itself on deleted component)."
"Retrieve the container that holds the component shapes (the page in components-v2
or the component itself in v1 or deleted component)."
[file-data component]
(if (not (:deleted component))
(let [component-page (get-component-page file-data component)]
(cfh/make-container component-page :page))
(cfh/make-container component :component)))
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2 (not (:deleted component)))
(let [component-page (get-component-page file-data component)]
(cfh/make-container component-page :page))
(cfh/make-container component :component))))
(defn get-component-root
"Retrieve the root shape of the component."
[file-data component]
(if (not (:deleted component))
(-> file-data
(get-component-page component)
(ctn/get-shape (:main-instance-id component)))
(ctk/get-component-root component)))
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2 (not (:deleted component)))
(-> file-data
(get-component-page component)
(ctn/get-shape (:main-instance-id component)))
(ctk/get-component-root component))))
(defn get-component-shape
"Retrieve one shape in the component by id. If with-context? is true, add the
file and container where the shape resides in its metadata."
[file-data component shape-id & {:keys [with-context?] :or {with-context? false}}]
(if (not (:deleted component))
(let [component-page (get-component-page file-data component)]
(when component-page
(let [child (cfh/get-child (:objects component-page)
(:main-instance-id component)
shape-id)]
(cond-> child
(and child with-context?)
(with-meta {:file {:id (:id file-data)
:data file-data}
:container (ctn/make-container component-page :page)})))))
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2 (not (:deleted component)))
(let [component-page (get-component-page file-data component)]
(when component-page
(let [child (cfh/get-child (:objects component-page)
(:main-instance-id component)
shape-id)]
(cond-> child
(and child with-context?)
(with-meta {:file {:id (:id file-data)
:data file-data}
:container (ctn/make-container component-page :page)})))))
(let [shape (dm/get-in component [:objects shape-id])]
(cond-> shape
(and shape with-context?)
(with-meta {:file {:id (:id file-data)
:data file-data}
:container (ctn/make-container component :component)})))))
(let [shape (dm/get-in component [:objects shape-id])]
(cond-> shape
(and shape with-context?)
(with-meta {:file {:id (:id file-data)
:data file-data}
:container (ctn/make-container component :component)}))))))
(defn get-ref-shape
"Retrieve the shape in the component that is referenced by the instance shape."
@@ -381,11 +384,12 @@
(defn get-component-shapes
"Retrieve all shapes of the component"
[file-data component]
(if (not (:deleted component)) ;; the deleted components have its children in the :objects property
(let [instance-page (get-component-page file-data component)]
(cfh/get-children-with-self (:objects instance-page) (:main-instance-id component)))
(vals (:objects component))))
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2
(not (:deleted component))) ;; the deleted components have its children in the :objects property
(let [instance-page (get-component-page file-data component)]
(cfh/get-children-with-self (:objects instance-page) (:main-instance-id component)))
(vals (:objects component)))))
;; Return true if the object is a component that exists on the file or its libraries (even a deleted one)
(defn is-main-of-known-component?
@@ -399,52 +403,44 @@
(defn load-component-objects
"Add an :objects property to the component, with only the shapes that belong to it"
([file-data component]
(load-component-objects file-data component (gpt/point 0 0)))
([file-data component delta]
(if (and component (empty? (:objects component))) ;; This operation may be called twice, e.g. in an idempotent change
(let [component-page (get-component-page file-data component)
page-objects (:objects component-page)
objects (->> (cons (:main-instance-id component)
(cfh/get-children-ids page-objects (:main-instance-id component)))
(map #(get page-objects %))
;; when it is an undo of a cut-paste, we need to undo the movement
;; of the shapes so we need to move them delta
(map #(gsh/move % delta))
(d/index-by :id))]
(assoc component :objects objects))
component)))
[file-data component]
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (and components-v2 component (empty? (:objects component))) ;; This operation may be called twice, e.g. in an idempotent change
(let [component-page (get-component-page file-data component)
page-objects (:objects component-page)
objects (->> (cons (:main-instance-id component)
(cfh/get-children-ids page-objects (:main-instance-id component)))
(map #(get page-objects %))
(d/index-by :id))]
(assoc component :objects objects))
component)))
(defn delete-component
"Mark a component as deleted and store the main instance shapes iside it, to
be able to be recovered later."
[file-data component-id skip-undelete? delta]
(let [delta (or delta (gpt/point 0 0))]
(if skip-undelete?
[file-data component-id skip-undelete? main-instance]
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
(if (or (not components-v2) skip-undelete?)
(ctkl/delete-component file-data component-id)
(-> file-data
(ctkl/update-component component-id #(load-component-objects file-data % delta))
(ctkl/mark-component-deleted component-id)))))
(let [set-main-instance ;; If there is a saved main-instance, restore it. This happens on the restore-component action
#(if main-instance
(assoc-in % [:objects (:main-instance-id %)] main-instance)
%)]
(-> file-data
(ctkl/update-component component-id (partial load-component-objects file-data))
(ctkl/update-component component-id set-main-instance)
(ctkl/mark-component-deleted component-id))))))
(defn restore-component
"Recover a deleted component and all its shapes and put all this again in place."
[file-data component-id page-id]
(let [update-page? (not (nil? page-id))
component (ctkl/get-component file-data component-id true)
main-instance-page (or page-id (:main-instance-page component))
main-instance (dm/get-in file-data [:pages-index main-instance-page
:objects (:main-instance-id component)])]
(cond-> file-data
:always
(->
(ctkl/update-component component-id #(dissoc % :objects))
(ctkl/mark-component-undeleted component-id))
update-page?
(ctkl/update-component component-id #(assoc % :main-instance-page page-id))
(ctk/is-variant? component)
(ctkl/update-component component-id #(assoc % :variant-id (:variant-id main-instance))))))
(let [components-v2 (dm/get-in file-data [:options :components-v2])
update-page? (and components-v2 (not (nil? page-id)))]
(-> file-data
(ctkl/update-component component-id #(dissoc % :objects))
(ctkl/mark-component-undeleted component-id)
(cond-> update-page?
(ctkl/update-component component-id #(assoc % :main-instance-page page-id))))))
(defn purge-component
"Remove permanently a component."
@@ -561,6 +557,7 @@
component
library-data
position
(dm/get-in file-data [:options :components-v2])
{:main-instance? true
:keep-ids? true})
@@ -592,7 +589,8 @@
:name (:name component)
:path (:path component)
:main-instance-id (:id main-instance-shape)
:main-instance-page page-id}))
:main-instance-page page-id
:shapes (get-component-shapes library-data component)}))
; Change all existing instances to point to the local file
remap-instances

View File

@@ -32,7 +32,6 @@
[app.common.types.shape.shadow :as ctss]
[app.common.types.shape.text :as ctsx]
[app.common.types.token :as cto]
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
[clojure.set :as set]))
@@ -234,7 +233,7 @@
[:map {:title "BoolAttrs"}
[:shapes [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]
[:bool-type [::sm/one-of bool-types]]
[:content ::ctsp/content]])
[:bool-content ::ctsp/content]])
(def ^:private schema:rect-attrs
[:map {:title "RectAttrs"}])
@@ -318,9 +317,7 @@
schema:frame-attrs
schema:shape-attrs
schema:shape-geom-attrs
schema:shape-base-attrs
::ctv/variant-shape
::ctv/variant-container]]
schema:shape-base-attrs]]
[:bool
[:merge {:title "BoolShape"}

View File

@@ -6,16 +6,7 @@
(ns app.common.types.shape.path
(:require
[app.common.schema :as sm])
(:import
#?(:cljs [goog.string StringBuffer]
:clj [java.nio ByteBuffer])))
#?(:clj (set! *warn-on-reflection* true))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA: PLAIN FORMAT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[app.common.schema :as sm]))
(def schema:line-to-segment
[:map
@@ -61,371 +52,5 @@
(def schema:path-content
[:vector schema:path-segment])
(def check-path-content
(sm/check-fn schema:path-content))
(sm/register! ::segment schema:path-segment)
(sm/register! ::content schema:path-content)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TYPE: PATH-DATA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:const SEGMENT-BYTE-SIZE 28)
(defprotocol IPathData
(-write-to [_ buffer offset] "write the content to the specified buffer"))
(defrecord PathSegment [command params])
(defn- get-path-string
"Format the path data structure to string"
[buffer size]
(let [builder #?(:clj (java.lang.StringBuilder. (int (* size 4)))
:cljs (StringBuffer.))]
(loop [index 0]
(when (< index size)
(let [offset (* index SEGMENT-BYTE-SIZE)
type #?(:clj (.getShort ^ByteBuffer buffer offset)
:cljs (.getInt16 buffer offset))]
(case (long type)
1 (let [x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
:cljs (.getFloat32 buffer (+ offset 20)))
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
:cljs (.getFloat32 buffer (+ offset 24)))]
(doto builder
(.append "M")
(.append x)
(.append ",")
(.append y)))
2 (let [x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
:cljs (.getFloat32 buffer (+ offset 20)))
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
:cljs (.getFloat32 buffer (+ offset 24)))]
(doto builder
(.append "L")
(.append x)
(.append ",")
(.append y)))
3 (let [c1x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 4))
:cljs (.getFloat32 buffer (+ offset 4)))
c1y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 8))
:cljs (.getFloat32 buffer (+ offset 8)))
c2x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 12))
:cljs (.getFloat32 buffer (+ offset 12)))
c2y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 16))
:cljs (.getFloat32 buffer (+ offset 16)))
x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
:cljs (.getFloat32 buffer (+ offset 20)))
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
:cljs (.getFloat32 buffer (+ offset 24)))]
(doto builder
(.append "C")
(.append c1x)
(.append ",")
(.append c1y)
(.append ",")
(.append c2x)
(.append ",")
(.append c2y)
(.append ",")
(.append x)
(.append ",")
(.append y)))
4 (doto builder
(.append "Z")))
(recur (inc index)))))
(.toString builder)))
(defn- read-segment
[buffer index]
(let [offset (* index SEGMENT-BYTE-SIZE)
type #?(:clj (.getShort ^ByteBuffer buffer offset)
:cljs (.getInt16 buffer offset))]
(case (long type)
1 (let [x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
:cljs (.getFloat32 buffer (+ offset 20)))
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
:cljs (.getFloat32 buffer (+ offset 24)))]
(->PathSegment :move-to {:x x :y y}))
2 (let [x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
:cljs (.getFloat32 buffer (+ offset 20)))
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
:cljs (.getFloat32 buffer (+ offset 24)))]
(->PathSegment :line-to {:x x :y y}))
3 (let [c1x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 4))
:cljs (.getFloat32 buffer (+ offset 4)))
c1y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 8))
:cljs (.getFloat32 buffer (+ offset 8)))
c2x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 12))
:cljs (.getFloat32 buffer (+ offset 12)))
c2y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 16))
:cljs (.getFloat32 buffer (+ offset 16)))
x #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 20))
:cljs (.getFloat32 buffer (+ offset 20)))
y #?(:clj (.getFloat ^ByteBuffer buffer (+ offset 24))
:cljs (.getFloat32 buffer (+ offset 24)))]
(->PathSegment :curve-to {:x x :y y :c1x c1x :c1y c1y :c2x c2x :c2y c2y}))
4 (->PathSegment :close-path {}))))
(defn- in-range?
[size i]
(and (< i size) (>= i 0)))
#?(:clj
(deftype PathData [size buffer]
Object
(toString [_]
(get-path-string buffer size))
clojure.lang.Sequential
clojure.lang.IPersistentCollection
(empty [_]
(throw (ex-info "not implemented" {})))
(equiv [_ other]
(if (instance? PathData other)
(.equals ^ByteBuffer buffer (.-buffer ^PathData other))
false))
(seq [this]
(when (pos? size)
(->> (range size)
(map (fn [i] (nth this i))))))
(cons [_ _val]
(throw (ex-info "not implemented" {})))
clojure.lang.IReduceInit
(reduce [_ f start]
(loop [index 0
result start]
(if (< index size)
(let [result (f result (read-segment buffer index))]
(if (reduced? result)
@result
(recur (inc index) result)))
result)))
clojure.lang.Indexed
(nth [_ i]
(if (in-range? size i)
(read-segment buffer i)
nil))
(nth [_ i default]
(if (in-range? size i)
(read-segment buffer i)
default))
clojure.lang.Counted
(count [_] size))
:cljs
(deftype PathData [size buffer dview]
Object
(toString [_]
(get-path-string dview size))
IPathData
(-write-to [_ into-buffer offset]
(assert (instance? js/ArrayBuffer into-buffer) "expected an instance of Uint32Array")
(let [size (.-byteLength buffer)
mem (js/Uint32Array. into-buffer offset size)]
(.set mem (js/Uint32Array. buffer))))
cljs.core/ISequential
cljs.core/IEquiv
(-equiv [_ other]
(if (instance? PathData other)
(let [obuffer (.-buffer other)
osize (.-byteLength obuffer)
csize (.-byteLength buffer)]
(if (= osize csize)
(let [cb (js/Uint32Array. buffer)
ob (js/Uint32Array. obuffer)]
(loop [i 0]
(if (< i osize)
(if (= (aget ob i)
(aget cb i))
(recur (inc i))
false)
true)))
false))
false))
cljs.core/IReduce
(-reduce [_ f]
(loop [index 1
result (if (pos? size)
(read-segment dview 0)
nil)]
(if (< index size)
(let [result (f result (read-segment dview index))]
(if (reduced? result)
@result
(recur (inc index) result)))
result)))
(-reduce [_ f start]
(loop [index 0
result start]
(if (< index size)
(let [result (f result (read-segment dview index))]
(if (reduced? result)
@result
(recur (inc index) result)))
result)))
cljs.core/IHash
(-hash [_]
(throw (ex-info "not-implemented" {})))
cljs.core/ICounted
(-count [_] size)
cljs.core/IIndexed
(-nth [_ i]
(if (in-range? size i)
(read-segment dview i)
nil))
(-nth [_ i default]
(if (in-range? i size)
(read-segment dview i)
default))
cljs.core/ISeqable
(-seq [this]
(when (pos? size)
(->> (range size)
(map (fn [i] (cljs.core/-nth this i))))))))
(defn- from-bytes
[buffer]
#?(:clj
(cond
(instance? ByteBuffer buffer)
(let [size (.capacity ^ByteBuffer buffer)
count (long (/ size SEGMENT-BYTE-SIZE))]
(PathData. count buffer))
(bytes? buffer)
(let [size (alength ^bytes buffer)
count (long (/ size SEGMENT-BYTE-SIZE))]
(PathData. count
(ByteBuffer/wrap buffer)))
:else
(throw (java.lang.IllegalArgumentException. "invalid data provided")))
:cljs
(cond
(instance? js/ArrayBuffer buffer)
(let [size (.-byteLength buffer)
count (long (/ size SEGMENT-BYTE-SIZE))]
(PathData. count
buffer
(js/DataView. buffer)))
(instance? js/DataView buffer)
(let [dview buffer
buffer (.-buffer dview)
size (.-byteLength buffer)
count (long (/ size SEGMENT-BYTE-SIZE))]
(PathData. count buffer dview))
:else
(throw (js/Error. "invalid data provided")))))
;; FIXME: consider implementing with reduce
;; FIXME: consider ensure fixed precision for avoid doing it on formatting
(defn- from-plain
"Create a PathData instance from plain data structures"
[content]
(assert (check-path-content content))
(let [content (vec content)
total (count content)
#?@(:cljs [buffer (new js/ArrayBuffer (* total SEGMENT-BYTE-SIZE))
dview (new js/DataView buffer)]
:clj [buffer (ByteBuffer/allocate (* total SEGMENT-BYTE-SIZE))])]
(loop [index 0]
(when (< index total)
(let [segment (nth content index)
offset (* index SEGMENT-BYTE-SIZE)]
(case (get segment :command)
:move-to
(let [params (get segment :params)
x (float (get params :x))
y (float (get params :y))]
#?(:clj (.putShort buffer (int offset) (short 1))
:cljs (.setInt16 dview offset 1))
#?(:clj (.putFloat buffer (+ offset 20) x)
:cljs (.setFloat32 dview (+ offset 20) x))
#?(:clj (.putFloat buffer (+ offset 24) y)
:cljs (.setFloat32 dview (+ offset 24) y)))
:line-to
(let [params (get segment :params)
x (float (get params :x))
y (float (get params :y))]
#?(:clj (.putShort buffer (int offset) (short 2))
:cljs (.setInt16 dview offset 2))
#?(:clj (.putFloat buffer (+ offset 20) x)
:cljs (.setFloat32 dview (+ offset 20) x))
#?(:clj (.putFloat buffer (+ offset 24) y)
:cljs (.setFloat32 dview (+ offset 24) y)))
:curve-to
(let [params (get segment :params)
x (float (get params :x))
y (float (get params :y))
c1x (float (get params :c1x x))
c1y (float (get params :c1y y))
c2x (float (get params :c2x x))
c2y (float (get params :c2y y))]
#?(:clj (.putShort buffer (int offset) (short 3))
:cljs (.setInt16 dview offset 3))
#?(:clj (.putFloat buffer (+ offset 4) c1x)
:cljs (.setFloat32 dview (+ offset 4) c1x))
#?(:clj (.putFloat buffer (+ offset 8) c1y)
:cljs (.setFloat32 dview (+ offset 8) c1y))
#?(:clj (.putFloat buffer (+ offset 12) c2x)
:cljs (.setFloat32 dview (+ offset 12) c2x))
#?(:clj (.putFloat buffer (+ offset 16) c2y)
:cljs (.setFloat32 dview (+ offset 16) c2y))
#?(:clj (.putFloat buffer (+ offset 20) x)
:cljs (.setFloat32 dview (+ offset 20) x))
#?(:clj (.putFloat buffer (+ offset 24) y)
:cljs (.setFloat32 dview (+ offset 24) y)))
:close-path
#?(:clj (.putShort buffer (int offset) (short 4))
:cljs (.setInt16 dview offset 4)))
(recur (inc index)))))
#?(:cljs (from-bytes dview)
:clj (from-bytes buffer))))
(defn path-data
"Create an instance of PathData, returns itself if it is already
PathData instance"
[data]
(cond
(instance? PathData data)
data
(sequential? data)
(from-plain data)
:else
(from-bytes data)))

View File

@@ -66,6 +66,16 @@
[n]
(string? n))
;; TODO Move this to tokens-lib
(sm/register!
^{::sm/type ::token}
[:map {:title "Token"}
[:name token-name-ref]
[:type [::sm/one-of token-types]]
[:value :any]
[:description {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::sm/inst]])
(sm/register!
^{::sm/type ::color}
[:map

View File

@@ -0,0 +1,28 @@
;; 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.common.types.token-theme
(:require
[app.common.schema :as sm]))
(sm/register!
^{::sm/type ::token-theme}
[:map {:title "TokenTheme"}
[:name :string]
[:group :string]
[:description [:maybe :string]]
[:is-source :boolean]
[:id :string]
[:modified-at {:optional true} ::sm/inst]
[:sets :any]])
(sm/register!
^{::sm/type ::token-set}
[:map {:title "TokenSet"}
[:name :string]
[:description {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::sm/inst]
[:tokens {:optional true} :any]])

View File

@@ -9,9 +9,7 @@
#?(:clj [app.common.fressian :as fres])
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.time :as dt]
[app.common.transit :as t]
[app.common.types.token :as cto]
@@ -119,15 +117,12 @@
[:name cto/token-name-ref]
[:type [::sm/one-of cto/token-types]]
[:value :any]
[:description {:optional true} :string]
[:modified-at {:optional true} ::sm/inst]])
(declare make-token)
[:description [:maybe :string]]
[:modified-at ::sm/inst]])
(def schema:token
[:and {:gen/gen (->> (sg/generator schema:token-attrs)
(sg/fmap #(make-token %)))}
(sm/required-keys schema:token-attrs)
[:and
schema:token-attrs
[:fn token?]])
(def check-token
@@ -326,7 +321,6 @@
(assoc-in [:ids temp-id] token))))
{:tokens-tree {} :ids {}} tokens))
(defprotocol ITokenSet
(update-name [_ set-name] "change a token set name while keeping the path")
(add-token [_ token] "add a token at the end of the list")
@@ -386,32 +380,15 @@
(def schema:token-set-attrs
[:map {:title "TokenSet"}
[:name :string]
[:description {:optional true} :string]
[:modified-at {:optional true} ::sm/inst]
[:tokens {:optional true
:gen/gen (->> (sg/generator [:map-of ::sm/text schema:token])
(sg/fmap #(into (d/ordered-map) %)))}
[:and
[:map-of {:gen/max 5
:decode/json (fn [v]
(cond
(d/ordered-map? v)
v
(map? v)
(into (d/ordered-map) v)
:else
v))}
:string schema:token]
[:fn d/ordered-map?]]]])
(declare make-token-set)
[:description [:maybe :string]]
[:modified-at ::sm/inst]
[:tokens [:and
[:map-of {:gen/max 5} :string schema:token]
[:fn d/ordered-map?]]]])
(def schema:token-set
[:and {:gen/gen (->> (sg/generator schema:token-set-attrs)
(sg/fmap #(make-token-set %)))}
(sm/required-keys schema:token-set-attrs)
[:and
schema:token-set-attrs
[:fn token-set?]])
(sm/register! ::token-set schema:token-set)
@@ -575,16 +552,16 @@
(def schema:token-theme-attrs
[:map {:title "TokenTheme"}
[:name :string]
[:group {:optional true} :string]
[:description {:optional true} :string]
[:is-source {:optional true} :boolean]
[:id {:optional true} :string]
[:modified-at {:optional true} ::sm/inst]
[:sets {:optional true} [:set {:gen/max 5} :string]]])
[:group :string]
[:description [:maybe :string]]
[:is-source [:maybe :boolean]]
[:id :string]
[:modified-at ::sm/inst]
[:sets [:set {:gen/max 5} :string]]])
(def schema:token-theme
[:and
(sm/required-keys schema:token-theme-attrs)
schema:token-theme-attrs
[:fn token-theme?]])
(sm/register! ::token-theme schema:token-theme)
@@ -826,7 +803,7 @@
(map-indexed (fn [index item]
(assoc item :index index))))))
(defn flatten-nested-tokens-json
(defn- flatten-nested-tokens-json
"Recursively flatten the dtcg token structure, joining keys with '.'."
[tokens token-path]
(reduce-kv
@@ -853,7 +830,7 @@
(declare make-tokens-lib)
(defn legacy-nodes->dtcg-nodes [sets-data]
(defn- legacy-nodes->dtcg-nodes [sets-data]
(walk/postwalk
(fn [node]
(cond-> node
@@ -889,6 +866,8 @@ Will return a value that matches this schema:
(get-active-themes-set-tokens [_] "set of set names that are active in the the active themes")
(encode-dtcg [_] "Encodes library to a dtcg compatible json string")
(decode-dtcg-json [_ parsed-json] "Decodes parsed json containing tokens and converts to library")
(decode-single-set-json [_ set-name tokens] "Decodes parsed json containing single token set and converts to library")
(decode-single-set-legacy-json [_ set-name tokens] "Decodes parsed legacy json containing single token set and converts to library")
(decode-legacy-json [_ parsed-json] "Decodes parsed legacy json containing tokens and converts to library")
(get-all-tokens [_] "all tokens in the lib")
(validate [_]))
@@ -943,7 +922,6 @@ Will return a value that matches this schema:
this)))
(delete-set [_ set-name]
(let [prefixed-path (set-name->prefixed-full-path set-name)]
(TokensLib. (d/dissoc-in sets prefixed-path)
@@ -1333,6 +1311,17 @@ Will return a value that matches this schema:
(assoc-in ["$metadata" "activeThemes"] active-themes-clear)
(assoc-in ["$metadata" "activeSets"] active-sets))))
(decode-single-set-json [this set-name tokens]
(assert (map? tokens) "expected a map data structure for `data`")
(add-set this (make-token-set :name (normalize-set-name set-name)
:tokens (flatten-nested-tokens-json tokens ""))))
(decode-single-set-legacy-json [this set-name tokens]
(assert (map? tokens) "expected a map data structure for `data`")
(decode-single-set-json this set-name (legacy-nodes->dtcg-nodes tokens)))
(decode-dtcg-json [_ data]
(assert (map? data) "expected a map data structure for `data`")
@@ -1492,14 +1481,6 @@ Will return a value that matches this schema:
{:encode/json encode-dtcg
:decode/json decode-dtcg}})
(defn duplicate-set [set-name lib & {:keys [suffix]}]
(let [sets (get-sets lib)
unames (map :name sets)
copy-name (cfh/generate-unique-name set-name unames :suffix suffix)]
(some-> (get-set lib set-name)
(assoc :name copy-name)
(assoc :modified-at (dt/now)))))
(sm/register! type:tokens-lib)
;; === Serialization handlers for RPC API (transit) and database (fressian)

View File

@@ -1,281 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.types.variant
(:require
[app.common.data :as d]
[app.common.files.helpers :as cfh]
[app.common.math :as math]
[app.common.schema :as sm]
[cuerdas.core :as str]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def schema:variant-property
[:map
[:name :string]
[:value :string]])
(def schema:variant-component
;; A component that is part of a variant set.
[:map
[:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector schema:variant-property]]])
(def schema:variant-shape
;; The root shape of the main instance of a variant component.
[:map
[:variant-id {:optional true} ::sm/uuid]
[:variant-name {:optional true} :string]])
(def schema:variant-container
;; is a board that contains all variant components of a variant set,
;; for grouping them visually in the workspace.
[:map
[:is-variant-container {:optional true} :boolean]])
(sm/register! ::variant-property schema:variant-property)
(sm/register! ::variant-component schema:variant-component)
(sm/register! ::variant-shape schema:variant-shape)
(sm/register! ::variant-container schema:variant-container)
(def valid-variant-component?
(sm/check-fn schema:variant-component))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def property-prefix "Property")
(def property-regex (re-pattern (str property-prefix "(\\d+)")))
(def value-prefix "Value ")
(defn properties-to-name
"Transform the properties into a name, with the values separated by comma"
[properties]
(->> properties
(map :value)
(remove str/empty?)
(str/join ", ")))
(defn next-property-number
"Returns the next property number, to avoid duplicates on the property names"
[properties]
(let [numbers (keep
#(some->> (:name %) (re-find property-regex) second d/parse-integer)
properties)
max-num (if (seq numbers)
(apply max numbers)
0)]
(inc (max max-num (count properties)))))
(defn add-new-prop
"Adds a new property with generated name and provided value to the existing props list."
[props value]
(conj props {:name (str property-prefix (next-property-number props))
:value value}))
(defn add-new-props
"Adds new properties with generated names and provided values to the existing props list."
[props values]
(let [next-prop-num (next-property-number props)
xf (map-indexed (fn [i v]
{:name (str property-prefix (+ next-prop-num i))
:value v}))]
(into props xf values)))
(defn path-to-properties
"From a list of properties and a name with path, assign each token of the
path as value of a different property"
([path properties]
(path-to-properties path properties 0))
([path properties min-props]
(let [cpath (cfh/split-path path)
total-props (max (count cpath) min-props)
assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range))
;; Add empty strings to the end of cpath to reach the minimum number of properties
cpath (take total-props (concat cpath (repeat "")))
remaining (drop (count properties) cpath)]
(add-new-props assigned remaining))))
(defn properties-map-to-string
"Transforms a map of properties to a string of properties omitting the empty ones"
[properties]
(->> properties
(keep (fn [{:keys [name value]}]
(when (not (str/blank? value))
(str name "=" value))))
(str/join ", ")))
(defn properties-string-to-map
"Transforms a string of properties to a map of properties"
[s]
(->> (str/split s ",")
(mapv #(str/split % "="))
(mapv (fn [[k v]]
{:name (str/trim k)
:value (str/trim v)}))))
(defn valid-properties-string?
"Checks if a string of properties has a processable format or not"
[s]
(let [pattern #"^([a-zA-Z0-9\s]+=[a-zA-Z0-9\s]+)(,\s*[a-zA-Z0-9\s]+=[a-zA-Z0-9\s]+)*$"]
(not (nil? (re-matches pattern s)))))
(defn find-properties-to-remove
"Compares two property maps to find which properties should be removed"
[prev-props upd-props]
(let [upd-names (set (map :name upd-props))]
(filterv #(not (contains? upd-names (:name %))) prev-props)))
(defn find-properties-to-update
"Compares two property maps to find which properties should be updated"
[prev-props upd-props]
(filterv #(some (fn [prop] (and (= (:name %) (:name prop))
(not= (:value %) (:value prop)))) prev-props) upd-props))
(defn find-properties-to-add
"Compares two property maps to find which properties should be added"
[prev-props upd-props]
(let [prev-names (set (map :name prev-props))]
(filterv #(not (contains? prev-names (:name %))) upd-props)))
(defn find-index-for-property-name
"Finds the index of a name in a property map"
[props name]
(some (fn [[idx prop]]
(when (= (:name prop) name)
idx))
(map-indexed vector props)))
(defn remove-prefix
"Removes the given prefix (with or without a trailing ' / ') from the beginning of the name"
[name prefix]
(let [long-name (str prefix " / ")]
(cond
(str/starts-with? name long-name)
(subs name (count long-name))
(str/starts-with? name prefix)
(subs name (count prefix))
:else
name)))
(def ^:private xf:map-name
(map :name))
(defn- matching-indices
[props1 props2]
(let [names-in-p2 (into #{} xf:map-name props2)
xform (comp
(map-indexed (fn [index {:keys [name]}]
(when (contains? names-in-p2 name)
index)))
(filter some?))]
(into #{} xform props1)))
(defn- find-index-by-name
"Returns the index of the first item in props with the given name, or nil if not found."
[name props]
(some (fn [[idx item]]
(when (= (:name item) name)
idx))
(map-indexed vector props)))
(defn- next-valid-position
"Returns the first non-negative integer not present in the used-pos set."
[used-pos]
(loop [p 0]
(if (contains? used-pos p)
(recur (inc p))
p)))
(defn- find-position
"Returns the index of the property with the given name in `props`,
or the next available index not in `used-pos` if not found."
[name props used-pos]
(or (find-index-by-name name props)
(next-valid-position used-pos)))
(defn merge-properties
"Merges props2 into props1 with the following rules:
- For each property p2 in props2:
- Skip it if its value is empty.
- If props1 contains a property with the same name, update its value with that of p2.
- Otherwise, assign p2's value to the first unused property in props1. A property is considered used if:
- Its name exists in both props1 and props2, or
- Its value has already been updated during the merge.
- If no unused properties are available in props1, append a new property with a default name and p2's value."
[props1 props2]
(let [props2 (remove #(str/empty? (:value %)) props2)]
(-> (reduce
(fn [{:keys [props used-pos]} prop]
(let [pos (find-position (:name prop) props used-pos)
used-pos (conj used-pos pos)]
(if (< pos (count props))
{:props (assoc-in (vec props) [pos :value] (:value prop)) :used-pos used-pos}
{:props (add-new-prop props (:value prop)) :used-pos used-pos})))
{:props (vec props1) :used-pos (matching-indices props1 props2)}
props2)
:props)))
(defn compare-properties
"Compares vectors of properties keeping the value if it is the same for all
or setting a custom value where their values do not coincide"
([props-list]
(compare-properties props-list nil))
([props-list distinct-mark]
(let [grouped (group-by :name (apply concat props-list))
check-values (fn [values]
(let [vals (map :value values)]
(if (apply = vals)
(first vals)
distinct-mark)))]
(mapv (fn [[name values]]
{:name name :value (check-values values)})
grouped))))
(defn same-variant?
"Determines if all elements belong to the same variant"
[components]
(let [variant-ids (distinct (map :variant-id components))
not-blank? (complement str/blank?)]
(and
(= 1 (count variant-ids))
(not-blank? (first variant-ids)))))
(defn distance
"Computes a weighted distance between two property lists `props1` and `props2`.
Latter properties weight less that previous ones"
[props1 props2]
(let [total-num-props (count props1)
xform (map-indexed
(fn [idx [p1 p2]]
(if (not= p1 p2)
(math/pow 2 (- total-num-props idx))
0)))]
(transduce
xform
+
(map vector props1 props2))))
(defn variant-name-to-name
"Transforms a variant-name (its properties values) into a standard name:
the real name of the shape joined by the properties values separated by '/'"
[variant]
(cfh/merge-path-item (:name variant) (str/replace (:variant-name variant) #", " " / ")))

View File

@@ -40,6 +40,8 @@
(:objects page)
(:id page)
(:id file)
true
nil
nil)
file' (thf/apply-changes file changes)
@@ -72,6 +74,8 @@
(:objects page)
(:id page)
(:id file)
true
nil
cfsh/prepare-create-artboard-from-selection)
file' (thf/apply-changes file changes)
@@ -107,6 +111,8 @@
(:objects page)
(:id page)
(:id file)
true
nil
cfsh/prepare-create-artboard-from-selection)
file' (thf/apply-changes file changes)
@@ -145,6 +151,8 @@
(:objects page)
(:id page)
(:id file)
true
nil
cfsh/prepare-create-artboard-from-selection)
file' (thf/apply-changes file changes)
@@ -183,6 +191,8 @@
(:objects page)
(:id page)
(:id file)
true
nil
nil)
file' (thf/apply-changes file changes)
@@ -222,6 +232,8 @@
(:objects page)
(:id page)
(:id file)
true
nil
cfsh/prepare-create-artboard-from-selection)
file' (thf/apply-changes file changes)
@@ -253,7 +265,8 @@
changes (cll/generate-rename-component (pcb/empty-changes)
(:id component)
"Test component after"
(:data file))
(:data file)
true)
file' (thf/apply-changes file changes)
@@ -432,8 +445,8 @@
(t/is (some? copy1-child'))
(t/is (ctk/instance-root? copy1-root'))
(t/is (ctk/instance-of? copy1-root' (:id file') (:id component')))
(t/is (ctk/is-main-of? main1-root' copy1-root'))
(t/is (ctk/is-main-of? main1-child' copy1-child'))
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
(t/deftest test-instantiate-component-from-lib
@@ -476,8 +489,8 @@
(t/is (some? copy1-child'))
(t/is (ctk/instance-root? copy1-root'))
(t/is (ctk/instance-of? copy1-root' (:id library) (:id component')))
(t/is (ctk/is-main-of? main1-root' copy1-root'))
(t/is (ctk/is-main-of? main1-child' copy1-child'))
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
(t/deftest test-instantiate-nested-component
@@ -520,8 +533,8 @@
(t/is (some? copy1-child'))
(t/is (ctk/instance-root? copy1-root'))
(t/is (ctk/instance-of? copy1-root' (:id file') (:id component')))
(t/is (ctk/is-main-of? main1-root' copy1-root'))
(t/is (ctk/is-main-of? main1-child' copy1-child'))
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
(t/deftest test-instantiate-nested-component-from-lib
@@ -567,8 +580,8 @@
(t/is (some? copy1-child'))
(t/is (ctk/instance-root? copy1-root'))
(t/is (ctk/instance-of? copy1-root' (:id library) (:id component')))
(t/is (ctk/is-main-of? main1-root' copy1-root'))
(t/is (ctk/is-main-of? main1-child' copy1-child'))
(t/is (ctk/is-main-of? main1-root' copy1-root' true))
(t/is (ctk/is-main-of? main1-child' copy1-child' true))
(t/is (ctst/parent-of? copy1-root' copy1-child'))))
(t/deftest test-detach-copy

View File

@@ -47,7 +47,8 @@
file-mdf
{(:id file-mdf) file-mdf}
page-mdf
(:id copy-root))
(:id copy-root)
true)
file' (thf/apply-changes file changes)
@@ -98,7 +99,8 @@
{(:id file-mdf) file-mdf
(:id library) library}
page-mdf
(:id copy-root))
(:id copy-root)
true)
file' (thf/apply-changes file changes)
@@ -149,7 +151,8 @@
file-mdf
{(:id file-mdf) file-mdf}
page-mdf
(:id copy-root))
(:id copy-root)
true)
file' (thf/apply-changes file changes)
@@ -195,7 +198,8 @@
file-mdf
{(:id file-mdf) file-mdf}
page-mdf
(:id copy-root))
(:id copy-root)
true)
file' (thf/apply-changes file changes)
@@ -242,7 +246,8 @@
file-mdf
{(:id file-mdf) file-mdf}
page-mdf
(:id copy-root))
(:id copy-root)
true)
file' (thf/apply-changes file changes)
@@ -286,7 +291,8 @@
file-mdf
{(:id file-mdf) file-mdf}
page-mdf
(:id copy2-root))
(:id copy2-root)
true)
file' (thf/apply-changes file changes)
@@ -332,7 +338,8 @@
file-mdf
{(:id file-mdf) file-mdf}
page-mdf
(:id copy2-root))
(:id copy2-root)
true)
file' (thf/apply-changes file changes)

View File

@@ -227,7 +227,7 @@
(t/is (= (:touched copy-root') nil))
(t/is (= (:touched copy-new-child') nil))
(t/is (ctst/parent-of? copy-root' copy-new-child'))
(t/is (ctk/is-main-of? main-free-shape' copy-new-child'))))
(t/is (ctk/is-main-of? main-free-shape' copy-new-child' true))))
(t/deftest test-sync-when-deleting-shape
(let [;; ==== Setup

View File

@@ -1,9 +1,3 @@
;; 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.logic.token-test
(:require
[app.common.files.changes-builder :as pcb]

View File

@@ -7,10 +7,7 @@
(ns common-tests.logic.variants-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.geom.point :as gpt]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.logic.variant-properties :as clvp]
[app.common.logic.variants :as clv]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
@@ -23,7 +20,7 @@
(t/deftest test-update-property-name
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
@@ -32,8 +29,8 @@
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-name v-id 0 "NewName1")
(clvp/generate-update-property-name v-id 1 "NewName2"))
(clv/generate-update-property-name v-id 0 "NewName1")
(clv/generate-update-property-name v-id 1 "NewName2"))
file' (thf/apply-changes file changes)
@@ -68,7 +65,7 @@
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id))
(clv/generate-add-new-property v-id))
file' (thf/apply-changes file changes)
@@ -104,7 +101,7 @@
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id {:fill-values? true}))
(clv/generate-add-new-property v-id {:fill-values? true}))
file' (thf/apply-changes file changes)
@@ -120,7 +117,7 @@
(t/is (= (count (:variant-properties comp01')) 2))
(t/is (= (count (:variant-properties comp02)) 1))
(t/is (= (count (:variant-properties comp02')) 2))
(t/is (= (-> comp01' :variant-properties last :value) "Value 1"))))
(t/is (= (-> comp01' :variant-properties last :value) "Value1"))))
@@ -135,7 +132,7 @@
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id))
(clv/generate-add-new-property v-id))
file (thf/apply-changes file changes)
@@ -150,7 +147,7 @@
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-remove-property v-id 0))
(clv/generate-remove-property v-id 0))
file' (thf/apply-changes file changes)
@@ -183,8 +180,8 @@
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-value (:id comp01) 0 "NewValue1")
(clvp/generate-update-property-value (:id comp02) 0 "NewValue2"))
(clv/generate-update-property-value (:id comp01) 0 "NewValue1")
(clv/generate-update-property-value (:id comp02) 0 "NewValue2"))
file' (thf/apply-changes file changes)
@@ -195,73 +192,3 @@
;; ==== Check
(t/is (= (-> comp01' :variant-properties first :value) "NewValue1"))
(t/is (= (-> comp02' :variant-properties first :value) "NewValue2"))))
(t/deftest test-duplicate-variant-container
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
data (:data file)
page (thf/current-page file)
objects (:objects page)
variant-container (ths/get-shape file :v01)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(cll/generate-duplicate-changes objects ;; objects
page ;; page
#{(:id variant-container)} ;; ids
(gpt/point 0 0) ;; delta
{(:id file) file} ;; libraries
(:data file) ;; library-data
(:id file))) ;; file-id
;; ==== Get
file' (thf/apply-changes file changes)
data' (:data file')
page' (thf/current-page file')
objects' (:objects page')]
;; ==== Check
(thf/validate-file! file')
(t/is (= (count (:components data)) 2))
(t/is (= (count (:components data')) 4))
(t/is (= (count objects) 4))
(t/is (= (count objects') 7))))
(t/deftest test-delete-variant
;; When a variant container becomes empty, it id automatically deleted
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
container (ths/get-shape file :v01)
m01-id (-> (ths/get-shape file :m01) :id)
m02-id (-> (ths/get-shape file :m02) :id)
page (thf/current-page file)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(#(second (cls/generate-delete-shapes % #{m01-id m02-id} {}))))
file' (thf/apply-changes file changes)
;; ==== Get
container' (ths/get-shape file' :v01)]
;; ==== Check
;; The variant containew was not nil before the deletion
(t/is (not (nil? container)))
;; The variant containew is nil after the deletion
(t/is (nil? container'))))

View File

@@ -41,7 +41,6 @@
[common-tests.types.modifiers-test]
[common-tests.types.shape-decode-encode-test]
[common-tests.types.shape-interactions-test]
[common-tests.types.shape-path-data-test]
[common-tests.types.tokens-lib-test]
[common-tests.uuid-test]))
@@ -91,5 +90,4 @@
'common-tests.types.tokens-lib-test
'common-tests.types.components-test
'common-tests.types.absorb-assets-test
'common-tests.types.shape-path-data-test
'common-tests.uuid-test))

View File

@@ -547,3 +547,4 @@
;; FOR POSSIBLE FUTURE TEST CASES
;; (str "M259.958 89.134c-6.88-.354-10.484-1.241-12.44-3.064-1.871-1.743-6.937-3.098-15.793-4.226-7.171-.913-17.179-2.279-22.24-3.034-5.06-.755-15.252-2.016-22.648-2.8-18.685-1.985-35.63-4.223-38.572-5.096-3.655-1.084-3.016-3.548.708-2.726 1.751.387 13.376 1.701 25.833 2.922 12.456 1.22 29.018 3.114 36.803 4.208 29.94 4.206 29.433 4.204 34.267.136 3.787-3.186 5.669-3.669 14.303-3.669 14.338 0 17.18 1.681 12.182 7.205-2.053 2.268-1.994 2.719.707 5.42 3.828 3.827 3.74 5.846-.238 5.5-1.752-.153-7.544-.502-12.872-.776zm7.563-3.194c0-.778-1.751-1.352-3.892-1.274l-3.893.141 3.539 1.133c1.946.624 3.698 1.197 3.893 1.275.194.077.354-.496.354-1.275zm-15.899-8.493c1.43-2.29 1.414-2.83-.084-2.83-2.05 0-5.25 2.76-5.25 4.529 0 2.226 3.599 1.08 5.334-1.699zm8.114 0c2.486-2.746 2.473-2.83-.438-2.83-1.65 0-3.683 1.273-4.516 2.83-1.175 2.196-1.077 2.831.438 2.831 1.075 0 3.107-1.274 4.516-2.83zm7.814.674c2.858-3.444.476-4.085-3.033-.816-2.451 2.284-2.677 2.973-.975 2.973 1.22 0 3.023-.97 4.008-2.157zm-49.571-4.509c-1.168-.43-3.294-1.802-4.725-3.051-2.112-1.843-9.304-2.595-38.219-3.994-46.474-2.25-63-4.077-60.27-6.665.324-.308 9.507.261 20.406 1.264 10.9 1.003 31.16 2.258 45.024 2.789l25.207.964 4.625-3.527c4.313-3.29 5.41-3.474 16.24-2.732 6.389.438 11.981 1.388 12.428 2.111.447.723-.517 2.73-2.141 4.46l-2.954 3.144c1.607 1.697 3.308 3.289 5.049 4.845 3.248 2.189-5.438 1.289-8.678 1.284-5.428-.061-10.825-.463-11.992-.892zm12.74-3.242c-1.123-.694-2.36-.943-2.75-.554-.389.39.21 1.275 1.334 1.97 1.122.693 2.36.942 2.749.553.389-.39-.21-1.275-1.334-1.97zm-5.663 0a1.42 1.42 0 00-1.415-1.416 1.42 1.42 0 00-1.416 1.416 1.42 1.42 0 001.416 1.415 1.42 1.42 0 001.415-1.415zm-8.464-6.404c.984-1.187 1.35-2.598.813-3.135-1.181-1.18-5.408 1.297-6.184 3.624-.806 2.42 3.265 2.048 5.37-.49zm6.863.258c.867-1.045 1.163-2.313.658-2.819-1.063-1.062-4.719 1.631-4.719 3.476 0 1.864 2.274 1.496 4.061-.657zm8.792-.36c1.637-1.972 1.448-2.197-1.486-1.77-1.848.27-3.622 1.287-3.943 2.26-.838 2.547 3.212 2.181 5.429-.49zm32.443-4.11c-6.156-2.228-67.1-6.138-119.124-7.642-39.208-1.134-72.072-.928-94.618.593-6.617.446-19.681 1.16-29.03 1.587-15.798.72-17.183.573-19.588-2.085-4.498-4.97-2.544-7.857 6.39-9.44 4.394-.778 9.164-2.436 10.6-3.685 5.44-4.729 20.332-14.06 31.14-19.509C65.717 11.88 78.955 7.79 103.837 3.08 121.686-.3 125.552-.642 129.318.82c2.44.948 12.4 1.948 22.132 2.221 15.37.432 20.004 1.18 35.294 5.698 22.36 6.606 39.732 15.1 56.55 27.653 7.307 5.452 14.086 9.913 15.066 9.913.98 0 2.148.956 2.596 2.124.55 1.432 2.798 2.123 6.914 2.123 6.213 0 12.4 3.046 12.38 6.096-.012 1.75-6.502 5.353-9.118 5.063-.818-.09-3.717-.972-6.442-1.958zm-16.986-7.436c0-1.575-33.326-18.118-43.173-21.43-23.008-7.739-54.084-12.922-77.136-12.866-16.863.041-37.877 3.628-52.465 8.956-18.062 6.596-26.563 10.384-29.181 13.002-1.205 1.205-5.306 3.769-9.112 5.698-7.754 3.929-8.841 5.482-3.029 4.325 13.494-2.685 66.794-3.773 110.913-2.264 38.005 1.3 96.812 4.435 102.122 5.443.584.111 1.061-.277 1.061-.864zm-236.39-3.18c0-.78-1.592-1.416-3.539-1.416-1.946 0-3.538.637-3.538 1.415 0 .779 1.592 1.416 3.538 1.416 1.947 0 3.54-.637 3.54-1.416zm7.078-1.416c0-.779-.956-1.416-2.124-1.416-1.167 0-2.123.637-2.123 1.416 0 .778.956 1.415 2.123 1.415 1.168 0 2.124-.637 2.124-1.415zm11.734-4.437c3.278-1.661 6.278-3.483 6.667-4.048 1.366-1.98 20.645-11.231 32.557-15.622 11.862-4.372 36.546-9.865 44.327-9.865 3.485 0 3.867-.404 3.012-3.185-.538-1.752-1.177-3.41-1.42-3.685-.907-1.026-36.72 7.16-45.065 10.302-17.226 6.484-47.566 24.27-47.566 27.886 0 1.786.845 1.585 7.488-1.783zm206.254-5.577c-12.298-10.518-53.842-27.166-70.896-28.41-5.526-.404-6.3-.097-6.695 2.655-.33 2.307.402 3.275 2.831 3.742 32.436 6.237 52.205 12.315 66.975 20.594 11.904 6.673 14.477 7.141 7.785 1.419zM150.1 11.04c-1.949-3.64-7.568-4.078-6.886-.538.256 1.329 2.054 2.817 3.997 3.309 4.498 1.137 4.816.832 2.888-2.771zm6.756.94c-.248-1.752-1.026-3.185-1.727-3.185-.7 0-1.493 1.433-1.76 3.185-.328 2.152.232 3.185 1.727 3.185 1.485 0 2.064-1.047 1.76-3.185zm-30.178-2.458c0-2.303-.908-3.694-2.627-4.025-3.6-.694-5.23 1.301-4.22 5.166 1.216 4.647 6.847 3.709 6.847-1.14zm12.544 2.104c-.448-1.168-1.224-2.132-1.725-2.142-.5-.013-2.343-.404-4.095-.873-2.569-.689-3.185-.274-3.185 2.142 0 2.476.854 2.996 4.91 2.996 3.783 0 4.723-.487 4.095-2.123z")

View File

@@ -54,7 +54,7 @@
(t/is (= (count components') 1))
(t/is (ctk/instance-of? copy-root' (:id file') (:id component')))
(t/is (ctk/is-main-of? main-root' copy-root'))
(t/is (ctk/is-main-of? main-root' copy-root' true))
(t/is (ctk/main-instance-of? (:id main-root') (:id (second pages')) component'))))
(t/deftest absorb-colors

View File

@@ -0,0 +1,6 @@
{"color":
{"red":
{"100":
{"value":"red",
"type":"color",
"description":""}}}}

View File

@@ -0,0 +1,6 @@
{"color":
{"red":
{"100":
{"$value":"red",
"$type":"color",
"$description":""}}}}

View File

@@ -1,59 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.types.shape-path-data-test
(:require
[app.common.data :as d]
[app.common.math :as mth]
[app.common.pprint :as pp]
[app.common.types.shape.path :as path]
[clojure.test :as t]))
(def sample-content
[{:command :move-to, :params {:x 480.0, :y 839.0}}
{:command :line-to, :params {:x 439.0, :y 802.0}}
{:command :curve-to, :params {:c1x 368.0, :c1y 737.0, :c2x 310.0, :c2y 681.0, :x 264.0, :y 634.0}}
{:command :close-path :params {}}])
(def sample-bytes
[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 67 -16 0 0 68 81 -64 0
0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 67 -37 -128 0 68 72 -128 0
0 3 0 0 67 -72 0 0 68 56 64 0 67 -101 0 0 68 42 64 0 67 -124 0 0 68 30 -128 0
0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0])
;; This means it implements IReduceInit/IReduce protocols
(t/deftest path-data-to-vector
(let [pdata (path/path-data sample-content)
result (vec pdata)]
(t/is (= 4 (count result)))
(t/is (= (get-in sample-content [0 :command])
(get-in result [0 :command])))
(t/is (= (get-in sample-content [1 :command])
(get-in result [1 :command])))
(t/is (= (get-in sample-content [2 :command])
(get-in result [2 :command])))
(t/is (= (get-in sample-content [3 :command])
(get-in result [3 :command])))
(t/is (= (get-in sample-content [0 :params])
(get-in result [0 :params])))
(t/is (= (get-in sample-content [1 :params])
(get-in result [1 :params])))
(t/is (= (get-in sample-content [2 :params])
(get-in result [2 :params])))
(t/is (= (get-in sample-content [3 :params])
(get-in result [3 :params])))))
(t/deftest path-data-plain-to-binary
(let [pdata (path/path-data sample-content)]
(t/is (= sample-bytes
(vec
#?(:cljs (js/Int8Array. (.-buffer pdata))
:clj (.array (.-buffer pdata))))))
(t/is (= (->> sample-content
(mapv path/map->PathSegment))
(vec pdata)))))

View File

@@ -120,6 +120,7 @@
(t/is (= ["Foo/Foo" "Foo/Baz" "Foo/Bar"] (move ["Foo"] ["Foo" "Foo"] ["Foo" "Baz"] false)))
(t/is (= ["Foo/Baz" "Foo/Bar" "Foo/Foo"] (move ["Foo"] ["Foo" "Foo"] nil false))))))
(t/deftest move-token-set-nested-2
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "a/b"))
@@ -219,6 +220,7 @@
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid params for token-theme"
(ctob/make-token-theme params)))))
(t/deftest make-tokens-lib
(let [tokens-lib (ctob/make-tokens-lib)]
(t/is (= (ctob/set-count tokens-lib) 0))))
@@ -313,58 +315,6 @@
(t/is (= (:sets token-theme') #{}))
(t/is (nil? token-set'))))
(t/deftest duplicate-token-set
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"
:tokens {"test-token"
(ctob/make-token :name "test-token"
:type :boolean
:value true)})))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
token (get-in token-set-copy [:tokens "test-token"])]
(t/is (some? token-set-copy))
(t/is (= (:name token-set-copy) "test-token-set-copy"))
(t/is (= (count (:tokens token-set-copy)) 1))
(t/is (= (:name token) "test-token"))))
(t/deftest duplicate-token-set-twice
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"
:tokens {"test-token"
(ctob/make-token :name "test-token"
:type :boolean
:value true)})))
tokens-lib (ctob/add-set tokens-lib (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"}))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
token (get-in token-set-copy [:tokens "test-token"])]
(t/is (some? token-set-copy))
(t/is (= (:name token-set-copy) "test-token-set-copy-2"))
(t/is (= (count (:tokens token-set-copy)) 1))
(t/is (= (:name token) "test-token"))))
(t/deftest duplicate-empty-token-set
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
tokens (get token-set-copy :tokens)]
(t/is (some? token-set-copy))
(t/is (= (:name token-set-copy) "test-token-set-copy"))
(t/is (= (count (:tokens token-set-copy)) 0))
(t/is (= (count tokens) 0))))
(t/deftest duplicate-not-existing-token-set
(let [tokens-lib (ctob/make-tokens-lib)
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})]
(t/is (nil? token-set-copy))))
(t/deftest active-themes-set-names
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
@@ -968,6 +918,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 update-token-in-sets-rename
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
@@ -1420,6 +1371,30 @@
(t/testing "invalid tokens got discarded"
(t/is (nil? (get-set-token "typography" "H1.Bold")))))))
#?(:clj
(t/deftest single-set-legacy-json-decoding
(let [json (-> (slurp "test/common_tests/types/data/legacy-single-set.json")
(tr/decode-str))
lib (ctob/decode-single-set-legacy-json (ctob/ensure-tokens-lib nil) "single_set" json)
get-set-token (fn [set-name token-name]
(some-> (ctob/get-set lib set-name)
(ctob/get-token token-name)))]
(t/is (= '("single_set") (ctob/get-ordered-set-names lib)))
(t/testing "token added"
(t/is (some? (get-set-token "single_set" "color.red.100")))))))
#?(:clj
(t/deftest single-set-dtcg-json-decoding
(let [json (-> (slurp "test/common_tests/types/data/single-set.json")
(tr/decode-str))
lib (ctob/decode-single-set-json (ctob/ensure-tokens-lib nil) "single_set" json)
get-set-token (fn [set-name token-name]
(some-> (ctob/get-set lib set-name)
(ctob/get-token token-name)))]
(t/is (= '("single_set") (ctob/get-ordered-set-names lib)))
(t/testing "token added"
(t/is (some? (get-set-token "single_set" "color.red.100")))))))
#?(:clj
(t/deftest dtcg-encoding-decoding-json
(let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-example.json")

View File

@@ -1,112 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.types.variant-test
(:require
[app.common.types.variant :as ctv]
[clojure.test :as t]))
(t/deftest variant-distance01
;;c1: primary, default, rounded, blue, dark
;;c2: primary, hover, squared, blue, dark
;;c3: primary, default, squared, blue, light
;; I have a copy of c1, and I change from rounded to squared
;; c2: 1 difference in pos 2
;; c3: 1 differences in pos 5
;; The min distance should be c3
(let [target [{:name "type" :value "primary"}
{:name "status" :value "default"}
{:name "borders" :value "squared"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
props2 [{:name "type" :value "primary"}
{:name "status" :value "hover"}
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
props3 [{:name "type" :value "primary"}
{:name "status" :value "default"}
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "light"}]
dist2 (ctv/distance target props2)
dist3 (ctv/distance target props3)]
(t/is (< dist3 dist2))))
(t/deftest variant-distance02
;;c1: primary, default, rounded, blue, dark
;;c2: primary, hover, squared, red, dark
;;c3: secondary, hover, rounded, blue, dark
;; I have a copy of c1, and I change from default to hover
;; c2: 2 differences in pos 3 and 4
;; c3: 1 differences in pos 1
;; The min distance should be c2
(let [target [{:name "type" :value "primary"}
{:name "status" :value "hover"}
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
props2 [{:name "type" :value "primary"}
{:name "status" :value "hover"}
{:name "borders" :value "squared"}
{:name "color" :value "red"}
{:name "theme" :value "dark"}]
props3 [{:name "type" :value "secondary"}
{:name "status" :value "hover"}
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
dist2 (ctv/distance target props2)
dist3 (ctv/distance target props3)]
(t/is (< dist2 dist3))))
(t/deftest variant-distance03
;;c1: primary, default, rounded, blue, dark
;;c2: secondary, default, rounded, blue, light
;;c3: secondary, hover, squared, blue, dark
;;c4: secondary, hover, rounded, blue, dark
;; I have a copy of c1, and I change from primary to secondary
;; c2: 1 difference in pos 4
;; c3: 2 differences in pos 1 and 2
;; c4: 1 difference in pos 1
;; The distances should be c2 < c4 < c3
(let [target [{:name "type" :value "secondary"}
{:name "status" :value "default"}
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
props2 [{:name "type" :value "secondary"}
{:name "status" :value "default"}
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "light"}]
props3 [{:name "type" :value "secondary"}
{:name "status" :value "hover"}
{:name "borders" :value "squared"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
props4 [{:name "type" :value "secondary"}
{:name "status" :value "hover"}
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
dist2 (ctv/distance target props2)
dist3 (ctv/distance target props3)
dist4 (ctv/distance target props4)]
(t/is (< dist2 dist4))
(t/is (< dist4 dist3))))

View File

@@ -1,108 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.variant-test
(:require
[app.common.types.variant :as ctv]
[clojure.test :as t]))
(t/deftest convert-between-variant-properties-maps-and-strings
(let [map-with-two-props [{:name "border" :value "yes"} {:name "color" :value "gray"}]
map-with-two-props-one-blank [{:name "border" :value "no"} {:name "color" :value ""}]
map-with-one-prop [{:name "border" :value "no"}]
map-with-spaces [{:name "border 1" :value "of course"} {:name "color 2" :value "dark gray"}]
string-valid-with-two-props "border=yes, color=gray"
string-valid-with-one-prop "border=no"
string-valid-with-spaces "border 1=of course, color 2=dark gray"
string-invalid "border=yes, color="]
(t/testing "convert map to string"
(t/is (= (ctv/properties-map-to-string map-with-two-props) string-valid-with-two-props))
(t/is (= (ctv/properties-map-to-string map-with-two-props-one-blank) string-valid-with-one-prop))
(t/is (= (ctv/properties-map-to-string map-with-spaces) string-valid-with-spaces)))
(t/testing "convert string to map"
(t/is (= (ctv/properties-string-to-map string-valid-with-two-props) map-with-two-props))
(t/is (= (ctv/properties-string-to-map string-valid-with-one-prop) map-with-one-prop))
(t/is (= (ctv/properties-string-to-map string-valid-with-spaces) map-with-spaces)))
(t/testing "check if a string is valid"
(t/is (= (ctv/valid-properties-string? string-valid-with-two-props) true))
(t/is (= (ctv/valid-properties-string? string-valid-with-one-prop) true))
(t/is (= (ctv/valid-properties-string? string-valid-with-spaces) true))
(t/is (= (ctv/valid-properties-string? string-invalid) false)))))
(t/deftest find-properties
(let [prev-props [{:name "border" :value "yes"} {:name "color" :value "gray"}]
upd-props-1 [{:name "border" :value "yes"}]
upd-props-2 [{:name "border" :value "yes"} {:name "color" :value "blue"}]
upd-props-3 [{:name "border" :value "yes"} {:name "color" :value "gray"} {:name "shadow" :value "large"}]
upd-props-4 [{:name "color" :value "yellow"} {:name "shadow" :value "large"}]]
(t/testing "a property to remove"
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-1)
[{:name "color" :value "gray"}]))
(t/is (= (ctv/find-properties-to-update prev-props upd-props-1)
[]))
(t/is (= (ctv/find-properties-to-add prev-props upd-props-1)
[])))
(t/testing "a property to update"
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-2)
[]))
(t/is (= (ctv/find-properties-to-update prev-props upd-props-2)
[{:name "color" :value "blue"}]))
(t/is (= (ctv/find-properties-to-add prev-props upd-props-2)
[])))
(t/testing "a property to add"
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-3)
[]))
(t/is (= (ctv/find-properties-to-update prev-props upd-props-3)
[]))
(t/is (= (ctv/find-properties-to-add prev-props upd-props-3)
[{:name "shadow" :value "large"}])))
(t/testing "properties to remove, update & add"
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-4)
[{:name "border" :value "yes"}]))
(t/is (= (ctv/find-properties-to-update prev-props upd-props-4)
[{:name "color" :value "yellow"}]))
(t/is (= (ctv/find-properties-to-add prev-props upd-props-4)
[{:name "shadow" :value "large"}])))
(t/testing "find property index"
(t/is (= (ctv/find-index-for-property-name prev-props "border") 0))
(t/is (= (ctv/find-index-for-property-name prev-props "color") 1)))))
(t/deftest compare-properties
(let [props-1 [{:name "border" :value "yes"} {:name "color" :value "gray"}]
props-2 [{:name "border" :value "yes"} {:name "color" :value "red"}]
props-3 [{:name "border" :value "no"} {:name "color" :value "gray"}]]
(t/testing "compare properties"
(t/is (= (ctv/compare-properties [props-1 props-2])
[{:name "border" :value "yes"} {:name "color" :value nil}]))
(t/is (= (ctv/compare-properties [props-1 props-2 props-3])
[{:name "border" :value nil} {:name "color" :value nil}]))
(t/is (= (ctv/compare-properties [props-1 props-2 props-3] "&")
[{:name "border" :value "&"} {:name "color" :value "&"}])))))
(t/deftest check-belong-same-variant
(let [components-1 [{:variant-id "a variant"} {:variant-id "a variant"}]
components-2 [{:variant-id "a variant"} {:variant-id "another variant"}]
components-3 [{:variant-id "a variant"} {}]
components-4 [{} {}]]
(t/testing "check-belong-same-variant"
(t/is (= (ctv/same-variant? components-1) true))
(t/is (= (ctv/same-variant? components-2) false))
(t/is (= (ctv/same-variant? components-3) false))
(t/is (= (ctv/same-variant? components-4) false)))))

View File

@@ -1,16 +1,15 @@
FROM ubuntu:24.04
FROM debian:bookworm
LABEL maintainer="Penpot <docker@penpot.app>"
ARG DEBIAN_FRONTEND=noninteractive
ENV NODE_VERSION=v22.14.0 \
ENV NODE_VERSION=v22.13.1 \
CLOJURE_VERSION=1.12.0.1501 \
CLJKONDO_VERSION=2025.01.16 \
BABASHKA_VERSION=1.12.196 \
CLJFMT_VERSION=0.13.0 \
RUSTUP_VERSION=1.27.1 \
RUST_VERSION=1.85.0 \
EMSCRIPTEN_VERSION=4.0.6 \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8
@@ -46,7 +45,7 @@ RUN set -ex; \
rm -rf /var/lib/apt/lists/*;
RUN set -ex; \
usermod -l penpot -d /home/penpot -G users -s /bin/bash ubuntu; \
useradd -m -g users -s /bin/bash penpot; \
passwd penpot -d; \
echo "penpot ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
@@ -64,6 +63,8 @@ RUN set -ex; \
woff-tools \
woff2 \
fontforge \
gconf-service \
libasound2 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcairo2 \
@@ -72,6 +73,7 @@ RUN set -ex; \
libexpat1 \
libfontconfig1 \
libgcc1 \
libgconf-2-4 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
@@ -93,6 +95,7 @@ RUN set -ex; \
libxss1 \
libxtst6 \
fonts-liberation \
libappindicator1 \
libnss3 \
libgbm1 \
xvfb \
@@ -104,12 +107,12 @@ RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
ESUM='18071047526ab4b53131f9bb323e8703485ae37fcb2f2c5ef0f1b7bab66d1b94'; \
BINARY_URL='https://github.com/adoptium/temurin24-binaries/releases/download/jdk-24%2B36/OpenJDK24U-jdk_aarch64_linux_hotspot_24_36.tar.gz'; \
ESUM='fb43ae1202402842559cb6223886ec1663b90ffbec48479abbcb92c92c9012eb'; \
BINARY_URL='https://github.com/adoptium/temurin23-binaries/releases/download/jdk-23.0.2%2B7/OpenJDK23U-jdk_aarch64_linux_hotspot_23.0.2_7.tar.gz'; \
;; \
amd64|x86_64) \
ESUM='c340dee97b6aa215d248bc196dcac5b56e7be9b5c5d45e691344d40d5d0b171d'; \
BINARY_URL='https://github.com/adoptium/temurin24-binaries/releases/download/jdk-24%2B36/OpenJDK24U-jdk_x64_linux_hotspot_24_36.tar.gz'; \
ESUM='870ac8c05c6fe563e7a3878a47d0234b83c050e83651d2c47e8b822ec74512dd'; \
BINARY_URL='https://github.com/adoptium/temurin23-binaries/releases/download/jdk-23.0.2%2B7/OpenJDK23U-jdk_x64_linux_hotspot_23.0.2_7.tar.gz'; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
@@ -134,7 +137,7 @@ RUN set -ex; \
RUN set -ex; \
install -d /usr/share/postgresql-common/pgdg; \
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc; \
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt noble-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" >> /etc/apt/sources.list.d/postgresql.list; \
apt-get -qq update; \
apt-get -qqy install postgresql-client-16; \
rm -rf /var/lib/apt/lists/*;
@@ -270,8 +273,8 @@ WORKDIR /usr/local
RUN set -eux; \
git clone https://github.com/emscripten-core/emsdk.git; \
cd emsdk; \
./emsdk install $EMSCRIPTEN_VERSION; \
./emsdk activate $EMSCRIPTEN_VERSION; \
./emsdk install latest; \
./emsdk activate latest; \
rustup target add wasm32-unknown-emscripten;
WORKDIR /home

View File

@@ -68,7 +68,7 @@ services:
- PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
minio:
image: "minio/minio:RELEASE.2025-04-03T14-56-28Z"
image: "minio/minio:RELEASE.2023-11-11T08-14-41Z"
command: minio server /mnt/data --console-address ":9001"
volumes:
@@ -83,7 +83,7 @@ services:
- 9001:9001
postgres:
image: postgres:16.8
image: postgres:16
command: postgres -c config_file=/etc/postgresql.conf
restart: always
stop_signal: SIGINT

View File

@@ -18,7 +18,7 @@
"luxon": "^3.5.0",
"playwright": "^1.50.0",
"raw-body": "^3.0.0",
"svgo": "penpot/svgo#v3.1",
"svgo": "penpot/svgo#v3",
"xml-js": "^1.6.11",
"xregexp": "^5.1.1"
},

View File

@@ -620,13 +620,13 @@ __metadata:
languageName: node
linkType: hard
"css-tree@npm:^3.1.0":
version: 3.1.0
resolution: "css-tree@npm:3.1.0"
"css-tree@npm:^3.0.0":
version: 3.0.0
resolution: "css-tree@npm:3.0.0"
dependencies:
mdn-data: "npm:2.12.2"
mdn-data: "npm:2.10.0"
source-map-js: "npm:^1.0.1"
checksum: 10c0/b5715852c2f397c715ca00d56ec53fc83ea596295ae112eb1ba6a1bda3b31086380e596b1d8c4b980fe6da09e7d0fc99c64d5bb7313030dd0fba9c1415f30979
checksum: 10c0/43d44fdf7004ae91d73d486f17894fef77efa33747a6752b9241cf0f5fb47fabc16ec34a96a993651d9014dfdeee803d7c5fcd3548214252ee19f4e5c98999b2
languageName: node
linkType: hard
@@ -898,7 +898,7 @@ __metadata:
raw-body: "npm:^3.0.0"
shadow-cljs: "npm:2.28.20"
source-map-support: "npm:^0.5.21"
svgo: "penpot/svgo#v3.1"
svgo: "penpot/svgo#v3"
xml-js: "npm:^1.6.11"
xregexp: "npm:^5.1.1"
languageName: unknown
@@ -1383,10 +1383,10 @@ __metadata:
languageName: node
linkType: hard
"mdn-data@npm:2.12.2":
version: 2.12.2
resolution: "mdn-data@npm:2.12.2"
checksum: 10c0/b22443b71d70f72ccc3c6ba1608035431a8fc18c3c8fc53523f06d20e05c2ac10f9b53092759a2ca85cf02f0d37036f310b581ce03e7b99ac74d388ef8152ade
"mdn-data@npm:2.10.0":
version: 2.10.0
resolution: "mdn-data@npm:2.10.0"
checksum: 10c0/f6f1a6a6eb092bab250d06f6f6c7cb1733a77a17e7119aac829ad67d4322bbf6a30df3c6d88686e71942e66bd49274b2ddfede22a1d3df0d6c49a56fbd09eb7c
languageName: node
linkType: hard
@@ -2286,16 +2286,16 @@ __metadata:
languageName: node
linkType: hard
"svgo@penpot/svgo#v3.1":
"svgo@penpot/svgo#v3":
version: 4.0.0
resolution: "svgo@https://github.com/penpot/svgo.git#commit=a46262c12c0d967708395972c374eb2adead4180"
resolution: "svgo@https://github.com/penpot/svgo.git#commit=71c0db44c3c2665f2ffc0c4c5383acaebd5c524f"
dependencies:
"@trysound/sax": "npm:0.2.0"
css-select: "npm:^5.1.0"
css-tree: "npm:^3.1.0"
css-tree: "npm:^3.0.0"
csso: "npm:^5.0.5"
lodash: "npm:^4.17.21"
checksum: 10c0/87a51a0cd1168a31c07ddfa9ffa544d0cad1412b3549dc20146143a179c66e36420a88ae40221cdb23146775876d684b47972663b08b3f62335eb4f98773677e
checksum: 10c0/642c583372a610e484382cbf8a8fe28256dd354598d2e65ade2a3a63bf841b4d3dab4106f929f183ae3610007db2fc1413e82acc23793fe1a2e882bc923acc72
languageName: node
linkType: hard

View File

@@ -49,6 +49,6 @@
cider/cider-nrepl {:mvn/version "0.48.0"}}}
:shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]
:jvm-opts ["--sun-misc-unsafe-memory-access=allow" "-Dpenpot.wasm.profile-marks=true"]}
{:main-opts ["-m" "shadow.cljs.devtools.cli"]}
}}

View File

@@ -103,9 +103,9 @@
"@penpot/draft-js": "portal:./vendor/draft-js",
"@penpot/hljs": "portal:./vendor/hljs",
"@penpot/mousetrap": "portal:./vendor/mousetrap",
"@penpot/svgo": "penpot/svgo#v3.1",
"@penpot/svgo": "penpot/svgo#c6fba7a4dcfbc27b643e7fc0c94fc98cf680b77b",
"@penpot/text-editor": "portal:./text-editor",
"@tokens-studio/sd-transforms": "1.2.11",
"@tokens-studio/sd-transforms": "^0.16.1",
"compression": "^1.7.5",
"date-fns": "^4.1.0",
"eventsource-parser": "^3.0.0",
@@ -124,7 +124,7 @@
"rxjs": "8.0.0-alpha.14",
"sax": "^1.4.1",
"source-map-support": "^0.5.21",
"style-dictionary": "5.0.0-rc.1",
"style-dictionary": "4.0.0-prerelease.36",
"tdigest": "^0.1.2",
"tinycolor2": "^1.6.0",
"ua-parser-js": "2.0.0",

View File

@@ -1,26 +0,0 @@
[
{
"~:features": {
"~#set": [
"render-wasm",
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true
},
"~:name": "Default",
"~:modified-at": "~m1713533116375",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
"~:created-at": "~m1713533116375",
"~:is-default": true
}
]

View File

@@ -1,26 +0,0 @@
[
{
"~:features": {
"~#set": [
"variants/v1",
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true
},
"~:name": "Default",
"~:modified-at": "~m1713533116375",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
"~:created-at": "~m1713533116375",
"~:is-default": true
}
]

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
{
"~:features": {
"~#set": [
"variants/v1",
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true
},
"~:name": "Default",
"~:modified-at": "~m1713533116375",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
"~:created-at": "~m1713533116375",
"~:is-default": true
}

View File

@@ -70,7 +70,6 @@ export class WorkspacePage extends BaseWebSocketPage {
);
this.toolbarOptions = page.getByTestId("toolbar-options");
this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" });
this.ellipseShapeButton = page.getByRole("button", { name: "Ellipse (E)" });
this.moveButton = page.getByRole("button", { name: "Move (V)" });
this.boardButton = page.getByRole("button", { name: "Board (B)" });
this.toggleToolbarButton = page.getByRole("button", {
@@ -199,13 +198,6 @@ export class WorkspacePage extends BaseWebSocketPage {
await this.page.mouse.up();
}
async clickAt(x, y) {
await this.page.waitForTimeout(100);
await this.viewport.hover({ position: { x, y } });
await this.page.mouse.down();
await this.page.mouse.up();
}
async panOnViewportAt(x, y, width, height) {
await this.page.waitForTimeout(100);
await this.viewport.hover({ position: { x, y } });

View File

@@ -1,23 +0,0 @@
import { test, expect } from "@playwright/test";
import { WorkspacePage } from "../pages/WorkspacePage";
import { BaseWebSocketPage } from "../pages/BaseWebSocketPage";
test.beforeEach(async ({ page }) => {
await WorkspacePage.init(page);
await BaseWebSocketPage.mockRPC(
page,
"get-teams",
"get-teams-render-wasm.json",
);
});
test("BUG 10867 - Crash when loading comments", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.goToWorkspace();
await workspacePage.showComments();
await expect(
workspacePage.rightSidebar.getByText("Show all comments"),
).toBeVisible();
});

View File

@@ -371,59 +371,6 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensTabPanel.getByLabel("color.dark.primary")).toBeEnabled();
});
test("User changes color token color while keeping custom color space", async ({
page,
}) => {
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
await setupEmptyTokensFile(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
await tokensTabPanel.getByTitle("Add token: Color").click();
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
const valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.click();
await valueField.fill("hsv(1,1,1)");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: #ff0400"),
).toBeVisible();
const colorBullet = tokensUpdateCreateModal.getByTestId(
"token-form-color-bullet",
);
await colorBullet.click();
const valueSaturationSelector = tokensUpdateCreateModal.getByTestId(
"value-saturation-selector",
);
await expect(valueSaturationSelector).toBeVisible();
// Check if color space doesnt get overwritten when changing color via the picker
// Not testing for exact value to avoid flakiness of px click
await valueSaturationSelector.click({ position: { x: 100, y: 100 } });
await expect(valueField).not.toHaveValue("hsv(1,1,1)");
await expect(valueField).toHaveValue(/^hsv.*$/);
// Clearing the input field should pick hex
await valueField.fill("");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: -"),
).toBeVisible();
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
await expect(valueField).toHaveValue(/^#[A-Fa-f\d]+$/);
// Changing opacity for hex values converts to rgba
const sliderOpacity = tokensUpdateCreateModal.getByTestId("slider-opacity");
await sliderOpacity.click({ position: { x: 50, y: 0 } });
await expect(valueField).toHaveValue(/^rgba(.*)$/);
// Changing color now will stay in rgba
await valueSaturationSelector.click({ position: { x: 0, y: 0 } });
await expect(valueField).toHaveValue(/^rgba(.*)$/);
});
test("User duplicate color token", async ({ page }) => {
const { tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
@@ -564,9 +511,7 @@ test.describe("Tokens: Sets Tab", () => {
// Creates nesting by renaming set with double click
await tokenThemesSetsSidebar
.getByRole("button", { name: "light-renamed" })
.click({ button: "right" });
await expect(tokenContextMenuForSet).toBeVisible();
await tokenContextMenuForSet.getByText("Rename").click();
.dblclick();
await changeSetInput(tokenThemesSetsSidebar, "nested/light");
await assertSetsList(tokenThemesSetsSidebar, [

View File

@@ -1,428 +0,0 @@
import { test, expect } from "@playwright/test";
import { WorkspacePage } from "../pages/WorkspacePage";
import { BaseWebSocketPage } from "../pages/BaseWebSocketPage";
test.beforeEach(async ({ page }) => {
await WorkspacePage.init(page);
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-variants.json");
});
const setupVariantsFile = async (workspacePage) => {
await workspacePage.setupEmptyFile();
await workspacePage.mockRPC(
"get-team?id=*",
"workspace/get-team-variants.json",
);
await workspacePage.setupEmptyFile();
await workspacePage.mockRPC(
/get\-file\?/,
"workspace/get-file-not-empty.json",
);
await workspacePage.mockRPC(
"update-file?id=*",
"workspace/update-file-create-rect.json",
);
await workspacePage.goToWorkspace({
fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374",
pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375",
});
};
const setupVariantsFileWithVariant = async (workspacePage) => {
await setupVariantsFile(workspacePage);
await workspacePage.clickLeafLayer("Rectangle");
await workspacePage.page.keyboard.press("Control+k");
await workspacePage.page.keyboard.press("Control+k");
};
const findVariant = async (workspacePage, num_variant) => {
const container = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("Rectangle") })
.filter({ has: workspacePage.page.locator(".icon-component") })
.nth(num_variant);
const variant1 = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("Value 1") })
.filter({ has: workspacePage.page.locator(".icon-variant") })
.nth(num_variant);
const variant2 = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("Value 2") })
.filter({ has: workspacePage.page.locator(".icon-variant") })
.nth(num_variant);
return {
container: container,
variant1: variant1,
variant2: variant2,
};
};
const validateVariant = async (variant) => {
//The variant container exists and is visible
await expect(variant.container).toBeVisible();
//The variants exists and are visible
await expect(variant.variant1).toBeVisible();
await expect(variant.variant2).toBeVisible();
// variant1 and variant2 are items inside the childs of variant_container
const parent_id = "children-" + (await variant.container.getAttribute("id"));
await expect(variant.variant1.locator("xpath=..")).toHaveAttribute(
"data-testid",
parent_id,
);
await expect(variant.variant2.locator("xpath=..")).toHaveAttribute(
"data-testid",
parent_id,
);
};
test("User creates a variant", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
await workspacePage.clickLeafLayer("Rectangle");
const variant = await findVariant(workspacePage, 0);
// The variant is valid
await validateVariant(variant);
// Extra validators
await variant.container.click();
const variants = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.locator(".icon-variant") })
.all();
// There are exactly two variants
expect(variants.length).toBe(2);
// The design tab shows the variant properties
await expect(
workspacePage.page.getByTitle("Property1: Value 1, Value 2"),
).toBeVisible();
});
test("User duplicates a variant container", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
// Select the variant container
await variant.container.click();
//Duplicate the variant container
await workspacePage.page.keyboard.press("Control+d");
const variant_original = await findVariant(workspacePage, 1); // On duplicate, the new item is the first
const variant_duplicate = await findVariant(workspacePage, 0);
// Expand the layers
await variant_duplicate.container.getByRole("button").first().click();
// The variants are valid
await validateVariant(variant_original);
await validateVariant(variant_duplicate);
});
test("User copy paste a variant container", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
// Select the variant container
await variant.container.click();
//Copy the variant container
await workspacePage.page.keyboard.press("Control+c");
//Paste the variant container
await workspacePage.clickAt(500, 500);
await workspacePage.page.keyboard.press("Control+v");
const variant_original = await findVariant(workspacePage, 0);
const variant_duplicate = await findVariant(workspacePage, 1);
// Expand the layers
await variant_duplicate.container.getByRole("button").first().click();
// The variants are valid
await validateVariant(variant_original);
await validateVariant(variant_duplicate);
});
test("User cut paste a variant container", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
// Select the variant container
await variant.container.click();
//Cut the variant container
await workspacePage.page.keyboard.press("Control+x");
//Paste the variant container
await workspacePage.clickAt(500, 500);
await workspacePage.page.keyboard.press("Control+v");
const variant_pasted = await findVariant(workspacePage, 0);
// Expand the layers
await variant_pasted.container.getByRole("button").first().click();
// The variants are valid
await validateVariant(variant_pasted);
});
test("[Bugfixing] User cut paste a variant container into a board, and undo twice", async ({
page,
}) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
//Create a board
await workspacePage.boardButton.click();
await workspacePage.clickWithDragViewportAt(500, 500, 200, 200);
await workspacePage.clickAt(495, 495);
const board = await workspacePage.rootShape.locator("Board");
// Select the variant container
await variant.container.click();
//Cut the variant container
await workspacePage.page.keyboard.press("Control+x");
//Select the board
await workspacePage.clickLeafLayer("Board");
//Paste the variant container inside the board
await workspacePage.page.keyboard.press("Control+v");
//Undo twice
await workspacePage.page.keyboard.press("Control+z");
await workspacePage.page.keyboard.press("Control+z");
const variant_after_undo = await findVariant(workspacePage, 0);
// The variants are valid
await validateVariant(variant_after_undo);
});
test("User copy paste a variant", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
// Select the variant1
await variant.variant1.click();
//Cut the variant
await workspacePage.page.keyboard.press("Control+c");
//Paste the variant
await workspacePage.clickAt(500, 500);
await workspacePage.page.keyboard.press("Control+v");
const copy = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("Rectangle") })
.filter({ has: workspacePage.page.locator(".icon-component-copy") });
//The copy exists and is visible
await expect(copy).toBeVisible();
});
test("User cut paste a variant outside the container", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
// Select the variant1
await variant.variant1.click();
//Cut the variant
await workspacePage.page.keyboard.press("Control+x");
//Paste the variant
await workspacePage.clickAt(500, 500);
await workspacePage.page.keyboard.press("Control+v");
const component = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("Rectangle / Value 1") })
.filter({ has: workspacePage.page.locator(".icon-component") });
//The component exists and is visible
await expect(component).toBeVisible();
});
test("User drag and drop a variant outside the container", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
// Drag and drop the variant
await workspacePage.clickWithDragViewportAt(350, 400, 0, 200);
const component = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("Rectangle / Value 1") })
.filter({ has: workspacePage.page.locator(".icon-component") });
//The component exists and is visible
await expect(component).toBeVisible();
});
test("User cut paste a component inside a variant", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
//Create a component
await workspacePage.ellipseShapeButton.click();
await workspacePage.clickWithDragViewportAt(500, 500, 20, 20);
await workspacePage.clickLeafLayer("Ellipse");
await workspacePage.page.keyboard.press("Control+k");
//Cut the component
await workspacePage.page.keyboard.press("Control+x");
//Paste the component inside the variant
await variant.container.click();
await workspacePage.page.keyboard.press("Control+v");
const variant3 = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("Ellipse") })
.filter({ has: workspacePage.page.locator(".icon-variant") })
.first();
//The new variant exists and is visible
await expect(variant3).toBeVisible();
});
test("User cut paste a component with path inside a variant", async ({
page,
}) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
//Create a component
await workspacePage.ellipseShapeButton.click();
await workspacePage.clickWithDragViewportAt(500, 500, 20, 20);
await workspacePage.clickLeafLayer("Ellipse");
await workspacePage.page.keyboard.press("Control+k");
//Rename the component
await workspacePage.layers.getByText("Ellipse").dblclick();
await workspacePage.page
.getByTestId("layer-item")
.getByRole("textbox")
.pressSequentially("button / hover");
await workspacePage.page.keyboard.press("Enter");
//Cut the component
await workspacePage.page.keyboard.press("Control+x");
//Paste the component inside the variant
await variant.container.click();
await workspacePage.page.keyboard.press("Control+v");
const variant3 = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("button, hover") })
.filter({ has: workspacePage.page.locator(".icon-variant") })
.first();
//The new variant exists and is visible
await expect(variant3).toBeVisible();
});
test("User drag and drop a component with path inside a variant", async ({
page,
}) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
const variant = await findVariant(workspacePage, 0);
//Create a component
await workspacePage.ellipseShapeButton.click();
await workspacePage.clickWithDragViewportAt(500, 500, 20, 20);
await workspacePage.clickLeafLayer("Ellipse");
await workspacePage.page.keyboard.press("Control+k");
//Rename the component
await workspacePage.layers.getByText("Ellipse").dblclick();
await workspacePage.page
.getByTestId("layer-item")
.getByRole("textbox")
.pressSequentially("button / hover");
await workspacePage.page.keyboard.press("Enter");
//Drag and drop the component the component
await workspacePage.clickWithDragViewportAt(510, 510, 0, -200);
const variant3 = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("button, hover") })
.filter({ has: workspacePage.page.locator(".icon-variant") })
.first();
//The new variant exists and is visible
await expect(variant3).toBeVisible();
});
test("User cut paste a variant into another container", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await setupVariantsFileWithVariant(workspacePage);
// Create anothe variant
await workspacePage.ellipseShapeButton.click();
await workspacePage.clickWithDragViewportAt(500, 500, 20, 20);
await workspacePage.clickLeafLayer("Ellipse");
await workspacePage.page.keyboard.press("Control+k");
await workspacePage.page.keyboard.press("Control+k");
const variant_origin = await findVariant(workspacePage, 1);
const variant_target = await findVariant(workspacePage, 0);
// Select the variant1
await variant_origin.variant1.click();
//Cut the variant
await workspacePage.page.keyboard.press("Control+x");
//Paste the variant
await workspacePage.layers.getByText("Ellipse").first().click();
await workspacePage.page.keyboard.press("Control+v");
const variant3 = await workspacePage.layers
.getByTestId("layer-row")
.filter({ has: workspacePage.page.getByText("Value 1, rectangle") })
.filter({ has: workspacePage.page.locator(".icon-variant") })
.first();
//The new variant exists and is visible
await expect(variant3).toBeVisible();
});

View File

@@ -4,13 +4,11 @@ import { presenceFixture } from "../../data/workspace/ws-notifications";
test.beforeEach(async ({ page }) => {
await WorkspacePage.init(page);
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile(page);
});
test("Save and restore version", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile(page);
await workspacePage.mockRPC(/get\-file\?/, "workspace/versions-init.json");
await workspacePage.mockRPC(
"get-file-fragment?file-id=*&fragment-id=406b7b01-d3e2-80e4-8005-3138b7cc5f0b",
@@ -89,20 +87,3 @@ test("Save and restore version", async ({ page }) => {
// check that the history panel is closed after restore
await expect(page.getByRole("tab", { name: "design" })).toBeVisible();
});
test("BUG 11006 - Fix history panel shortcut", async ({ page }) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.mockRPC(/get\-file\?/, "workspace/versions-init.json");
await workspacePage.mockRPC(
"get-file-snapshots?file-id=*",
"workspace/versions-snapshot-1.json",
);
await workspacePage.goToWorkspace();
await page.keyboard.press("Control+Alt+h");
await expect(
workspacePage.rightSidebar.getByText("There are no versions yet"),
).toBeVisible();
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 16 16">
<path d="M8 1.8a.35.35 0 0 0-.3.18l-2.08 3.9a.7.7 0 0 1-1.06.21l-3-2.55a.35.35 0 0 0-.55.36l1.98 7.15a.7.7 0 0 0 .67.5h8.68a.7.7 0 0 0 .67-.5l1.98-7.15a.35.35 0 0 0-.56-.36l-3 2.55a.7.7 0 0 1-1.06-.2L8.31 1.98A.35.35 0 0 0 8 1.8ZM3.1 14.35h9.8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 365 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 16 16">
<path d="M7.411 4.104c.237-.028.475-.042.714-.041 2.623 0 4.725 1.632 5.625 3.937a6.502 6.502 0 0 1-.875 1.568M5.043 4.917C3.895 5.617 3.006 6.702 2.5 8c.9 2.305 3.001 3.938 5.625 3.938a5.874 5.874 0 0 0 3.082-.856M6.932 6.807a1.69 1.69 0 0 0 .757 2.824 1.686 1.686 0 0 0 1.63-.437M3.625 3.5l9 9"/>
</svg>

Before

Width:  |  Height:  |  Size: 415 B

View File

@@ -25,36 +25,6 @@
(def size-presets
[{:name "APPLE"}
{:name "iPhone 16"
:width 393
:height 852}
{:name "iPhone 16 Pro"
:width 402
:height 874}
{:name "iPhone 16 Pro Max"
:width 440
:height 956}
{:name "iPhone 16 Plus"
:width 430
:height 932}
{:name "14/15 Pro Max"
:width 430
:height 932}
{:name "iPhone 15/15 Pro"
:width 393
:height 852}
{:name "iPhone 13/14 "
:width 390
:height 844}
{:name "iPhone 14 Plus"
:width 428
:height 926}
{:name "iPhone 13 Mini"
:width 375
:height 812}
{:name "iPhone SE"
:width 320
:height 568}
{:name "iPhone 12/12 Pro"
:width 390
:height 844}
@@ -70,86 +40,47 @@
{:name "iPhone XS Max/XR/11"
:width 414
:height 896}
{:name "iPhone 6/7/8 Plus"
:width 414
:height 736}
{:name "iPhone 6/7/8/SE2"
:width 375
:height 667}
{:name "iPhone 5/SE"
:width 320
:height 568}
{:name "iPad"
:width 768
:height 1024}
{:name "iPad Mini 8.3in"
:width 744
:height 1133}
{:name "iPad Pro 10.5in"
:width 834
:height 1112}
{:name "iPad Pro 11in"
:width 834
:height 1194}
{:name "iPad Pro 12.9in"
:width 1027
:width 1024
:height 1366}
{:name "Watch Series 10"
:width 416
:height 496}
{:name "Watch 45mm"
:width 396
:height 484}
{:name "Watch 44mm"
:width 368
:height 448}
{:name "Watch 42mm"
:width 312
:height 390}
{:name "Watch 41mm"
:width 352
:height 430}
{:name "Watch 40mm"
:width 324
:height 394}
{:name "Watch 38mm"
:width 272
:height 340}
{:name "MacBook Air"
:width 1280
:height 832}
{:name "MacBook Pro 14in"
:width 1512
:height 982}
{:name "MacBook Pro 16in"
:width 1728
:height 1117}
{:name "ANDROID"}
{:name "Expanded"
:width 1280
:height 800}
{:name "Compact"
:width 412
:height 917}
{:name "Large"
:width 360
:height 800}
{:name "Medium"
:width 700
:height 840}
{:name "Small"
:width 360
:height 640}
{:name "Mobile"
:width 360
:height 640}
{:name "Tablet"
:width 768
:height 1024}
{:name "Google Pixel 7 Pro"
:width 1440
:height 3120}
{:name "Google Pixel 6a/6"
:width 1080
:height 2400}
{:name "Google Pixel 4a/5"
:width 393
:height 851}
{:name "Samsung Galaxy S22"
:width 1080
:height 2340}
{:name "Samsung Galaxy S20+"
:width 384
:height 854}
@@ -164,17 +95,11 @@
{:name "Surface Pro 4/5/6/7"
:width 1368
:height 912}
{:name "Surface Pro 8"
:width 140
:height 960}
{:name "ReMarkable"}
{:name "Remarkable 2"
:width 1404
:height 1872}
{:name "Remarkable Pro"
:width 1620
:height 2160}
:width 840
:height 1120}
{:name "WEB"}
{:name "Web 1280"
@@ -190,20 +115,6 @@
:width 1920
:height 1080}
{:name "MIXED"}
{:name "Desktop/Wireframe"
:width 1440
:height 1024}
{:name "TV"
:width 1280
:height 720}
{:name "Slide 16:9"
:width 1920
:height 1080}
{:name "Slide 4:3"
:width 1027
:height 768}
{:name "PRINT (96dpi)"}
{:name "A0"
:width 3179
@@ -239,7 +150,7 @@
:height 320}
{:name "Instagram post"
:width 1080
:height 1350}
:height 1080}
{:name "Instagram story"
:width 1080
:height 1920}
@@ -259,24 +170,15 @@
:width 1584
:height 396}
{:name "LinkedIn post"
:width 520
:height 320}
{:name "Bluesky profile"
:width 1200
:height 627}
{:name "Twitter profile"
:width 400
:height 400}
{:name "Bluesky cover"
:width 3000
:height 1000}
{:name "Bluesky post"
:width 1080
:height 1350}
{:name "X profile"
:width 400
:height 400}
{:name "X header"
{:name "Twitter header"
:width 1500
:height 500}
{:name "X post"
{:name "Twitter post"
:width 1024
:height 512}
{:name "YouTube profile"
@@ -285,9 +187,6 @@
{:name "YouTube banner"
:width 2560
:height 1440}
{:name "YouTube cover"
:width 2048
:height 1152}
{:name "YouTube thumb"
:width 1280
:height 720}])

View File

@@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.tokens.library-edit
(ns app.main.data.tokens
(:require
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
@@ -18,9 +18,10 @@
[app.main.data.helpers :as dsh]
[app.main.data.notifications :as ntf]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.tokens.propagation :as dwtp]
[app.main.ui.workspace.tokens.update :as wtu]
[app.util.i18n :refer [tr]]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk]))
(declare set-selected-token-set-name)
@@ -123,7 +124,7 @@
(pcb/update-active-token-themes active-token-themes' prev-active-token-themes))]
(rx/of
(dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(wtu/update-workspace-tokens))))))
(defn delete-token-theme [group theme-name]
(ptk/reify ::delete-token-theme
@@ -135,7 +136,7 @@
(pcb/set-token-theme group theme-name nil))]
(rx/of
(dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(wtu/update-workspace-tokens))))))
(defn create-token-set
[set-name]
@@ -191,23 +192,6 @@
(rx/of (set-selected-token-set-name name)
(dch/commit-changes changes))))))))
(defn duplicate-token-set
[id is-group]
(ptk/reify ::duplicate-token-set
ptk/WatchEvent
(watch [it state _]
(let [data (dsh/lookup-file-data state)
name (ctob/normalize-set-name id)
tokens-lib (get data :tokens-lib)
suffix (tr "workspace.token.duplicate-suffix")]
(when-let [set (ctob/duplicate-set name tokens-lib {:suffix suffix})]
(let [changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/set-token-set (:name set) is-group set))]
(rx/of (set-selected-token-set-name name)
(dch/commit-changes changes))))))))
(defn toggle-token-set
[name]
(assert (string? name) "expected a string for `name`")
@@ -221,7 +205,7 @@
(clt/generate-toggle-token-set tlib name))]
(rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(wtu/update-workspace-tokens))))))
(defn toggle-token-set-group [group-path]
(ptk/reify ::toggle-token-set-group
@@ -233,7 +217,7 @@
(clt/generate-toggle-token-set-group (get-tokens-lib state) group-path))]
(rx/of
(dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(wtu/update-workspace-tokens))))))
(defn import-tokens-lib [lib]
(ptk/reify ::import-tokens-lib
@@ -244,7 +228,7 @@
(pcb/with-library-data data)
(pcb/set-tokens-lib lib))]
(rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(wtu/update-workspace-tokens))))))
(defn delete-token-set-path
[group? path]
@@ -256,7 +240,7 @@
(pcb/with-library-data data)
(pcb/set-token-set (ctob/join-set-path path) group? nil))]
(rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(wtu/update-workspace-tokens))))))
(defn drop-error [{:keys [error to-path]}]
(ptk/reify ::drop-error
@@ -283,7 +267,7 @@
(when-let [changes (clt/generate-move-token-set-group (pcb/empty-changes it) (get-tokens-lib state) drop-opts)]
(rx/of
(dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))
(wtu/update-workspace-tokens)))
(catch :default e
(rx/of
(drop-error (ex-data e))))))))
@@ -300,7 +284,7 @@
changes (-> (pcb/empty-changes it)
(clt/generate-move-token-set tokens-lib params))]
(rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))
(wtu/update-workspace-tokens)))
(catch :default cause
(rx/of (drop-error (ex-data cause))))))))
@@ -401,8 +385,17 @@
(when-let [token (ctob/get-token token-set token-name)]
(let [tokens (ctob/get-tokens token-set)
unames (map :name tokens)
suffix (tr "workspace.token.duplicate-suffix")
copy-name (cfh/generate-unique-name token-name unames :suffix suffix)]
suffix-fn
(fn [copy-count]
(let [suffix (tr "workspace.token.duplicate-suffix")]
(str/concat "-"
suffix
(when (> copy-count 1)
(str "-" copy-count)))))
copy-name
(cfh/generate-unique-name token-name unames :suffix-fn suffix-fn)]
(rx/of (create-token (assoc token :name copy-name)))))))))

View File

@@ -13,7 +13,6 @@
[app.common.features :as cfeat]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv]
[app.common.geom.align :as gal]
[app.common.geom.point :as gpt]
[app.common.geom.proportions :as gpp]
@@ -26,7 +25,7 @@
[app.common.schema :as sm]
[app.common.text :as txt]
[app.common.transit :as t]
[app.common.types.component :as ctc]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
@@ -76,7 +75,6 @@
[app.main.data.workspace.thumbnails :as dwth]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.variants :as dwva]
[app.main.data.workspace.viewport :as dwv]
[app.main.data.workspace.zoom :as dwz]
[app.main.errors]
@@ -87,7 +85,6 @@
[app.main.streams :as ms]
[app.main.worker :as uw]
[app.render-wasm :as wasm]
[app.render-wasm.api :as api]
[app.util.code-gen.style-css :as css]
[app.util.dom :as dom]
[app.util.globals :as ug]
@@ -396,13 +393,6 @@
(rx/filter dch/commit?)
(rx/map deref)
(rx/mapcat (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}]
(when render-wasm?
(let [added (->> redo-changes
(filter #(= (:type %) :add-obj))
(map :obj))]
(doseq [shape added]
(api/process-object shape))))
(if (and save-undo? (seq undo-changes))
(let [entry {:undo-changes undo-changes
:redo-changes redo-changes
@@ -581,7 +571,7 @@
name (cfh/generate-unique-name base-name unames :suffix-fn suffix-fn)
objects (update-vals (:objects page) #(dissoc % :use-for-thumbnail))
main-instances-ids (set (keep #(when (ctc/main-instance? (val %)) (key %)) objects))
main-instances-ids (set (keep #(when (ctk/main-instance? (val %)) (key %)) objects))
ids-to-remove (set (apply concat (map #(cfh/get-children-ids objects %) main-instances-ids)))
add-component-copy
@@ -592,6 +582,7 @@
component
fdata
(gpt/point (:x shape) (:y shape))
true
{:keep-ids? true :force-frame-id (:frame-id shape)})
children (into {} (map (fn [shape] [(:id shape) shape]) new-shapes))
objs (assoc objs id new-shape)]
@@ -707,7 +698,7 @@
(defn rename-file
[id name]
{:pre [(uuid? id) (string? name)]}
(let [name (dm/truncate name 200)]
(let [name (str/prune name 200)]
(ptk/reify ::rename-file
IDeref
(-deref [_]
@@ -797,36 +788,27 @@
([] (end-rename-shape nil nil))
([shape-id name]
(ptk/reify ::end-rename-shape
ptk/UpdateEvent
(update [_ state]
;; Remove rename state from workspace local state
(update state :workspace-local dissoc :shape-for-rename))
ptk/WatchEvent
(watch [_ state _]
(when-let [shape-id (d/nilv shape-id (dm/get-in state [:workspace-local :shape-for-rename]))]
(let [shape (dsh/lookup-shape state shape-id)
name (str/trim name)
clean-name (cfh/clean-path name)
valid? (and (not (str/ends-with? name "/"))
(string? clean-name)
(not (str/blank? clean-name)))
component-id (:component-id shape)
undo-id (js/Symbol)]
(let [shape (dsh/lookup-shape state shape-id)
name (str/trim name)
clean-name (cfh/clean-path name)
valid? (and (not (str/ends-with? name "/"))
(string? clean-name)
(not (str/blank? clean-name)))]
(rx/concat
;; Remove rename state from workspace local state
(rx/of #(update % :workspace-local dissoc :shape-for-rename))
;; Rename the shape if string is not empty/blank
(when valid?
(rx/of (update-shape shape-id {:name clean-name})))
(when valid?
(if (ctc/is-variant-container? shape)
;; Rename the full variant when it is a variant container
(rx/of (dwva/rename-variant shape-id clean-name))
(rx/of
(dwu/start-undo-transaction undo-id)
;; Rename the shape if string is not empty/blank
(update-shape shape-id {:name clean-name})
;; Update the component in case shape is a main instance
(when (and (some? component-id) (ctc/main-instance? shape))
(dwl/rename-component component-id clean-name))
(dwu/commit-undo-transaction undo-id))))))))))
;; Update the component in case if shape is a main instance
(when (and valid? (:main-instance shape))
(when-let [component-id (:component-id shape)]
(rx/of (dwl/rename-component component-id clean-name)))))))))))
;; --- Update Selected Shapes attrs
@@ -1218,26 +1200,22 @@
(ptk/reify ::show-component-in-assets
ptk/WatchEvent
(watch [_ state _]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
component (cfv/get-primary-component fdata component-id)
cpath (:path component)
cpath (cfh/split-path cpath)
paths (map (fn [i] (cfh/join-path (take (inc i) cpath)))
(range (count cpath)))]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
cpath (dm/get-in fdata [:components component-id :path])
cpath (cfh/split-path cpath)
paths (map (fn [i] (cfh/join-path (take (inc i) cpath)))
(range (count cpath)))]
(rx/concat
(rx/from (map #(set-assets-group-open file-id :components % true) paths))
(rx/of (dcm/go-to-workspace :layout :assets)
(set-assets-section-open file-id :library true)
(set-assets-section-open file-id :components true)
(select-single-asset file-id (:id component) :components)))))
(select-single-asset file-id component-id :components)))))
ptk/EffectEvent
(effect [_ state _]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
component (cfv/get-primary-component fdata component-id)
wrapper-id (str "component-shape-id-" (:id component))]
(effect [_ _ _]
(let [wrapper-id (str "component-shape-id-" component-id)]
(tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1374,31 +1352,15 @@
(assoc obj ::images images))))
(rx/of obj))))
(collect-variants [state shape]
(let [page-id (:current-page-id state)
data (dsh/lookup-file-data state)
objects (-> (dsh/get-page data page-id)
(get :objects))
components (cfv/find-variant-components data objects (:id shape))]
(into {} (map (juxt :id :variant-properties) components))))
;; Collects all the items together and split images into a
;; separated data structure for a more easy paste process.
;; Also collects the variant properties of the copied variants
(collect-data [state result {:keys [id ::images] :as item}]
(collect-data [result {:keys [id ::images] :as item}]
(cond-> result
:always
(update :objects assoc id (dissoc item ::images))
(some? images)
(update :images into images)
(ctc/is-variant-container? item)
(update :variant-properties merge (collect-variants state item))))
(update :images into images)))
(maybe-translate [shape objects parent-frame-id]
(if (= parent-frame-id uuid/zero)
@@ -1420,7 +1382,7 @@
heads))))
(advance-copy [file libraries page objects shape]
(if (and (ctc/instance-head? shape) (not (ctc/main-instance? shape)))
(if (and (ctk/instance-head? shape) (not (ctk/main-instance? shape)))
(let [level-delta (ctn/get-nesting-level-delta (:objects page) shape uuid/zero)]
(if (pos? level-delta)
(reduce (partial advance-shape file libraries page level-delta)
@@ -1483,7 +1445,7 @@
(fn [resolve reject]
(->> (rx/from shapes)
(rx/merge-map (partial prepare-object objects frame-id))
(rx/reduce (partial collect-data state) initial)
(rx/reduce collect-data initial)
(rx/map (partial sort-selected state))
(rx/map (partial advance-copies state selected))
(rx/map #(t/encode-str % {:type :json-verbose}))
@@ -1498,7 +1460,7 @@
;; https://caniuse.com/?search=ClipboardItem
(->> (rx/from shapes)
(rx/merge-map (partial prepare-object objects frame-id))
(rx/reduce (partial collect-data state) initial)
(rx/reduce collect-data initial)
(rx/map (partial sort-selected state))
(rx/map (partial advance-copies state selected))
(rx/map #(t/encode-str % {:type :json-verbose}))
@@ -2086,9 +2048,6 @@
objects (:objects pdata)
variant-props (:variant-properties pdata)
position (deref ms/mouse-position)
;; Calculate position for the pasted elements
@@ -2101,8 +2060,12 @@
libraries (dsh/lookup-libraries state)
ldata (dsh/lookup-file-data state file-id)
;; full-libs (assoc-in libraries [(:id ldata) :data] ldata)
full-libs libraries
[parent-id
frame-id] (ctn/find-valid-parent-and-frame-ids candidate-parent-id page-objects (vals objects) true libraries)
frame-id] (ctn/find-valid-parent-and-frame-ids candidate-parent-id page-objects (vals objects) true full-libs)
index (if (= candidate-parent-id parent-id)
index
@@ -2116,12 +2079,12 @@
all-objects (merge page-objects objects)
drop-cell (when (ctl/grid-layout? all-objects parent-id)
(gslg/get-drop-cell frame-id all-objects position))
changes (-> (pcb/empty-changes it)
(cll/generate-duplicate-changes all-objects page selected delta
libraries ldata file-id {:variant-props variant-props})
(cll/generate-duplicate-changes all-objects page selected delta libraries ldata file-id)
(pcb/amend-changes (partial process-rchange media-idx))
(pcb/amend-changes (partial change-add-obj-index objects selected index)))
@@ -2150,7 +2113,7 @@
undo-id (js/Symbol)]
(rx/concat
(->> (filter ctc/instance-head? orig-shapes)
(->> (filter ctk/instance-head? orig-shapes)
(map (fn [{:keys [component-file]}]
(ptk/event ::ev/event
{::ev/name "use-library-component"
@@ -2465,7 +2428,7 @@
(let [objects (dsh/lookup-page-objects state)
copies (->> objects
vals
(filter #(and (ctc/instance-head? %) (not (ctc/main-instance? %)))))
(filter #(and (ctk/instance-head? %) (not (ctk/main-instance? %)))))
copies-no-ref (filter #(not (:shape-ref %)) copies)
find-childs-no-ref (fn [acc-map item]

View File

@@ -42,6 +42,7 @@
[app.main.data.workspace.transforms :as dwtr]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.zoom :as dwz]
[app.main.features :as features]
[app.main.features.pointer-map :as fpmap]
[app.main.refs :as refs]
[app.main.repo :as rp]
@@ -394,9 +395,9 @@
(defn- add-component2
"This is the second step of the component creation."
([selected]
(add-component2 nil selected))
([id-ref selected]
([selected components-v2]
(add-component2 nil selected components-v2))
([id-ref selected components-v2]
(ptk/reify ::add-component2
ev/Event
(-data [_]
@@ -412,7 +413,8 @@
parents (into #{} (map :parent-id) shapes)]
(when-not (empty? shapes)
(let [[root component-id changes]
(cll/generate-add-component (pcb/empty-changes it) shapes objects page-id file-id
(cll/generate-add-component (pcb/empty-changes it) shapes objects page-id file-id components-v2
dwg/prepare-create-group
cfsh/prepare-create-artboard-from-selection)]
(when id-ref
(reset! id-ref component-id))
@@ -437,11 +439,12 @@
selected (->> (d/nilv ids (dsh/lookup-selected state))
(cfh/clean-loops objects))
selected-objects (map #(get objects %) selected)
components-v2 (features/active-feature? state "components/v2")
;; We don't want to change the structure of component copies
can-make-component (every? true? (map #(ctn/valid-shape-for-component? objects %) selected-objects))]
(when can-make-component
(rx/of (add-component2 id-ref selected))))))))
(rx/of (add-component2 id-ref selected components-v2))))))))
(defn add-multiple-components
"Add several new components to current file library, from the currently selected shapes."
@@ -449,14 +452,15 @@
(ptk/reify ::add-multiple-components
ptk/WatchEvent
(watch [_ state _]
(let [objects (dsh/lookup-page-objects state)
(let [components-v2 (features/active-feature? state "components/v2")
objects (dsh/lookup-page-objects state)
selected (->> (dsh/lookup-selected state)
(cfh/clean-loops objects))
selected-objects (map #(get objects %) selected)
;; We don't want to change the structure of component copies
can-make-component (every? true? (map #(ctn/valid-shape-for-component? objects %) selected-objects))
added-components (map (fn [id]
(with-meta (add-component2 [id])
(with-meta (add-component2 [id] components-v2)
{:multiple true}))
selected)
undo-id (js/Symbol)]
@@ -485,7 +489,7 @@
(rx/empty)
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(cll/generate-rename-component id new-name data))]
(cll/generate-rename-component id new-name data true))]
(rx/of (dch/commit-changes changes))))))))
(defn rename-component-and-main-instance
@@ -508,6 +512,7 @@
(rx/concat
(rx/of (rename-component component-id clean-name))
;; NOTE: only when components-v2 is enabled
(when (and shape-id page-id)
(rx/of (dwsh/update-shapes [shape-id] #(assoc % :name clean-name) {:page-id page-id :stack-undo? true}))))))))))
@@ -521,10 +526,11 @@
(watch [it state _]
(let [libraries (dsh/lookup-libraries state)
library (get libraries library-id)
components-v2 (features/active-feature? state "components/v2")
[main-instance changes]
(-> (pcb/empty-changes it nil)
(cll/generate-duplicate-component library component-id new-component-id))]
(cll/generate-duplicate-component library component-id new-component-id components-v2))]
(rx/of
(ptk/data-event :layout/update {:ids [(:id main-instance)]})
(dch/commit-changes changes)))))))
@@ -554,7 +560,8 @@
[all-parents changes]
(-> (pcb/empty-changes it page-id)
;; Deleting main root triggers component delete
(cls/generate-delete-shapes fdata page objects #{root-id} {:undo-group undo-group
(cls/generate-delete-shapes fdata page objects #{root-id} {:components-v2 true
:undo-group undo-group
:undo-id undo-id}))]
(rx/of
(dwu/start-undo-transaction undo-id)
@@ -581,14 +588,11 @@
changes (-> (pcb/empty-changes it)
(cll/generate-restore-component ldata component-id library-id page objects))
page-id
(->> changes :redo-changes (keep :page-id) first)
frames
(->> changes :redo-changes (keep :frame-id))]
(rx/of (dch/commit-changes changes)
(ptk/data-event :layout/update {:page-id page-id :ids frames}))))))
(ptk/data-event :layout/update {:ids frames}))))))
(defn restore-components
@@ -828,7 +832,7 @@
changes
(-> (pcb/empty-changes it)
(cll/generate-reset-component file libraries container id))]
(cll/generate-reset-component file libraries container id true))]
(log/debug :msg "RESET-COMPONENT finished" :js/rchanges (log-changes
(:redo-changes changes)
@@ -880,7 +884,7 @@
(-> (pcb/empty-changes it)
(pcb/set-undo-group undo-group)
(pcb/with-container container)
(cll/generate-sync-shape-inverse fdata libraries container id))
(cll/generate-sync-shape-inverse fdata libraries container id true))
ldata (->> (:component-file shape)
(dsh/lookup-file-data state))
@@ -1270,8 +1274,10 @@
[]
(ptk/reify ::watch-component-changes
ptk/WatchEvent
(watch [_ _ stream]
(let [stopper-s
(watch [_ state stream]
(let [components-v2? (features/active-feature? state "components/v2")
stopper-s
(->> stream
(rx/filter #(or (= ::dw/finalize-page (ptk/type %))
(= ::watch-component-changes (ptk/type %)))))
@@ -1334,7 +1340,7 @@
(rx/debounce 5000)
(rx/tap #(log/trc :hint "buffer initialized")))]
(when (contains? cf/flags :component-thumbnails)
(when (and components-v2? (contains? cf/flags :component-thumbnails))
(->> (rx/merge
changes-s

View File

@@ -24,6 +24,7 @@
[app.main.data.helpers :as dsh]
[app.main.data.media :as dmm]
[app.main.data.notifications :as ntf]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.svg-upload :as svg]
[app.main.repo :as rp]
@@ -234,6 +235,16 @@
(rx/catch #(handle-media-error % on-error))
(rx/finalize #(st/emit! (ntf/hide :tag :media-loading))))))))
;; Deprecated in components-v2
(defn upload-media-asset
[params]
(let [params (assoc params
:force-media true
:local? false
:on-image #(st/emit! (dwl/add-media %))
:on-svg #(st/emit! (dwl/add-media %)))]
(process-media-objects params)))
(defn upload-media-workspace
[{:keys [position file-id] :as params}]
(let [params (assoc params
@@ -367,7 +378,7 @@
(defn- add-shapes-and-component
[it file-data page name [shape children]]
(let [[component-shape updated-shapes]
(let [[component-shape component-shapes updated-shapes]
(ctn/convert-shape-in-component shape children (:id file-data))
changes (-> (pcb/empty-changes it)
@@ -378,6 +389,7 @@
(pcb/add-component (:id component-shape)
""
name
component-shapes
updated-shapes
(:id shape)
(:id page)))]

View File

@@ -30,7 +30,6 @@
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.render-wasm.api :as wasm.api]
[app.render-wasm.shape :as wasm.shape]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@@ -163,46 +162,17 @@
change-to-fixed?
(assoc :grow-type :fixed))))
(defn- set-wasm-props!
[objects prev-wasm-props wasm-props]
(let [;; Set old value for previous properties
clean-props
(->> prev-wasm-props
(map (fn [[id {:keys [property] :as change}]]
(let [shape (get objects id)]
[id (assoc change :value (get shape property))]))))
wasm-props
(concat clean-props wasm-props)
wasm-props
(-> (group-by first wasm-props)
(update-vals #(map second %)))]
;; Props are grouped by id and then assoc to the shape the new value
(doseq [[id properties] wasm-props]
(let [shape
(->> properties
(reduce
(fn [shape {:keys [property value]}]
(assoc shape property value))
(get objects id)))]
;; With the new values to the shape change multi props
(wasm.shape/set-wasm-multi-attrs! shape (->> properties (map :property)))))))
(defn clear-local-transform []
(ptk/reify ::clear-local-transform
ptk/EffectEvent
(effect [_ state _]
(when (features/active-feature? state "render-wasm/v1")
(wasm.api/clean-modifiers)
(set-wasm-props! (dsh/lookup-page-objects state) (:wasm-props state) [])))
(wasm.api/set-modifiers nil)))
ptk/UpdateEvent
(update [_ state]
(-> state
(dissoc :workspace-modifiers :wasm-props :prev-wasm-props)
(dissoc :workspace-modifiers)
(dissoc :app.main.data.workspace.transforms/current-move-selected)))))
(defn create-modif-tree
@@ -447,57 +417,6 @@
modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree page-id params)]
(assoc state :workspace-modifiers modifiers))))))
(defn- parse-structure-modifiers
[modif-tree]
(into
[]
(mapcat
(fn [[parent-id data]]
(when (ctm/has-structure? (:modifiers data))
(->> data
:modifiers
:structure-parent
(mapcat
(fn [modifier]
(case (:type modifier)
:remove-children
(->> (:value modifier)
(map (fn [child-id]
{:type :remove-children
:parent parent-id
:id child-id
:index 0})))
:add-children
(->> (:value modifier)
(map (fn [child-id]
{:type :add-children
:parent parent-id
:id child-id
:index (:index modifier)})))
nil)))))))
modif-tree))
(defn- parse-geometry-modifiers
[modif-tree]
(into
[]
(keep
(fn [[id data]]
(when (ctm/has-geometry? (:modifiers data))
{:id id
:transform (ctm/modifiers->transform (:modifiers data))})))
modif-tree))
(defn- extract-property-changes
[modif-tree]
(->> modif-tree
(mapcat (fn [[id {:keys [modifiers]}]]
(->> (:structure-parent modifiers)
(map #(vector id %)))))
(filter (fn [[_ {:keys [type]}]]
(= type :change-property)))))
(defn set-wasm-modifiers
([modif-tree]
(set-wasm-modifiers modif-tree false))
@@ -510,31 +429,17 @@
([modif-tree _ignore-constraints _ignore-snap-pixel _params]
(ptk/reify ::set-wasm-modifiers
ptk/UpdateEvent
(update [_ state]
(let [property-changes
(extract-property-changes modif-tree)]
(-> state
(assoc :prev-wasm-props (:wasm-props state))
(assoc :wasm-props property-changes))))
ptk/EffectEvent
(effect [_ state _]
(wasm.api/clean-modifiers)
(effect [_ _ _]
(let [entries
(->> modif-tree
(mapv (fn [[id data]]
{:id id
:transform (ctm/modifiers->transform (:modifiers data))})))
(let [prev-wasm-props (:prev-wasm-props state)
wasm-props (:wasm-props state)
objects (dsh/lookup-page-objects state)]
(set-wasm-props! objects prev-wasm-props wasm-props)
(let [structure-entries (parse-structure-modifiers modif-tree)]
(wasm.api/set-structure-modifiers structure-entries)
(let [geometry-entries (parse-geometry-modifiers modif-tree)
modifiers-new
(wasm.api/propagate-modifiers geometry-entries)]
(wasm.api/set-modifiers modifiers-new))))))))
modifiers-new
(wasm.api/propagate-modifiers entries)]
(wasm.api/set-modifiers modifiers-new))))))
(defn set-selrect-transform
[modifiers]
@@ -749,3 +654,4 @@
(if undo-transation?
(rx/of (dwu/commit-undo-transaction undo-id))
(rx/empty))))))))

View File

@@ -97,17 +97,15 @@
;; Never call this directly but through the data-event `:layout/update`
;; Otherwise a lot of cycle dependencies could be generated
(defn- update-layout-positions
[{:keys [page-id ids undo-group]}]
[{:keys [ids undo-group]}]
(ptk/reify ::update-layout-positions
ptk/WatchEvent
(watch [_ state _]
(let [page-id (or page-id (:current-page-id state))
objects (dsh/lookup-page-objects state page-id)
(let [objects (dsh/lookup-page-objects state)
ids (->> ids (filter #(contains? objects %)))]
(if (d/not-empty? ids)
(let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))]
(rx/of (dwm/apply-modifiers {:page-id page-id
:modifiers modif-tree
(rx/of (dwm/apply-modifiers {:modifiers modif-tree
:stack-undo? true
:undo-group undo-group})))
(rx/empty))))))
@@ -129,9 +127,8 @@
(rx/filter #(d/not-empty? %))
(rx/map
(fn [data]
(let [page-id (->> data (keep :page-id) first)
ids (reduce #(into %1 (:ids %2)) #{} data)]
(update-layout-positions {:page-id page-id :ids ids}))))
(let [ids (reduce #(into %1 (:ids %2)) #{} data)]
(update-layout-positions {:ids ids}))))
(rx/take-until stopper))))))
(defn finalize-shape-layout
@@ -245,13 +242,12 @@
selected-shapes (map (d/getf objects) selected)
single? (= (count selected-shapes) 1)
is-frame? (= :frame (:type (first selected-shapes)))
has-layout? (ctl/any-layout? (first selected-shapes))
is-variant-cont? (ctc/is-variant-container? (first selected-shapes))
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(if (and single? is-frame? (not has-layout?))
(if (and single? is-frame? (not is-variant-cont?))
(create-layout-from-id (first selected) type :from-frame? true)
(create-layout-from-selection type))
(dwu/commit-undo-transaction undo-id))))))

View File

@@ -24,6 +24,7 @@
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@@ -170,10 +171,12 @@
page (dsh/get-page fdata page-id)
objects (:objects page)
components-v2 (features/active-feature? state "components/v2")
undo-id (or (:undo-id options) (js/Symbol))
[all-parents changes] (-> (pcb/empty-changes it (:id page))
(cls/generate-delete-shapes fdata page objects ids
{:ignore-touched (:component-swap options)
{:components-v2 components-v2
:ignore-touched (:component-swap options)
:undo-group (:undo-group options)
:undo-id undo-id}))]
@@ -234,8 +237,6 @@
([id parent-id index]
(create-artboard-from-selection id parent-id index nil))
([id parent-id index name]
(create-artboard-from-selection id parent-id index name nil))
([id parent-id index name delta]
(ptk/reify ::create-artboard-from-selection
ptk/WatchEvent
(watch [it state _]
@@ -259,9 +260,7 @@
selected
index
name
false
nil
delta)
false)
undo-id (js/Symbol)]

View File

@@ -464,11 +464,11 @@
:subsections [:panels]
:fn #(st/emit! (dcm/go-to-workspace :layout :assets))}
:toggle-history {:tooltip (ds/meta-alt "H")
:command (ds/ca-mod "h")
:toggle-history {:tooltip (ds/alt "H")
:command (ds/a-mod "h")
:subsections [:panels]
:fn #(emit-when-no-readonly
(dw/toggle-layout-flag :document-history))}
(dcm/go-to-workspace :layout :document-history))}
:toggle-colorpalette {:tooltip (ds/alt "P")
:command (ds/a-mod "p")

View File

@@ -10,7 +10,6 @@
[app.common.files.helpers :as cfh]
[app.common.logging :as l]
[app.common.thumbnails :as thc]
[app.common.types.component :as ctc]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
@@ -185,11 +184,11 @@
(rx/filter (ptk/type? ::clear-thumbnail))
(rx/filter #(= (deref %) object-id))))))))))
(defn- extract-frame-changes
(defn- extract-root-frame-changes
"Process a changes set in a commit to extract the frames that are changing"
[page-id [event [old-data new-data]]]
(let [changes (:changes event)
lookup-data-objects
(fn [data page-id]
(dm/get-in data [:pages-index page-id :objects]))
@@ -206,28 +205,21 @@
get-frame-ids
(fn get-frame-ids [id]
(let [old-objects (lookup-data-objects old-data page-id)
new-objects (lookup-data-objects new-data page-id)
(let [old-objects (lookup-data-objects old-data page-id)
new-objects (lookup-data-objects new-data page-id)
new-shape (get new-objects id)
old-shape (get old-objects id)
new-shape (get new-objects id)
old-shape (get old-objects id)
old-frame-id (if (cfh/frame-shape? old-shape) id (:frame-id old-shape))
new-frame-id (if (cfh/frame-shape? new-shape) id (:frame-id new-shape))
root-frame-old? (cfh/root-frame? old-objects old-frame-id)
root-frame-new? (cfh/root-frame? new-objects new-frame-id)
instance-root? (ctc/instance-root? new-shape)]
old-frame-id (if (cfh/frame-shape? old-shape) id (:frame-id old-shape))
new-frame-id (if (cfh/frame-shape? new-shape) id (:frame-id new-shape))]
(cond-> #{}
root-frame-old?
(conj ["frame" old-frame-id])
(cfh/root-frame? old-objects old-frame-id)
(conj old-frame-id)
root-frame-new?
(conj ["frame" new-frame-id])
instance-root?
(conj ["component" id])
(cfh/root-frame? new-objects new-frame-id)
(conj new-frame-id)
(and (uuid? (:frame-id old-shape))
(not= uuid/zero (:frame-id old-shape)))
@@ -274,7 +266,7 @@
(rx/map deref)
(rx/observe-on :async)
(rx/with-latest-from workspace-data-s)
(rx/merge-map (partial extract-frame-changes page-id))
(rx/merge-map (partial extract-root-frame-changes page-id))
(rx/tap #(l/trc :hint "inconming change" :origin "all" :frame-id (dm/str %)))
(rx/share))
@@ -289,15 +281,15 @@
;; and interrupt any ongoing update-thumbnail process
;; related to current frame-id
(->> all-commits-s
(rx/mapcat (fn [[tag frame-id]]
(rx/of (clear-thumbnail file-id page-id frame-id tag)))))
(rx/mapcat (fn [frame-id]
(rx/of (clear-thumbnail file-id page-id frame-id "frame")
(clear-thumbnail file-id page-id frame-id "component")))))
;; Generate thumbnails in batches, once user becomes
;; inactive for some instant.
(->> all-commits-s
(rx/buffer-until notifier-s)
(rx/mapcat #(into #{} %))
(rx/map (fn [[tag frame-id]]
(update-thumbnail file-id page-id frame-id tag "watch-state-changes")))))
(rx/map #(update-thumbnail file-id page-id % "frame" "watch-state-changes"))))
(rx/take-until stopper-s))))))

View File

@@ -1,21 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.tokens.color
(:require
[app.common.files.tokens :as cft]
[app.main.data.tinycolor :as tinycolor]))
(defn color-bullet-color [token-color-value]
(when-let [tc (tinycolor/valid-color token-color-value)]
(if (tinycolor/alpha tc)
{:color (tinycolor/->hex-string tc)
:opacity (tinycolor/alpha tc)}
(tinycolor/->hex-string tc))))
(defn resolved-token-bullet-color [{:keys [resolved-value] :as token}]
(when (and resolved-value (cft/color-token? token))
(color-bullet-color resolved-value)))

View File

@@ -11,7 +11,7 @@
[app.common.types.tokens-lib :as ctob]
[app.main.data.helpers :as dsh]))
(defn- get-selected-token-set-name [state]
(defn get-selected-token-set-name [state]
(or (get-in state [:workspace-tokens :selected-token-set-name])
(some-> (dsh/lookup-file-data state)
(get :tokens-lib)

View File

@@ -8,16 +8,13 @@
(:require
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv]
[app.common.geom.point :as gpt]
[app.common.logic.variant-properties :as clvp]
[app.common.logic.libraries :as cll]
[app.common.logic.variants :as clv]
[app.common.types.component :as ctc]
[app.common.types.components-list :as ctkl]
[app.common.types.shape.layout :as ctsl]
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
@@ -26,65 +23,23 @@
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.util.dom :as dom]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn update-properties-names-and-values
"Compares the previous properties with the updated ones and executes the correspondent action
for each one depending on if it needs to be removed, updated or added"
[component-id variant-id previous-properties updated-properties]
(ptk/reify ::update-properties-names-and-values
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local dissoc :shape-for-rename))
(dm/export clv/find-related-components)
ptk/WatchEvent
(watch [it state _]
(let [page-id (:current-page-id state)
data (dsh/lookup-file-data state)
objects (-> (dsh/get-page data page-id)
(get :objects))
properties-to-remove (ctv/find-properties-to-remove previous-properties updated-properties)
properties-to-add (ctv/find-properties-to-add previous-properties updated-properties)
properties-to-update (ctv/find-properties-to-update previous-properties updated-properties)
changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
(pcb/with-library-data data))
changes (reduce
(fn [changes {:keys [name]}]
(-> changes
(clvp/generate-update-property-value component-id (ctv/find-index-for-property-name previous-properties name) "")))
changes
properties-to-remove)
changes (reduce
(fn [changes {:keys [name value]}]
(-> changes
(clvp/generate-update-property-value component-id (ctv/find-index-for-property-name previous-properties name) value)))
changes
properties-to-update)
changes (reduce
(fn [changes [idx {:keys [name value]}]]
(-> changes
(clvp/generate-add-new-property variant-id {:property-name name})
(clvp/generate-update-property-value component-id (+ idx (count previous-properties)) value)))
changes
(map-indexed vector properties-to-add))
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(dwu/commit-undo-transaction undo-id))))))
(defn is-secondary-variant?
[component data]
(if-let [variant-id (:variant-id component)]
(let [page-id (:main-instance-page component)
objects (-> (dsh/get-page data page-id)
(get :objects))
shapes (dm/get-in objects [variant-id :shapes])]
(not= (:main-instance-id component) (last shapes)))
false))
(defn update-property-name
"Update the variant property name on the position pos
@@ -101,7 +56,7 @@
changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
(pcb/with-library-data data)
(clvp/generate-update-property-name variant-id pos new-name))
(clv/generate-update-property-name variant-id pos new-name))
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
@@ -122,7 +77,7 @@
changes (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data)
(pcb/with-objects objects)
(clvp/generate-update-property-value component-id pos value))
(clv/generate-update-property-value component-id pos value))
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
@@ -145,7 +100,7 @@
changes (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data)
(pcb/with-objects objects)
(clvp/generate-remove-property variant-id pos))
(clv/generate-remove-property variant-id pos))
undo-id (js/Symbol)]
(rx/of
@@ -169,7 +124,7 @@
changes (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data)
(pcb/with-objects objects)
(clvp/generate-add-new-property variant-id options))
(clv/generate-add-new-property variant-id options))
undo-id (js/Symbol)]
(rx/of
@@ -177,7 +132,7 @@
(dch/commit-changes changes)
(dwu/commit-undo-transaction undo-id))))))
(defn- set-variant-id
(defn set-variant-id
"Sets the variant-id on a component"
[component-id variant-id]
(ptk/reify ::set-variant-id
@@ -194,7 +149,7 @@
(dch/commit-changes changes)
(dwu/commit-undo-transaction undo-id))))))
(defn- focus-property
(defn focus-property
[shape-id prop-num]
(ptk/reify ::focus-property
ptk/EffectEvent
@@ -202,25 +157,6 @@
(dom/focus! (dom/get-element (str "variant-prop-" shape-id prop-num))))))
(defn- resposition-and-resize-variant
"Resize the variant container, and move the shape (that is a variant) to the right"
[shape-id]
(ptk/reify ::resposition-and-resize-variant
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
shape (get objects shape-id)
container (get objects (:parent-id shape))
width (+ (:width container) (:width shape) 20) ;; 20 is the default gap for variants
x (- width (+ (:width shape) 30))] ;; 30 is the default margin for variants
(rx/of
(dwt/update-dimensions [(:parent-id shape)] :width width)
(dwt/update-position shape-id
{:x x}
{:absolute? false}))))))
(defn add-new-variant
"Create a new variant and add it to the variant-container"
[shape-id]
@@ -238,28 +174,39 @@
component-id (:component-id shape)
component (ctkl/get-component data component-id)
container-id (:parent-id shape)
variant-container (get objects container-id)
has-layout? (ctsl/any-layout? variant-container)
new-component-id (uuid/next)
new-shape-id (uuid/next)
value (str clv/value-prefix
(-> (clv/extract-properties-values data objects (:variant-id component))
last
:value
count
inc))
prop-num (dec (count (:variant-properties component)))
changes (-> (pcb/empty-changes it page-id)
[new-shape changes] (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data)
(pcb/with-objects objects)
(pcb/with-page-id page-id)
(clv/generate-add-new-variant shape (:variant-id component) new-component-id new-shape-id prop-num))
(cll/generate-duplicate-component
{:data data}
component-id
new-component-id
true
{:new-shape-id new-shape-id :apply-changes-local-library? true}))
changes (-> changes
(clv/generate-update-property-value new-component-id prop-num value)
(pcb/change-parent (:parent-id shape) [new-shape] 0))
undo-id (js/Symbol)]
(rx/concat
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(when-not has-layout?
(resposition-and-resize-variant new-shape-id))
(dwu/commit-undo-transaction undo-id)
(ptk/data-event :layout/update {:ids [(:parent-id shape)]})
(dws/select-shape new-shape-id))
@@ -279,7 +226,7 @@
page-id (:current-page-id state)
objects (dsh/lookup-page-objects state file-id page-id)
main (get objects main-instance-id)
parent (get objects (:parent-id main))
main-id (:id main)
component-id (:component-id main)
cpath (cfh/split-path (:name main))
name (first cpath)
@@ -303,17 +250,9 @@
:stroke-color "#bb97d8" ;; todo use color var?
:stroke-opacity 1
:stroke-width 2}
;; Move the position of the variant container so the main shape doesn't
;; change its position
delta (if (ctsl/any-layout? parent)
(gpt/point 0 0)
(gpt/point -30 -30))
undo-id (js/Symbol)]
;;TODO Refactor all called methods in order to be able to
;;generate changes instead of call the events
(rx/concat
(rx/of
(dwu/start-undo-transaction undo-id)
@@ -322,11 +261,11 @@
(dwl/rename-component component-id name))
;; Create variant container
(dwsh/create-artboard-from-selection variant-id nil nil nil delta)
(dwsh/create-artboard-from-selection variant-id)
(cl/remove-all-fills variant-vec {:color clr/black :opacity 1})
(dwsl/create-layout-from-id variant-id :flex)
(dwsh/update-shapes variant-vec #(merge % cont-props))
(dwsh/update-shapes [main-instance-id] #(merge % main-props))
(dwsh/update-shapes [main-id] #(merge % main-props))
(cl/add-stroke variant-vec stroke-props)
(set-variant-id component-id variant-id))
@@ -344,11 +283,9 @@
(rx/of
(add-new-variant main-instance-id)
(dwu/commit-undo-transaction undo-id)
(ptk/data-event :layout/update {:ids [variant-id]})))))))
(dwu/commit-undo-transaction undo-id)))))))
(defn add-component-or-variant
"Manage the shared shortcut, and do the pertinent action"
[]
(ptk/reify ::add-component-or-variant
@@ -382,7 +319,6 @@
(rx/of (dwl/add-component)))))))
(defn duplicate-or-add-variant
"Manage the shared shortcut, and do the pertinent action"
[]
(ptk/reify ::duplicate-or-add-variant
ptk/WatchEvent
@@ -400,43 +336,3 @@
(rx/from (map add-new-variant selected-ids))
(rx/of (dwu/commit-undo-transaction undo-id)))
(rx/of (dws/duplicate-selected true)))))))
(defn rename-variant
"Rename the variant container and all components belonging to this variant"
[variant-id name]
(ptk/reify ::rename-variant
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
data (dsh/lookup-file-data state)
objects (-> (dsh/get-page data page-id)
(get :objects))
variant-components (cfv/find-variant-components data objects variant-id)
clean-name (cfh/clean-path name)
undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id)
(dwsh/update-shapes [variant-id] #(assoc % :name clean-name)))
(rx/from (map
#(dwl/rename-component-and-main-instance (:id %) clean-name)
variant-components))
(rx/of (dwu/commit-undo-transaction undo-id)))))))
(defn rename-comp-or-variant-and-main
"If the component is in a variant, rename the variant.
If it is not, rename the component and its main"
[component-id name]
(ptk/reify ::rename-comp-or-variant-and-main
ptk/WatchEvent
(watch [_ state _]
(let [data (dsh/lookup-file-data state)
component (ctkl/get-component data component-id)]
(if (ctc/is-variant? component)
(rx/of (rename-variant (:variant-id component) name))
(rx/of (dwl/rename-component-and-main-instance component-id name)))))))

View File

@@ -260,14 +260,6 @@
(let [message (tr "errors.feature-not-supported" (:feature error))]
(st/emit! (modal/show {:type :alert :message message})))
(= :file-in-components-v1 code)
(st/emit! (modal/show {:type :alert
:message (tr "errors.deprecated")
:link-message {:before (tr "errors.deprecated.contact.before")
:text (tr "errors.deprecated.contact.text")
:after (tr "errors.deprecated.contact.after")
:on-click #(st/emit! (rt/nav :settings-feedback))}}))
:else
(print-cause! "Restriction Error" error)))

View File

@@ -7,38 +7,33 @@
(ns app.main.fonts
"A fonts loading macros."
(:require
[app.common.uuid :as uuid]
[clojure.data.json :as json]
[clojure.java.io :as io]
[cuerdas.core :as str]))
(defn- parse-gfont-variant
[variant files]
[variant]
(cond
(= "regular" variant)
{:id "regular" :name "regular" :weight "400" :style "normal" :ttf-url (get files "regular")}
{:id "regular" :name "regular" :weight "400" :style "normal"}
(= "italic" variant)
{:id "italic" :name "italic" :weight "400" :style "italic" :ttf-url (get files "italic")}
{:id "italic" :name "italic" :weight "400" :style "italic"}
:else
(when-let [[id weight style] (re-find #"^(\d+)(.*)$" variant)]
{:id id
:name variant
:weight weight
:style (if (str/empty? style) "normal" style)
:ttf-url (get files id)})))
(when-let [[a b c] (re-find #"^(\d+)(.*)$" variant)]
(if (str/empty? c)
{:id a :name b :weight b :style "normal"}
{:id a :name (str b " (" c ")") :weight b :style c}))))
(defn- parse-gfont
[font]
(let [family (get font "family")
variants (get font "variants")
files (get font "files")]
variants (get font "variants")]
{:id (str "gfont-" (str/slug family))
:uuid (uuid/random)
:family family
:name family
:variants (into [] (comp (map (fn [variant] (parse-gfont-variant variant files)))
:variants (into [] (comp (map parse-gfont-variant)
(filter identity))
variants)}))

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