Merge remote-tracking branch 'penpot/develop' into token-studio-develop

This commit is contained in:
Florian Schroedl
2024-05-07 14:39:33 +02:00
76 changed files with 3388 additions and 2445 deletions

View File

@@ -93,7 +93,7 @@ jobs:
working_directory: "./common" working_directory: "./common"
command: | command: |
yarn test yarn test
clojure -X:dev:test :patterns '["common-tests.*-test"]' clojure -M:dev:test
- run: - run:
name: "frontend tests" name: "frontend tests"

View File

@@ -43,8 +43,7 @@ Penpot is available on browser and [self host](https://penpot.app/self-host). It
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. 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.
🎇 **Penpot Fest is back!** Our design, code & Open Source event is happening in Barcelona | June 5-7th. [Get your tickets](https://www.eventbrite.es/e/penpot-fest-2024-tickets-859331883797) to join other designers and developers from open-source communities and beyond. 🎇 **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)!
Check out the highlights from [Penpot Fest 2023 edition](https://www.youtube.com/watch?v=sOpLZaK5mDc)!
## Table of contents ## ## Table of contents ##

View File

@@ -12,7 +12,6 @@
[app.common.files.changes :as cp] [app.common.files.changes :as cp]
[app.common.files.changes-builder :as fcb] [app.common.files.changes-builder :as fcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.libraries-helpers :as cflh]
[app.common.files.migrations :as fmg] [app.common.files.migrations :as fmg]
[app.common.files.shapes-helpers :as cfsh] [app.common.files.shapes-helpers :as cfsh]
[app.common.files.validate :as cfv] [app.common.files.validate :as cfv]
@@ -23,6 +22,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.path :as gshp] [app.common.geom.shapes.path :as gshp]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.logic.libraries :as cll]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.svg :as csvg] [app.common.svg :as csvg]
@@ -1451,14 +1451,14 @@
(cons shape children)) (cons shape children))
[_ _ changes] [_ _ changes]
(cflh/generate-add-component changes (cll/generate-add-component changes
[shape] [shape]
(:objects page) (:objects page)
(:id page) (:id page)
file-id file-id
true true
nil nil
cfsh/prepare-create-artboard-from-selection)] cfsh/prepare-create-artboard-from-selection)]
(shape-cb shape) (shape-cb shape)
(:redo-changes changes))) (:redo-changes changes)))

View File

@@ -6,13 +6,10 @@
(ns app.rpc.commands.files-create (ns app.rpc.commands.files-create
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.features :as cfeat] [app.common.features :as cfeat]
[app.common.files.defaults :refer [version]]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
[app.features.fdata :as feat.fdata] [app.features.fdata :as feat.fdata]
@@ -40,7 +37,7 @@
(defn create-file (defn create-file
[{:keys [::db/conn] :as cfg} [{:keys [::db/conn] :as cfg}
{:keys [id name project-id is-shared revn {:keys [id name project-id is-shared revn
modified-at deleted-at create-page modified-at deleted-at create-page page-id
ignore-sync-until features] ignore-sync-until features]
:or {is-shared false revn 0 create-page true} :or {is-shared false revn 0 create-page true}
:as params}] :as params}]
@@ -51,23 +48,17 @@
(binding [pmap/*tracked* (pmap/create-tracked) (binding [pmap/*tracked* (pmap/create-tracked)
cfeat/*current* features] cfeat/*current* features]
(let [id (or id (uuid/next)) (let [file (ctf/make-file {:id id
:project-id project-id
data (if create-page :name name
(ctf/make-file-data id) :revn revn
(ctf/make-file-data id nil)) :is-shared is-shared
:features features
file {:id id :ignore-sync-until ignore-sync-until
:project-id project-id :modified-at modified-at
:name name :deleted-at deleted-at
:revn revn :create-page create-page
:is-shared is-shared :page-id page-id})
:version version
:data data
:features features
:ignore-sync-until ignore-sync-until
:modified-at modified-at
:deleted-at deleted-at}
file (if (contains? features "fdata/objects-map") file (if (contains? features "fdata/objects-map")
(feat.fdata/enable-objects-map file) (feat.fdata/enable-objects-map file)
@@ -75,9 +66,7 @@
file (if (contains? features "fdata/pointer-map") file (if (contains? features "fdata/pointer-map")
(feat.fdata/enable-pointer-map file) (feat.fdata/enable-pointer-map file)
file) file)]
file (d/without-nils file)]
(db/insert! conn :file (db/insert! conn :file
(-> file (-> file
@@ -86,9 +75,9 @@
{::db/return-keys false}) {::db/return-keys false})
(when (contains? features "fdata/pointer-map") (when (contains? features "fdata/pointer-map")
(feat.fdata/persist-pointers! cfg id)) (feat.fdata/persist-pointers! cfg (:id file)))
(->> (assoc params :file-id id :role :owner) (->> (assoc params :file-id (:id file) :role :owner)
(create-file-role! conn)) (create-file-role! conn))
(db/update! conn :project (db/update! conn :project

View File

@@ -16,6 +16,7 @@
[app.db.sql :as sql] [app.db.sql :as sql]
[app.features.components-v2 :as feat.compv2] [app.features.components-v2 :as feat.compv2]
[app.features.fdata :as fdata] [app.features.fdata :as fdata]
[app.loggers.audit :as audit]
[app.rpc :as-alias rpc] [app.rpc :as-alias rpc]
[app.rpc.commands.files :as files] [app.rpc.commands.files :as files]
[app.rpc.commands.files-create :as files.create] [app.rpc.commands.files-create :as files.create]
@@ -23,6 +24,7 @@
[app.rpc.commands.projects :as projects] [app.rpc.commands.projects :as projects]
[app.rpc.commands.teams :as teams] [app.rpc.commands.teams :as teams]
[app.rpc.doc :as-alias doc] [app.rpc.doc :as-alias doc]
[app.rpc.helpers :as rph]
[app.util.blob :as blob] [app.util.blob :as blob]
[app.util.pointer-map :as pmap] [app.util.pointer-map :as pmap]
[app.util.services :as sv] [app.util.services :as sv]
@@ -100,7 +102,9 @@
:revn revn :revn revn
:data nil :data nil
:changes (blob/encode changes)}) :changes (blob/encode changes)})
nil))) (rph/with-meta (rph/wrap nil)
{::audit/replace-props {:file-id id
:revn revn}}))))
;; --- MUTATION COMMAND: persist-temp-file ;; --- MUTATION COMMAND: persist-temp-file

View File

@@ -76,12 +76,8 @@
:ns-default build} :ns-default build}
:test :test
{:extra-paths ["test"] {:main-opts ["-m" "kaocha.runner"]
:extra-deps :extra-deps {lambdaisland/kaocha {:mvn/version "1.88.1376"}}}
{io.github.cognitect-labs/test-runner
{:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}
:shadow-cljs :shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]} {:main-opts ["-m" "shadow.cljs.devtools.cli"]}

View File

@@ -147,7 +147,7 @@
[file data] [file data]
(dm/assert! (nil? (:current-component-id file))) (dm/assert! (nil? (:current-component-id file)))
(let [page-id (or (:id data) (uuid/next)) (let [page-id (or (:id data) (uuid/next))
page (-> (ctp/make-empty-page page-id "Page 1") page (-> (ctp/make-empty-page {:id page-id :name "Page 1"})
(d/deep-merge data))] (d/deep-merge data))]
(-> file (-> file
(commit-change (commit-change

View File

@@ -596,7 +596,7 @@
(ex/raise :type :conflict (ex/raise :type :conflict
:hint "id+name or page should be provided, never both")) :hint "id+name or page should be provided, never both"))
(let [page (if (and (string? name) (uuid? id)) (let [page (if (and (string? name) (uuid? id))
(ctp/make-empty-page id name) (ctp/make-empty-page {:id id :name name})
page)] page)]
(ctpl/add-page data page))) (ctpl/add-page data page)))

View File

@@ -4,7 +4,7 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.files.libraries-helpers (ns app.common.logic.libraries
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
@@ -14,6 +14,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.grid-layout :as gslg] [app.common.geom.shapes.grid-layout :as gslg]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.logic.shapes :as cls]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
@@ -21,10 +22,8 @@
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@@ -41,7 +40,6 @@
(declare generate-sync-shape-direct) (declare generate-sync-shape-direct)
(declare generate-sync-shape-direct-recursive) (declare generate-sync-shape-direct-recursive)
(declare generate-sync-shape-inverse)
(declare generate-sync-shape-inverse-recursive) (declare generate-sync-shape-inverse-recursive)
(declare compare-children) (declare compare-children)
@@ -166,7 +164,7 @@
[new-component-shape new-component-shapes ; <- null in components-v2 [new-component-shape new-component-shapes ; <- null in components-v2
new-main-instance-shape new-main-instance-shapes] new-main-instance-shape new-main-instance-shapes]
(duplicate-component (:data library) component new-component-id)] (duplicate-component component new-component-id (:data library))]
(-> changes (-> changes
(pcb/with-page main-instance-page) (pcb/with-page main-instance-page)
@@ -847,7 +845,6 @@
reset? reset?
components-v2)))) components-v2))))
(defn generate-rename-component (defn generate-rename-component
"Generate the changes for rename the component with the given id, in the current file library." "Generate the changes for rename the component with the given id, in the current file library."
[changes id new-name library-data components-v2] [changes id new-name library-data components-v2]
@@ -867,9 +864,6 @@
(pcb/with-library-data library-data) (pcb/with-library-data library-data)
(pcb/update-component id update-fn)))) (pcb/update-component id update-fn))))
(defn generate-sync-shape-inverse (defn generate-sync-shape-inverse
"Generate changes to update the component a shape is linked to, from "Generate changes to update the component a shape is linked to, from
the values in the shape and all its children." the values in the shape and all its children."
@@ -1821,175 +1815,6 @@
(pcb/with-objects (:objects container)) (pcb/with-objects (:objects container))
(generate-detach-instance container libraries id)))) (generate-detach-instance container libraries id))))
(defn generate-update-shape-flags
[changes ids objects {:keys [blocked hidden] :as flags}]
(let [update-fn
(fn [obj]
(cond-> obj
(boolean? blocked) (assoc :blocked blocked)
(boolean? hidden) (assoc :hidden hidden)))
ids (if (boolean? blocked)
(into ids (->> ids (mapcat #(cfh/get-children-ids objects %))))
ids)]
(-> changes
(pcb/update-shapes ids update-fn {:attrs #{:blocked :hidden}}))))
(defn generate-delete-shapes
[changes file page objects ids {:keys [components-v2 ignore-touched component-swap]}]
(let [ids (cfh/clean-loops objects ids)
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))))
[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 []])
changes (-> changes
(pcb/with-page page)
(pcb/with-objects objects)
(pcb/with-library-data file))
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)
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))
ids-set (set ids-to-delete)
guides-to-remove
(->> (dm/get-in page [:options :guides])
(vals)
(filter #(contains? ids-set (:frame-id %)))
(map :id))
guides
(->> guides-to-remove
(reduce dissoc (dm/get-in page [:options :guides])))
starting-flows
(filter (fn [flow]
;; If any of the deleted is a frame that starts a flow,
;; this must be deleted, too.
(contains? ids-to-delete (:starting-frame flow)))
(-> page :options :flows))
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)
ids-to-delete)
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? %)))
(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
;; Any parent whose children are all deleted, must be deleted too.
(into (d/ordered-set) (find-all-empty-parents #{}))
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))
[])
changes (-> changes
(pcb/set-page-option :guides guides))
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)
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)))))
(cond-> (seq starting-flows)
(pcb/update-page-option :flows (fn [flows]
(->> (map :id starting-flows)
(reduce ctp/remove-flow flows))))))]
[all-parents changes]))
(defn generate-new-shape-for-swap (defn generate-new-shape-for-swap
[changes shape file page libraries id-new-component index target-cell keep-props-values] [changes shape file page libraries id-new-component index target-cell keep-props-values]
(let [objects (:objects page) (let [objects (:objects page)
@@ -2040,8 +1865,8 @@
[changes objects shape file page libraries id-new-component index target-cell keep-props-values] [changes objects shape file page libraries id-new-component index target-cell keep-props-values]
(let [[all-parents changes] (let [[all-parents changes]
(-> changes (-> changes
(generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:components-v2 true (cls/generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:components-v2 true
:component-swap true})) :component-swap true}))
[new-shape changes] [new-shape changes]
(-> changes (-> changes
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))] (generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]

View File

@@ -0,0 +1,519 @@
;; 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.shapes
(:require
[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.geom.shapes :as gsh]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.page :as ctp]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]))
(defn generate-update-shapes
[changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}]
(let [changes (reduce
(fn [changes id]
(let [opts {:attrs attrs
:ignore-geometry? (get ignore-tree id)
:ignore-touched ignore-touched
:with-objects? with-objects?}]
(pcb/update-shapes changes [id] update-fn (d/without-nils opts))))
(-> changes
(pcb/with-objects objects))
ids)
grid-ids (->> ids (filter (partial ctl/grid-layout? objects)))
changes (pcb/update-shapes changes grid-ids ctl/assign-cell-positions {:with-objects? true})
changes (pcb/reorder-grid-children changes ids)]
changes))
(defn- generate-update-shape-flags
[changes ids objects {:keys [blocked hidden] :as flags}]
(let [update-fn
(fn [obj]
(cond-> obj
(boolean? blocked) (assoc :blocked blocked)
(boolean? hidden) (assoc :hidden hidden)))
ids (if (boolean? blocked)
(into ids (->> ids (mapcat #(cfh/get-children-ids objects %))))
ids)]
(-> changes
(pcb/update-shapes ids update-fn {:attrs #{:blocked :hidden}}))))
(defn generate-delete-shapes
[changes file page objects ids {:keys [components-v2 ignore-touched component-swap]}]
(let [ids (cfh/clean-loops objects ids)
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))))
[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 []])
changes (-> changes
(pcb/with-page page)
(pcb/with-objects objects)
(pcb/with-library-data file))
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)
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))
ids-set (set ids-to-delete)
guides-to-remove
(->> (dm/get-in page [:options :guides])
(vals)
(filter #(contains? ids-set (:frame-id %)))
(map :id))
guides
(->> guides-to-remove
(reduce dissoc (dm/get-in page [:options :guides])))
starting-flows
(filter (fn [flow]
;; If any of the deleted is a frame that starts a flow,
;; this must be deleted, too.
(contains? ids-to-delete (:starting-frame flow)))
(-> page :options :flows))
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)
ids-to-delete)
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? %)))
(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
;; Any parent whose children are all deleted, must be deleted too.
(into (d/ordered-set) (find-all-empty-parents #{}))
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))
[])
changes (-> changes
(pcb/set-page-option :guides guides))
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)
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)))))
(cond-> (seq starting-flows)
(pcb/update-page-option :flows (fn [flows]
(->> (map :id starting-flows)
(reduce ctp/remove-flow flows))))))]
[all-parents changes]))
(defn generate-relocate-shapes [changes objects parents parent-id page-id to-index ids]
(let [groups-to-delete
(loop [current-id (first parents)
to-check (rest parents)
removed-id? (set ids)
result #{}]
(if-not current-id
;; Base case, no next element
result
(let [group (get objects current-id)]
(if (and (not= :frame (:type group))
(not= current-id parent-id)
(empty? (remove removed-id? (:shapes group))))
;; Adds group to the remove and check its parent
(let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])]
(recur (first to-check)
(rest to-check)
(conj removed-id? current-id)
(conj result current-id)))
;; otherwise recur
(recur (first to-check)
(rest to-check)
removed-id?
result)))))
groups-to-unmask
(reduce (fn [group-ids id]
;; When a masked group loses its mask shape, because it's
;; moved outside the group, the mask condition must be
;; removed, and it must be converted to a normal group.
(let [obj (get objects id)
parent (get objects (:parent-id obj))]
(if (and (:masked-group parent)
(= id (first (:shapes parent)))
(not= (:id parent) parent-id))
(conj group-ids (:id parent))
group-ids)))
#{}
ids)
;; TODO: Probably implementing this using loop/recur will
;; be more efficient than using reduce and continuous data
;; desturcturing.
;; Sets the correct components metadata for the moved shapes
;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside
;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component
;; `shapes-to-reroot` Adds a root flag when a nested component instance is moved outside
[shapes-to-detach shapes-to-deroot shapes-to-reroot]
(reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id]
(let [shape (get objects id)
parent (get objects parent-id)
component-shape (ctn/get-component-shape objects shape)
component-shape-parent (ctn/get-component-shape objects parent {:allow-main? true})
root-parent (ctn/get-instance-root objects parent)
detach? (and (ctk/in-component-copy-not-head? shape)
(not= (:id component-shape)
(:id component-shape-parent)))
deroot? (and (ctk/instance-root? shape)
root-parent)
reroot? (and (ctk/subinstance-head? shape)
(not component-shape-parent))
ids-to-detach (when detach?
(cons id (cfh/get-children-ids objects id)))]
[(cond-> shapes-to-detach detach? (into ids-to-detach))
(cond-> shapes-to-deroot deroot? (conj id))
(cond-> shapes-to-reroot reroot? (conj id))]))
[[] [] []]
(->> ids
(mapcat #(ctn/get-child-heads objects %))
(map :id)))
shapes-to-unconstraint ids
ordered-indexes (cfh/order-by-indexed-shapes objects ids)
shapes (map (d/getf objects) ordered-indexes)
parent (get objects parent-id)
component-main-parent (ctn/find-component-main objects parent false)
child-heads
(->> ordered-indexes
(mapcat #(ctn/get-child-heads objects %))
(map :id))]
(-> changes
(pcb/with-page-id page-id)
(pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? parent))
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data))
;; Remove the hide in viewer flag
(cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent))
(pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))))
;; Remove the swap slots if it is moving to a different component
(pcb/update-shapes child-heads
(fn [shape]
(cond-> shape
(not= component-main-parent (ctn/find-component-main objects shape false))
(ctk/remove-swap-slot))))
;; Add component-root property when moving a component outside a component
(cond-> (not (ctn/get-instance-root objects parent))
(pcb/update-shapes child-heads #(assoc % :component-root true)))
;; Move the shapes
(pcb/change-parent parent-id
shapes
to-index)
;; Remove empty groups
(pcb/remove-objects groups-to-delete)
;; Unmask groups whose mask have moved outside
(pcb/update-shapes groups-to-unmask
(fn [shape]
(assoc shape :masked-group false)))
;; Detach shapes moved out of their component
(pcb/update-shapes shapes-to-detach ctk/detach-shape)
;; Make non root a component moved inside another one
(pcb/update-shapes shapes-to-deroot
(fn [shape]
(assoc shape :component-root nil)))
;; Make root a subcomponent moved outside its parent component
(pcb/update-shapes shapes-to-reroot
(fn [shape]
(assoc shape :component-root true)))
;; Reset constraints depending on the new parent
(pcb/update-shapes shapes-to-unconstraint
(fn [shape]
(let [frame-id (if (= (:type parent) :frame)
(:id parent)
(:frame-id parent))
moved-shape (assoc shape
:parent-id parent-id
:frame-id frame-id)]
(assoc shape
:constraints-h (gsh/default-constraints-h moved-shape)
:constraints-v (gsh/default-constraints-v moved-shape))))
{:ignore-touched true})
;; Fix the sizing when moving a shape
(pcb/update-shapes parents
(fn [parent]
(if (ctl/flex-layout? parent)
(cond-> parent
(ctl/change-h-sizing? (:id parent) objects (:shapes parent))
(assoc :layout-item-h-sizing :fix)
(ctl/change-v-sizing? (:id parent) objects (:shapes parent))
(assoc :layout-item-v-sizing :fix))
parent)))
;; Update grid layout
(cond-> (ctl/grid-layout? objects parent-id)
(pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index)))
(pcb/update-shapes parents
(fn [parent objects]
(cond-> parent
(ctl/grid-layout? parent)
(ctl/assign-cells objects)))
{:with-objects? true})
(pcb/reorder-grid-children parents)
;; If parent locked, lock the added shapes
(cond-> (:blocked parent)
(pcb/update-shapes ordered-indexes #(assoc % :blocked true)))
;; Resize parent containers that need to
(pcb/resize-parents parents))))
(defn generate-move-shapes-to-frame
[changes ids frame-id page-id objects drop-index [row column :as cell]]
(let [lookup (d/getf objects)
frame (get objects frame-id)
layout? (:layout frame)
component-main-frame (ctn/find-component-main objects frame false)
shapes (->> ids
(cfh/clean-loops objects)
(keep lookup)
;;remove shapes inside copies, because we can't change the structure of copies
(remove #(ctk/in-component-copy? (get objects (:parent-id %)))))
moving-shapes
(cond->> shapes
(not layout?)
(remove #(= (:frame-id %) frame-id))
layout?
(remove #(and (= (:frame-id %) frame-id)
(not= (:parent-id %) frame-id))))
ordered-indexes (cfh/order-by-indexed-shapes objects (map :id moving-shapes))
moving-shapes (map (d/getf objects) ordered-indexes)
all-parents
(reduce (fn [res id]
(into res (cfh/get-parent-ids objects id)))
(d/ordered-set)
ids)
find-all-empty-parents
(fn recursive-find-empty-parents [empty-parents]
(let [all-ids (into empty-parents ids)
contains? (partial contains? all-ids)
xform (comp (map lookup)
(filter cfh/group-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
;; Any empty parent whose children are moved to another frame should be deleted
(if (empty? moving-shapes)
#{}
(into (d/ordered-set) (find-all-empty-parents #{})))
;; Not move absolute shapes that won't change parent
moving-shapes
(->> moving-shapes
(remove (fn [shape]
(and (ctl/position-absolute? shape)
(= frame-id (:parent-id shape))))))
frame-component
(ctn/get-component-shape objects frame)
shape-ids-to-detach
(reduce (fn [result shape]
(if (and (some? shape) (ctk/in-component-copy-not-head? shape))
(let [shape-component (ctn/get-component-shape objects shape)]
(if (= (:id frame-component) (:id shape-component))
result
(into result (cfh/get-children-ids-with-self objects (:id shape)))))
result))
#{}
moving-shapes)
moving-shapes-ids
(map :id moving-shapes)
moving-shapes-children-ids
(->> moving-shapes-ids
(mapcat #(cfh/get-children-ids-with-self objects %)))
child-heads
(->> moving-shapes-ids
(mapcat #(ctn/get-child-heads objects %))
(map :id))]
(-> changes
(pcb/with-page-id page-id)
(pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? objects frame-id))
(pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data))
;; Remove the swap slots if it is moving to a different component
(pcb/update-shapes
child-heads
(fn [shape]
(cond-> shape
(not= component-main-frame (ctn/find-component-main objects shape false))
(ctk/remove-swap-slot))))
;; Remove component-root property when moving a shape inside a component
(cond-> (ctn/get-instance-root objects frame)
(pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root)))
;; Add component-root property when moving a component outside a component
(cond-> (not (ctn/get-instance-root objects frame))
(pcb/update-shapes child-heads #(assoc % :component-root true)))
(pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))
(pcb/update-shapes shape-ids-to-detach ctk/detach-shape)
(pcb/change-parent frame-id moving-shapes drop-index)
;; Change the grid cell in a grid layout
(cond-> (ctl/grid-layout? objects frame-id)
(-> (pcb/update-shapes
[frame-id]
(fn [frame objects]
(-> frame
;; Assign the cell when pushing into a specific grid cell
(cond-> (some? cell)
(-> (ctl/free-cell-shapes moving-shapes-ids)
(ctl/push-into-cell moving-shapes-ids row column)
(ctl/assign-cells objects)))
(ctl/assign-cell-positions objects)))
{:with-objects? true})
(pcb/reorder-grid-children [frame-id])))
(pcb/remove-objects empty-parents))))

View File

@@ -22,6 +22,7 @@
[app.common.svg :as csvg] [app.common.svg :as csvg]
[app.common.svg.path :as path] [app.common.svg.path :as path]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[cuerdas.core :as str])) [cuerdas.core :as str]))
(def default-rect (def default-rect
@@ -78,67 +79,68 @@
(declare parse-svg-element) (declare parse-svg-element)
(defn create-svg-shapes (defn create-svg-shapes
[svg-data {:keys [x y]} objects frame-id parent-id selected center?] ([svg-data pos objects frame-id parent-id selected center?]
(let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) (create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?))
([id svg-data {:keys [x y]} objects frame-id parent-id selected center?]
(let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data)
unames (cfh/get-used-names objects)
svg-name (str/replace (:name svg-data) ".svg" "")
unames (cfh/get-used-names objects) svg-data (-> svg-data
svg-name (str/replace (:name svg-data) ".svg" "") (assoc :x (mth/round
(if center?
(- x vb-x (/ vb-width 2))
x)))
(assoc :y (mth/round
(if center?
(- y vb-y (/ vb-height 2))
y)))
(assoc :offset-x vb-x)
(assoc :offset-y vb-y)
(assoc :width vb-width)
(assoc :height vb-height)
(assoc :name svg-name))
svg-data (-> svg-data [def-nodes svg-data]
(assoc :x (mth/round (-> svg-data
(if center? (csvg/fix-default-values)
(- x vb-x (/ vb-width 2)) (csvg/fix-percents)
x))) (csvg/extract-defs))
(assoc :y (mth/round
(if center?
(- y vb-y (/ vb-height 2))
y)))
(assoc :offset-x vb-x)
(assoc :offset-y vb-y)
(assoc :width vb-width)
(assoc :height vb-height)
(assoc :name svg-name))
[def-nodes svg-data] ;; In penpot groups have the size of their children. To
(-> svg-data ;; respect the imported svg size and empty space let's create
(csvg/fix-default-values) ;; a transparent shape as background to respect the imported
(csvg/fix-percents) ;; size
(csvg/extract-defs)) background
{:tag :rect
:attrs {:x (dm/str vb-x)
:y (dm/str vb-y)
:width (dm/str vb-width)
:height (dm/str vb-height)
:fill "none"
:id "base-background"}
:hidden true
:content []}
;; In penpot groups have the size of their children. To svg-data (-> svg-data
;; respect the imported svg size and empty space let's create (assoc :defs def-nodes)
;; a transparent shape as background to respect the imported (assoc :content (into [background] (:content svg-data))))
;; size
background
{:tag :rect
:attrs {:x (dm/str vb-x)
:y (dm/str vb-y)
:width (dm/str vb-width)
:height (dm/str vb-height)
:fill "none"
:id "base-background"}
:hidden true
:content []}
svg-data (-> svg-data root-shape (create-svg-root id frame-id parent-id svg-data)
(assoc :defs def-nodes) root-id (:id root-shape)
(assoc :content (into [background] (:content svg-data))))
root-shape (create-svg-root frame-id parent-id svg-data) ;; Create the root shape
root-id (:id root-shape) root-attrs (-> (:attrs svg-data)
(csvg/format-styles))
;; Create the root shape [_ children]
root-attrs (-> (:attrs svg-data) (reduce (partial create-svg-children objects selected frame-id root-id svg-data)
(csvg/format-styles)) [unames []]
(d/enumerate (->> (:content svg-data)
(mapv #(csvg/inherit-attributes root-attrs %)))))]
[_ children] [root-shape children])))
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
[unames []]
(d/enumerate (->> (:content svg-data)
(mapv #(csvg/inherit-attributes root-attrs %)))))]
[root-shape children]))
(defn create-raw-svg (defn create-raw-svg
[name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}] [name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}]
@@ -157,12 +159,13 @@
:svg-viewbox vbox}))) :svg-viewbox vbox})))
(defn create-svg-root (defn create-svg-root
[frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] [id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}]
(let [props (-> (dissoc attrs :viewBox :view-box :xmlns) (let [props (-> (dissoc attrs :viewBox :view-box :xmlns)
(d/without-keys csvg/inheritable-props) (d/without-keys csvg/inheritable-props)
(csvg/attrs->props))] (csvg/attrs->props))]
(cts/setup-shape (cts/setup-shape
{:type :group {:id id
:type :group
:name name :name name
:frame-id frame-id :frame-id frame-id
:parent-id parent-id :parent-id parent-id

View File

@@ -13,6 +13,7 @@
[app.common.types.color.generic :as-alias color-generic] [app.common.types.color.generic :as-alias color-generic]
[app.common.types.color.gradient :as-alias color-gradient] [app.common.types.color.gradient :as-alias color-gradient]
[app.common.types.color.gradient.stop :as-alias color-gradient-stop] [app.common.types.color.gradient.stop :as-alias color-gradient-stop]
[app.common.uuid :as uuid]
[clojure.test.check.generators :as tgen])) [clojure.test.check.generators :as tgen]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -105,6 +106,22 @@
;; HELPERS ;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- factory
(defn make-color
[{:keys [id name path value color opacity ref-id ref-file gradient image]}]
(-> {:id (or id (uuid/next))
:name (or name color "Black")
:path path
:value value
:color (or color "#000000")
:opacity (or opacity 1)
:ref-id ref-id
:ref-file ref-file
:gradient gradient
:image image}
(d/without-nils)))
;; --- fill ;; --- fill
(defn fill->shape-color (defn fill->shape-color

View File

@@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.features :as cfeat] [app.common.features :as cfeat]
[app.common.files.defaults :refer [version]]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
@@ -81,7 +82,7 @@
([file-id page-id] ([file-id page-id]
(let [page (when (some? page-id) (let [page (when (some? page-id)
(ctp/make-empty-page page-id "Page 1"))] (ctp/make-empty-page {:id page-id :name "Page 1"}))]
(cond-> (assoc empty-file-data :id file-id) (cond-> (assoc empty-file-data :id file-id)
(some? page-id) (some? page-id)
@@ -90,6 +91,34 @@
(contains? cfeat/*current* "components/v2") (contains? cfeat/*current* "components/v2")
(assoc-in [:options :components-v2] true))))) (assoc-in [:options :components-v2] true)))))
(defn make-file
[{:keys [id project-id name revn is-shared features
ignore-sync-until modified-at deleted-at
create-page page-id]
:or {is-shared false revn 0 create-page true}}]
(let [id (or id (uuid/next))
data (if create-page
(if page-id
(make-file-data id page-id)
(make-file-data id))
(make-file-data id nil))
file {:id id
:project-id project-id
:name name
:revn revn
:is-shared is-shared
:version version
:data data
:features features
:ignore-sync-until ignore-sync-until
:modified-at modified-at
:deleted-at deleted-at}]
(d/without-nils file)))
;; Helpers ;; Helpers
(defn file-data (defn file-data
@@ -460,7 +489,7 @@
(gpt/point 0 0) (gpt/point 0 0)
(ctn/shapes-seq library-page))] (ctn/shapes-seq library-page))]
[file-data (:id library-page) position]) [file-data (:id library-page) position])
(let [library-page (ctp/make-empty-page (uuid/next) "Main components")] (let [library-page (ctp/make-empty-page {:id (uuid/next) :name "Main components"})]
[(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)]))))
(defn- absorb-components (defn- absorb-components

View File

@@ -69,10 +69,10 @@
:name "Root Frame"})}}) :name "Root Frame"})}})
(defn make-empty-page (defn make-empty-page
[id name] [{:keys [id name]}]
(-> empty-page-data (-> empty-page-data
(assoc :id id) (assoc :id (or id (uuid/next)))
(assoc :name name))) (assoc :name (or name "Page 1"))))
;; --- Helpers for flow ;; --- Helpers for flow

View File

@@ -1281,6 +1281,21 @@
(let [cells+index (d/enumerate cells)] (let [cells+index (d/enumerate cells)]
(d/seek #(in-cell? (second %) row column) cells+index))) (d/seek #(in-cell? (second %) row column) cells+index)))
(defn free-cell-shapes
"Removes the shape-ids from the cells previously assigned."
[parent shape-ids]
(let [shape-ids (set shape-ids)]
(letfn [(free-cells
[cells]
(reduce-kv
(fn [m k v]
(if (some shape-ids (:shapes v))
(assoc-in m [k :shapes] [])
m))
cells
cells))]
(update parent :layout-grid-cells free-cells))))
(defn push-into-cell (defn push-into-cell
"Push the shapes into the row/column cell and moves the rest" "Push the shapes into the row/column cell and moves the rest"
[parent shape-ids row column] [parent shape-ids row column]
@@ -1295,16 +1310,17 @@
;; Move shift the `shapes` attribute between cells ;; Move shift the `shapes` attribute between cells
(->> (range start-index (inc to-index)) (->> (range start-index (inc to-index))
(map vector shape-ids) (map vector shape-ids)
(reduce (fn [[parent cells] [shape-id idx]] (reduce
;; If the shape to put in a cell is the same that is already in the cell we do nothing (fn [[parent cells] [shape-id idx]]
(if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0])) ;; If the shape to put in a cell is the same that is already in the cell we do nothing
[parent cells] (if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0]))
(let [[parent cells] (free-cell-push parent cells idx)] [parent cells]
[(update-in parent [:layout-grid-cells (get-in cells [idx :id])] (let [[parent cells] (free-cell-push parent cells idx)]
assoc :position :manual [(update-in parent [:layout-grid-cells (get-in cells [idx :id])]
:shapes [shape-id]) assoc :position :manual
cells]))) :shapes [shape-id])
[parent cells]) cells])))
[parent cells])
(first))) (first)))
parent))) parent)))

View File

@@ -6,8 +6,10 @@
(ns app.common.types.typography (ns app.common.types.typography
(:require (:require
[app.common.data :as d]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.text :as txt])) [app.common.text :as txt]
[app.common.uuid :as uuid]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA ;; SCHEMA
@@ -36,6 +38,23 @@
;; HELPERS ;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn make-typography
[{:keys [id name path font-id font-family font-variant-id font-size
font-weight font-style line-height letter-spacing text-transform]}]
(-> {:id (or id (uuid/next))
:name (or name "Typography 1")
:path path
:font-id (or font-id "sourcesanspro")
:font-family (or font-family "sourcesanspro")
:font-variant-id (or font-variant-id "regular")
:font-size (or font-size "14")
:font-weight (or font-weight "480")
:font-style (or font-style "normal")
:line-height (or line-height "1.2")
:letter-spacing (or letter-spacing "0")
:text-transform (or text-transform "none")}
(d/without-nils)))
(defn uses-library-typographies? (defn uses-library-typographies?
"Check if the shape uses any typography in the given library." "Check if the shape uses any typography in the given library."
[shape library-id] [shape library-id]

Binary file not shown.

View File

@@ -203,7 +203,7 @@
(t/is (mth/close? 1.5 (:x rs))) (t/is (mth/close? 1.5 (:x rs)))
(t/is (mth/close? 3.5 (:y rs))))) (t/is (mth/close? 3.5 (:y rs)))))
(t/deftest transform-point (t/deftest ^:kaocha/skip transform-point
;;todo ;;todo
) )

View File

@@ -6,141 +6,148 @@
(ns common-tests.helpers.components (ns common-tests.helpers.components
(:require (:require
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.logic.libraries :as cll]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[clojure.test :as t])) [app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst]
[common-tests.helpers.files :as thf]
[common-tests.helpers.ids-map :as thi]
[common-tests.helpers.shapes :as ths]))
;; ---- Helpers to manage libraries and synchronization (defn make-component
[file label root-label & {:keys [] :as params}]
(let [page (thf/current-page file)
root (ths/get-shape file root-label)]
(defn check-instance-root (dm/assert!
[shape] "Need that root is already a frame"
(t/is (some? (:shape-ref shape))) (cfh/frame-shape? root))
(t/is (some? (:component-id shape)))
(t/is (= (:component-root shape) true)))
(defn check-instance-subroot (let [[_new-root _new-shapes updated-shapes]
[shape] (ctn/convert-shape-in-component root (:objects page) (:id file))
(t/is (some? (:shape-ref shape)))
(t/is (some? (:component-id shape)))
(t/is (nil? (:component-root shape))))
(defn check-instance-child updated-root (first updated-shapes)] ; Can't use new-root because it has a new id
[shape]
(t/is (some? (:shape-ref shape)))
(t/is (nil? (:component-id shape)))
(t/is (nil? (:component-file shape)))
(t/is (nil? (:component-root shape))))
(defn check-instance-inner (thi/set-id! label (:component-id updated-root))
[shape]
(if (some? (:component-id shape))
(check-instance-subroot shape)
(check-instance-child shape)))
(defn check-noninstance (ctf/update-file-data
[shape] file
(t/is (nil? (:shape-ref shape))) (fn [file-data]
(t/is (nil? (:component-id shape))) (as-> file-data $
(t/is (nil? (:component-file shape))) (reduce (fn [file-data shape]
(t/is (nil? (:component-root shape))) (ctpl/update-page file-data
(t/is (nil? (:remote-synced? shape))) (:id page)
(t/is (nil? (:touched shape)))) #(update % :objects assoc (:id shape) shape)))
$
updated-shapes)
(ctkl/add-component $ (assoc params
:id (:component-id updated-root)
:name (:name updated-root)
:main-instance-id (:id updated-root)
:main-instance-page (:id page)
:shapes updated-shapes))))))))
(defn check-from-file (defn get-component
[shape file] [file label]
(t/is (= (:component-file shape) (ctkl/get-component (:data file) (thi/id label)))
(:id file))))
(defn resolve-instance (defn get-component-by-id
"Get the shape with the given id and all its children, and [file id]
verify that they are a well constructed instance tree." (ctkl/get-component (:data file) id))
[page root-inst-id]
(let [root-inst (ctn/get-shape page root-inst-id)
shapes-inst (cfh/get-children-with-self (:objects page)
root-inst-id)]
(check-instance-root (first shapes-inst))
(run! check-instance-inner (rest shapes-inst))
shapes-inst)) (defn set-child-label
[file shape-label child-idx label]
(let [id (-> (ths/get-shape file shape-label)
:shapes
(nth child-idx))]
(when id
(thi/set-id! label id))))
(defn resolve-noninstance (defn instantiate-component
"Get the shape with the given id and all its children, and [file component-label copy-root-label & {:keys [parent-label library children-labels] :as params}]
verify that they are not a component instance." (let [page (thf/current-page file)
[page root-inst-id] library (or library file)
(let [root-inst (ctn/get-shape page root-inst-id) component (get-component library component-label)
shapes-inst (cfh/get-children-with-self (:objects page) parent-id (when parent-label
root-inst-id)] (thi/id parent-label))
(run! check-noninstance shapes-inst) parent (when parent-id
(ctst/get-shape page parent-id))
frame-id (if (cfh/frame-shape? parent)
(:id parent)
(:frame-id parent))
shapes-inst)) [copy-root copy-shapes]
(ctn/make-component-instance page
component
(:data library)
(gpt/point 100 100)
true
{:force-id (thi/new-id! copy-root-label)
:force-frame-id frame-id})
(defn resolve-instance-and-main copy-root' (cond-> copy-root
"Get the shape with the given id and all its children, and also (some? parent)
the main component and all its shapes." (assoc :parent-id parent-id)
[page root-inst-id libraries]
(let [root-inst (ctn/get-shape page root-inst-id)
component (ctf/get-component libraries (:component-file root-inst) (:component-id root-inst)) (some? frame-id)
(assoc :frame-id frame-id)
shapes-inst (cfh/get-children-with-self (:objects page) root-inst-id) (and (some? parent) (ctn/in-any-component? (:objects page) parent))
shapes-main (cfh/get-children-with-self (:objects component) (:shape-ref root-inst)) (dissoc :component-root))
file' (ctf/update-file-data
file
(fn [file-data]
(as-> file-data $
(ctpl/update-page $
(:id page)
#(ctst/add-shape (:id copy-root')
copy-root'
%
frame-id
parent-id
nil
true))
(reduce (fn [file-data shape]
(ctpl/update-page file-data
(:id page)
#(ctst/add-shape (:id shape)
shape
%
(:parent-id shape)
(:frame-id shape)
nil
true)))
$
(remove #(= (:id %) (:did copy-root')) copy-shapes)))))]
(when children-labels
(dotimes [idx (count children-labels)]
(set-child-label file' copy-root-label idx (nth children-labels idx))))
file'))
unique-refs (into #{} (map :shape-ref) shapes-inst) (defn component-swap
[file shape-label new-component-label new-shape-label & {:keys [library] :as params}]
(let [shape (ths/get-shape file shape-label)
library (or library file)
libraries {(:id library) library}
page (thf/current-page file)
objects (:objects page)
id-new-component (-> (get-component library new-component-label)
:id)
main-exists? (fn [shape] ;; Store the properties that need to be maintained when the component is swapped
(let [component-shape keep-props-values (select-keys shape ctk/swap-keep-attrs)
(ctn/get-component-shape (:objects page) shape)
component
(ctf/get-component libraries (:component-file component-shape) (:component-id component-shape))
main-shape
(ctn/get-shape component (:shape-ref shape))]
(t/is (some? main-shape))))]
;; Validate that the instance tree is well constructed
(check-instance-root (first shapes-inst))
(run! check-instance-inner (rest shapes-inst))
(t/is (= (count shapes-inst)
(count shapes-main)
(count unique-refs)))
(run! main-exists? shapes-inst)
[shapes-inst shapes-main component]))
(defn resolve-instance-and-main-allow-dangling
"Get the shape with the given id and all its children, and also
the main component and all its shapes. Allows shapes with the
corresponding component shape missing."
[page root-inst-id libraries]
(let [root-inst (ctn/get-shape page root-inst-id)
component (ctf/get-component libraries (:component-file root-inst) (:component-id root-inst))
shapes-inst (cfh/get-children-with-self (:objects page) root-inst-id)
shapes-main (cfh/get-children-with-self (:objects component) (:shape-ref root-inst))
unique-refs (into #{} (map :shape-ref) shapes-inst)
main-exists? (fn [shape]
(let [component-shape
(ctn/get-component-shape (:objects page) shape)
component
(ctf/get-component libraries (:component-file component-shape) (:component-id component-shape))
main-shape
(ctn/get-shape component (:shape-ref shape))]
(t/is (some? main-shape))))]
;; Validate that the instance tree is well constructed
(check-instance-root (first shapes-inst))
[shapes-inst shapes-main component]))
[new_shape _ changes]
(-> (pcb/empty-changes nil (:id page))
(cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))]
(thi/set-id! new-shape-label (:id new_shape))
(thf/apply-changes file changes)))

View File

@@ -0,0 +1,169 @@
;; 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.helpers.compositions
(:require
[app.common.data :as d]
[common-tests.helpers.components :as thc]
[common-tests.helpers.shapes :as ths]))
(defn add-rect
[file rect-label & {:keys [] :as params}]
;; Generated shape tree:
;; :rect-label [:type :rect :name: Rect1]
(ths/add-sample-shape file rect-label
(merge {:type :rect
:name "Rect1"}
params)))
(defn add-frame
[file frame-label & {:keys [] :as params}]
;; Generated shape tree:
;; :frame-label [:type :frame :name: Frame1]
(ths/add-sample-shape file frame-label
(merge {:type :frame
:name "Frame1"}
params)))
(defn add-frame-with-child
[file frame-label child-label & {:keys [frame-params child-params]}]
;; Generated shape tree:
;; :frame-label [:name: Frame1]
;; :child-label [:name: Rect1]
(-> file
(add-frame frame-label frame-params)
(ths/add-sample-shape child-label
(merge {:type :rect
:name "Rect1"
:parent-label frame-label}
child-params))))
(defn add-simple-component
[file component-label root-label child-label
& {:keys [component-params root-params child-params]}]
;; Generated shape tree:
;; {:root-label} [:name: Frame1] # [Component :component-label]
;; :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)))
(defn add-simple-component-with-copy
[file component-label main-root-label main-child-label copy-root-label
& {: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]
;;
;; :copy-root-label [:name: Frame1] #--> [Component :component-label] :main-root-label
;; <no-label> [:name: Rect1] ---> :main-child-label
(-> file
(add-simple-component component-label
main-root-label
main-child-label
:component-params component-params
:root-params main-root-params
:child-params main-child-params)
(thc/instantiate-component component-label copy-root-label copy-root-params)))
(defn add-component-with-many-children
[file component-label root-label child-labels
& {: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]
(as-> file $
(add-frame $ root-label root-params)
(reduce (fn [file [index [label params]]]
(ths/add-sample-shape file
label
(merge {:type :rect
:name (str "Rect" (inc index))
:parent-label root-label}
params)))
$
(d/enumerate (d/zip-all child-labels child-params-list)))
(thc/make-component $ component-label root-label component-params)))
(defn add-component-with-many-children-and-copy
[file component-label main-root-label main-child-labels copy-root-label
& {: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]
;;
;; :copy-root-label [:name: Frame1] #--> [Component :component-label] :root-label
;; <no-label> [:name: Rect1] ---> :child1-label
;; <no-label> [:name: Rect2] ---> :child2-label
;; <no-label> [:name: Rect3] ---> :child3-label
(-> file
(add-component-with-many-children component-label
main-root-label
main-child-labels
:component-params component-params
:root-params main-root-params
:child-params-list main-child-params-list)
(thc/instantiate-component component-label copy-root-label copy-root-params)))
(defn add-nested-component
[file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label
& {: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]
;;
;; {:main2-root-label} [:name: Frame2] # [Component :component2-label]
;; :nested-head-label [:name: Frame1] @--> [Component :component1-label] :main1-root-label
;; <no-label> [:name: Rect1] ---> :main1-child-label
(-> file
(add-simple-component component1-label
main1-root-label
main1-child-label
:component-params component1-params
:root-params root1-params
:child-params main1-child-params)
(add-frame main2-root-label (merge {:name "Frame2"}
main2-root-params))
(thc/instantiate-component component1-label
nested-head-label
(assoc nested-head-params
:parent-label main2-root-label))
(thc/make-component component2-label
main2-root-label
component2-params)))
(defn add-nested-component-with-copy
[file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label copy2-label
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params copy2-params]}]
;; Generated shape tree:
;; {:main1-root-label} [:name: Frame1] # [Component :component1-label]
;; :main1-child-label [:name: Rect1]
;;
;; {:main2-root-label} [:name: Frame2] # [Component :component2-label]
;; :nested-head-label [:name: Frame1] @--> [Component :component1-label] :main1-root-label
;; <no-label> [:name: Rect1] ---> :main1-child-label
;;
;; :copy2-label [:name: Frame2] #--> [Component :component2-label] :main2-root-label
;; <no-label> [:name: Frame1] @--> [Component :component1-label] :nested-head-label
;; <no-label> [:name: Rect1] ---> <no-label>
(-> file
(add-nested-component component1-label
main1-root-label
main1-child-label
component2-label
main2-root-label
nested-head-label
:component1-params component1-params
:root1-params root1-params
:main1-child-params main1-child-params
:component2-params component2-params
:main2-root-params main2-root-params
:nested-head-params nested-head-params)
(thc/instantiate-component component2-label copy2-label copy2-params)))

View File

@@ -6,150 +6,153 @@
(ns common-tests.helpers.files (ns common-tests.helpers.files
(:require (:require
[app.common.data :as d]
[app.common.features :as ffeat] [app.common.features :as ffeat]
[app.common.geom.point :as gpt] [app.common.files.changes :as cfc]
[app.common.types.colors-list :as ctcl] [app.common.files.validate :as cfv]
[app.common.types.components-list :as ctkl] [app.common.pprint :refer [pprint]]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts] [app.common.uuid :as uuid]
[app.common.types.shape-tree :as ctst] [common-tests.helpers.ids-map :as thi]
[app.common.types.typographies-list :as ctyl] [cuerdas.core :as str]))
[app.common.uuid :as uuid]))
(defn- make-file-data ;; ----- Files
[file-id page-id]
(binding [ffeat/*current* #{"components/v2"}]
(ctf/make-file-data file-id page-id)))
(def ^:private idmap (atom {}))
(defn reset-idmap!
[next]
(reset! idmap {})
(next))
(defn id
[label]
(get @idmap label))
(defn sample-file (defn sample-file
([file-id page-id] (sample-file file-id page-id nil)) [label & {:keys [page-label name] :as params}]
([file-id page-id props] (binding [ffeat/*current* #{"components/v2"}]
(merge {:id file-id (let [params (cond-> params
:name (get props :name "File1") label
:data (make-file-data file-id page-id)} (assoc :id (thi/new-id! label))
props)))
(defn sample-shape page-label
[file label type page-id props] (assoc :page-id (thi/new-id! page-label))
(ctf/update-file-data
file
(fn [file-data]
(let [frame-id (get props :frame-id uuid/zero)
parent-id (get props :parent-id uuid/zero)
shape (cts/setup-shape
(-> {:type type
:width 1
:height 1}
(merge props)))]
(swap! idmap assoc label (:id shape)) (nil? name)
(ctpl/update-page file-data (assoc :name "Test file"))
page-id
#(ctst/add-shape (:id shape)
shape
%
frame-id
parent-id
0
true))))))
(defn sample-component file (-> (ctf/make-file (dissoc params :page-label))
[file label page-id shape-id] (assoc :features #{"components/v2"}))
(ctf/update-file-data
file
(fn [file-data]
(let [page (ctpl/get-page file-data page-id)
[component-shape component-shapes updated-shapes] page (-> file
(ctn/make-component-shape (ctn/get-shape page shape-id) :data
(:objects page) (ctpl/pages-seq)
(:id file) (first))]
true)]
(swap! idmap assoc label (:id component-shape)) (with-meta file
(-> file-data {:current-page-id (:id page)}))))
(ctpl/update-page page-id
#(reduce (fn [page shape] (ctst/set-shape page shape))
%
updated-shapes))
(ctkl/add-component {:id (:id component-shape)
:name (:name component-shape)
:path ""
:main-instance-id shape-id
:main-instance-page page-id
:shapes component-shapes}))))))
(defn sample-instance (defn validate-file!
[file label page-id library component-id] ([file] (validate-file! file {}))
(ctf/update-file-data ([file libraries]
file (cfv/validate-file-schema! file)
(fn [file-data] (cfv/validate-file! file libraries)))
(let [[instance-shape instance-shapes]
(ctn/make-component-instance (ctpl/get-page file-data page-id)
(ctkl/get-component (:data library) component-id)
(:data library)
(gpt/point 0 0)
true)]
(swap! idmap assoc label (:id instance-shape)) (defn apply-changes
(-> file-data [file changes]
(ctpl/update-page page-id (let [file' (ctf/update-file-data file #(cfc/process-changes % (:redo-changes changes) true))]
#(reduce (fn [page shape] (validate-file! file')
(ctst/add-shape (:id shape) file'))
shape
page
uuid/zero
(:parent-id shape)
0
true))
%
instance-shapes)))))))
(defn sample-color ;; ----- Pages
[file label props]
(ctf/update-file-data
file
(fn [file-data]
(let [id (uuid/next)
props (merge {:id id
:name "Color 1"
:color "#000000"
:opacity 1}
props)]
(swap! idmap assoc label id)
(ctcl/add-color file-data props)))))
(defn sample-typography (defn sample-page
[file label props] [label & {:keys [] :as params}]
(ctf/update-file-data (ctp/make-empty-page (assoc params :id (thi/new-id! label))))
file
(fn [file-data]
(let [id (uuid/next)
props (merge {:id id
:name "Typography 1"
:font-id "sourcesanspro"
:font-family "sourcesanspro"
:font-size "14"
:font-style "normal"
:font-variant-id "regular"
:font-weight "400"
:line-height "1.2"
:letter-spacing "0"
:text-transform "none"}
props)]
(swap! idmap assoc label id)
(ctyl/add-typography file-data props)))))
(defn add-sample-page
[file label & {:keys [] :as params}]
(let [page (sample-page label params)]
(-> file
(ctf/update-file-data #(ctpl/add-page % page))
(vary-meta assoc :current-page-id (:id page)))))
(defn get-page
[file label]
(ctpl/get-page (:data file) (thi/id label)))
(defn current-page-id
[file]
(:current-page-id (meta file)))
(defn current-page
[file]
(ctpl/get-page (:data file) (current-page-id file)))
(defn switch-to-page
[file label]
(vary-meta file assoc :current-page-id (thi/id label)))
;; ----- Debug
(defn dump-file-type
"Dump a file using dump-tree function in common.types.file."
[file & {:keys [page-label libraries] :as params}]
(let [params (-> params
(or {:show-ids true :show-touched true})
(dissoc page-label libraries))
page (if (some? page-label)
(:id (get-page file page-label))
(current-page-id file))
libraries (or libraries {})]
(ctf/dump-tree file page libraries params)))
(defn pprint-file
"Pretry print a file trying to limit the quantity of info shown."
[file & {:keys [level length] :or {level 10 length 1000}}]
(pprint file {:level level :length length}))
(defn dump-shape
"Dump a shape, with each attribute in a line."
[shape]
(println "{")
(doseq [[k v] (sort shape)]
(when (some? v)
(println (str " " k " : " v))))
(println "}"))
(defn- stringify-keys [m keys]
(apply str (interpose ", " (map #(str % ": " (get m %)) keys))))
(defn- dump-page-shape
[shape keys padding]
(println (str/pad (str padding
(when (:main-instance shape) "{")
(or (thi/label (:id shape)) "<no-label>")
(when (:main-instance shape) "}")
(when keys
(str " [" (stringify-keys shape keys) "]")))
{:length 40 :type :right})
(if (nil? (:shape-ref shape))
(if (:component-root shape)
(str "# [Component " (or (thi/label (:component-id shape)) "<no-label>") "]")
"")
(str/format "%s--> %s%s"
(cond (:component-root shape) "#"
(:component-id shape) "@"
:else "-")
(if (:component-root shape)
(str "[Component " (or (thi/label (:component-id shape)) "<no-label>") "] ")
"")
(or (thi/label (:shape-ref shape)) "<no-label>")))))
(defn dump-page
"Dump the layer tree of the page. Print the label of each shape, and the specified keys."
([page keys]
(dump-page page uuid/zero "" keys))
([page root-id padding keys]
(let [lookupf (d/getf (:objects page))
root-shape (lookupf root-id)
shapes (map lookupf (:shapes root-shape))]
(doseq [shape shapes]
(dump-page-shape shape keys padding)
(dump-page page (:id shape) (str padding " ") keys)))))
(defn dump-file
"Dump the current page of the file, using dump-page above.
Example: (thf/dump-file file [:id :touched])"
([file] (dump-file file []))
([file keys] (dump-page (current-page file) keys)))

View File

@@ -0,0 +1,42 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.helpers.ids-map
(:require
[app.common.uuid :as uuid]))
;; ---- Helpers to manage ids as known identifiers
(def ^:private idmap (atom {}))
(defn reset-idmap! []
(reset! idmap {}))
(defn set-id!
[label id]
(swap! idmap assoc label id))
(defn new-id!
[label]
(let [id (uuid/next)]
(set-id! label id)
id))
(defn id
[label]
(get @idmap label))
(defn test-fixture
;; Ensure that each test starts with a clean ids map
[f]
(reset-idmap!)
(f))
(defn label [id]
(->> @idmap
(filter #(= id (val %)))
(map key)
(first)))

View File

@@ -0,0 +1,101 @@
;; 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.helpers.shapes
(:require
[app.common.colors :as clr]
[app.common.files.helpers :as cfh]
[app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl]
[app.common.types.file :as ctf]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.typographies-list :as cttl]
[app.common.types.typography :as ctt]
[common-tests.helpers.files :as thf]
[common-tests.helpers.ids-map :as thi]))
(defn sample-shape
[label & {:keys [type] :as params}]
(let [params (cond-> params
label
(assoc :id (thi/new-id! label))
(nil? type)
(assoc :type :rect))]
(cts/setup-shape params)))
(defn add-sample-shape
[file label & {:keys [parent-label] :as params}]
(let [page (thf/current-page file)
shape (sample-shape label (dissoc params :parent-label))
parent-id (when parent-label
(thi/id parent-label))
parent (when parent-id
(ctst/get-shape page parent-id))
frame-id (if (cfh/frame-shape? parent)
(:id parent)
(:frame-id parent))]
(ctf/update-file-data
file
(fn [file-data]
(ctpl/update-page file-data
(:id page)
#(ctst/add-shape (:id shape)
shape
%
frame-id
parent-id
nil
true))))))
(defn get-shape
[file label & {:keys [page-label]}]
(let [page (if page-label
(thf/get-page file page-label)
(thf/current-page file))]
(ctst/get-shape page (thi/id label))))
(defn get-shape-by-id
[file id & {:keys [page-label]}]
(let [page (if page-label
(thf/get-page file page-label)
(thf/current-page file))]
(ctst/get-shape page id)))
(defn sample-color
[label & {:keys [] :as params}]
(ctc/make-color (assoc params :id (thi/new-id! label))))
(defn sample-fill-color
[& {:keys [fill-color fill-opacity] :as params}]
(let [params (cond-> params
(nil? fill-color)
(assoc :fill-color clr/black)
(nil? fill-opacity)
(assoc :fill-opacity 1))]
params))
(defn sample-fills-color
[& {:keys [] :as params}]
[(sample-fill-color params)])
(defn add-sample-library-color
[file label & {:keys [] :as params}]
(let [color (sample-color label params)]
(ctf/update-file-data file #(ctcl/add-color % color))))
(defn sample-typography
[label & {:keys [] :as params}]
(ctt/make-typography (assoc params :id (thi/new-id! label))))
(defn add-sample-typography
[file label & {:keys [] :as params}]
(let [typography (sample-typography label params)]
(ctf/update-file-data file #(cttl/add-typography % typography))))

View File

@@ -0,0 +1,349 @@
;; 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.comp-remove-swap-slots-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.shapes :as cls]
[app.common.types.component :as ctk]
[app.common.uuid :as uuid]
[clojure.test :as t]
[common-tests.helpers.components :as thc]
[common-tests.helpers.compositions :as tho]
[common-tests.helpers.files :as thf]
[common-tests.helpers.ids-map :as thi]
[common-tests.helpers.shapes :as ths]))
(t/use-fixtures :each thi/test-fixture)
;; Related .penpot file: common/test/cases/remove-swap-slots.penpot
(defn- setup-file
[]
;; :frame-b1 [:id: 3aee2370-44e4-81c8-8004-46e56a459d70, :touched: ]
;; :blue1 [:id: 3aee2370-44e4-81c8-8004-46e56a45fc55, :touched: #{:swap-slot-3aee2370-44e4-81c8-8004-46e56a459d75}]
;; :green-copy [:id: 3aee2370-44e4-81c8-8004-46e56a45fc56, :touched: ]
;; :blue-copy-in-green-copy [:id: 3aee2370-44e4-81c8-8004-46e56a4631a4, :touched: #{:swap-slot-3aee2370-44e4-81c8-8004-46e56a459d6f}]
;; :frame-yellow [:id: 3aee2370-44e4-81c8-8004-46e56a459d73, :touched: ]
;; :frame-green [:id: 3aee2370-44e4-81c8-8004-46e56a459d6c, :touched: ]
;; :red-copy-green [:id: 3aee2370-44e4-81c8-8004-46e56a459d6f, :touched: ]
;; :frame-blue [:id: 3aee2370-44e4-81c8-8004-46e56a459d69, :touched: ]
;; :frame-b2 [:id: 3aee2370-44e4-81c8-8004-46e56a4631a5, :touched: ]
;; :frame-red [:id: 3aee2370-44e4-81c8-8004-46e56a459d66, :touched: ]
(-> (thf/sample-file :file1)
(tho/add-frame :frame-red)
(thc/make-component :red :frame-red)
(tho/add-frame :frame-blue)
(thc/make-component :blue :frame-blue)
(tho/add-frame :frame-green)
(thc/make-component :green :frame-green)
(thc/instantiate-component :red :red-copy-green :parent-label :frame-green)
(tho/add-frame :frame-b1)
(thc/make-component :b1 :frame-b1)
(tho/add-frame :frame-yellow :parent-label :frame-b1)
(thc/instantiate-component :red :red-copy :parent-label :frame-b1)
(thc/component-swap :red-copy :blue :blue1)
(thc/instantiate-component :green :green-copy :parent-label :frame-b1 :children-labels [:red-copy-in-green-copy])
(thc/component-swap :red-copy-in-green-copy :blue :blue-copy-in-green-copy)
(tho/add-frame :frame-b2)
(thc/make-component :b2 :frame-b2)))
(t/deftest test-keep-swap-slot-relocating-blue1-to-root
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
blue1 (ths/get-shape file :blue1)
;; ==== Action
changes (cls/generate-relocate-shapes (pcb/empty-changes nil)
(:objects page)
#{(:parent-id blue1)} ;; parents
uuid/zero ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
file' (thf/apply-changes file changes)
;; ==== Get
blue1' (ths/get-shape file' :blue1)]
;; ==== Check
;; blue1 had swap-id before move
(t/is (some? (ctk/get-swap-slot blue1)))
;; blue1 has not swap-id after move
(t/is (some? blue1'))
(t/is (nil? (ctk/get-swap-slot blue1')))))
(t/deftest test-keep-swap-slot-move-blue1-to-root
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
blue1 (ths/get-shape file :blue1)
;; ==== Action
changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil)
#{(:id blue1)} ;; ids
uuid/zero ;; frame-id
(:id page) ;; page-id
(:objects page) ;; objects
0 ;; drop-index
nil) ;; cell
file' (thf/apply-changes file changes)
;; ==== Get
blue1' (ths/get-shape file' :blue1)]
;; ==== Check
;; blue1 had swap-id before move
(t/is (some? (ctk/get-swap-slot blue1)))
;; blue1 has not swap-id after move
(t/is (some? blue1'))
(t/is (nil? (ctk/get-swap-slot blue1')))))
(t/deftest test-keep-swap-slot-relocating-blue1-to-b2
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
blue1 (ths/get-shape file :blue1)
b2 (ths/get-shape file :frame-b2)
;; ==== Action
changes (cls/generate-relocate-shapes (pcb/empty-changes nil)
(:objects page)
#{(:parent-id blue1)} ;; parents
(:id b2) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
file' (thf/apply-changes file changes)
;; ==== Get
blue1' (ths/get-shape file' :blue1)]
;; ==== Check
;; blue1 had swap-id before move
(t/is (some? (ctk/get-swap-slot blue1)))
;; blue1 has not swap-id after move
(t/is (some? blue1'))
(t/is (nil? (ctk/get-swap-slot blue1')))))
(t/deftest test-keep-swap-slot-move-blue1-to-b2
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
blue1 (ths/get-shape file :blue1)
b2 (ths/get-shape file :frame-b2)
;; ==== Action
changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil)
#{(:id blue1)} ;; ids
(:id b2) ;; frame-id
(:id page) ;; page-id
(:objects page) ;; objects
0 ;; drop-index
nil) ;; cell
file' (thf/apply-changes file changes)
;; ==== Get
blue1' (ths/get-shape file' :blue1)]
;; ==== Check
;; blue1 had swap-id before move
(t/is (some? (ctk/get-swap-slot blue1)))
;; blue1 has not swap-id after move
(t/is (some? blue1'))
(t/is (nil? (ctk/get-swap-slot blue1')))))
(t/deftest test-keep-swap-slot-relocating-yellow-to-root
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
blue1 (ths/get-shape file :blue1)
yellow (ths/get-shape file :frame-yellow)
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate-shapes (pcb/empty-changes nil)
(:objects page)
#{(:parent-id blue1)} ;; parents
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
file' (thf/apply-changes file changes)
page' (thf/current-page file')
yellow' (ths/get-shape file' :frame-yellow)
;; Move yellow into root
changes' (cls/generate-relocate-shapes (pcb/empty-changes nil)
(:objects page')
#{(:parent-id yellow')} ;; parents
uuid/zero ;; parent-id
(:id page') ;; page-id
0 ;; to-index
#{(:id yellow')}) ;; ids
file'' (thf/apply-changes file' changes')
;; ==== Get
blue1'' (ths/get-shape file'' :blue1)]
;; ==== Check
;; blue1 had swap-id before move
(t/is (some? (ctk/get-swap-slot blue1)))
;; blue1 has not swap-id after move
(t/is (some? blue1''))
(t/is (nil? (ctk/get-swap-slot blue1'')))))
(t/deftest test-keep-swap-slot-move-yellow-to-root
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
blue1 (ths/get-shape file :blue1)
yellow (ths/get-shape file :frame-yellow)
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil)
#{(:id blue1)} ;; ids
(:id yellow) ;; frame-id
(:id page) ;; page-id
(:objects page) ;; objects
0 ;; drop-index
nil) ;; cell
file' (thf/apply-changes file changes)
page' (thf/current-page file')
yellow' (ths/get-shape file' :frame-yellow)
;; Move yellow into root
changes' (cls/generate-move-shapes-to-frame (pcb/empty-changes nil)
#{(:id yellow')} ;; ids
uuid/zero ;; frame-id
(:id page') ;; page-id
(:objects page') ;; objects
0 ;; drop-index
nil) ;; cell
file'' (thf/apply-changes file' changes')
;; ==== Get
blue1'' (ths/get-shape file'' :blue1)]
;; ==== Check
;; blue1 had swap-id before move
(t/is (some? (ctk/get-swap-slot blue1)))
;; blue1 has not swap-id after move
(t/is (some? blue1''))
(t/is (nil? (ctk/get-swap-slot blue1'')))))
(t/deftest test-keep-swap-slot-relocating-yellow-to-b2
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
blue1 (ths/get-shape file :blue1)
yellow (ths/get-shape file :frame-yellow)
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate-shapes (pcb/empty-changes nil)
(:objects page)
#{(:parent-id blue1)} ;; parents
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
file' (thf/apply-changes file changes)
page' (thf/current-page file')
yellow' (ths/get-shape file' :frame-yellow)
b2' (ths/get-shape file' :frame-b2)
;; Move yellow into b2
changes' (cls/generate-relocate-shapes (pcb/empty-changes nil)
(:objects page')
#{(:parent-id yellow')} ;; parents
(:id b2') ;; parent-id
(:id page') ;; page-id
0 ;; to-index
#{(:id yellow')}) ;; ids
file'' (thf/apply-changes file' changes')
;; ==== Get
blue1'' (ths/get-shape file'' :blue1)]
;; ==== Check
;; blue1 had swap-id before move
(t/is (some? (ctk/get-swap-slot blue1)))
;; blue1 has not swap-id after move
(t/is (some? blue1''))
(t/is (nil? (ctk/get-swap-slot blue1'')))))
(t/deftest test-keep-swap-slot-move-yellow-to-b2
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
blue1 (ths/get-shape file :blue1)
yellow (ths/get-shape file :frame-yellow)
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil)
#{(:id blue1)} ;; ids
(:id yellow) ;; frame-id
(:id page) ;; page-id
(:objects page) ;; objects
0 ;; drop-index
nil) ;; cell
file' (thf/apply-changes file changes)
page' (thf/current-page file')
yellow' (ths/get-shape file' :frame-yellow)
b2' (ths/get-shape file' :frame-b2)
;; Move yellow into b2
changes' (cls/generate-move-shapes-to-frame (pcb/empty-changes nil)
#{(:id yellow')} ;; ids
(:id b2') ;; frame-id
(:id page') ;; page-id
(:objects page') ;; objects
0 ;; drop-index
nil) ;; cell
file'' (thf/apply-changes file' changes')
;; ==== Get
blue1'' (ths/get-shape file'' :blue1)]
;; ==== Check
;; blue1 had swap-id before move
(t/is (some? (ctk/get-swap-slot blue1)))
;; blue1 has not swap-id after move
(t/is (some? blue1''))
(t/is (nil? (ctk/get-swap-slot blue1'')))))

View File

@@ -0,0 +1,47 @@
;; 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.component-creation-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.libraries :as cll]
[clojure.test :as t]
[common-tests.helpers.components :as thc]
[common-tests.helpers.files :as thf]
[common-tests.helpers.ids-map :as thi]
[common-tests.helpers.shapes :as ths]))
(t/use-fixtures :each thi/test-fixture)
(t/deftest test-add-component-from-single-shape
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(ths/add-sample-shape :shape1 :type :frame))
page (thf/current-page file)
shape1 (ths/get-shape file :shape1)
;; ==== Action
[_ component-id changes]
(cll/generate-add-component (pcb/empty-changes)
[shape1]
(:objects page)
(:id page)
(:id file)
true
nil
nil)
file' (thf/apply-changes file changes)
;; ==== Get
component (thc/get-component-by-id file' component-id)
root (ths/get-shape-by-id file' (:main-instance-id component))]
;; ==== Check
(t/is (some? component))
(t/is (some? root))
(t/is (= (:component-id root) (:id component)))))

View File

@@ -0,0 +1,234 @@
;; 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.components-touched-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.shapes :as cls]
[clojure.test :as t]
[common-tests.helpers.compositions :as tho]
[common-tests.helpers.files :as thf]
[common-tests.helpers.ids-map :as thi]
[common-tests.helpers.shapes :as ths]))
(t/use-fixtures :each thi/test-fixture)
(t/deftest test-touched-when-changing-attribute
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-simple-component-with-copy :component1
:main-root
:main-child
:copy-root
:main-child-params {:fills (ths/sample-fills-color
:fill-color "#abcdef")}))
page (thf/current-page file)
copy-root (ths/get-shape file :copy-root)
;; ==== Action
update-fn (fn [shape]
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
(:shapes copy-root)
update-fn
(:objects page)
{})
file' (thf/apply-changes file changes)
;; ==== Get
copy-root' (ths/get-shape file' :copy-root)
copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))
fills' (:fills copy-child')
fill' (first fills')]
;; ==== Check
(t/is (= (count fills') 1))
(t/is (= (:fill-color fill') "#fabada"))
(t/is (= (:fill-opacity fill') 1))
(t/is (= (:touched copy-root') nil))
(t/is (= (:touched copy-child') #{:fill-group}))))
(t/deftest test-not-touched-when-adding-shape
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-simple-component-with-copy :component1
:main-root
:main-child
:copy-root)
(ths/add-sample-shape :free-shape))
page (thf/current-page file)
copy-root (ths/get-shape file :copy-root)
;; ==== Action
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate-shapes (pcb/empty-changes)
(:objects page)
#{(:parent-id copy-root)} ; parents
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
0 ; to-index
#{(thi/id :free-shape)}) ; ids
file' (thf/apply-changes file changes)
;; ==== Get
copy-root' (ths/get-shape file' :copy-root)
copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))]
;; ==== Check
(t/is (= (:touched copy-root') nil))
(t/is (= (:touched copy-child') nil))))
(t/deftest test-touched-when-deleting-shape
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-simple-component-with-copy :component1
:main-root
:main-child
:copy-root))
page (thf/current-page file)
copy-root (ths/get-shape file :copy-root)
;; ==== Action
;; IMPORTANT: as modifying copies structure is now forbidden, this action will not
;; delete the child shape, but hide it (thus setting the visibility group).
[_all-parents changes]
(cls/generate-delete-shapes (pcb/empty-changes)
file
page
(:objects page)
(set (:shapes copy-root))
{:components-v2 true})
file' (thf/apply-changes file changes)
;; ==== Get
copy-root' (ths/get-shape file' :copy-root)
copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))]
;; ==== Check
(t/is (= (:touched copy-root') nil))
(t/is (= (:touched copy-child') #{:visibility-group}))))
(t/deftest test-not-touched-when-moving-shape
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-component-with-many-children-and-copy :component1
:main-root
[:main-child1 :main-child2 :main-child3]
:copy-root)
(ths/add-sample-shape :free-shape))
page (thf/current-page file)
copy-root (ths/get-shape file :copy-root)
copy-child1 (ths/get-shape-by-id file (first (:shapes copy-root)))
;; ==== Action
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate-shapes (pcb/empty-changes)
(:objects page)
#{(:parent-id copy-child1)} ; parents
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
2 ; to-index
#{(:id copy-child1)}) ; ids
file' (thf/apply-changes file changes)
;; ==== Get
copy-root' (ths/get-shape file' :copy-root)
copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))]
;; ==== Check
(t/is (= (:touched copy-root') nil))
(t/is (= (:touched copy-child') nil))))
(t/deftest test-touched-when-changing-upper
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-nested-component-with-copy :component1
:main1-root
:main1-child
:component2
:main2-root
:main2-nested-head
:copy2-root
:root2-params {:fills (ths/sample-fills-color
:fill-color "#abcdef")}))
page (thf/current-page file)
copy2-root (ths/get-shape file :copy2-root)
;; ==== Action
update-fn (fn [shape]
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id copy2-root)}
update-fn
(:objects page)
{})
file' (thf/apply-changes file changes)
;; ==== Get
copy2-root' (ths/get-shape file' :copy2-root)
fills' (:fills copy2-root')
fill' (first fills')]
;; ==== Check
(t/is (= (count fills') 1))
(t/is (= (:fill-color fill') "#fabada"))
(t/is (= (:fill-opacity fill') 1))
(t/is (= (:touched copy2-root') #{:fill-group}))))
(t/deftest test-touched-when-changing-lower
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-nested-component-with-copy :component1
:main1-root
:main1-child
:component2
:main2-root
:main2-nested-head
:copy2-root
:nested-head-params {:fills (ths/sample-fills-color
:fill-color "#abcdef")}))
page (thf/current-page file)
copy2-root (ths/get-shape file :copy2-root)
;; ==== Action
update-fn (fn [shape]
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
(:shapes copy2-root)
update-fn
(:objects page)
{})
file' (thf/apply-changes file changes)
;; ==== Get
copy2-root' (ths/get-shape file' :copy2-root)
copy2-child' (ths/get-shape-by-id file' (first (:shapes copy2-root')))
fills' (:fills copy2-child')
fill' (first fills')]
;; ==== Check
(t/is (= (count fills') 1))
(t/is (= (:fill-color fill') "#fabada"))
(t/is (= (:fill-opacity fill') 1))
(t/is (= (:touched copy2-root') nil))
(t/is (= (:touched copy2-child') #{:fill-group}))))

View File

@@ -0,0 +1,175 @@
;; 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.swap-and-reset-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.libraries :as cll]
[app.common.types.component :as ctk]
[app.common.types.file :as ctf]
[clojure.test :as t]
[common-tests.helpers.components :as thc]
[common-tests.helpers.compositions :as tho]
[common-tests.helpers.files :as thf]
[common-tests.helpers.ids-map :as thi]
[common-tests.helpers.shapes :as ths]))
(t/use-fixtures :each thi/test-fixture)
;; Related .penpot file: common/test/cases/swap-and-reset.penpot
(t/deftest test-simple-swap
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tho/add-simple-component-with-copy :component-1
:component-1-main-root
:component-1-main-child
:component-1-copy-root)
(tho/add-simple-component :component-2
:component-2-root
:component-2-child))
component-1-copy-root (ths/get-shape file :component-1-copy-root)
component-2 (thc/get-component file :component-2)
page (thf/current-page file)
;; ==== Action
[new-shape _all-parents changes]
(cll/generate-component-swap (pcb/empty-changes)
(:objects page)
component-1-copy-root
(:data file)
page
{(:id file) file}
(:id component-2)
0
nil
{})
file' (thf/apply-changes file changes)
;; ==== Get
swapped (ths/get-shape-by-id file' (:id new-shape))]
;; ==== Check
(t/is (not= (:component-id component-1-copy-root) (:component-id swapped)))
(t/is (= (:id component-2) (:component-id swapped)))
(t/is (= (:id file) (:component-file swapped)))))
(t/deftest test-swap-nested
(let [;; ==== Setup
file
(-> (thf/sample-file :file1)
(tho/add-simple-component :component-1 :component-1-main-root :component-1-main-child)
(tho/add-frame :component-container)
(thc/instantiate-component :component-1 :component-1-copy-root :parent-label :component-container)
(thc/make-component :component-container-main :component-container)
(thc/instantiate-component :component-container-main :component-container-instance)
(tho/add-simple-component :component-2 :component-2-main-root :component-2-main-child))
page (thf/current-page file)
component-2 (thc/get-component file :component-2)
copy
(->>
(ths/get-shape file :component-container-instance)
:shapes
first
(ths/get-shape-by-id file))
libraries {(:id file) file}
;; ==== Action
[new-shape _all-parents changes]
(cll/generate-component-swap (pcb/empty-changes)
(:objects page)
copy
(:data file)
page
libraries
(:id component-2)
0
nil
{})
file' (thf/apply-changes file changes)
libraries' {(:id file') file'}
page' (thf/current-page file')
;; ==== Get
swapped (ths/get-shape-by-id file' (:id new-shape))
component-1-copy-root (ths/get-shape file' :component-1-copy-root)
slot (-> (ctf/find-swap-slot swapped
page'
file'
libraries')
(ctk/build-swap-slot-group))]
;; ==== Check
(t/is (not= (:component-id copy) (:component-id swapped)))
(t/is (= (:id component-2) (:component-id swapped)))
(t/is (= (:id file) (:component-file swapped)))
(t/is (contains? (:touched swapped) slot))
(t/is (= (ctk/get-swap-slot swapped) (:id component-1-copy-root)))))
(t/deftest test-swap-and-reset-override
(let [;; ==== Setup
file
(-> (thf/sample-file :file1)
(tho/add-simple-component :component-1 :component-1-main-root :component-1-main-child)
(tho/add-frame :component-container)
(thc/instantiate-component :component-1 :component-1-copy-root :parent-label :component-container)
(thc/make-component :component-container-main :component-container)
(thc/instantiate-component :component-container-main :component-container-instance)
(tho/add-simple-component :component-2 :component-2-main-root :component-2-main-child))
page (thf/current-page file)
component-1 (thc/get-component file :component-1)
component-2 (thc/get-component file :component-2)
copy
(->>
(ths/get-shape file :component-container-instance)
:shapes
first
(ths/get-shape-by-id file))
;; ==== Action
[new-shape _all-parents changes-swap]
(cll/generate-component-swap (pcb/empty-changes)
(:objects page)
copy
(:data file)
page
{(:id file) file}
(:id component-2)
0
nil
{})
file-swap (thf/apply-changes file changes-swap)
page-swap (thf/current-page file-swap)
changes
(cll/generate-reset-component (pcb/empty-changes)
file-swap
{(:id file-swap) file-swap}
page-swap
(:id new-shape)
true)
file' (thf/apply-changes file changes)
;; ==== Get
reset
(->>
(ths/get-shape file' :component-container-instance)
:shapes
first
(ths/get-shape-by-id file'))]
;; ==== Check
(t/is (= (:id component-1) (:component-id reset)))
(t/is (nil? (ctk/get-swap-slot reset)))))

View File

@@ -27,7 +27,6 @@
(t/testing "unknown assoc" (t/testing "unknown assoc"
(let [o (assoc o :c 176)] (let [o (assoc o :c 176)]
(prn o)
(t/is (= 1 (:a o))) (t/is (= 1 (:a o)))
(t/is (= 2 (:b o))) (t/is (= 2 (:b o)))
(t/is (= 176 (:c o))))) (t/is (= 176 (:c o)))))

View File

@@ -0,0 +1,192 @@
;; 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.types-libraries-test
(:require
[app.common.data :as d]
[app.common.text :as txt]
[app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.types.pages-list :as ctpl]
[app.common.types.typographies-list :as ctyl]
[clojure.test :as t]
[common-tests.helpers.components :as thc]
[common-tests.helpers.compositions :as tho]
[common-tests.helpers.files :as thf]
[common-tests.helpers.ids-map :as thi]
[common-tests.helpers.shapes :as ths]))
(t/use-fixtures :each thi/test-fixture)
(t/deftest test-create-file
(let [f1 (thf/sample-file :file1)
f2 (thf/sample-file :file2 :page-label :page1)
f3 (thf/sample-file :file3 :name "testing file")
f4 (-> (thf/sample-file :file4 :page-label :page2)
(thf/add-sample-page :page3 :name "testing page")
(ths/add-sample-shape :shape1))
f5 (-> f4
(ths/add-sample-shape :shape2)
(thf/switch-to-page :page2)
(ths/add-sample-shape :shape3 :name "testing shape" :width 100))
s1 (ths/get-shape f4 :shape1)
s2 (ths/get-shape f5 :shape2 :page-label :page3)
s3 (ths/get-shape f5 :shape3)]
;; (thf/pprint-file f4)
(t/is (= (:name f1) "Test file"))
(t/is (= (:name f3) "testing file"))
(t/is (= (:id f2) (thi/id :file2)))
(t/is (= (:id f4) (thi/id :file4)))
(t/is (= (-> f4 :data :pages-index vals first :id) (thi/id :page2)))
(t/is (= (-> f4 :data :pages-index vals first :name) "Page 1"))
(t/is (= (-> f4 :data :pages-index vals second :id) (thi/id :page3)))
(t/is (= (-> f4 :data :pages-index vals second :name) "testing page"))
(t/is (= (:id (thf/current-page f2)) (thi/id :page1)))
(t/is (= (:id (thf/current-page f4)) (thi/id :page3)))
(t/is (= (:id (thf/current-page f5)) (thi/id :page2)))
(t/is (= (:id s1) (thi/id :shape1)))
(t/is (= (:name s1) "Rectangle"))
(t/is (= (:id s2) (thi/id :shape2)))
(t/is (= (:name s2) "Rectangle"))
(t/is (= (:id s3) (thi/id :shape3)))
(t/is (= (:name s3) "testing shape"))
(t/is (= (:width s3) 100))
(t/is (= (:width (:selrect s3)) 100))))
(t/deftest test-create-components
(let [f1 (-> (thf/sample-file :file1)
(tho/add-simple-component-with-copy :component1 :main-root :main-child :copy-root))]
#_(thf/dump-file f1)
#_(thf/pprint-file f4)
(t/is (= (:name f1) "Test file"))))
(t/deftest test-absorb-components
(let [;; Setup
library (-> (thf/sample-file :library :is-shared true)
(tho/add-simple-component :component1 :main-root :rect1))
file (-> (thf/sample-file :file)
(thc/instantiate-component :component1 :copy-root :library library))
;; Action
file' (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))
_ (thf/validate-file! file')
;; Get
pages' (ctpl/pages-seq (ctf/file-data file'))
components' (ctkl/components-seq (ctf/file-data file'))
component' (first components')
copy-root' (ths/get-shape file' :copy-root)
main-root' (ctf/get-ref-shape (ctf/file-data file') component' copy-root')]
;; Check
(t/is (= (count pages') 2))
(t/is (= (:name (first pages')) "Page 1"))
(t/is (= (:name (second pages')) "Main components"))
(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' true))
(t/is (ctk/main-instance-of? (:id main-root') (:id (second pages')) component'))))
(t/deftest test-absorb-colors
(let [;; Setup
library (-> (thf/sample-file :library :is-shared true)
(ths/add-sample-library-color :color1 {:name "Test color"
:color "#abcdef"}))
file (-> (thf/sample-file :file)
(ths/add-sample-shape :shape1
:type :rect
:name "Rect1"
:fills [{:fill-color "#abcdef"
:fill-opacity 1
:fill-color-ref-id (thi/id :color1)
:fill-color-ref-file (thi/id :library)}]))
;; Action
file' (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))
_ (thf/validate-file! file')
;; Get
colors' (ctcl/colors-seq (ctf/file-data file'))
shape1' (ths/get-shape file' :shape1)
fill' (first (:fills shape1'))]
;; Check
(t/is (= (count colors') 1))
(t/is (= (:id (first colors')) (thi/id :color1)))
(t/is (= (:name (first colors')) "Test color"))
(t/is (= (:color (first colors')) "#abcdef"))
(t/is (= (:fill-color fill') "#abcdef"))
(t/is (= (:fill-color-ref-id fill') (thi/id :color1)))
(t/is (= (:fill-color-ref-file fill') (:id file')))))
(t/deftest test-absorb-typographies
(let [;; Setup
library (-> (thf/sample-file :library :is-shared true)
(ths/add-sample-typography :typography1 {:name "Test typography"}))
file (-> (thf/sample-file :file)
(ths/add-sample-shape :shape1
:type :text
:name "Text1"
:content {:type "root"
:children [{:type "paragraph-set"
:children [{:type "paragraph"
:key "67uep"
:children [{:text "Example text"
:typography-ref-id (thi/id :typography1)
:typography-ref-file (thi/id :library)
:line-height "1.2"
:font-style "normal"
:text-transform "none"
:text-align "left"
:font-id "sourcesanspro"
:font-family "sourcesanspro"
:font-size "14"
:font-weight "400"
:font-variant-id "regular"
:text-decoration "none"
:letter-spacing "0"
:fills [{:fill-color "#000000"
:fill-opacity 1}]}]}]}]}))
;; Action
file' (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))
_ (thf/validate-file! file')
;; Get
typographies' (ctyl/typographies-seq (ctf/file-data file'))
shape1' (ths/get-shape file' :shape1)
text-node' (d/seek #(some? (:text %)) (txt/node-seq (:content shape1')))]
;; Check
(t/is (= (count typographies') 1))
(t/is (= (:id (first typographies')) (thi/id :typography1)))
(t/is (= (:name (first typographies')) "Test typography"))
(t/is (= (:typography-ref-id text-node') (thi/id :typography1)))
(t/is (= (:typography-ref-file text-node') (:id file')))))

View File

@@ -1,207 +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-file-test
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.text :as txt]
[app.common.types.colors-list :as ctcl]
[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 :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.typographies-list :as ctyl]
[app.common.uuid :as uuid]
[clojure.pprint :refer [pprint]]
[clojure.test :as t]
[common-tests.helpers.components :as thk]
[common-tests.helpers.files :as thf]
[cuerdas.core :as str]))
(t/use-fixtures :each thf/reset-idmap!)
#_(t/deftest test-absorb-components
(let [library-id (uuid/custom 1 1)
library-page-id (uuid/custom 2 2)
file-id (uuid/custom 3 3)
file-page-id (uuid/custom 4 4)
library (-> (thf/sample-file library-id library-page-id {:is-shared true})
(thf/sample-shape :group1
:group
library-page-id
{:name "Group1"})
(thf/sample-shape :shape1
:rect
library-page-id
{:name "Rect1"
:parent-id (thf/id :group1)})
(thf/sample-component :component1
library-page-id
(thf/id :group1)))
file (-> (thf/sample-file file-id file-page-id)
(thf/sample-instance :instance1
file-page-id
library
(thf/id :component1)))
absorbed-file (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))
pages (ctpl/pages-seq (ctf/file-data absorbed-file))
components (ctkl/components-seq (ctf/file-data absorbed-file))
shapes-1 (ctn/shapes-seq (first pages))
shapes-2 (ctn/shapes-seq (second pages))
[[p-group p-shape] [c-group1 c-shape1] component1]
(thk/resolve-instance-and-main
(first pages)
(:id (second shapes-1))
{file-id absorbed-file})
[[lp-group lp-shape] [c-group2 c-shape2] component2]
(thk/resolve-instance-and-main
(second pages)
(:id (second shapes-2))
{file-id absorbed-file})]
;; Uncomment to debug
;; (println "\n===== library")
;; (ctf/dump-tree (:data library)
;; library-page-id
;; {}
;; true)
;; (println "\n===== file")
;; (ctf/dump-tree (:data file)
;; file-page-id
;; {library-id library}
;; true)
;; (println "\n===== absorbed file")
;; (println (str "\n<" (:name (first pages)) ">"))
;; (ctf/dump-tree (:data absorbed-file)
;; (:id (first pages))
;; {file-id absorbed-file}
;; false)
;; (println (str "\n<" (:name (second pages)) ">"))
;; (ctf/dump-tree (:data absorbed-file)
;; (:id (second pages))
;; {file-id absorbed-file}
;; false)
(t/is (= (count pages) 2))
(t/is (= (:name (first pages)) "Page 1"))
(t/is (= (:name (second pages)) "Main components"))
(t/is (= (count components) 1))
(t/is (= (:name p-group) "Group1"))
(t/is (ctk/instance-of? p-group file-id (:id component1)))
(t/is (not (:main-instance? p-group)))
(t/is (not (ctk/main-instance-of? (:id p-group) file-page-id component1)))
(t/is (ctk/is-main-of? c-group1 p-group))
(t/is (= (:name p-shape) "Rect1"))
(t/is (ctk/is-main-of? c-shape1 p-shape))))
(t/deftest test-absorb-colors
(let [library-id (uuid/custom 1 1)
library-page-id (uuid/custom 2 2)
file-id (uuid/custom 3 3)
file-page-id (uuid/custom 4 4)
library (-> (thf/sample-file library-id library-page-id {:is-shared true})
(thf/sample-color :color1 {:name "Test color"
:color "#abcdef"}))
file (-> (thf/sample-file file-id file-page-id)
(thf/sample-shape :shape1
:rect
file-page-id
{:name "Rect1"
:fills [{:fill-color "#abcdef"
:fill-opacity 1
:fill-color-ref-id (thf/id :color1)
:fill-color-ref-file library-id}]}))
absorbed-file (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))
colors (ctcl/colors-seq (ctf/file-data absorbed-file))
page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id)
shape1 (ctn/get-shape page (thf/id :shape1))
fill (first (:fills shape1))]
(t/is (= (count colors) 1))
(t/is (= (:id (first colors)) (thf/id :color1)))
(t/is (= (:name (first colors)) "Test color"))
(t/is (= (:color (first colors)) "#abcdef"))
(t/is (= (:fill-color fill) "#abcdef"))
(t/is (= (:fill-color-ref-id fill) (thf/id :color1)))
(t/is (= (:fill-color-ref-file fill) file-id))))
(t/deftest test-absorb-typographies
(let [library-id (uuid/custom 1 1)
library-page-id (uuid/custom 2 2)
file-id (uuid/custom 3 3)
file-page-id (uuid/custom 4 4)
library (-> (thf/sample-file library-id library-page-id {:is-shared true})
(thf/sample-typography :typography1 {:name "Test typography"}))
file (-> (thf/sample-file file-id file-page-id)
(thf/sample-shape :shape1
:text
file-page-id
{:name "Text1"
:content {:type "root"
:children [{:type "paragraph-set"
:children [{:type "paragraph"
:key "67uep"
:children [{:text "Example text"
:typography-ref-id (thf/id :typography1)
:typography-ref-file library-id
:line-height "1.2"
:font-style "normal"
:text-transform "none"
:text-align "left"
:font-id "sourcesanspro"
:font-family "sourcesanspro"
:font-size "14"
:font-weight "400"
:font-variant-id "regular"
:text-decoration "none"
:letter-spacing "0"
:fills [{:fill-color "#000000"
:fill-opacity 1}]}]}]}]}}))
absorbed-file (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))
typographies (ctyl/typographies-seq (ctf/file-data absorbed-file))
page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id)
shape1 (ctn/get-shape page (thf/id :shape1))
text-node (d/seek #(some? (:text %)) (txt/node-seq (:content shape1)))]
(t/is (= (count typographies) 1))
(t/is (= (:id (first typographies)) (thf/id :typography1)))
(t/is (= (:name (first typographies)) "Test typography"))
(t/is (= (:typography-ref-id text-node) (thf/id :typography1)))
(t/is (= (:typography-ref-file text-node) file-id))))

4
common/tests.edn Normal file
View File

@@ -0,0 +1,4 @@
#kaocha/v1
{:tests [{:id :unit
:test-paths ["test"]}]
:kaocha/reporter [kaocha.report/dots]}

View File

@@ -30,7 +30,7 @@
"translations:find-unused": "node ./scripts/find-unused-translations.js", "translations:find-unused": "node ./scripts/find-unused-translations.js",
"compile": "node ./scripts/compile.js", "compile": "node ./scripts/compile.js",
"watch": "node ./scripts/watch.js", "watch": "node ./scripts/watch.js",
"e2e:server": "NODE_NO_WARNINGS=1 http-server ./resources/public -p 3500 -a 0.0.0.0", "e2e:server": "node ./scripts/e2e-server.js",
"e2e:test": "playwright test", "e2e:test": "playwright test",
"storybook:compile": "gulp template:storybook && clojure -M:dev:shadow-cljs compile storybook", "storybook:compile": "gulp template:storybook && clojure -M:dev:shadow-cljs compile storybook",
"storybook:watch": "npm run storybook:compile && concurrently \"clojure -M:dev:shadow-cljs watch storybook\" \"storybook dev -p 6006\"", "storybook:watch": "npm run storybook:compile && concurrently \"clojure -M:dev:shadow-cljs watch storybook\" \"storybook dev -p 6006\"",
@@ -51,6 +51,7 @@
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"draft-js": "git+https://github.com/penpot/draft-js.git", "draft-js": "git+https://github.com/penpot/draft-js.git",
"express": "^4.19.2",
"fancy-log": "^2.0.0", "fancy-log": "^2.0.0",
"gettext-parser": "^8.0.0", "gettext-parser": "^8.0.0",
"gulp": "4.0.2", "gulp": "4.0.2",
@@ -62,7 +63,6 @@
"gulp-sass": "^5.1.0", "gulp-sass": "^5.1.0",
"gulp-sourcemaps": "^3.0.0", "gulp-sourcemaps": "^3.0.0",
"gulp-svg-sprite": "^2.0.3", "gulp-svg-sprite": "^2.0.3",
"http-server": "^14.1.1",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"map-stream": "0.0.7", "map-stream": "0.0.7",
"marked": "^12.0.0", "marked": "^12.0.0",

View File

@@ -28,6 +28,8 @@ export default defineConfig({
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry", trace: "on-first-retry",
locale: "en-US"
}, },
/* Configure projects for major browsers */ /* Configure projects for major browsers */

View File

@@ -0,0 +1,4 @@
{
"~:type": "~:validation",
"~:code": "~:wrong-credentials"
}

View File

@@ -1,9 +1,14 @@
export const interceptRPC = async (page, path, jsonFilename) => { export const interceptRPC = async (page, path, jsonFilename, options = {}) => {
await page.route(`**/api/rpc/command/${path}`, (route) => { const interceptConfig = {
route.fulfill({ status: 200,
status: 200, ...options,
};
await page.route(`**/api/rpc/command/${path}`, async (route) => {
await route.fulfill({
...interceptConfig,
contentType: "application/transit+json", contentType: "application/transit+json",
path: `playwright/fixtures/${jsonFilename}`, path: `playwright/data/${jsonFilename}`,
}); });
}); });
}; };

View File

@@ -0,0 +1,8 @@
import { interceptRPC } from "./index";
export const setupNotLogedIn = async (page) => {
await interceptRPC(page, "get-profile", "get-profile-anonymous.json");
};

View File

@@ -1,56 +0,0 @@
import { test, expect } from "@playwright/test";
import { interceptRPC } from "./helpers";
const setupLoggedOutUser = async (page) => {
await interceptRPC(page, "get-profile", "get-profile-anonymous.json");
await interceptRPC(page, "login-with-password", "logged-in-user/login-with-password-success.json");
};
// TODO: maybe Playwright's fixtures are the right way to do this?
const setupDashboardUser = async (page) => {
await interceptRPC(page, "get-profile", "logged-in-user/get-profile-logged-in.json");
await interceptRPC(page, "get-teams", "logged-in-user/get-teams-default.json");
await interceptRPC(page, "get-font-variants?team-id=*", "logged-in-user/get-font-variants-empty.json");
await interceptRPC(page, "get-projects?team-id=*", "logged-in-user/get-projects-default.json");
await interceptRPC(page, "get-team-members?team-id=*", "logged-in-user/get-team-members-your-penpot.json");
await interceptRPC(page, "get-team-users?team-id=*", "logged-in-user/get-team-users-single-user.json");
await interceptRPC(
page,
"get-unread-comment-threads?team-id=*",
"logged-in-user/get-team-users-single-user.json",
);
await interceptRPC(
page,
"get-team-recent-files?team-id=*",
"logged-in-user/get-team-recent-files-empty.json",
);
await interceptRPC(
page,
"get-profiles-for-file-comments",
"logged-in-user/get-profiles-for-file-comments-empty.json",
);
};
test("Shows login page when going to index and user is logged out", async ({ page }) => {
setupLoggedOutUser(page);
await page.goto("/");
await expect(page).toHaveURL(/auth\/login$/);
await expect(page.getByText("Log into my account")).toBeVisible();
});
test("User logs in by filling the login form", async ({ page }) => {
setupLoggedOutUser(page);
await page.goto("/#/auth/login");
setupDashboardUser(page);
await page.getByLabel("Email").fill("foo@example.com");
await page.getByLabel("Password").fill("loremipsum");
await page.getByRole("button", { name: "Login" }).click();
await expect(page).toHaveURL(/dashboard/);
});

View File

@@ -0,0 +1,76 @@
import { interceptRPC } from "../../helpers/index";
class LoginPage {
constructor(page) {
this.page = page;
this.loginButton = page.getByRole("button", { name: "Login" });
this.password = page.getByLabel("Password");
this.userName = page.getByLabel("Email");
this.message = page.getByText("Email or password is incorrect");
this.badLoginMsg = page.getByText("Enter a valid email please");
this.initialHeading = page.getByRole("heading", { name: "Log into my account" });
}
url() {
return this.page.url();
}
context() {
return this.page.context();
}
async fillEmailAndPasswordInputs(email, password) {
await this.userName.fill(email);
await this.password.fill(password);
}
async clickLoginButton() {
await this.loginButton.click();
}
async setupAllowedUser() {
await interceptRPC(this.page, "get-profile", "logged-in-user/get-profile-logged-in.json");
await interceptRPC(this.page, "get-teams", "logged-in-user/get-teams-default.json");
await interceptRPC(
this.page,
"get-font-variants?team-id=*",
"logged-in-user/get-font-variants-empty.json",
);
await interceptRPC(this.page, "get-projects?team-id=*", "logged-in-user/get-projects-default.json");
await interceptRPC(
this.page,
"get-team-members?team-id=*",
"logged-in-user/get-team-members-your-penpot.json",
);
await interceptRPC(
this.page,
"get-team-users?team-id=*",
"logged-in-user/get-team-users-single-user.json",
);
await interceptRPC(
this.page,
"get-unread-comment-threads?team-id=*",
"logged-in-user/get-team-users-single-user.json",
);
await interceptRPC(
this.page,
"get-team-recent-files?team-id=*",
"logged-in-user/get-team-recent-files-empty.json",
);
await interceptRPC(
this.page,
"get-profiles-for-file-comments",
"logged-in-user/get-profiles-for-file-comments-empty.json",
);
}
async setupLoginSuccess() {
await interceptRPC(this.page, "login-with-password", "logged-in-user/login-with-password-success.json");
}
async setupLoginError() {
await interceptRPC(this.page, "login-with-password", "login-with-password-error.json", { status: 400 });
}
}
export default LoginPage;

View File

@@ -5,7 +5,7 @@ test("Has title", async ({ page }) => {
route.fulfill({ route.fulfill({
status: 200, status: 200,
contentType: "application/transit+json", contentType: "application/transit+json",
path: "playwright/fixtures/get-profile-anonymous.json", path: "playwright/data/get-profile-anonymous.json",
}); });
}); });
await page.goto("/"); await page.goto("/");

View File

@@ -0,0 +1,54 @@
import { test, expect } from "@playwright/test";
import { setupNotLogedIn } from "../../helpers/intercepts";
import LoginPage from "../pages/login-page";
test.beforeEach(async ({ page }) => {
await setupNotLogedIn(page);
await page.goto("/#/auth/login");
});
test("Shows login page when going to index and user is logged out", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupAllowedUser();
await expect(loginPage.url()).toMatch(/auth\/login$/);
await expect(loginPage.initialHeading).toBeVisible();
});
test("User submit a wrong formated email ", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupLoginSuccess();
await loginPage.fillEmailAndPasswordInputs("foo", "lorenIpsum");
await expect(loginPage.badLoginMsg).toBeVisible();
});
test("User logs in by filling the login form", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupLoginSuccess();
await loginPage.setupAllowedUser();
await loginPage.fillEmailAndPasswordInputs("foo@example.com", "loremipsum");
await loginPage.clickLoginButton();
await page.waitForURL('**/dashboard/**');
await expect(page).toHaveURL(/dashboard/);
// await expect(loginPage.url()).toMatch(/dashboard/);
});
test("User submits wrong credentials", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.setupLoginError();
await loginPage.fillEmailAndPasswordInputs("test@example.com", "loremipsum");
await loginPage.clickLoginButton();
await expect(loginPage.message).toBeVisible();
await expect(loginPage.url()).toMatch(/auth\/login$/);
});

View File

@@ -0,0 +1,13 @@
import express from "express";
import { fileURLToPath } from "url";
import path from "path";
const app = express();
const port = 3500;
const staticPath = path.join(fileURLToPath(import.meta.url), "../../resources/public");
app.use(express.static(staticPath));
app.listen(port, () => {
console.log(`Listening at 0.0.0.0:${port}`);
});

View File

@@ -19,6 +19,7 @@
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.grid-layout :as gslg] [app.common.geom.shapes.grid-layout :as gslg]
[app.common.logic.shapes :as cls]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.transit :as t] [app.common.transit :as t]
@@ -786,115 +787,10 @@
;; --- Change Shape Order (D&D Ordering) ;; --- Change Shape Order (D&D Ordering)
(defn relocate-shapes-changes [it objects parents parent-id page-id to-index ids
groups-to-delete groups-to-unmask shapes-to-detach
shapes-to-reroot shapes-to-deroot shapes-to-unconstraint]
(let [ordered-indexes (cfh/order-by-indexed-shapes objects ids)
shapes (map (d/getf objects) ordered-indexes)
parent (get objects parent-id)
component-main-parent (ctn/find-component-main objects parent false)
child-heads
(->> ordered-indexes
(mapcat #(ctn/get-child-heads objects %))
(map :id))]
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? parent))
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data))
;; Remove the hide in viewer flag
(cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent))
(pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))))
;; Remove the swap slots if it is moving to a different component
(pcb/update-shapes child-heads
(fn [shape]
(cond-> shape
(not= component-main-parent (ctn/find-component-main objects shape false))
(ctk/remove-swap-slot))))
;; Add component-root property when moving a component outside a component
(cond-> (not (ctn/get-instance-root objects parent))
(pcb/update-shapes child-heads #(assoc % :component-root true)))
;; Move the shapes
(pcb/change-parent parent-id
shapes
to-index)
;; Remove empty groups
(pcb/remove-objects groups-to-delete)
;; Unmask groups whose mask have moved outside
(pcb/update-shapes groups-to-unmask
(fn [shape]
(assoc shape :masked-group false)))
;; Detach shapes moved out of their component
(pcb/update-shapes shapes-to-detach ctk/detach-shape)
;; Make non root a component moved inside another one
(pcb/update-shapes shapes-to-deroot
(fn [shape]
(assoc shape :component-root nil)))
;; Make root a subcomponent moved outside its parent component
(pcb/update-shapes shapes-to-reroot
(fn [shape]
(assoc shape :component-root true)))
;; Reset constraints depending on the new parent
(pcb/update-shapes shapes-to-unconstraint
(fn [shape]
(let [frame-id (if (= (:type parent) :frame)
(:id parent)
(:frame-id parent))
moved-shape (assoc shape
:parent-id parent-id
:frame-id frame-id)]
(assoc shape
:constraints-h (gsh/default-constraints-h moved-shape)
:constraints-v (gsh/default-constraints-v moved-shape))))
{:ignore-touched true})
;; Fix the sizing when moving a shape
(pcb/update-shapes parents
(fn [parent]
(if (ctl/flex-layout? parent)
(cond-> parent
(ctl/change-h-sizing? (:id parent) objects (:shapes parent))
(assoc :layout-item-h-sizing :fix)
(ctl/change-v-sizing? (:id parent) objects (:shapes parent))
(assoc :layout-item-v-sizing :fix))
parent)))
;; Update grid layout
(cond-> (ctl/grid-layout? objects parent-id)
(pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index)))
(pcb/update-shapes parents
(fn [parent objects]
(cond-> parent
(ctl/grid-layout? parent)
(ctl/assign-cells objects)))
{:with-objects? true})
(pcb/reorder-grid-children parents)
;; If parent locked, lock the added shapes
(cond-> (:blocked parent)
(pcb/update-shapes ordered-indexes #(assoc % :blocked true)))
;; Resize parent containers that need to
(pcb/resize-parents parents))))
(defn relocate-shapes (defn relocate-shapes
[ids parent-id to-index & [ignore-parents?]] [ids parent-id to-index & [ignore-parents?]]
(dm/assert! (every? uuid? ids)) (dm/assert! (every? uuid? ids))
(dm/assert! (set? ids))
(dm/assert! (uuid? parent-id)) (dm/assert! (uuid? parent-id))
(dm/assert! (number? to-index)) (dm/assert! (number? to-index))
@@ -913,97 +809,13 @@
all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids) all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids)
parents (if ignore-parents? #{parent-id} all-parents) parents (if ignore-parents? #{parent-id} all-parents)
groups-to-delete changes (cls/generate-relocate-shapes (pcb/empty-changes it)
(loop [current-id (first parents) objects
to-check (rest parents) parents
removed-id? (set ids) parent-id
result #{}] page-id
to-index
(if-not current-id ids)
;; Base case, no next element
result
(let [group (get objects current-id)]
(if (and (not= :frame (:type group))
(not= current-id parent-id)
(empty? (remove removed-id? (:shapes group))))
;; Adds group to the remove and check its parent
(let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])]
(recur (first to-check)
(rest to-check)
(conj removed-id? current-id)
(conj result current-id)))
;; otherwise recur
(recur (first to-check)
(rest to-check)
removed-id?
result)))))
groups-to-unmask
(reduce (fn [group-ids id]
;; When a masked group loses its mask shape, because it's
;; moved outside the group, the mask condition must be
;; removed, and it must be converted to a normal group.
(let [obj (get objects id)
parent (get objects (:parent-id obj))]
(if (and (:masked-group parent)
(= id (first (:shapes parent)))
(not= (:id parent) parent-id))
(conj group-ids (:id parent))
group-ids)))
#{}
ids)
;; TODO: Probably implementing this using loop/recur will
;; be more efficient than using reduce and continuous data
;; desturcturing.
;; Sets the correct components metadata for the moved shapes
;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside
;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component
;; `shapes-to-reroot` Adds a root flag when a nested component instance is moved outside
[shapes-to-detach shapes-to-deroot shapes-to-reroot]
(reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id]
(let [shape (get objects id)
parent (get objects parent-id)
component-shape (ctn/get-component-shape objects shape)
component-shape-parent (ctn/get-component-shape objects parent {:allow-main? true})
root-parent (ctn/get-instance-root objects parent)
detach? (and (ctk/in-component-copy-not-head? shape)
(not= (:id component-shape)
(:id component-shape-parent)))
deroot? (and (ctk/instance-root? shape)
root-parent)
reroot? (and (ctk/subinstance-head? shape)
(not component-shape-parent))
ids-to-detach (when detach?
(cons id (cfh/get-children-ids objects id)))]
[(cond-> shapes-to-detach detach? (into ids-to-detach))
(cond-> shapes-to-deroot deroot? (conj id))
(cond-> shapes-to-reroot reroot? (conj id))]))
[[] [] []]
(->> ids
(mapcat #(ctn/get-child-heads objects %))
(map :id)))
changes (relocate-shapes-changes it
objects
parents
parent-id
page-id
to-index
ids
groups-to-delete
groups-to-unmask
shapes-to-detach
shapes-to-reroot
shapes-to-deroot
ids)
undo-id (js/Symbol)] undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)

View File

@@ -13,9 +13,9 @@
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cph] [app.common.files.helpers :as cph]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.logic.shapes :as cls]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
@@ -74,23 +74,19 @@
(filter #(some update-layout-attr? (pcb/changed-attrs % objects update-fn {:attrs attrs :with-objects? with-objects?}))) (filter #(some update-layout-attr? (pcb/changed-attrs % objects update-fn {:attrs attrs :with-objects? with-objects?})))
(map :id)) (map :id))
changes (reduce changes (-> (pcb/empty-changes it page-id)
(fn [changes id] (pcb/set-save-undo? save-undo?)
(let [opts {:attrs attrs (pcb/set-stack-undo? stack-undo?)
:ignore-geometry? (get ignore-tree id) (cls/generate-update-shapes ids
:ignore-touched ignore-touched update-fn
:with-objects? with-objects?}] objects
(pcb/update-shapes changes [id] update-fn (d/without-nils opts)))) {:attrs attrs
(-> (pcb/empty-changes it page-id) :ignore-tree ignore-tree
(pcb/set-save-undo? save-undo?) :ignore-touched ignore-touched
(pcb/set-stack-undo? stack-undo?) :with-objects? with-objects?})
(pcb/with-objects objects) (cond-> undo-group
(cond-> undo-group (pcb/set-undo-group undo-group)))
(pcb/set-undo-group undo-group)))
ids)
grid-ids (->> ids (filter (partial ctl/grid-layout? objects)))
changes (pcb/update-shapes changes grid-ids ctl/assign-cell-positions {:with-objects? true})
changes (pcb/reorder-grid-children changes ids)
changes (add-undo-group changes state)] changes (add-undo-group changes state)]
(rx/concat (rx/concat
(if (seq (:redo-changes changes)) (if (seq (:redo-changes changes))

View File

@@ -15,6 +15,7 @@
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
@@ -68,7 +69,7 @@
result))))))) result)))))))
(defn prepare-create-group (defn prepare-create-group
[changes objects page-id shapes base-name keep-name?] [changes id objects page-id shapes base-name keep-name?]
(let [frame-id (:frame-id (first shapes)) (let [frame-id (:frame-id (first shapes))
parent-id (:parent-id (first shapes)) parent-id (:parent-id (first shapes))
gname (if (and keep-name? gname (if (and keep-name?
@@ -84,7 +85,8 @@
(cfh/get-position-on-parent objects) (cfh/get-position-on-parent objects)
inc) inc)
group (cts/setup-shape {:type :group group (cts/setup-shape {:id id
:type :group
:name gname :name gname
:shapes (mapv :id shapes) :shapes (mapv :id shapes)
:selrect selrect :selrect selrect
@@ -144,18 +146,12 @@
(map-indexed vector) (map-indexed vector)
(filter #(#{(:id group)} (second %))) (filter #(#{(:id group)} (second %)))
(ffirst) (ffirst)
inc) inc)]
;; Shapes that are in a component (including root) must be detached,
;; because cannot be easyly synchronized back to the main component.
shapes-to-detach (filter ctk/in-component-copy?
(cfh/get-children-with-self objects (:id group)))]
(-> (pcb/empty-changes it page-id) (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects) (pcb/with-objects objects)
(pcb/change-parent parent-id children index-in-parent) (pcb/change-parent parent-id children index-in-parent)
(pcb/remove-objects [(:id group)]) (pcb/remove-objects [(:id group)]))))
(pcb/update-shapes (map :id shapes-to-detach) ctk/detach-shape))))
(defn remove-frame-changes (defn remove-frame-changes
[it page-id frame objects] [it page-id frame objects]
@@ -179,30 +175,43 @@
;; GROUPS ;; GROUPS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def group-selected (defn group-shapes
(ptk/reify ::group-selected [id ids & {:keys [change-selection?] :or {change-selection? false}}]
(ptk/reify ::group-shapes
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [page-id (:current-page-id state) (let [id (d/nilv id (uuid/next))
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
selected (->> (wsh/lookup-selected state)
(cfh/clean-loops objects) shapes
(remove #(ctn/has-any-copy-parent? objects (get objects %)))) (->> ids
shapes (shapes-for-grouping objects selected) (cfh/clean-loops objects)
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
(shapes-for-grouping objects))
parents (into #{} (map :parent-id) shapes)] parents (into #{} (map :parent-id) shapes)]
(when-not (empty? shapes) (when-not (empty? shapes)
(let [[group changes] (let [[group changes]
(prepare-create-group (pcb/empty-changes it) objects page-id shapes "Group" false)] (prepare-create-group (pcb/empty-changes it) id objects page-id shapes "Group" false)]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dws/select-shapes (d/ordered-set (:id group))) (when change-selection?
(dws/select-shapes (d/ordered-set (:id group))))
(ptk/data-event :layout/update {:ids parents})))))))) (ptk/data-event :layout/update {:ids parents}))))))))
(def ungroup-selected (def group-selected
(ptk/reify ::ungroup-selected (ptk/reify ::group-selected
ptk/WatchEvent
(watch [_ state _]
(let [selected (wsh/lookup-selected state)]
(rx/of (group-shapes nil selected))))))
(defn ungroup-shapes
[ids & {:keys [change-selection?] :or {change-selection? false}}]
(ptk/reify ::ungroup-shapes
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
prepare prepare
(fn [shape-id] (fn [shape-id]
@@ -219,35 +228,42 @@
(ctl/grid-layout? objects (:parent-id shape)) (ctl/grid-layout? objects (:parent-id shape))
(pcb/update-shapes [(:parent-id shape)] ctl/assign-cells {:with-objects? true})))) (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells {:with-objects? true}))))
selected (->> (wsh/lookup-selected state) ids (->> ids
(remove #(ctn/has-any-copy-parent? objects (get objects %))) (remove #(ctn/has-any-copy-parent? objects (get objects %)))
;; components can't be ungrouped ;; components can't be ungrouped
(remove #(ctk/instance-head? (get objects %)))) (remove #(ctk/instance-head? (get objects %))))
changes-list (sequence
(keep prepare) changes-list (sequence (keep prepare) ids)
selected)
parents (into #{} parents (into #{}
(comp (map #(cfh/get-parent objects %)) (comp (map #(cfh/get-parent objects %))
(keep :id)) (keep :id))
selected) ids)
child-ids child-ids
(into (d/ordered-set) (into (d/ordered-set)
(mapcat #(dm/get-in objects [% :shapes])) (mapcat #(dm/get-in objects [% :shapes]))
selected) ids)
changes {:redo-changes (vec (mapcat :redo-changes changes-list)) changes {:redo-changes (vec (mapcat :redo-changes changes-list))
:undo-changes (vec (mapcat :undo-changes changes-list)) :undo-changes (vec (mapcat :undo-changes changes-list))
:origin it} :origin it}
undo-id (js/Symbol)] undo-id (js/Symbol)]
(when-not (empty? selected) (when-not (empty? ids)
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes) (dch/commit-changes changes)
(ptk/data-event :layout/update {:ids parents}) (ptk/data-event :layout/update {:ids parents})
(dwu/commit-undo-transaction undo-id) (dwu/commit-undo-transaction undo-id)
(dws/select-shapes child-ids))))))) (when change-selection?
(dws/select-shapes child-ids))))))))
(def ungroup-selected
(ptk/reify ::ungroup-selected
ptk/WatchEvent
(watch [_ state _]
(let [selected (wsh/lookup-selected state)]
(rx/of (ungroup-shapes selected :change-selection? true))))))
(def mask-group (def mask-group
(ptk/reify ::mask-group (ptk/reify ::mask-group
@@ -268,7 +284,7 @@
(= (:type (first shapes)) :group)) (= (:type (first shapes)) :group))
[first-shape (-> (pcb/empty-changes it page-id) [first-shape (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects))] (pcb/with-objects objects))]
(prepare-create-group (pcb/empty-changes it) objects page-id shapes "Mask" true)) (prepare-create-group (pcb/empty-changes it) (uuid/next) objects page-id shapes "Mask" true))
changes (-> changes changes (-> changes
(pcb/update-shapes (:shapes group) (pcb/update-shapes (:shapes group)

View File

@@ -11,10 +11,11 @@
[app.common.files.changes :as ch] [app.common.files.changes :as ch]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.libraries-helpers :as cflh]
[app.common.files.shapes-helpers :as cfsh] [app.common.files.shapes-helpers :as cfsh]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
@@ -351,9 +352,9 @@
parents (into #{} (map :parent-id) shapes)] parents (into #{} (map :parent-id) shapes)]
(when-not (empty? shapes) (when-not (empty? shapes)
(let [[root _ changes] (let [[root _ changes]
(cflh/generate-add-component (pcb/empty-changes it) shapes objects page-id file-id components-v2 (cll/generate-add-component (pcb/empty-changes it) shapes objects page-id file-id components-v2
dwg/prepare-create-group dwg/prepare-create-group
cfsh/prepare-create-artboard-from-selection)] cfsh/prepare-create-artboard-from-selection)]
(when-not (empty? (:redo-changes changes)) (when-not (empty? (:redo-changes changes))
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dws/select-shapes (d/ordered-set (:id root))) (dws/select-shapes (d/ordered-set (:id root)))
@@ -417,7 +418,7 @@
(let [library-data (get state :workspace-data) (let [library-data (get state :workspace-data)
components-v2 (features/active-feature? state "components/v2") components-v2 (features/active-feature? state "components/v2")
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(cflh/generate-rename-component id new-name library-data components-v2))] (cll/generate-rename-component id new-name library-data components-v2))]
(rx/of (dch/commit-changes changes)))))))) (rx/of (dch/commit-changes changes))))))))
@@ -452,7 +453,7 @@
library (get libraries library-id) library (get libraries library-id)
components-v2 (features/active-feature? state "components/v2") components-v2 (features/active-feature? state "components/v2")
changes (-> (pcb/empty-changes it nil) changes (-> (pcb/empty-changes it nil)
(cflh/generate-duplicate-component library component-id components-v2))] (cll/generate-duplicate-component library component-id components-v2))]
(rx/of (dch/commit-changes changes)))))) (rx/of (dch/commit-changes changes))))))
@@ -478,9 +479,9 @@
[all-parents changes] [all-parents changes]
(-> (pcb/empty-changes it page-id) (-> (pcb/empty-changes it page-id)
;; Deleting main root triggers component delete ;; Deleting main root triggers component delete
(cflh/generate-delete-shapes file page objects #{root-id} {:components-v2 components-v2 (cls/generate-delete-shapes file page objects #{root-id} {:components-v2 components-v2
:undo-group undo-group :undo-group undo-group
:undo-id undo-id}))] :undo-id undo-id}))]
(rx/of (rx/of
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
(dwt/clear-thumbnail (:current-file-id state) page-id root-id "component") (dwt/clear-thumbnail (:current-file-id state) page-id root-id "component")
@@ -508,7 +509,7 @@
library-data (wsh/get-file state library-id) library-data (wsh/get-file state library-id)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(cflh/generate-restore-component library-data component-id library-id current-page objects))] (cll/generate-restore-component library-data component-id library-id current-page objects))]
(rx/of (dch/commit-changes changes)))))) (rx/of (dch/commit-changes changes))))))
@@ -545,13 +546,13 @@
(pcb/with-objects objects)) (pcb/with-objects objects))
[new-shape changes] [new-shape changes]
(cflh/generate-instantiate-component changes (cll/generate-instantiate-component changes
objects objects
file-id file-id
component-id component-id
position position
page page
libraries) libraries)
undo-id (js/Symbol)] undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes) (dch/commit-changes changes)
@@ -574,7 +575,7 @@
libraries (wsh/get-libraries state) libraries (wsh/get-libraries state)
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(cflh/generate-detach-component id file page-id libraries))] (cll/generate-detach-component id file page-id libraries))]
(rx/of (dch/commit-changes changes)))))) (rx/of (dch/commit-changes changes))))))
@@ -610,7 +611,7 @@
changes (when can-detach? changes (when can-detach?
(reduce (reduce
(fn [changes id] (fn [changes id]
(cflh/generate-detach-instance changes container libraries id)) (cll/generate-detach-instance changes container libraries id))
(pcb/empty-changes it) (pcb/empty-changes it)
selected))] selected))]
@@ -696,7 +697,7 @@
changes changes
(-> (pcb/empty-changes it) (-> (pcb/empty-changes it)
(cflh/generate-reset-component file-full libraries container id components-v2))] (cll/generate-reset-component file-full libraries container id components-v2))]
(log/debug :msg "RESET-COMPONENT finished" :js/rchanges (log-changes (log/debug :msg "RESET-COMPONENT finished" :js/rchanges (log-changes
(:redo-changes changes) (:redo-changes changes)
@@ -751,7 +752,7 @@
(-> (pcb/empty-changes it) (-> (pcb/empty-changes it)
(pcb/set-undo-group undo-group) (pcb/set-undo-group undo-group)
(pcb/with-container container) (pcb/with-container container)
(cflh/generate-sync-shape-inverse full-file libraries container id components-v2)) (cll/generate-sync-shape-inverse full-file libraries container id components-v2))
file-id (:component-file shape) file-id (:component-file shape)
file (wsh/get-file state file-id) file (wsh/get-file state file-id)
@@ -890,7 +891,7 @@
[new-shape all-parents changes] [new-shape all-parents changes]
(-> (pcb/empty-changes it (:id page)) (-> (pcb/empty-changes it (:id page))
(pcb/set-undo-group undo-group) (pcb/set-undo-group undo-group)
(cflh/generate-component-swap objects shape file page libraries id-new-component index target-cell keep-props-values))] (cll/generate-component-swap objects shape file page libraries id-new-component index target-cell keep-props-values))]
(rx/of (rx/of
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
@@ -976,7 +977,7 @@
libraries (wsh/get-libraries state) libraries (wsh/get-libraries state)
current-file-id (:current-file-id state) current-file-id (:current-file-id state)
changes (cflh/generate-sync-file-changes changes (cll/generate-sync-file-changes
(pcb/empty-changes it) (pcb/empty-changes it)
undo-group undo-group
asset-type asset-type

View File

@@ -87,7 +87,17 @@
(->> (svg/upload-images svg-data file-id) (->> (svg/upload-images svg-data file-id)
(rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position)))))) (rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position))))))
(defn- process-uris
(defn upload-media-url
[name file-id url]
(rp/cmd!
:create-file-media-object-from-url
{:name name
:file-id file-id
:url url
:is-local true}))
(defn process-uris
[{:keys [file-id local? name uris mtype on-image on-svg]}] [{:keys [file-id local? name uris mtype on-image on-svg]}]
(letfn [(svg-url? [url] (letfn [(svg-url? [url]
(or (and mtype (= mtype "image/svg+xml")) (or (and mtype (= mtype "image/svg+xml"))
@@ -449,3 +459,12 @@
(rx/tap on-success) (rx/tap on-success)
(rx/catch on-error) (rx/catch on-error)
(rx/finalize #(st/emit! (msg/hide-tag :media-loading))))))))) (rx/finalize #(st/emit! (msg/hide-tag :media-loading)))))))))
(defn create-svg-shape
[id name svg-string position]
(ptk/reify ::create-svg-shape
ptk/WatchEvent
(watch [_ _ _]
(->> (svg->clj [name svg-string])
(rx/take 1)
(rx/map #(svg/add-svg-shapes id % position {:change-selection? false}))))))

View File

@@ -11,10 +11,10 @@
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.focus :as cpf] [app.common.files.focus :as cpf]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.libraries-helpers :as cflh]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.logic.libraries :as cll]
[app.common.record :as cr] [app.common.record :as cr]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
@@ -434,20 +434,20 @@
(gpt/subtract (-> origin-frame :selrect gpt/point))) (gpt/subtract (-> origin-frame :selrect gpt/point)))
instantiate-component instantiate-component
#(cflh/generate-instantiate-component changes #(cll/generate-instantiate-component changes
objects objects
file-id file-id
(:component-id component-root) (:component-id component-root)
pos pos
page page
libraries libraries
(:id component-root) (:id component-root)
parent-id parent-id
frame-id frame-id
{}) {})
restore-component restore-component
#(let [restore (cflh/prepare-restore-component changes library-data (:component-id component-root) page delta (:id component-root) parent-id frame-id)] #(let [restore (cll/prepare-restore-component changes library-data (:component-id component-root) page delta (:id component-root) parent-id frame-id)]
[(:shape restore) (:changes restore)]) [(:shape restore) (:changes restore)])
[_shape changes] [_shape changes]
@@ -498,7 +498,7 @@
regenerate-component regenerate-component
(fn [changes shape] (fn [changes shape]
(let [components-v2 (dm/get-in library-data [:options :components-v2]) (let [components-v2 (dm/get-in library-data [:options :components-v2])
[_ changes] (cflh/generate-add-component-changes changes shape objects file-id (:id page) components-v2)] [_ changes] (cll/generate-add-component-changes changes shape objects file-id (:id page) components-v2)]
changes)) changes))
new-obj new-obj
@@ -723,62 +723,76 @@
(gpt/subtract new-pos pt-obj))))) (gpt/subtract new-pos pt-obj)))))
(defn duplicate-shapes
[ids & {:keys [move-delta? alt-duplication? change-selection? return-ref]
:or {move-delta? false alt-duplication? false change-selection? true return-ref nil}}]
(ptk/reify ::duplicate-shapes
ptk/WatchEvent
(watch [it state _]
(let [page (wsh/lookup-page state)
objects (:objects page)
ids (into #{}
(comp (map (d/getf objects))
(filter #(ctk/allow-duplicate? objects %))
(map :id))
ids)]
(when (seq ids)
(let [obj (get objects (first ids))
delta (if move-delta?
(calc-duplicate-delta obj state objects)
(gpt/point 0 0))
file-id (:current-file-id state)
libraries (wsh/get-libraries state)
library-data (wsh/get-file state file-id)
changes (->> (prepare-duplicate-changes objects page ids delta it libraries library-data file-id)
(duplicate-changes-update-indices objects ids))
tags (or (:tags changes) #{})
changes (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication)))
id-original (first ids)
new-ids (->> changes
:redo-changes
(filter #(= (:type %) :add-obj))
(filter #(ids (:old-id %)))
(map #(get-in % [:obj :id]))
(into (d/ordered-set)))
id-duplicated (first new-ids)
frames (into #{}
(map #(get-in objects [% :frame-id]))
ids)
undo-id (js/Symbol)]
;; Warning: This order is important for the focus mode.
(->> (rx/of
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(when change-selection?
(select-shapes new-ids))
(ptk/data-event :layout/update {:ids frames})
(memorize-duplicated id-original id-duplicated)
(dwu/commit-undo-transaction undo-id))
(rx/tap #(when (some? return-ref)
(reset! return-ref id-duplicated))))))))))
(defn duplicate-selected (defn duplicate-selected
([move-delta?] ([move-delta?]
(duplicate-selected move-delta? false)) (duplicate-selected move-delta? false))
([move-delta? alt-duplication?] ([move-delta? alt-duplication?]
(ptk/reify ::duplicate-selected (ptk/reify ::duplicate-selected
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [_ state _]
(when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform]))) (when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform])))
(let [page (wsh/lookup-page state) (let [selected (wsh/lookup-selected state)]
objects (:objects page) (rx/of (duplicate-shapes selected
selected (->> (wsh/lookup-selected state) :move-delta? move-delta?
(map (d/getf objects)) :alt-duplication? alt-duplication?))))))))
(filter #(ctk/allow-duplicate? objects %))
(map :id)
set)]
(when (seq selected)
(let [obj (get objects (first selected))
delta (if move-delta?
(calc-duplicate-delta obj state objects)
(gpt/point 0 0))
file-id (:current-file-id state)
libraries (wsh/get-libraries state)
library-data (wsh/get-file state file-id)
changes (->> (prepare-duplicate-changes objects page selected delta it libraries library-data file-id)
(duplicate-changes-update-indices objects selected))
tags (or (:tags changes) #{})
changes (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication)))
id-original (first selected)
new-selected (->> changes
:redo-changes
(filter #(= (:type %) :add-obj))
(filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into (d/ordered-set)))
id-duplicated (first new-selected)
frames (into #{}
(map #(get-in objects [% :frame-id]))
selected)
undo-id (js/Symbol)]
;; Warning: This order is important for the focus mode.
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(select-shapes new-selected)
(ptk/data-event :layout/update {:ids frames})
(memorize-duplicated id-original id-duplicated)
(dwu/commit-undo-transaction undo-id))))))))))
(defn change-hover-state (defn change-hover-state
[id value] [id value]

View File

@@ -72,7 +72,7 @@
:layout-grid-columns []}) :layout-grid-columns []})
(defn get-layout-initializer (defn get-layout-initializer
[type from-frame?] [type from-frame? calculate-params?]
(let [[initial-layout-data calculate-params] (let [[initial-layout-data calculate-params]
(case type (case type
:flex [initial-flex-layout flex/calculate-params] :flex [initial-flex-layout flex/calculate-params]
@@ -87,9 +87,11 @@
(cond-> (not from-frame?) (cond-> (not from-frame?)
(assoc :show-content true :hide-in-viewer true))) (assoc :show-content true :hide-in-viewer true)))
params (calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape)] params (when calculate-params?
(calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape))]
(cond-> (merge shape params) (cond-> (merge shape params)
(= type :grid) (-> (ctl/assign-cells objects) ctl/reorder-grid-children)))))) (= type :grid)
(-> (ctl/assign-cells objects) ctl/reorder-grid-children))))))
;; Never call this directly but through the data-event `:layout/update` ;; Never call this directly but through the data-event `:layout/update`
;; Otherwise a lot of cycle dependencies could be generated ;; Otherwise a lot of cycle dependencies could be generated
@@ -124,7 +126,7 @@
(ptk/reify ::finalize)) (ptk/reify ::finalize))
(defn create-layout-from-id (defn create-layout-from-id
[id type from-frame?] [id type & {:keys [from-frame? calculate-params?] :or {from-frame? false calculate-params? true}}]
(dm/assert! (dm/assert!
"expected uuid for `id`" "expected uuid for `id`"
(uuid? id)) (uuid? id))
@@ -135,7 +137,7 @@
(let [objects (wsh/lookup-page-objects state) (let [objects (wsh/lookup-page-objects state)
parent (get objects id) parent (get objects id)
undo-id (js/Symbol) undo-id (js/Symbol)
layout-initializer (get-layout-initializer type from-frame?)] layout-initializer (get-layout-initializer type from-frame? calculate-params?)]
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes [id] layout-initializer {:with-objects? true}) (dch/update-shapes [id] layout-initializer {:with-objects? true})
@@ -177,7 +179,7 @@
(dwse/select-shapes ordered-ids) (dwse/select-shapes ordered-ids)
(dwsh/create-artboard-from-selection new-shape-id parent-id group-index (:name (first selected-shapes))) (dwsh/create-artboard-from-selection new-shape-id parent-id group-index (:name (first selected-shapes)))
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1}) (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
(create-layout-from-id new-shape-id type false) (create-layout-from-id new-shape-id type)
(dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto)) (dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
(dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix)) (dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))
(dwsh/delete-shapes page-id selected) (dwsh/delete-shapes page-id selected)
@@ -188,7 +190,7 @@
(rx/of (rx/of
(dwsh/create-artboard-from-selection new-shape-id) (dwsh/create-artboard-from-selection new-shape-id)
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1}) (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
(create-layout-from-id new-shape-id type false) (create-layout-from-id new-shape-id type)
(dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto)) (dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
(dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix)))) (dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))))
@@ -227,7 +229,7 @@
(rx/of (rx/of
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
(if (and single? is-frame?) (if (and single? is-frame?)
(create-layout-from-id (first selected) type true) (create-layout-from-id (first selected) type :from-frame? true)
(create-layout-from-selection type)) (create-layout-from-selection type))
(dwu/commit-undo-transaction undo-id)))))) (dwu/commit-undo-transaction undo-id))))))

View File

@@ -10,8 +10,8 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.libraries-helpers :as cflh]
[app.common.files.shapes-helpers :as cfsh] [app.common.files.shapes-helpers :as cfsh]
[app.common.logic.shapes :as cls]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
@@ -105,10 +105,10 @@
components-v2 (features/active-feature? state "components/v2") components-v2 (features/active-feature? state "components/v2")
undo-id (or (:undo-id options) (js/Symbol)) undo-id (or (:undo-id options) (js/Symbol))
[all-parents changes] (-> (pcb/empty-changes it (:id page)) [all-parents changes] (-> (pcb/empty-changes it (:id page))
(cflh/generate-delete-shapes file page objects ids {:components-v2 components-v2 (cls/generate-delete-shapes file page objects ids {:components-v2 components-v2
:ignore-touched (:component-swap options) :ignore-touched (:component-swap options)
:undo-group (:undo-group options) :undo-group (:undo-group options)
:undo-id undo-id}))] :undo-id undo-id}))]
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dc/detach-comment-thread ids) (dc/detach-comment-thread ids)

View File

@@ -13,6 +13,7 @@
[app.common.svg :as csvg] [app.common.svg :as csvg]
[app.common.svg.shapes-builder :as csvg.shapes-builder] [app.common.svg.shapes-builder :as csvg.shapes-builder]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
@@ -60,52 +61,57 @@
(rx/reduce conj {}))) (rx/reduce conj {})))
(defn add-svg-shapes (defn add-svg-shapes
[svg-data position] ([svg-data position]
(ptk/reify ::add-svg-shapes (add-svg-shapes nil svg-data position nil))
ptk/WatchEvent
(watch [it state _]
(try
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
frame-id (ctst/top-nested-frame objects position)
selected (wsh/lookup-selected state)
base (cfh/get-base-shape objects selected)
selected-id (first selected) ([id svg-data position {:keys [change-selection?] :or {change-selection? false}}]
selected-frame? (and (= 1 (count selected)) (ptk/reify ::add-svg-shapes
(= :frame (dm/get-in objects [selected-id :type]))) ptk/WatchEvent
(watch [it state _]
(try
(let [id (d/nilv id (uuid/next))
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
frame-id (ctst/top-nested-frame objects position)
selected (wsh/lookup-selected state)
base (cfh/get-base-shape objects selected)
parent-id (if (or selected-frame? (empty? selected)) selected-id (first selected)
frame-id selected-frame? (and (= 1 (count selected))
(:parent-id base)) (= :frame (dm/get-in objects [selected-id :type])))
[new-shape new-children] parent-id (if (or selected-frame? (empty? selected))
(csvg.shapes-builder/create-svg-shapes svg-data position objects frame-id parent-id selected true) frame-id
(:parent-id base))
changes (-> (pcb/empty-changes it page-id) [new-shape new-children]
(pcb/with-objects objects) (csvg.shapes-builder/create-svg-shapes id svg-data position objects frame-id parent-id selected true)
(pcb/add-object new-shape))
changes (reduce (fn [changes new-child] changes (-> (pcb/empty-changes it page-id)
(pcb/add-object changes new-child)) (pcb/with-objects objects)
changes (pcb/add-object new-shape))
new-children)
changes (pcb/resize-parents changes changes (reduce (fn [changes new-child]
(->> (:redo-changes changes) (pcb/add-object changes new-child))
(filter #(= :add-obj (:type %))) changes
(map :id) new-children)
(reverse)
(vec)))
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id) changes (pcb/resize-parents changes
(dch/commit-changes changes) (->> (:redo-changes changes)
(dws/select-shapes (d/ordered-set (:id new-shape))) (filter #(= :add-obj (:type %)))
(ptk/data-event :layout/update {:ids [(:id new-shape)]}) (map :id)
(dwu/commit-undo-transaction undo-id))) (reverse)
(vec)))
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(when change-selection?
(dws/select-shapes (d/ordered-set (:id new-shape))))
(ptk/data-event :layout/update {:ids [(:id new-shape)]})
(dwu/commit-undo-transaction undo-id)))
(catch :default cause
(rx/throw {:type :svg-parser
:data cause})))))))
(catch :default cause
(js/console.log (.-stack cause))
(rx/throw {:type :svg-parser
:data cause}))))))

View File

@@ -18,6 +18,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.flex-layout :as gslf] [app.common.geom.shapes.flex-layout :as gslf]
[app.common.geom.shapes.grid-layout :as gslg] [app.common.geom.shapes.grid-layout :as gslg]
[app.common.logic.shapes :as cls]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
@@ -831,129 +832,14 @@
:ignore-constraints false :ignore-constraints false
:ignore-snap-pixel true})))))) :ignore-snap-pixel true}))))))
(defn- move-shapes-to-frame (defn move-shapes-to-frame
[ids frame-id drop-index [row column :as cell]] [ids frame-id drop-index cell]
(ptk/reify ::move-shapes-to-frame (ptk/reify ::move-shapes-to-frame
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id) objects (wsh/lookup-page-objects state page-id)
lookup (d/getf objects) changes (cls/generate-move-shapes-to-frame (pcb/empty-changes it) ids frame-id page-id objects drop-index cell)]
frame (get objects frame-id)
layout? (:layout frame)
component-main-frame (ctn/find-component-main objects frame false)
shapes (->> ids
(cfh/clean-loops objects)
(keep lookup)
;;remove shapes inside copies, because we can't change the structure of copies
(remove #(ctk/in-component-copy? (get objects (:parent-id %)))))
moving-shapes
(cond->> shapes
(not layout?)
(remove #(= (:frame-id %) frame-id))
layout?
(remove #(and (= (:frame-id %) frame-id)
(not= (:parent-id %) frame-id))))
ordered-indexes (cfh/order-by-indexed-shapes objects (map :id moving-shapes))
moving-shapes (map (d/getf objects) ordered-indexes)
all-parents
(reduce (fn [res id]
(into res (cfh/get-parent-ids objects id)))
(d/ordered-set)
ids)
find-all-empty-parents
(fn recursive-find-empty-parents [empty-parents]
(let [all-ids (into empty-parents ids)
contains? (partial contains? all-ids)
xform (comp (map lookup)
(filter cfh/group-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
;; Any empty parent whose children are moved to another frame should be deleted
(if (empty? moving-shapes)
#{}
(into (d/ordered-set) (find-all-empty-parents #{})))
;; Not move absolute shapes that won't change parent
moving-shapes
(->> moving-shapes
(remove (fn [shape]
(and (ctl/position-absolute? shape)
(= frame-id (:parent-id shape))))))
frame-component
(ctn/get-component-shape objects frame)
shape-ids-to-detach
(reduce (fn [result shape]
(if (and (some? shape) (ctk/in-component-copy-not-head? shape))
(let [shape-component (ctn/get-component-shape objects shape)]
(if (= (:id frame-component) (:id shape-component))
result
(into result (cfh/get-children-ids-with-self objects (:id shape)))))
result))
#{}
moving-shapes)
moving-shapes-ids
(map :id moving-shapes)
moving-shapes-children-ids
(->> moving-shapes-ids
(mapcat #(cfh/get-children-ids-with-self objects %)))
child-heads
(->> moving-shapes-ids
(mapcat #(ctn/get-child-heads objects %))
(map :id))
changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? objects frame-id))
(pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data))
;; Remove the swap slots if it is moving to a different component
(pcb/update-shapes child-heads
(fn [shape]
(cond-> shape
(not= component-main-frame (ctn/find-component-main objects shape false))
(ctk/remove-swap-slot))))
;; Remove component-root property when moving a shape inside a component
(cond-> (ctn/get-instance-root objects frame)
(pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root)))
;; Add component-root property when moving a component outside a component
(cond-> (not (ctn/get-instance-root objects frame))
(pcb/update-shapes child-heads #(assoc % :component-root true)))
(pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))
(pcb/update-shapes shape-ids-to-detach ctk/detach-shape)
(pcb/change-parent frame-id moving-shapes drop-index)
(cond-> (ctl/grid-layout? objects frame-id)
(-> (pcb/update-shapes
[frame-id]
(fn [frame objects]
(-> frame
;; Assign the cell when pushing into a specific grid cell
(cond-> (some? cell)
(-> (ctl/push-into-cell moving-shapes-ids row column)
(ctl/assign-cells objects)))
(ctl/assign-cell-positions objects)))
{:with-objects? true})
(pcb/reorder-grid-children [frame-id])))
(pcb/remove-objects empty-parents))]
(when (and (some? frame-id) (d/not-empty? changes)) (when (and (some? frame-id) (d/not-empty? changes))
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)

View File

@@ -6,6 +6,8 @@
(ns app.main.data.workspace.zoom (ns app.main.data.workspace.zoom
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.geom.align :as gal] [app.common.geom.align :as gal]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
@@ -54,14 +56,20 @@
#(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01))))))))) #(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01)))))))))
(defn set-zoom (defn set-zoom
[center scale] ([scale]
(ptk/reify ::set-zoom (set-zoom nil scale))
ptk/UpdateEvent ([center scale]
(update [_ state] (ptk/reify ::set-zoom
(update state :workspace-local ptk/UpdateEvent
#(impl-update-zoom % center (fn [z] (-> (* z scale) (update [_ state]
(max 0.01) (let [vp (dm/get-in state [:workspace-local :vbox])
(min 200)))))))) x (+ (:x vp) (/ (:width vp) 2))
y (+ (:y vp) (/ (:height vp) 2))
center (d/nilv center (gpt/point x y))]
(update state :workspace-local
#(impl-update-zoom % center (fn [z] (-> (* z scale)
(max 0.01)
(min 200))))))))))
(def reset-zoom (def reset-zoom
(ptk/reify ::reset-zoom (ptk/reify ::reset-zoom
@@ -110,6 +118,31 @@
(assoc :zoom-inverse (/ 1 zoom)) (assoc :zoom-inverse (/ 1 zoom))
(update :vbox merge srect))))))))))) (update :vbox merge srect)))))))))))
(defn fit-to-shapes
[ids]
(ptk/reify ::fit-to-shapes
ptk/UpdateEvent
(update [_ state]
(if (empty? ids)
state
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
srect (->> ids
(map #(get objects %))
(gsh/shapes->rect))]
(update state :workspace-local
(fn [{:keys [vport] :as local}]
(let [srect (gal/adjust-to-viewport
vport srect
{:padding 40})
zoom (/ (:width vport)
(:width srect))]
(-> local
(assoc :zoom zoom)
(assoc :zoom-inverse (/ 1 zoom))
(update :vbox merge srect))))))))))
(defn start-zooming [pt] (defn start-zooming [pt]
(ptk/reify ::start-zooming (ptk/reify ::start-zooming
ptk/WatchEvent ptk/WatchEvent

View File

@@ -133,7 +133,10 @@
(defn- fetch-gfont-css (defn- fetch-gfont-css
[url] [url]
(->> (http/send! {:method :get :uri url :mode :cors :response-type :text}) (->> (http/send! {:method :get :uri url :mode :cors :response-type :text})
(rx/map :body))) (rx/map :body)
(rx/catch (fn [err]
(.warn js/console "Cannot find the font" (obj/get err "message"))
(rx/empty)))))
(defmethod load-font :google (defmethod load-font :google
[{:keys [id ::on-loaded] :as font}] [{:keys [id ::on-loaded] :as font}]

View File

@@ -7,15 +7,11 @@
(ns app.main.ui.dashboard.projects (ns app.main.ui.dashboard.projects
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.config :as cf]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.data.messages :as msg]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.errors :as errors]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.dashboard.grid :refer [line-grid]] [app.main.ui.dashboard.grid :refer [line-grid]]
@@ -100,80 +96,6 @@
(def builtin-templates (def builtin-templates
(l/derived :builtin-templates st/state)) (l/derived :builtin-templates st/state))
(mf/defc tutorial-project
[{:keys [close-tutorial default-project-id] :as props}]
(let [state (mf/use-state {:status :waiting
:file nil})
templates (mf/deref builtin-templates)
template (d/seek #(= (:id %) "tutorial-for-beginners") templates)
on-template-cloned-success
(mf/use-fn
(mf/deps default-project-id)
(fn [response]
(swap! state #(assoc % :status :success :file (:first response)))
(st/emit! (dd/go-to-workspace {:id (first response) :project-id default-project-id :name "tutorial"})
(du/update-profile-props {:viewed-tutorial? true}))))
on-template-cloned-error
(mf/use-fn
(fn [cause]
(swap! state assoc :status :error)
(errors/print-error! cause)
(st/emit! (msg/error (tr "dashboard.libraries-and-templates.import-error")))))
download-tutorial
(mf/use-fn
(mf/deps template default-project-id)
(fn []
(let [mdata {:on-success on-template-cloned-success
:on-error on-template-cloned-error}
params {:project-id default-project-id
:template-id (:id template)}]
(swap! state #(assoc % :status :importing))
(st/emit! (with-meta (dd/clone-template (with-meta params mdata))
{::ev/origin "get-started-hero-block"})))))]
[:article {:class (stl/css :tutorial)}
[:div {:class (stl/css :thumbnail)}]
[:div {:class (stl/css :text)}
[:h2 {:class (stl/css :title)} (tr "dasboard.tutorial-hero.title")]
[:p {:class (stl/css :info)} (tr "dasboard.tutorial-hero.info")]
[:button {:class (stl/css :btn-primary :action)
:on-click download-tutorial}
(case (:status @state)
:waiting (tr "dasboard.tutorial-hero.start")
:importing [:span.loader i/loader-pencil]
:success "")]]
[:button {:class (stl/css :close)
:on-click close-tutorial
:aria-label (tr "labels.close")}
close-icon]]))
(mf/defc interface-walkthrough
{::mf/wrap [mf/memo]}
[{:keys [close-walkthrough] :as props}]
(let [handle-walkthrough-link
(fn []
(st/emit! (ptk/event ::ev/event {::ev/name "show-walkthrough"
::ev/origin "get-started-hero-block"
:section "dashboard"})))]
[:article {:class (stl/css :walkthrough)}
[:div {:class (stl/css :thumbnail)}]
[:div {:class (stl/css :text)}
[:h2 {:class (stl/css :title)} (tr "dasboard.walkthrough-hero.title")]
[:p {:class (stl/css :info)} (tr "dasboard.walkthrough-hero.info")]
[:a {:class (stl/css :btn-primary :action)
:href " https://design.penpot.app/walkthrough"
:target "_blank"
:on-click handle-walkthrough-link}
(tr "dasboard.walkthrough-hero.start")]]
[:button {:class (stl/css :close)
:on-click close-walkthrough
:aria-label (tr "labels.close")}
close-icon]]))
(mf/defc project-item (mf/defc project-item
[{:keys [project first? team files] :as props}] [{:keys [project first? team files] :as props}]
(let [locale (mf/deref i18n/locale) (let [locale (mf/deref i18n/locale)
@@ -365,7 +287,7 @@
(l/derived :dashboard-recent-files st/state)) (l/derived :dashboard-recent-files st/state))
(mf/defc projects-section (mf/defc projects-section
[{:keys [team projects profile default-project-id] :as props}] [{:keys [team projects profile] :as props}]
(let [projects (->> (vals projects) (let [projects (->> (vals projects)
(sort-by :modified-at) (sort-by :modified-at)
(reverse)) (reverse))
@@ -378,8 +300,6 @@
(:team-hero? props true) (:team-hero? props true)
(not (:is-default team))) (not (:is-default team)))
tutorial-viewed? (:viewed-tutorial? props true)
walkthrough-viewed? (:viewed-walkthrough? props true)
is-my-penpot (= (:default-team-id profile) (:id team)) is-my-penpot (= (:default-team-id profile) (:id team))
team-id (:id team) team-id (:id team)
@@ -391,28 +311,6 @@
(ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero" (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero"
::ev/origin "dashboard"})))) ::ev/origin "dashboard"}))))
close-tutorial
(mf/use-fn
(fn []
(st/emit! (du/update-profile-props {:viewed-tutorial? true})
(ptk/data-event ::ev/event {::ev/name "dont-show-tutorial"
::ev/origin "get-started-hero"
:type "tutorial"
:section "dashboard"}))))
close-walkthrough
(mf/use-fn
(fn []
(st/emit! (du/update-profile-props {:viewed-walkthrough? true})
(ptk/data-event ::ev/event {::ev/name "dont-show-walkthrough"
::ev/origin "get-started-hero"
:type "walkthrough"
:section "dashboard"}))))
show-hero? (and is-my-penpot
(or (not tutorial-viewed?)
(not walkthrough-viewed?)))
show-team-hero? (and (not is-my-penpot) team-hero?)] show-team-hero? (and (not is-my-penpot) team-hero?)]
(mf/with-effect [team] (mf/with-effect [team]
@@ -433,22 +331,9 @@
(when team-hero? (when team-hero?
[:& team-hero {:team team :close-fn close-banner}]) [:& team-hero {:team team :close-fn close-banner}])
(when (and (contains? cf/flags :dashboard-templates-section)
show-hero?)
[:div {:class (stl/css :hero-projects)}
(when (and (not tutorial-viewed?) (:is-default team))
[:& tutorial-project
{:close-tutorial close-tutorial
:default-project-id default-project-id}])
(when (and (not walkthrough-viewed?) (:is-default team))
[:& interface-walkthrough
{:close-walkthrough close-walkthrough}])])
[:div {:class (stl/css-case :dashboard-container true [:div {:class (stl/css-case :dashboard-container true
:no-bg true :no-bg true
:dashboard-projects true :dashboard-projects true
:with-hero show-hero?
:with-team-hero show-team-hero?)} :with-team-hero show-team-hero?)}
(for [{:keys [id] :as project} projects] (for [{:keys [id] :as project} projects]
(let [files (when recent-map (let [files (when recent-map

View File

@@ -20,7 +20,6 @@
height: calc(100vh - $s-64); height: calc(100vh - $s-64);
} }
.with-hero,
.with-team-hero { .with-team-hero {
height: calc(100vh - $s-280); height: calc(100vh - $s-280);
} }
@@ -242,88 +241,3 @@
width: 0; width: 0;
} }
} }
.hero-projects {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: $s-32;
margin: 0 $s-16 $s-16 $s-20;
@media (max-width: 1366px) {
grid-template-columns: 1fr;
}
.tutorial,
.walkthrough {
display: grid;
grid-template-columns: auto 1fr;
position: relative;
border-radius: $br-8;
min-height: $s-216;
background-color: $db-tertiary;
padding: $s-8;
.thumbnail {
width: $s-200;
height: $s-200;
border-radius: $br-6;
padding: $s-32;
display: block;
background-color: var(--color-canvas);
}
img {
border-radius: $br-4;
margin-bottom: 0;
width: $s-232;
}
.text {
padding: $s-32;
display: flex;
flex-direction: column;
}
.title {
color: $df-primary;
font-size: $fs-24;
font-weight: $fw400;
margin-bottom: $s-8;
}
.info {
flex: 1;
color: $df-secondary;
margin-bottom: $s-20;
font-size: $fs-16;
}
.invite {
height: $s-32;
}
.action {
width: $s-180;
height: $s-40;
}
}
.walkthrough {
.thumbnail {
background-image: url("/images/walkthrough-cover.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
}
.tutorial {
.thumbnail {
background-image: url("/images/hands-on-tutorial.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.loader {
display: flex;
svg#loader-pencil {
width: $s-32;
}
}
}
}

View File

@@ -332,7 +332,7 @@
:title (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))} :title (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))}
i/comments]) i/comments])
(when (or (= (:type permissions) :membership) (when (or (:in-team permissions)
(and (= (:type permissions) :share-link) (and (= (:type permissions) :share-link)
(= (:who-inspect permissions) "all"))) (= (:who-inspect permissions) "all")))
[:button {:on-click go-to-inspect [:button {:on-click go-to-inspect

View File

@@ -9,15 +9,23 @@
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.changes-builder :as cb] [app.common.files.changes-builder :as cb]
[app.common.geom.point :as gpt]
[app.common.record :as cr] [app.common.record :as cr]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace.changes :as ch] [app.main.data.workspace.changes :as ch]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.media :as dwm]
[app.main.store :as st] [app.main.store :as st]
[app.plugins.events :as events] [app.plugins.events :as events]
[app.plugins.file :as file] [app.plugins.file :as file]
[app.plugins.page :as page] [app.plugins.page :as page]
[app.plugins.shape :as shape])) [app.plugins.shape :as shape]
[app.plugins.utils :as utils]
[app.plugins.viewport :as viewport]
[app.util.object :as obj]
[beicon.v2.core :as rx]
[promesa.core :as p]))
;; ;;
;; PLUGINS PUBLIC API - The plugins will able to access this functions ;; PLUGINS PUBLIC API - The plugins will able to access this functions
@@ -28,12 +36,30 @@
(map val) (map val)
(map shape/data->shape-proxy))) (map shape/data->shape-proxy)))
(defn create-shape
[type]
(let [page-id (:current-page-id @st/state)
page (dm/get-in @st/state [:workspace-data :pages-index page-id])
shape (cts/setup-shape {:type type
:x 0 :y 0 :width 100 :height 100})
changes
(-> (cb/empty-changes)
(cb/with-page page)
(cb/with-objects (:objects page))
(cb/add-object shape))]
(st/emit! (ch/commit-changes changes))
(shape/data->shape-proxy shape)))
(deftype PenpotContext [] (deftype PenpotContext []
Object Object
(addListener (addListener
[_ type callback] [_ type callback]
(events/add-listener type callback)) (events/add-listener type callback))
(getViewport
[_]
(viewport/create-proxy))
(getFile (getFile
[_] [_]
(file/data->file-proxy (:workspace-file @st/state) (:workspace-data @st/state))) (file/data->file-proxy (:workspace-file @st/state) (:workspace-data @st/state)))
@@ -70,19 +96,46 @@
"dark" "dark"
(get-in @st/state [:profile :theme])))) (get-in @st/state [:profile :theme]))))
(uploadMediaUrl
[_ name url]
(let [file-id (get-in @st/state [:workspace-file :id])]
(p/create
(fn [resolve reject]
(->> (dwm/upload-media-url name file-id url)
(rx/map utils/to-js)
(rx/take 1)
(rx/subs! resolve reject))))))
(group
[_ shapes]
(let [page-id (:current-page-id @st/state)
id (uuid/next)
ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)]
(st/emit! (dwg/group-shapes id ids))
(shape/data->shape-proxy
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id]))))
(ungroup
[_ group & rest]
(let [shapes (concat [group] rest)
ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)]
(st/emit! (dwg/ungroup-shapes ids))))
(createFrame
[_]
(create-shape :frame))
(createRectangle (createRectangle
[_] [_]
(let [page-id (:current-page-id @st/state) (create-shape :rect))
page (dm/get-in @st/state [:workspace-data :pages-index page-id])
shape (cts/setup-shape {:type :rect (createShapeFromSvg
:x 0 :y 0 :width 100 :height 100}) [_ svg-string]
changes (let [id (uuid/next)
(-> (cb/empty-changes) page-id (:current-page-id @st/state)]
(cb/with-page page) (st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0)))
(cb/with-objects (:objects page)) (shape/data->shape-proxy
(cb/add-object shape))] (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id])))))
(st/emit! (ch/commit-changes changes))
(shape/data->shape-proxy shape))))
(defn create-context (defn create-context
[] []
@@ -90,4 +143,5 @@
(PenpotContext.) (PenpotContext.)
{:name "root" :get #(.getRoot ^js %)} {:name "root" :get #(.getRoot ^js %)}
{:name "currentPage" :get #(.getPage ^js %)} {:name "currentPage" :get #(.getPage ^js %)}
{:name "selection" :get #(.getSelectedShapes ^js %)})) {:name "selection" :get #(.getSelectedShapes ^js %)}
{:name "viewport" :get #(.getViewport ^js %)}))

View File

@@ -0,0 +1,173 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.plugins.grid
(:require
[app.common.data :as d]
[app.common.record :as crc]
[app.common.spec :as us]
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.plugins.utils :as utils :refer [get-data get-state]]
[app.util.object :as obj]
[potok.v2.core :as ptk]))
(defn- make-tracks
[tracks]
(.freeze
js/Object
(apply array (->> tracks (map utils/to-js)))))
(deftype GridLayout [_data]
Object
(addRow
[self type value]
(let [id (get-data self :id)
type (keyword type)]
(st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value}))))
(addRowAtIndex
[self type value index]
(let [id (get-data self :id)
type (keyword type)]
(st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value} index))))
(addColumn
[self type value]
(let [id (get-data self :id)
type (keyword type)]
(st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value}))))
(addColumnAtIndex
[self type value index]
(let [id (get-data self :id)
type (keyword type)]
(st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value} index))))
(removeRow
[self index]
(let [id (get-data self :id)]
(st/emit! (dwsl/remove-layout-track #{id} :row index))))
(removeColumn
[self index]
(let [id (get-data self :id)]
(st/emit! (dwsl/remove-layout-track #{id} :column index))))
(setColumn
[self index type value]
(let [id (get-data self :id)
type (keyword type)]
(st/emit! (dwsl/change-layout-track #{id} :column index (d/without-nils {:type type :value value})))))
(setRow
[self index type value]
(let [id (get-data self :id)
type (keyword type)]
(st/emit! (dwsl/change-layout-track #{id} :row index (d/without-nils {:type type :value value})))))
(remove
[self]
(let [id (get-data self :id)]
(st/emit! (dwsl/remove-layout #{id}))))
(appendChild
[self child row column]
(let [parent-id (get-data self :id)
child-id (uuid/uuid (obj/get child "id"))]
(st/emit! (dwt/move-shapes-to-frame #{child-id} parent-id nil [row column])
(ptk/data-event :layout/update {:ids [parent-id]})))))
(defn grid-layout-proxy
[data]
(-> (GridLayout. data)
(crc/add-properties!
{:name "dir"
:get #(get-state % :layout-grid-dir d/name)
:set
(fn [self value]
(let [id (get-data self :id)
value (keyword value)]
(when (contains? ctl/grid-direction-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))}
{:name "rows"
:get #(get-state % :layout-grid-rows make-tracks)}
{:name "columns"
:get #(get-state % :layout-grid-columns make-tracks)}
{:name "alignItems"
:get #(get-state % :layout-align-items d/name)
:set
(fn [self value]
(let [id (get-data self :id)
value (keyword value)]
(when (contains? ctl/align-items-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))}
{:name "alignContent"
:get #(get-state % :layout-align-content d/name)
:set
(fn [self value]
(let [id (get-data self :id)
value (keyword value)]
(when (contains? ctl/align-content-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))}
{:name "justifyItems"
:get #(get-state % :layout-justify-items d/name)
:set
(fn [self value]
(let [id (get-data self :id)
value (keyword value)]
(when (contains? ctl/justify-items-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))}
{:name "justifyContent"
:get #(get-state % :layout-justify-content d/name)
:set
(fn [self value]
(let [id (get-data self :id)
value (keyword value)]
(when (contains? ctl/justify-content-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))}
{:name "rowGap"
:get #(:row-gap (get-state % :layout-gap))
:set
(fn [self value]
(let [id (get-data self :id)]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))}
{:name "columnGap"
:get #(:column-gap (get-state % :layout-gap))
:set
(fn [self value]
(let [id (get-data self :id)]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))}
{:name "verticalPadding"
:get #(:p1 (get-state % :layout-padding))
:set
(fn [self value]
(let [id (get-data self :id)]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))}
{:name "horizontalPadding"
:get #(:p2 (get-state % :layout-padding))
:set
(fn [self value]
(let [id (get-data self :id)]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))})))

View File

@@ -7,16 +7,20 @@
(ns app.plugins.shape (ns app.plugins.shape
"RPC for plugins runtime." "RPC for plugins runtime."
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.record :as crc] [app.common.record :as crc]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.uuid :as uuid]
[app.main.data.workspace :as udw] [app.main.data.workspace :as udw]
[app.main.data.workspace.changes :as dwc] [app.main.data.workspace.changes :as dwc]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.store :as st] [app.main.store :as st]
[app.plugins.utils :refer [get-data get-data-fn]] [app.plugins.grid :as grid]
[cuerdas.core :as str])) [app.plugins.utils :as utils :refer [get-data get-data-fn get-state]]
[app.util.object :as obj]))
(declare data->shape-proxy) (declare data->shape-proxy)
@@ -24,52 +28,65 @@
[fills] [fills]
(.freeze (.freeze
js/Object js/Object
(apply array (apply array (->> fills (map utils/to-js)))))
(->> fills
;; TODO: Transform explicitly instead of cljs->js?
(map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))}))))))
(defn- make-strokes (defn- make-strokes
[strokes] [strokes]
(.freeze (.freeze
js/Object js/Object
(apply array (apply array (->> strokes (map utils/to-js)))))
(->> strokes
;; TODO: Transform explicitly instead of cljs->js?
(map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))}))))))
(defn- locate-shape (defn- locate-shape
[shape-id] [shape-id]
(let [page-id (:current-page-id @st/state)] (let [page-id (:current-page-id @st/state)]
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects shape-id]))) (dm/get-in @st/state [:workspace-data :pages-index page-id :objects shape-id])))
(defn- get-state (deftype ShapeProxy [#_:clj-kondo/ignore _data]
([self attr]
(let [id (get-data self :id)
page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))]
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr])))
([self attr mapfn]
(-> (get-state self attr)
(mapfn))))
(deftype ShapeProxy [^:mutable #_:clj-kondo/ignore _data]
Object Object
(resize
[self width height]
(let [id (get-data self :id)]
(st/emit! (udw/update-dimensions [id] :width width)
(udw/update-dimensions [id] :height height))))
(clone [self]
(let [id (get-data self :id)
page-id (:current-page-id @st/state)
ret-v (atom nil)]
(st/emit! (dws/duplicate-shapes #{id} :change-selection? false :return-ref ret-v))
(let [new-id (deref ret-v)
shape (dm/get-in @st/state [:workspace-data :pages-index page-id :objects new-id])]
(data->shape-proxy shape))))
(remove [self]
(let [id (get-data self :id)]
(st/emit! (dwsh/delete-shapes #{id}))))
;; Only for frames + groups + booleans
(getChildren (getChildren
[self] [self]
(apply array (->> (get-state self :shapes) (apply array (->> (get-state self :shapes)
(map locate-shape) (map locate-shape)
(map data->shape-proxy)))) (map data->shape-proxy))))
(resize (appendChild [self child]
[self width height] (let [parent-id (get-data self :id)
child-id (uuid/uuid (obj/get child "id"))]
(st/emit! (udw/relocate-shapes #{child-id} parent-id 0))))
(insertChild [self index child]
(let [parent-id (get-data self :id)
child-id (uuid/uuid (obj/get child "id"))]
(st/emit! (udw/relocate-shapes #{child-id} parent-id index))))
;; Only for frames
(addFlexLayout [self]
(let [id (get-data self :id)] (let [id (get-data self :id)]
(st/emit! (udw/update-dimensions [id] :width width) (st/emit! (dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false))))
(udw/update-dimensions [id] :height height))))
(clone [_] (.log js/console (clj->js _data))) (addGridLayout [self]
(delete [_] (.log js/console (clj->js _data))) (let [id (get-data self :id)]
(appendChild [_] (.log js/console (clj->js _data)))) (st/emit! (dwsl/create-layout-from-id id :grid :from-frame? true :calculate-params? false)))))
(crc/define-properties! (crc/define-properties!
ShapeProxy ShapeProxy
@@ -88,7 +105,7 @@
:get (get-data-fn :id str)} :get (get-data-fn :id str)}
{:name "type" {:name "type"
:get (get-data-fn :type)} :get (get-data-fn :type name)}
{:name "x" {:name "x"
:get #(get-state % :x) :get #(get-state % :x)
@@ -104,6 +121,62 @@
(let [id (get-data self :id)] (let [id (get-data self :id)]
(st/emit! (udw/update-position id {:y value}))))} (st/emit! (udw/update-position id {:y value}))))}
{:name "parentX"
:get (fn [self]
(let [page-id (:current-page-id @st/state)
parent-id (get-state self :parent-id)
parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])]
(- (get-state self :x) parent-x)))
:set
(fn [self value]
(let [page-id (:current-page-id @st/state)
id (get-data self :id)
parent-id (get-state self :parent-id)
parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])]
(st/emit! (udw/update-position id {:x (+ parent-x value)}))))}
{:name "parentY"
:get (fn [self]
(let [page-id (:current-page-id @st/state)
parent-id (get-state self :parent-id)
parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])]
(- (get-state self :y) parent-y)))
:set
(fn [self value]
(let [page-id (:current-page-id @st/state)
id (get-data self :id)
parent-id (get-state self :parent-id)
parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])]
(st/emit! (udw/update-position id {:y (+ parent-y value)}))))}
{:name "frameX"
:get (fn [self]
(let [page-id (:current-page-id @st/state)
frame-id (get-state self :frame-id)
frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])]
(- (get-state self :x) frame-x)))
:set
(fn [self value]
(let [page-id (:current-page-id @st/state)
id (get-data self :id)
frame-id (get-state self :frame-id)
frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])]
(st/emit! (udw/update-position id {:x (+ frame-x value)}))))}
{:name "frameY"
:get (fn [self]
(let [page-id (:current-page-id @st/state)
frame-id (get-state self :frame-id)
frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])]
(- (get-state self :y) frame-y)))
:set
(fn [self value]
(let [page-id (:current-page-id @st/state)
id (get-data self :id)
frame-id (get-state self :frame-id)
frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])]
(st/emit! (udw/update-position id {:y (+ frame-y value)}))))}
{:name "width" {:name "width"
:get #(get-state % :width)} :get #(get-state % :width)}
@@ -116,18 +189,50 @@
(let [id (get-data self :id)] (let [id (get-data self :id)]
(st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))} (st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))}
{:name "children"
:get #(.getChildren ^js %)}
{:name "fills" {:name "fills"
:get #(get-state % :fills make-fills) :get #(get-state % :fills make-fills)
;;:set (fn [self value] (.log js/console self value)) :set (fn [self value]
} (let [id (get-data self :id)
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))}
{:name "strokes" {:name "strokes"
:get #(get-state % :strokes make-strokes) :get #(get-state % :strokes make-strokes)
;;:set (fn [self value] (.log js/console self value)) :set (fn [self value]
}) (let [id (get-data self :id)
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))})
(cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))
(crc/add-properties!
{:name "children"
:get #(.getChildren ^js %)}))
(cond-> (not (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)))
(-> (obj/unset! "appendChild")
(obj/unset! "insertChild")
(obj/unset! "getChildren")))
(cond-> (cfh/frame-shape? data)
(-> (crc/add-properties!
{:name "grid"
:get
(fn [self]
(let [layout (get-state self :layout)]
(when (= :grid layout)
(grid/grid-layout-proxy data))))})
#_(crc/add-properties!
{:name "flex"
:get
(fn [self]
(let [layout (get-state self :layout)]
(when (= :flex layout)
(flex-layout-proxy data))))})))
(cond-> (not (cfh/frame-shape? data))
(-> (obj/unset! "addGridLayout")
(obj/unset! "addFlexLayout")))
(cond-> (cfh/text-shape? data) (cond-> (cfh/text-shape? data)
(crc/add-properties! (crc/add-properties!
@@ -136,4 +241,3 @@
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (get-data self :id)]
(st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))})))) (st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))}))))

View File

@@ -7,7 +7,14 @@
(ns app.plugins.utils (ns app.plugins.utils
"RPC for plugins runtime." "RPC for plugins runtime."
(:require (:require
[app.util.object :as obj])) [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.store :as st]
[app.util.object :as obj]
[cuerdas.core :as str]
[promesa.core :as p]))
(defn get-data (defn get-data
([self attr] ([self attr]
@@ -27,4 +34,62 @@
(fn [self] (fn [self]
(get-data self attr transform-fn)))) (get-data self attr transform-fn))))
(defn get-state
([self attr]
(let [id (get-data self :id)
page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))]
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr])))
([self attr mapfn]
(-> (get-state self attr)
(mapfn))))
(defn from-js
"Converts the object back to js"
([obj]
(from-js obj identity))
([obj vfn]
(let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})]
(reduce-kv
(fn [m k v]
(let [k (keyword (str/kebab k))
v (cond (map? v)
(from-js v)
(and (string? v) (re-matches us/uuid-rx v))
(uuid/uuid v)
:else (vfn k v))]
(assoc m k v)))
{}
ret))))
(defn to-js
"Converts to javascript an camelize the keys"
[obj]
(let [result
(reduce-kv
(fn [m k v]
(let [v (cond (object? v) (to-js v)
(uuid? v) (dm/str v)
:else v)]
(assoc m (str/camel (name k)) v)))
{}
obj)]
(clj->js result)))
(defn result-p
"Creates a pair of atom+promise. The promise will be resolved when the atom gets a value.
We use this to return the promise to the library clients and resolve its value when a value is passed
to the atom"
[]
(let [ret-v (atom nil)
ret-p
(p/create
(fn [resolve _]
(add-watch
ret-v
::watcher
(fn [_ _ _ value]
(remove-watch ret-v ::watcher)
(resolve value)))))]
[ret-v ret-p]))

View File

@@ -0,0 +1,78 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.plugins.viewport
"RPC for plugins runtime."
(:require
[app.common.data.macros :as dm]
[app.common.record :as crc]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.data.workspace.viewport :as dwv]
[app.main.data.workspace.zoom :as dwz]
[app.main.store :as st]
[app.util.object :as obj]))
(deftype ViewportProxy []
Object
(zoomIntoView [_ shapes]
(let [ids
(->> shapes
(map (fn [v]
(if (string? v)
(uuid/uuid v)
(uuid/uuid (obj/get v "x"))))))]
(st/emit! (dwz/fit-to-shapes ids)))))
(crc/define-properties!
ViewportProxy
{:name js/Symbol.toStringTag
:get (fn [] (str "ViewportProxy"))})
(defn create-proxy
[]
(crc/add-properties!
(ViewportProxy.)
{:name "center"
:get
(fn [_]
(let [vp (dm/get-in @st/state [:workspace-local :vbox])
x (+ (:x vp) (/ (:width vp) 2))
y (+ (:y vp) (/ (:height vp) 2))]
(.freeze js/Object #js {:x x :y y})))
:set
(fn [_ value]
(let [new-x (obj/get value "x")
new-y (obj/get value "y")]
(when (and (us/safe-number? new-x) (us/safe-number? new-y))
(let [vb (dm/get-in @st/state [:workspace-local :vbox])
old-x (+ (:x vb) (/ (:width vb) 2))
old-y (+ (:y vb) (/ (:height vb) 2))
delta-x (- new-x old-x)
delta-y (- new-y old-y)
to-position
{:x #(+ % delta-x)
:y #(+ % delta-y)}]
(st/emit! (dwv/update-viewport-position to-position))))))}
{:name "zoom"
:get
(fn [_]
(dm/get-in @st/state [:workspace-local :zoom]))
:set
(fn [_ value]
(when (us/safe-number? value)
(let [z (dm/get-in @st/state [:workspace-local :zoom])]
(st/emit! (dwz/set-zoom (/ value z))))))}
{:name "bounds"
:get
(fn [_]
(let [vport (dm/get-in @st/state [:workspace-local :vport])]
(.freeze js/Object (clj->js vport))))}))

View File

@@ -9,9 +9,9 @@
[app.common.files.changes :as cp] [app.common.files.changes :as cp]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.libraries-helpers :as cflh]
[app.common.files.shapes-helpers :as cfsh] [app.common.files.shapes-helpers :as cfsh]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.logic.libraries :as cll]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace.groups :as dwg] [app.main.data.workspace.groups :as dwg]
@@ -132,14 +132,14 @@
shapes (dwg/shapes-for-grouping objects shape-ids) shapes (dwg/shapes-for-grouping objects shape-ids)
[group component-id changes] [group component-id changes]
(cflh/generate-add-component (pcb/empty-changes nil) (cll/generate-add-component (pcb/empty-changes nil)
shapes shapes
(:objects page) (:objects page)
(:id page) (:id page)
current-file-id current-file-id
true true
dwg/prepare-create-group dwg/prepare-create-group
cfsh/prepare-create-artboard-from-selection)] cfsh/prepare-create-artboard-from-selection)]
(swap! idmap assoc instance-label (:id group) (swap! idmap assoc instance-label (:id group)
component-label component-id) component-label component-id)
@@ -158,13 +158,13 @@
(pcb/with-objects objects)) (pcb/with-objects objects))
[new-shape changes] [new-shape changes]
(cflh/generate-instantiate-component changes (cll/generate-instantiate-component changes
objects objects
file-id file-id
component-id component-id
(gpt/point 100 100) (gpt/point 100 100)
page page
libraries)] libraries)]
(swap! idmap assoc label (:id new-shape)) (swap! idmap assoc label (:id new-shape))
(update state :workspace-data (update state :workspace-data

View File

@@ -7,7 +7,6 @@
(ns frontend-tests.state-components-sync-test (ns frontend-tests.state-components-sync-test
(:require (:require
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.types.file :as ctf]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
@@ -22,595 +21,6 @@
(t/use-fixtures :each (t/use-fixtures :each
{:before thp/reset-idmap!}) {:before thp/reset-idmap!})
;; === Test touched ======================
(t/deftest test-touched
(t/async done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"
:fill-color clr/white
:fill-opacity 1})
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/instantiate-component :instance1
(thp/id :component1)))
[_group1 shape1']
(thl/resolve-instance state (thp/id :instance1))
store (the/prepare-store state done
(fn [new-state]
;; Uncomment to debug
;; (ctf/dump-tree (get new-state :workspace-data)
;; (get new-state :current-page-id)
;; (get new-state :workspace-libraries)
;; false true)
;; Expected shape tree:
;;;
;; [Page]
;; Root Frame
;; Rect 1
;; Rect 1
;; Rect 1 #--> Rect 1
;; Rect 1* ---> Rect 1
;; #{:fill-group}
;;;
;; [Rect 1]
;; page1 / Rect 1
;;;
(let [[[group shape1] [c-group c-shape1] _component]
(thl/resolve-instance-and-main
new-state
(thp/id :instance1))]
(t/is (= (:name group) "Rect 1"))
(t/is (= (:touched group) nil))
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:touched shape1) #{:fill-group}))
(t/is (= (:fill-color shape1) clr/test))
(t/is (= (:fill-opacity shape1) 0.5))
(t/is (= (:name c-group) "Rect 1"))
(t/is (= (:touched c-group) nil))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:touched c-shape1) nil))
(t/is (= (:fill-color c-shape1) clr/white))
(t/is (= (:fill-opacity c-shape1) 1)))))]
(ptk/emit!
store
(dch/update-shapes [(:id shape1')]
(fn [shape]
(merge shape {:fill-color clr/test
:fill-opacity 0.5})))
:the/end))))
(t/deftest test-touched-children-add
(t/async done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"
:fill-color clr/white
:fill-opacity 1})
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/instantiate-component :instance1
(thp/id :component1))
(thp/sample-shape :shape2 :circle
{:name "Circle 1"}))
instance1 (thp/get-shape state :instance1)
shape2 (thp/get-shape state :shape2)
store (the/prepare-store state done
(fn [new-state]
;; Expected shape tree:
;; [Page: Page 1]
;; Root Frame
;; {Rect 1}
;; Rect1
;; Rect 1 #--> Rect 1
;; Rect 1 ---> Rect 1
;; Circle 1
;;
;; [Component: Rect 1] core.cljs:200:23
;; --> [Page 1] Rect 1
(let [[[group shape1] [c-group c-shape1] _component]
(thl/resolve-instance-and-main-allow-dangling
new-state
(thp/id :instance1))]
(t/is (= (:name group) "Rect 1"))
(t/is (nil? (:touched group)))
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:touched shape1) nil))
(t/is (not= (:shape-ref shape1) nil))
(t/is (= (:name c-group) "Rect 1"))
(t/is (= (:touched c-group) nil))
(t/is (= (:shape-ref c-group) nil))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:touched c-shape1) nil))
(t/is (= (:shape-ref c-shape1) nil)))))]
(ptk/emit!
store
(dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) ;; We cant't change the structure of component copies, so this operation will do nothing
:the/end))))
(t/deftest test-touched-children-delete
(t/async done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/sample-shape :shape2 :rect
{:name "Rect 2"})
(thp/make-component :main1 :component1
[(thp/id :shape1)
(thp/id :shape2)])
(thp/instantiate-component :instance1
(thp/id :component1)))
[_group1 shape1']
(thl/resolve-instance state (thp/id :instance1))
store (the/prepare-store state done
(fn [new-state]
;; Expected shape tree:
;;;
;; [Page]
;; Root Frame
;; Component 1
;; Rect 1
;; Rect 2
;; Component 1 #--> Component 1
;; Rect 1* ---> Rect 1
;; #{:visibility-group}
;; Rect 2 ---> Rect 2
;;;
;; [Component 1]
;; page1 / Component 1
;;
(let [[[group shape1 shape2] [c-group c-shape1 c-shape2] _component]
(thl/resolve-instance-and-main-allow-dangling
new-state
(thp/id :instance1))]
(t/is (= (:name group) "Component 1"))
(t/is (= (:touched group) nil))
(t/is (not= (:shape-ref group) nil))
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:hidden shape1) true)) ; Instance shapes are not deleted but hidden
(t/is (= (:touched shape1) #{:visibility-group}))
(t/is (not= (:shape-ref shape1) nil))
(t/is (= (:name shape2) "Rect 2"))
(t/is (= (:touched shape2) nil))
(t/is (not= (:shape-ref shape2) nil))
(t/is (= (:name c-group) "Component 1"))
(t/is (= (:touched c-group) nil))
(t/is (= (:shape-ref c-group) nil))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:touched c-shape1) nil))
(t/is (= (:shape-ref c-shape1) nil))
(t/is (= (:name c-shape2) "Rect 2"))
(t/is (= (:touched c-shape2) nil))
(t/is (= (:shape-ref c-shape2) nil)))))]
(ptk/emit!
store
(dwsh/delete-shapes #{(:id shape1')})
:the/end))))
(t/deftest test-touched-children-move
(t/async done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"})
(thp/sample-shape :shape2 :rect
{:name "Rect 2"})
(thp/sample-shape :shape3 :rect
{:name "Rect 3"})
(thp/make-component :main1 :component1
[(thp/id :shape1)
(thp/id :shape2)
(thp/id :shape3)])
(thp/instantiate-component :instance1
(thp/id :component1)))
[group1' shape1']
(thl/resolve-instance state (thp/id :instance1))
store (the/prepare-store state done
(fn [new-state]
;; Expected shape tree:
;; [Page: Page 1]
;; Root Frame
;; {Component 1} #
;; Rect 1
;; Rect 2
;; Rect 3
;; Component 1 #--> Component 1
;; Rect 1 ---> Rect 1
;; Rect 2 ---> Rect 2
;; Rect 3 ---> Rect 3
;;
;; ========= Local library
;;
;; [Component: Component 1]
;; --> [Page 1] Component 1
(let [[[group shape1 shape2 shape3]
[c-group c-shape1 c-shape2 c-shape3] _component]
(thl/resolve-instance-and-main-allow-dangling
new-state
(thp/id :instance1))]
(t/is (= (:name group) "Component 1"))
(t/is (nil? (:touched group)))
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:touched shape1) nil))
(t/is (not= (:shape-ref shape1) nil))
(t/is (= (:name shape2) "Rect 2"))
(t/is (= (:touched shape2) nil))
(t/is (not= (:shape-ref shape2) nil))
(t/is (= (:name shape3) "Rect 3"))
(t/is (= (:touched shape3) nil))
(t/is (not= (:shape-ref shape3) nil))
(t/is (= (:name c-group) "Component 1"))
(t/is (= (:touched c-group) nil))
(t/is (= (:shape-ref c-group) nil))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:touched c-shape1) nil))
(t/is (= (:shape-ref c-shape1) nil))
(t/is (= (:name c-shape2) "Rect 2"))
(t/is (= (:touched c-shape2) nil))
(t/is (= (:shape-ref c-shape2) nil))
(t/is (= (:name c-shape3) "Rect 3"))
(t/is (= (:touched c-shape3) nil))
(t/is (= (:shape-ref c-shape3) nil)))))]
(ptk/emit!
store
(dw/relocate-shapes #{(:id shape1')} (:id group1') 2) ;; We cant't change the structure of component copies, so this operation will do nothing
:the/end))))
(t/deftest test-touched-from-lib
(t/async
done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"
:fill-color clr/white
:fill-opacity 1})
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/move-to-library :lib1 "Library 1")
(thp/sample-page)
(thp/instantiate-component :instance1
(thp/id :component1)
(thp/id :lib1)))
[_group1 shape1']
(thl/resolve-instance state (thp/id :instance1))
store (the/prepare-store state done
(fn [new-state]
;; Expected shape tree:
;;
;; [Page]
;; Root Frame
;; Rect 1 #--> <Library 1> Rect 1
;; Rect 1* ---> <Library 1> Rect 1
;; #{:fill-group}
;;
(let [[[group shape1] [c-group c-shape1] _component]
(thl/resolve-instance-and-main
new-state
(thp/id :instance1))]
(t/is (= (:name group) "Rect 1"))
(t/is (= (:touched group) nil))
(t/is (= (:name shape1) "Rect 1"))
(t/is (= (:touched shape1) #{:fill-group}))
(t/is (= (:fill-color shape1) clr/test))
(t/is (= (:fill-opacity shape1) 0.5))
(t/is (= (:name c-group) "Rect 1"))
(t/is (= (:touched c-group) nil))
(t/is (= (:name c-shape1) "Rect 1"))
(t/is (= (:touched c-shape1) nil))
(t/is (= (:fill-color c-shape1) clr/white))
(t/is (= (:fill-opacity c-shape1) 1)))))]
(ptk/emit!
store
(dch/update-shapes [(:id shape1')]
(fn [shape]
(merge shape {:fill-color clr/test
:fill-opacity 0.5})))
:the/end))))
(t/deftest test-touched-nested-upper
(t/async
done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"
:fill-color clr/white
:fill-opacity 1})
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/instantiate-component :instance1
(thp/id :component1))
(thp/sample-shape :shape2 :circle
{:name "Circle 1"
:fill-color clr/black
:fill-opacity 0})
(thp/frame-shapes :frame1
[(thp/id :instance1)
(thp/id :shape2)])
(thp/make-component :main2 :component2
[(thp/id :frame1)])
(thp/instantiate-component :instance2
(thp/id :component2)))
[_instance2 _instance1 shape1' _shape2']
(thl/resolve-instance state (thp/id :instance2))
store (the/prepare-store state done
(fn [new-state]
;; Expected shape tree:
;;
;; [Page]
;; Root Frame
;; Rect 1
;; Rect 1
;; Group
;; Rect 1 #--> Rect 1
;; Rect 1 ---> Rect 1
;; Circle 1
;; Group #--> Group
;; Rect 1 @--> Rect 1
;; Rect 1 ---> Rect 1
;; Circle 1* ---> Circle 1
;; #{:fill-group}
;;
;; [Rect 1]
;; page1 / Rect 1
;;
;; [Group]
;; page1 / Group
;;
(let [[[instance2 instance1 shape1 shape2]
[c-instance2 c-instance1 c-shape1 c-shape2] _component]
(thl/resolve-instance-and-main
new-state
(thp/id :instance2))]
(t/is (= (:name instance2) "Board"))
(t/is (= (:touched instance2) nil))
(t/is (= (:name instance1) "Rect 1"))
(t/is (= (:touched instance1) nil))
(t/is (= (:name shape1) "Circle 1"))
(t/is (= (:touched shape1) #{:fill-group}))
(t/is (= (:fill-color shape1) clr/test))
(t/is (= (:fill-opacity shape1) 0.5))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:touched shape2) nil))
(t/is (= (:fill-color shape2) clr/white))
(t/is (= (:fill-opacity shape2) 1))
(t/is (= (:name c-instance2) "Board"))
(t/is (= (:touched c-instance2) nil))
(t/is (= (:name c-instance1) "Rect 1"))
(t/is (= (:touched c-instance1) nil))
(t/is (= (:name c-shape1) "Circle 1"))
(t/is (= (:touched c-shape1) nil))
(t/is (= (:fill-color c-shape1) clr/black))
(t/is (= (:fill-opacity c-shape1) 0))
(t/is (= (:name c-shape2) "Rect 1"))
(t/is (= (:touched c-shape2) nil))
(t/is (= (:fill-color c-shape2) clr/white))
(t/is (= (:fill-opacity c-shape2) 1)))))]
(ptk/emit!
store
(dch/update-shapes [(:id shape1')]
(fn [shape]
(merge shape {:fill-color clr/test
:fill-opacity 0.5})))
:the/end))))
(t/deftest test-touched-nested-lower-near
(t/async done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"
:fill-color clr/white
:fill-opacity 1})
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/instantiate-component :instance1
(thp/id :component1))
(thp/sample-shape :shape2 :circle
{:name "Circle 1"
:fill-color clr/black
:fill-opacity 0})
(thp/frame-shapes :frame1
[(thp/id :instance1)
(thp/id :shape2)])
(thp/make-component :instance2 :component2
[(thp/id :frame1)])
(thp/instantiate-component :instance2
(thp/id :component2)))
[_instance2 _instance1 _shape1' shape2']
(thl/resolve-instance state (thp/id :instance2))
store (the/prepare-store state done
(fn [new-state]
;; Expected shape tree:
;;
;; [Page]
;; Root Frame
;; Rect 1
;; Rect 1
;; Group
;; Rect 1 #--> Rect 1
;; Rect 1 ---> Rect 1
;; Circle 1
;; Group #--> Group
;; Rect 1 @--> Rect 1
;; Rect 1* ---> Rect 1
;; #{:fill-group}
;; Circle 1 ---> Circle 1
;;
;; [Rect 1]
;; page1 / Rect 1
;;
;; [Group]
;; page1 / Group
;;
(let [[[instance2 instance1 shape1 shape2]
[c-instance2 c-instance1 c-shape1 c-shape2] _component]
(thl/resolve-instance-and-main
new-state
(thp/id :instance2))]
(t/is (= (:name instance2) "Board"))
(t/is (= (:touched instance2) nil))
(t/is (= (:name instance1) "Rect 1"))
(t/is (= (:touched instance1) nil))
(t/is (= (:name shape1) "Circle 1"))
(t/is (= (:touched shape1) nil))
(t/is (= (:fill-color shape1) clr/black))
(t/is (= (:fill-opacity shape1) 0))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:touched shape2) #{:fill-group}))
(t/is (= (:fill-color shape2) clr/test))
(t/is (= (:fill-opacity shape2) 0.5))
(t/is (= (:name c-instance2) "Board"))
(t/is (= (:touched c-instance2) nil))
(t/is (= (:name c-instance1) "Rect 1"))
(t/is (= (:touched c-instance1) nil))
(t/is (= (:name c-shape1) "Circle 1"))
(t/is (= (:touched c-shape1) nil))
(t/is (= (:fill-color c-shape1) clr/black))
(t/is (= (:fill-opacity c-shape1) 0))
(t/is (= (:name c-shape2) "Rect 1"))
(t/is (= (:touched c-shape2) nil))
(t/is (= (:fill-color c-shape2) clr/white))
(t/is (= (:fill-opacity c-shape2) 1)))))]
(ptk/emit!
store
(dch/update-shapes [(:id shape2')]
(fn [shape]
(merge shape {:fill-color clr/test
:fill-opacity 0.5})))
:the/end))))
(t/deftest test-touched-nested-lower-remote
(t/async done
(let [state (-> thp/initial-state
(thp/sample-page)
(thp/sample-shape :shape1 :rect
{:name "Rect 1"
:fill-color clr/white
:fill-opacity 1})
(thp/make-component :main1 :component1
[(thp/id :shape1)])
(thp/instantiate-component :instance1
(thp/id :component1))
(thp/sample-shape :shape2 :circle
{:name "Circle 1"
:fill-color clr/black
:fill-opacity 0})
(thp/frame-shapes :frame1
[(thp/id :instance1)
(thp/id :shape2)])
(thp/make-component :instance2 :component2
[(thp/id :frame1)])
(thp/instantiate-component :instance2
(thp/id :component2)))
[instance2 _instance1 _shape1' shape2']
(thl/resolve-instance state (thp/id :instance2))
store (the/prepare-store state done
(fn [new-state]
;; Expected shape tree:
;;
;; [Page]
;; Root Frame
;; Rect 1
;; Rect 1
;; Group
;; Rect 1 #--> Rect 1
;; Rect 1* ---> Rect 1
;; #{:fill-group}
;; Circle 1
;; Group #--> Group
;; Rect 1 @--> Rect 1
;; Rect 1 ---> Rect 1
;; Circle 1 ---> Circle 1
;;
;; [Rect 1]
;; page1 / Rect 1
;;
;; [Group]
;; page1 / Group
;;
(let [[[instance2 instance1 shape1 shape2]
[c-instance2 c-instance1 c-shape1 c-shape2] _component]
(thl/resolve-instance-and-main
new-state
(thp/id :instance2))]
(t/is (= (:name instance2) "Board"))
(t/is (= (:touched instance2) nil))
(t/is (= (:name instance1) "Rect 1"))
(t/is (= (:touched instance1) nil))
(t/is (= (:name shape1) "Circle 1"))
(t/is (= (:touched shape1) nil))
(t/is (= (:fill-color shape1) clr/black))
(t/is (= (:fill-opacity shape1) 0))
(t/is (= (:name shape2) "Rect 1"))
(t/is (= (:touched shape2) #{:fill-group}))
(t/is (= (:fill-color shape2) clr/test))
(t/is (= (:fill-opacity shape2) 0.5))
(t/is (= (:name c-instance2) "Board"))
(t/is (= (:touched c-instance2) nil))
(t/is (= (:name c-instance1) "Rect 1"))
(t/is (= (:touched c-instance1) nil))
(t/is (= (:name c-shape1) "Circle 1"))
(t/is (= (:touched c-shape1) nil))
(t/is (= (:fill-color c-shape1) clr/black))
(t/is (= (:fill-opacity c-shape1) 0))
(t/is (= (:name c-shape2) "Rect 1"))
(t/is (= (:touched c-shape2) #{:fill-group})))))]
(ptk/emit!
store
(dch/update-shapes [(:id shape2')]
(fn [shape]
(merge shape {:fill-color clr/test
:fill-opacity 0.5})))
(dwl/update-component (:id instance2))
:the/end))))
;; === Test reset changes ====================== ;; === Test reset changes ======================
(t/deftest test-reset-changes (t/deftest test-reset-changes

View File

@@ -4913,15 +4913,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"async@npm:^2.6.4":
version: 2.6.4
resolution: "async@npm:2.6.4"
dependencies:
lodash: "npm:^4.17.14"
checksum: 0ebb3273ef96513389520adc88e0d3c45e523d03653cc9b66f5c46f4239444294899bfd13d2b569e7dbfde7da2235c35cf5fd3ece9524f935d41bbe4efccdad0
languageName: node
linkType: hard
"async@npm:^3.2.3, async@npm:^3.2.4": "async@npm:^3.2.3, async@npm:^3.2.4":
version: 3.2.5 version: 3.2.5
resolution: "async@npm:3.2.5" resolution: "async@npm:3.2.5"
@@ -5074,15 +5065,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"basic-auth@npm:^2.0.1":
version: 2.0.1
resolution: "basic-auth@npm:2.0.1"
dependencies:
safe-buffer: "npm:5.1.2"
checksum: 05f56db3a0fc31c89c86b605231e32ee143fb6ae38dc60616bc0970ae6a0f034172def99e69d3aed0e2c9e7cac84e2d63bc51a0b5ff6ab5fc8808cc8b29923c1
languageName: node
linkType: hard
"better-opn@npm:^3.0.2": "better-opn@npm:^3.0.2":
version: 3.0.2 version: 3.0.2
resolution: "better-opn@npm:3.0.2" resolution: "better-opn@npm:3.0.2"
@@ -5174,6 +5156,26 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"body-parser@npm:1.20.2":
version: 1.20.2
resolution: "body-parser@npm:1.20.2"
dependencies:
bytes: "npm:3.1.2"
content-type: "npm:~1.0.5"
debug: "npm:2.6.9"
depd: "npm:2.0.0"
destroy: "npm:1.2.0"
http-errors: "npm:2.0.0"
iconv-lite: "npm:0.4.24"
on-finished: "npm:2.4.1"
qs: "npm:6.11.0"
raw-body: "npm:2.5.2"
type-is: "npm:~1.6.18"
unpipe: "npm:1.0.0"
checksum: 06f1438fff388a2e2354c96aa3ea8147b79bfcb1262dfcc2aae68ec13723d01d5781680657b74e9f83c808266d5baf52804032fbde2b7382b89bd8cdb273ace9
languageName: node
linkType: hard
"boolbase@npm:^1.0.0": "boolbase@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "boolbase@npm:1.0.0" resolution: "boolbase@npm:1.0.0"
@@ -5511,19 +5513,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"call-bind@npm:^1.0.7":
version: 1.0.7
resolution: "call-bind@npm:1.0.7"
dependencies:
es-define-property: "npm:^1.0.0"
es-errors: "npm:^1.3.0"
function-bind: "npm:^1.1.2"
get-intrinsic: "npm:^1.2.4"
set-function-length: "npm:^1.2.1"
checksum: a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d
languageName: node
linkType: hard
"camelcase@npm:^3.0.0": "camelcase@npm:^3.0.0":
version: 3.0.0 version: 3.0.0
resolution: "camelcase@npm:3.0.0" resolution: "camelcase@npm:3.0.0"
@@ -6061,7 +6050,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"content-type@npm:^1.0.5, content-type@npm:~1.0.4": "content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5":
version: 1.0.5 version: 1.0.5
resolution: "content-type@npm:1.0.5" resolution: "content-type@npm:1.0.5"
checksum: b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af checksum: b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af
@@ -6096,6 +6085,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cookie@npm:0.6.0":
version: 0.6.0
resolution: "cookie@npm:0.6.0"
checksum: f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686
languageName: node
linkType: hard
"copy-descriptor@npm:^0.1.0": "copy-descriptor@npm:^0.1.0":
version: 0.1.1 version: 0.1.1
resolution: "copy-descriptor@npm:0.1.1" resolution: "copy-descriptor@npm:0.1.1"
@@ -6136,13 +6132,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"corser@npm:^2.0.1":
version: 2.0.1
resolution: "corser@npm:2.0.1"
checksum: 1f319a752a560342dd22d936e5a4c158bfcbc332524ef5b05a7277236dad8b0b2868fd5cf818559f29954ec4d777d82e797fccd76601fcfe431610e4143c8acc
languageName: node
linkType: hard
"create-ecdh@npm:^4.0.0": "create-ecdh@npm:^4.0.0":
version: 4.0.4 version: 4.0.4
resolution: "create-ecdh@npm:4.0.4" resolution: "create-ecdh@npm:4.0.4"
@@ -6384,7 +6373,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"debug@npm:3.X, debug@npm:^3.2.7": "debug@npm:3.X":
version: 3.2.7 version: 3.2.7
resolution: "debug@npm:3.2.7" resolution: "debug@npm:3.2.7"
dependencies: dependencies:
@@ -6507,17 +6496,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"define-data-property@npm:^1.1.4":
version: 1.1.4
resolution: "define-data-property@npm:1.1.4"
dependencies:
es-define-property: "npm:^1.0.0"
es-errors: "npm:^1.3.0"
gopd: "npm:^1.0.1"
checksum: dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37
languageName: node
linkType: hard
"define-lazy-prop@npm:^2.0.0": "define-lazy-prop@npm:^2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "define-lazy-prop@npm:2.0.0" resolution: "define-lazy-prop@npm:2.0.0"
@@ -7037,22 +7015,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"es-define-property@npm:^1.0.0":
version: 1.0.0
resolution: "es-define-property@npm:1.0.0"
dependencies:
get-intrinsic: "npm:^1.2.4"
checksum: 6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4
languageName: node
linkType: hard
"es-errors@npm:^1.3.0":
version: 1.3.0
resolution: "es-errors@npm:1.3.0"
checksum: 0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85
languageName: node
linkType: hard
"es-get-iterator@npm:^1.1.3": "es-get-iterator@npm:^1.1.3":
version: 1.1.3 version: 1.1.3
resolution: "es-get-iterator@npm:1.1.3" resolution: "es-get-iterator@npm:1.1.3"
@@ -7425,13 +7387,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eventemitter3@npm:^4.0.0":
version: 4.0.7
resolution: "eventemitter3@npm:4.0.7"
checksum: 5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b
languageName: node
linkType: hard
"events@npm:^3.0.0, events@npm:^3.3.0": "events@npm:^3.0.0, events@npm:^3.3.0":
version: 3.3.0 version: 3.3.0
resolution: "events@npm:3.3.0" resolution: "events@npm:3.3.0"
@@ -7561,6 +7516,45 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"express@npm:^4.19.2":
version: 4.19.2
resolution: "express@npm:4.19.2"
dependencies:
accepts: "npm:~1.3.8"
array-flatten: "npm:1.1.1"
body-parser: "npm:1.20.2"
content-disposition: "npm:0.5.4"
content-type: "npm:~1.0.4"
cookie: "npm:0.6.0"
cookie-signature: "npm:1.0.6"
debug: "npm:2.6.9"
depd: "npm:2.0.0"
encodeurl: "npm:~1.0.2"
escape-html: "npm:~1.0.3"
etag: "npm:~1.8.1"
finalhandler: "npm:1.2.0"
fresh: "npm:0.5.2"
http-errors: "npm:2.0.0"
merge-descriptors: "npm:1.0.1"
methods: "npm:~1.1.2"
on-finished: "npm:2.4.1"
parseurl: "npm:~1.3.3"
path-to-regexp: "npm:0.1.7"
proxy-addr: "npm:~2.0.7"
qs: "npm:6.11.0"
range-parser: "npm:~1.2.1"
safe-buffer: "npm:5.2.1"
send: "npm:0.18.0"
serve-static: "npm:1.15.0"
setprototypeof: "npm:1.2.0"
statuses: "npm:2.0.1"
type-is: "npm:~1.6.18"
utils-merge: "npm:1.0.1"
vary: "npm:~1.1.2"
checksum: e82e2662ea9971c1407aea9fc3c16d6b963e55e3830cd0ef5e00b533feda8b770af4e3be630488ef8a752d7c75c4fcefb15892868eeaafe7353cb9e3e269fdcb
languageName: node
linkType: hard
"ext@npm:^1.1.2": "ext@npm:^1.1.2":
version: 1.7.0 version: 1.7.0
resolution: "ext@npm:1.7.0" resolution: "ext@npm:1.7.0"
@@ -7928,16 +7922,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"follow-redirects@npm:^1.0.0":
version: 1.15.6
resolution: "follow-redirects@npm:1.15.6"
peerDependenciesMeta:
debug:
optional: true
checksum: 9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071
languageName: node
linkType: hard
"for-each@npm:^0.3.3": "for-each@npm:^0.3.3":
version: 0.3.3 version: 0.3.3
resolution: "for-each@npm:0.3.3" resolution: "for-each@npm:0.3.3"
@@ -8034,6 +8018,7 @@ __metadata:
date-fns: "npm:^3.3.1" date-fns: "npm:^3.3.1"
draft-js: "git+https://github.com/penpot/draft-js.git" draft-js: "git+https://github.com/penpot/draft-js.git"
eventsource-parser: "npm:^1.1.2" eventsource-parser: "npm:^1.1.2"
express: "npm:^4.19.2"
fancy-log: "npm:^2.0.0" fancy-log: "npm:^2.0.0"
gettext-parser: "npm:^8.0.0" gettext-parser: "npm:^8.0.0"
gulp: "npm:4.0.2" gulp: "npm:4.0.2"
@@ -8046,7 +8031,6 @@ __metadata:
gulp-sourcemaps: "npm:^3.0.0" gulp-sourcemaps: "npm:^3.0.0"
gulp-svg-sprite: "npm:^2.0.3" gulp-svg-sprite: "npm:^2.0.3"
highlight.js: "npm:^11.9.0" highlight.js: "npm:^11.9.0"
http-server: "npm:^14.1.1"
js-beautify: "npm:^1.15.1" js-beautify: "npm:^1.15.1"
jsdom: "npm:^24.0.0" jsdom: "npm:^24.0.0"
jszip: "npm:^3.10.1" jszip: "npm:^3.10.1"
@@ -8288,19 +8272,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"get-intrinsic@npm:^1.2.4":
version: 1.2.4
resolution: "get-intrinsic@npm:1.2.4"
dependencies:
es-errors: "npm:^1.3.0"
function-bind: "npm:^1.1.2"
has-proto: "npm:^1.0.1"
has-symbols: "npm:^1.0.3"
hasown: "npm:^2.0.0"
checksum: 0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7
languageName: node
linkType: hard
"get-nonce@npm:^1.0.0": "get-nonce@npm:^1.0.0":
version: 1.0.1 version: 1.0.1
resolution: "get-nonce@npm:1.0.1" resolution: "get-nonce@npm:1.0.1"
@@ -8791,15 +8762,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"has-property-descriptors@npm:^1.0.2":
version: 1.0.2
resolution: "has-property-descriptors@npm:1.0.2"
dependencies:
es-define-property: "npm:^1.0.0"
checksum: 253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236
languageName: node
linkType: hard
"has-proto@npm:^1.0.1": "has-proto@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "has-proto@npm:1.0.1" resolution: "has-proto@npm:1.0.1"
@@ -8892,15 +8854,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"he@npm:^1.2.0":
version: 1.2.0
resolution: "he@npm:1.2.0"
bin:
he: bin/he
checksum: a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17
languageName: node
linkType: hard
"highlight.js@npm:^11.9.0": "highlight.js@npm:^11.9.0":
version: 11.9.0 version: 11.9.0
resolution: "highlight.js@npm:11.9.0" resolution: "highlight.js@npm:11.9.0"
@@ -8935,15 +8888,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"html-encoding-sniffer@npm:^3.0.0":
version: 3.0.0
resolution: "html-encoding-sniffer@npm:3.0.0"
dependencies:
whatwg-encoding: "npm:^2.0.0"
checksum: b17b3b0fb5d061d8eb15121c3b0b536376c3e295ecaf09ba48dd69c6b6c957839db124fe1e2b3f11329753a4ee01aa7dedf63b7677999e86da17fbbdd82c5386
languageName: node
linkType: hard
"html-encoding-sniffer@npm:^4.0.0": "html-encoding-sniffer@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "html-encoding-sniffer@npm:4.0.0" resolution: "html-encoding-sniffer@npm:4.0.0"
@@ -8990,40 +8934,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"http-proxy@npm:^1.18.1":
version: 1.18.1
resolution: "http-proxy@npm:1.18.1"
dependencies:
eventemitter3: "npm:^4.0.0"
follow-redirects: "npm:^1.0.0"
requires-port: "npm:^1.0.0"
checksum: 148dfa700a03fb421e383aaaf88ac1d94521dfc34072f6c59770528c65250983c2e4ec996f2f03aa9f3fe46cd1270a593126068319311e3e8d9e610a37533e94
languageName: node
linkType: hard
"http-server@npm:^14.1.1":
version: 14.1.1
resolution: "http-server@npm:14.1.1"
dependencies:
basic-auth: "npm:^2.0.1"
chalk: "npm:^4.1.2"
corser: "npm:^2.0.1"
he: "npm:^1.2.0"
html-encoding-sniffer: "npm:^3.0.0"
http-proxy: "npm:^1.18.1"
mime: "npm:^1.6.0"
minimist: "npm:^1.2.6"
opener: "npm:^1.5.1"
portfinder: "npm:^1.0.28"
secure-compare: "npm:3.0.1"
union: "npm:~0.5.0"
url-join: "npm:^4.0.1"
bin:
http-server: bin/http-server
checksum: c5770ddd722dd520ce0af25efee6bfb7c6300ff4e934636d4eec83fa995739e64de2e699e89e7a795b3a1894bcc37bec226617c1023600aacd7871fd8d6ffe6d
languageName: node
linkType: hard
"https-browserify@npm:^1.0.0": "https-browserify@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "https-browserify@npm:1.0.0" resolution: "https-browserify@npm:1.0.0"
@@ -10384,7 +10294,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lodash@npm:^4.17.14, lodash@npm:^4.17.21": "lodash@npm:^4.17.21":
version: 4.17.21 version: 4.17.21
resolution: "lodash@npm:4.17.21" resolution: "lodash@npm:4.17.21"
checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c
@@ -10779,7 +10689,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mime@npm:1.6.0, mime@npm:^1.6.0": "mime@npm:1.6.0":
version: 1.6.0 version: 1.6.0
resolution: "mime@npm:1.6.0" resolution: "mime@npm:1.6.0"
bin: bin:
@@ -10976,7 +10886,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.6": "mkdirp@npm:^0.5.4":
version: 0.5.6 version: 0.5.6
resolution: "mkdirp@npm:0.5.6" resolution: "mkdirp@npm:0.5.6"
dependencies: dependencies:
@@ -11550,15 +11460,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"opener@npm:^1.5.1":
version: 1.5.2
resolution: "opener@npm:1.5.2"
bin:
opener: bin/opener-bin.js
checksum: dd56256ab0cf796585617bc28e06e058adf09211781e70b264c76a1dbe16e90f868c974e5bf5309c93469157c7d14b89c35dc53fe7293b0e40b4d2f92073bc79
languageName: node
linkType: hard
"opentype.js@npm:^1.3.4": "opentype.js@npm:^1.3.4":
version: 1.3.4 version: 1.3.4
resolution: "opentype.js@npm:1.3.4" resolution: "opentype.js@npm:1.3.4"
@@ -12132,17 +12033,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"portfinder@npm:^1.0.28":
version: 1.0.32
resolution: "portfinder@npm:1.0.32"
dependencies:
async: "npm:^2.6.4"
debug: "npm:^3.2.7"
mkdirp: "npm:^0.5.6"
checksum: cef8b567b78aabccc59fe8e103bac8b394bb45a6a69be626608f099f454124c775aaf47b274c006332c07ab3f501cde55e49aaeb9d49d78d90362d776a565cbf
languageName: node
linkType: hard
"posix-character-classes@npm:^0.1.0": "posix-character-classes@npm:^0.1.0":
version: 0.1.1 version: 0.1.1
resolution: "posix-character-classes@npm:0.1.1" resolution: "posix-character-classes@npm:0.1.1"
@@ -12569,15 +12459,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"qs@npm:^6.4.0":
version: 6.12.0
resolution: "qs@npm:6.12.0"
dependencies:
side-channel: "npm:^1.0.6"
checksum: e165a77ac5f3ca60c15c5f3d51b321ddec7aa438804436b29d160117bc6fb7bf7dab94abd0c7d7c0785890d3a75ae41e1d6346e158aaf1540c6fe53a31f11675
languageName: node
linkType: hard
"querystring-es3@npm:^0.2.0": "querystring-es3@npm:^0.2.0":
version: 0.2.1 version: 0.2.1
resolution: "querystring-es3@npm:0.2.1" resolution: "querystring-es3@npm:0.2.1"
@@ -12651,6 +12532,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"raw-body@npm:2.5.2":
version: 2.5.2
resolution: "raw-body@npm:2.5.2"
dependencies:
bytes: "npm:3.1.2"
http-errors: "npm:2.0.0"
iconv-lite: "npm:0.4.24"
unpipe: "npm:1.0.0"
checksum: b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4
languageName: node
linkType: hard
"react-colorful@npm:^5.1.2": "react-colorful@npm:^5.1.2":
version: 5.6.1 version: 5.6.1
resolution: "react-colorful@npm:5.6.1" resolution: "react-colorful@npm:5.6.1"
@@ -13715,13 +13608,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"secure-compare@npm:3.0.1":
version: 3.0.1
resolution: "secure-compare@npm:3.0.1"
checksum: af3102f3f555d917c8ffff7a5f6f00f70195708f4faf82d48794485c9f3cb365cee0dd4da6b4e53e8964f172970bce6069b6101ba3ce8c309bff54f460d1f650
languageName: node
linkType: hard
"semver-greatest-satisfied-range@npm:^1.1.0": "semver-greatest-satisfied-range@npm:^1.1.0":
version: 1.1.0 version: 1.1.0
resolution: "semver-greatest-satisfied-range@npm:1.1.0" resolution: "semver-greatest-satisfied-range@npm:1.1.0"
@@ -13812,20 +13698,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"set-function-length@npm:^1.2.1":
version: 1.2.2
resolution: "set-function-length@npm:1.2.2"
dependencies:
define-data-property: "npm:^1.1.4"
es-errors: "npm:^1.3.0"
function-bind: "npm:^1.1.2"
get-intrinsic: "npm:^1.2.4"
gopd: "npm:^1.0.1"
has-property-descriptors: "npm:^1.0.2"
checksum: 82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c
languageName: node
linkType: hard
"set-function-name@npm:^2.0.0": "set-function-name@npm:^2.0.0":
version: 2.0.1 version: 2.0.1
resolution: "set-function-name@npm:2.0.1" resolution: "set-function-name@npm:2.0.1"
@@ -13957,18 +13829,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"side-channel@npm:^1.0.6":
version: 1.0.6
resolution: "side-channel@npm:1.0.6"
dependencies:
call-bind: "npm:^1.0.7"
es-errors: "npm:^1.3.0"
get-intrinsic: "npm:^1.2.4"
object-inspect: "npm:^1.13.1"
checksum: d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f
languageName: node
linkType: hard
"siginfo@npm:^2.0.0": "siginfo@npm:^2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "siginfo@npm:2.0.0" resolution: "siginfo@npm:2.0.0"
@@ -15309,15 +15169,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"union@npm:~0.5.0":
version: 0.5.0
resolution: "union@npm:0.5.0"
dependencies:
qs: "npm:^6.4.0"
checksum: 9ac158d99991063180e56f408f5991e808fa07594713439c098116da09215c154672ee8c832e16a6b39b037609c08bcaff8ff07c1e3e46c3cc622897972af2aa
languageName: node
linkType: hard
"unique-filename@npm:^3.0.0": "unique-filename@npm:^3.0.0":
version: 3.0.0 version: 3.0.0
resolution: "unique-filename@npm:3.0.0" resolution: "unique-filename@npm:3.0.0"
@@ -15461,13 +15312,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"url-join@npm:^4.0.1":
version: 4.0.1
resolution: "url-join@npm:4.0.1"
checksum: ac65e2c7c562d7b49b68edddcf55385d3e922bc1dd5d90419ea40b53b6de1607d1e45ceb71efb9d60da02c681d13c6cb3a1aa8b13fc0c989dfc219df97ee992d
languageName: node
linkType: hard
"url-parse@npm:^1.5.3": "url-parse@npm:^1.5.3":
version: 1.5.10 version: 1.5.10
resolution: "url-parse@npm:1.5.10" resolution: "url-parse@npm:1.5.10"
@@ -15923,15 +15767,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"whatwg-encoding@npm:^2.0.0":
version: 2.0.0
resolution: "whatwg-encoding@npm:2.0.0"
dependencies:
iconv-lite: "npm:0.6.3"
checksum: 91b90a49f312dc751496fd23a7e68981e62f33afe938b97281ad766235c4872fc4e66319f925c5e9001502b3040dd25a33b02a9c693b73a4cbbfdc4ad10c3e3e
languageName: node
linkType: hard
"whatwg-encoding@npm:^3.1.1": "whatwg-encoding@npm:^3.1.1":
version: 3.1.1 version: 3.1.1
resolution: "whatwg-encoding@npm:3.1.1" resolution: "whatwg-encoding@npm:3.1.1"