Compare commits

..

1 Commits

Author SHA1 Message Date
Andres Gonzalez
2e3df1dc73 📚 Add disclaimer about SH updates to the tech guide 2025-10-02 16:06:27 +02:00
43 changed files with 509 additions and 1211 deletions

View File

@@ -1,18 +1,6 @@
# CHANGELOG
## 2.10.1
### :sparkles: New features & Enhancements
- Improve workpace file loading [Github 7366](https://github.com/penpot/penpot/pull/7366)
### :bug: Bugs fixed
- Fix regression with text shapes creation with Plugins API [Taiga #12244](https://tree.taiga.io/project/penpot/issue/12244)
## 2.10.0
## 2.10.0 (Unreleased)
### :rocket: Epics and highlights

View File

@@ -35,7 +35,6 @@
[app.util.blob :as blob]
[clj-async-profiler.core :as prof]
[clojure.contrib.humanize :as hum]
[clojure.datafy :refer [datafy]]
[clojure.java.io :as io]
[clojure.pprint :refer [pprint print-table]]
[clojure.repl :refer :all]

View File

@@ -12,13 +12,12 @@
[app.common.files.helpers :as cfh]
[app.common.files.migrations :as fmg]
[app.common.logging :as l]
[app.common.types.objects-map :as omap]
[app.common.types.path :as path]
[app.db :as db]
[app.db.sql :as-alias sql]
[app.storage :as sto]
[app.util.blob :as blob]
[app.util.objects-map :as omap.legacy]
[app.util.objects-map :as omap]
[app.util.pointer-map :as pmap]
[app.worker :as wrk]
[promesa.exec :as px]))
@@ -39,7 +38,10 @@
[file & _opts]
(let [update-page
(fn [page]
(update page :objects omap/wrap))
(if (and (pmap/pointer-map? page)
(not (pmap/loaded? page)))
page
(update page :objects omap/wrap)))
update-data
(fn [fdata]
@@ -49,20 +51,6 @@
(update :data update-data)
(update :features conj "fdata/objects-map"))))
(defn disable-objects-map
[file & _opts]
(let [update-page
(fn [page]
(update page :objects #(into {} %)))
update-data
(fn [fdata]
(update fdata :pages-index d/update-vals update-page))]
(-> file
(update :data update-data)
(update :features disj "fdata/objects-map"))))
(defn process-objects
"Apply a function to all objects-map on the file. Usualy used for convert
the objects-map instances to plain maps"
@@ -72,8 +60,7 @@
(fn [page]
(update page :objects
(fn [objects]
(if (or (omap/objects-map? objects)
(omap.legacy/objects-map? objects))
(if (omap/objects-map? objects)
(update-fn objects)
objects)))))
fdata))

View File

@@ -44,8 +44,7 @@
(def default-headers
{"Content-Type" "text/event-stream;charset=UTF-8"
"Cache-Control" "no-cache, no-store, max-age=0, must-revalidate"
"Pragma" "no-cache"
"X-Accel-Buffering" "no"})
"Pragma" "no-cache"})
(defn response
[handler & {:keys [buf] :or {buf 32} :as opts}]

View File

@@ -342,24 +342,14 @@
(cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file)))
(as-> file file
;; This operation is needed for backward comapatibility with
;; frontends that does not support pointer-map resolution
;; mechanism; this just resolves the pointers on backend and
;; return a complete file
(if (and (contains? (:features file) "fdata/pointer-map")
(not (contains? (:features params) "fdata/pointer-map")))
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
(update file :data feat.fdata/process-pointers deref))
file)
;; This operation is needed for backward comapatibility with
;; frontends that does not support objects-map mechanism; this
;; just converts all objects map instaces to plain maps
(if (and (contains? (:features file) "fdata/objects-map")
(not (contains? (:features params) "fdata/objects-map")))
(update file :data feat.fdata/process-objects (partial into {}))
file)))))
;; This operation is needed for backward comapatibility with frontends that
;; does not support pointer-map resolution mechanism; this just resolves the
;; pointers on backend and return a complete file.
(if (and (contains? (:features file) "fdata/pointer-map")
(not (contains? (:features params) "fdata/pointer-map")))
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
(update file :data feat.fdata/process-pointers deref))
file))))
;; --- COMMAND QUERY: get-file-fragment (by id)

View File

@@ -112,15 +112,14 @@
;; FIXME: IMPORTANT: this code can have race conditions, because
;; we have no locks for updating team so, creating two files
;; concurrently can lead to lost team features updating
(when-let [features (-> features
(set/difference (:features team))
(set/difference cfeat/no-team-inheritable-features)
(not-empty))]
(let [features (-> features
(set/union (:features team))
(set/difference cfeat/no-team-inheritable-features)
(into-array))]
(let [features (->> features
(set/union (:features team))
(db/create-array conn "text"))]
(db/update! conn :team
{:features features}
{:id (:id team)}

View File

@@ -160,6 +160,7 @@
tpoint (ct/tpoint)]
(when (not= (:vern params)
(:vern file))
(ex/raise :type :validation
@@ -182,15 +183,15 @@
(set/difference (:features team))
(set/difference cfeat/no-team-inheritable-features)
(not-empty))]
(let [features (-> features
(set/union (:features team))
(set/difference cfeat/no-team-inheritable-features)
(into-array))]
(let [features (->> features
(set/union (:features team))
(db/create-array conn "text"))]
(db/update! conn :team
{:features features}
{:id (:id team)}
{::db/return-keys false})))
(mtx/run! metrics {:id :update-file-changes :inc (count changes)})
(binding [l/*context* (some-> (meta params)

View File

@@ -503,7 +503,7 @@
(let [features (-> (cfeat/get-enabled-features cf/flags)
(set/difference cfeat/frontend-only-features)
(set/difference cfeat/no-team-inheritable-features))
(cfeat/check-client-features! (:features params)))
params (-> params
(assoc :profile-id profile-id)
(assoc :features features))

View File

@@ -40,7 +40,6 @@
[app.util.blob :as blob]
[app.util.pointer-map :as pmap]
[app.worker :as wrk]
[clojure.datafy :refer [datafy]]
[clojure.java.io :as io]
[clojure.pprint :refer [print-table]]
[clojure.stacktrace :as strace]

View File

@@ -67,6 +67,11 @@
"design-tokens/v1"
"variants/v1"})
;; A set of features that should not be propagated to team on creating
;; or modifying a file
(def no-team-inheritable-features
#{"fdata/path-data"})
;; A set of features which only affects on frontend and can be enabled
;; and disabled freely by the user any time. This features does not
;; persist on file features field but can be permanently enabled on
@@ -80,14 +85,8 @@
;; Features that are mainly backend only or there are a proper
;; fallback when frontend reports no support for it
(def backend-only-features
#{"fdata/pointer-map"
"fdata/objects-map"})
;; A set of features that should not be propagated to team on creating
;; or modifying a file or creating or modifying a team
(def no-team-inheritable-features
#{"fdata/path-data"
"fdata/shape-data-type"})
#{"fdata/objects-map"
"fdata/pointer-map"})
;; This is a set of features that does not require an explicit
;; migration like components/v2 or the migration is not mandatory to
@@ -223,6 +222,8 @@
:hint (str/ffmt "enabled feature '%' not present in file (missing migration)"
not-supported)))
(check-supported-features! file-features)
;; Components v1 is deprecated
(when-not (contains? file-features "components/v2")
(ex/raise :type :restriction

View File

@@ -6,29 +6,14 @@
(ns app.common.files.indices
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.uuid :as uuid]))
(defn- generate-index
"An optimized algorithm for calculate parents index that walk from top
to down starting from a provided shape-id. Usefull when you want to
create an index for the whole objects or subpart of the tree."
[index objects shape-id parents]
(let [shape (get objects shape-id)
index (assoc index shape-id parents)
parents (cons shape-id parents)]
(reduce (fn [index shape-id]
(generate-index index objects shape-id parents))
index
(:shapes shape))))
(defn generate-child-all-parents-index
"Creates an index where the key is the shape id and the value is a set
with all the parents"
([objects]
(generate-index {} objects uuid/zero []))
(generate-child-all-parents-index objects (vals objects)))
([objects shapes]
(let [shape->entry
@@ -39,25 +24,24 @@
(defn create-clip-index
"Retrieves the mask information for an object"
[objects parents-index]
(let [get-clip-parents
(fn [shape]
(let [shape-id (dm/get-prop shape :id)]
(cond-> []
(or (and (cfh/frame-shape? shape)
(not (:show-content shape))
(not= uuid/zero shape-id))
(cfh/bool-shape? shape))
(conj shape)
(:masked-group shape)
(conj (get objects (->> shape :shapes first))))))
xform
(comp (map (d/getf objects))
(mapcat get-clip-parents))
populate-with-clips
(let [retrieve-clips
(fn [parents]
(into [] xform parents))]
(let [lookup-object (fn [id] (get objects id))
get-clip-parents
(fn [shape]
(cond-> []
(or (and (= :frame (:type shape))
(not (:show-content shape))
(not= uuid/zero (:id shape)))
(cfh/bool-shape? shape))
(conj shape)
(d/update-vals parents-index populate-with-clips)))
(:masked-group shape)
(conj (get objects (->> shape :shapes first)))))]
(into []
(comp (map lookup-object)
(mapcat get-clip-parents))
parents)))]
(-> parents-index
(update-vals retrieve-clips))))

View File

@@ -158,8 +158,7 @@
:enable-component-thumbnails
:enable-render-wasm-dpr
:enable-token-units
:enable-token-typography-types
:enable-feature-fdata-objects-map])
:enable-token-typography-types])
(defn parse
[& flags]

View File

@@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC
(ns app.common.schema.generators
(:refer-clojure :exclude [set subseq uuid filter map let boolean vector keyword int double not-empty])
(:refer-clojure :exclude [set subseq uuid filter map let boolean vector keyword int double])
#?(:cljs (:require-macros [app.common.schema.generators]))
(:require
[app.common.math :as mth]
@@ -146,5 +146,3 @@
(def any
(tg/one-of [text boolean double int keyword]))
(def not-empty tg/not-empty)

View File

@@ -1,521 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.types.objects-map
"Implements a specialized map-like data structure for store an UUID =>
OBJECT mappings. The main purpose of this data structure is be able
to serialize it on fressian as byte-array and have the ability to
decode each field separatelly without the need to decode the whole
map from the byte-array.
It works transparently, so no aditional dynamic vars are needed. It
only works by reference equality and the hash-code is calculated
properly from each value."
(:require
#?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as json])
[app.common.transit :as t]
[clojure.core :as c]
[clojure.core.protocols :as cp])
#?(:clj
(:import
clojure.lang.Murmur3
clojure.lang.RT
java.util.Iterator)))
#?(:clj (set! *warn-on-reflection* true))
(declare create)
(declare ^:private do-compact)
(defprotocol IObjectsMap
(^:no-doc compact [this])
(^:no-doc get-data [this] "retrieve internal data")
(^:no-doc -hash-for-key [this key] "retrieve a hash for a key"))
#?(:cljs
(deftype ObjectsMapEntry [key omap]
c/IMapEntry
(-key [_] key)
(-val [_] (get omap key))
c/IHash
(-hash [_]
(-hash-for-key omap key))
c/IEquiv
(-equiv [this other]
(and (c/map-entry? other)
(= (key this)
(key other))
(= (val this)
(val other))))
c/ISequential
c/ISeqable
(-seq [this]
(cons key (lazy-seq (cons (c/-val this) nil))))
c/ICounted
(-count [_] 2)
c/IIndexed
(-nth [node n]
(cond (== n 0) key
(== n 1) (c/-val node)
:else (throw (js/Error. "Index out of bounds"))))
(-nth [node n not-found]
(cond (== n 0) key
(== n 1) (c/-val node)
:else not-found))
c/ILookup
(-lookup [node k]
(c/-nth node k nil))
(-lookup [node k not-found]
(c/-nth node k not-found))
c/IFn
(-invoke [node k]
(c/-nth node k))
(-invoke [node k not-found]
(c/-nth node k not-found))
c/IPrintWithWriter
(-pr-writer [this writer opts]
(c/pr-sequential-writer
writer
(fn [item w _]
(c/-write w (pr-str item)))
"[" ", " "]"
opts
this)))
:clj
(deftype ObjectsMapEntry [key omap]
clojure.lang.IMapEntry
(key [_] key)
(getKey [_] key)
(val [_]
(get omap key))
(getValue [_]
(get omap key))
clojure.lang.Indexed
(nth [node n]
(cond
(== n 0) key
(== n 1) (val node)
:else (throw (IllegalArgumentException. "Index out of bounds"))))
(nth [node n not-found]
(cond
(== n 0) key
(== n 1) (val node)
:else not-found))
clojure.lang.IPersistentCollection
(empty [_] [])
(count [_] 2)
(seq [this]
(cons key (lazy-seq (cons (val this) nil))))
(cons [this item]
(.cons ^clojure.lang.IPersistentCollection (vec this) item))
clojure.lang.IHashEq
(hasheq [_]
(-hash-for-key omap key))))
#?(:cljs
(deftype ObjectMapIterator [iterator omap]
Object
(hasNext [_]
(.hasNext ^js iterator))
(next [_]
(let [entry (.next iterator)]
(ObjectsMapEntry. (key entry) omap)))
(remove [_]
(js/Error. "Unsupported operation")))
:clj
(deftype ObjectsMapIterator [^Iterator iterator omap]
Iterator
(hasNext [_]
(.hasNext iterator))
(next [_]
(let [entry (.next iterator)]
(ObjectsMapEntry. (key entry) omap)))))
#?(:cljs
(deftype ObjectsMap [metadata cache
^:mutable data
^:mutable modified
^:mutable hash]
Object
(toString [this]
(pr-str* this))
(equiv [this other]
(c/-equiv this other))
(keys [this]
(c/es6-iterator (keys this)))
(entries [this]
(c/es6-entries-iterator (seq this)))
(values [this]
(es6-iterator (vals this)))
(has [this k]
(c/contains? this k))
(get [this k not-found]
(c/-lookup this k not-found))
(forEach [this f]
(run! (fn [[k v]] (f v k)) this))
cp/Datafiable
(datafy [_]
{:data data
:cache cache
:modified modified
:hash hash})
IObjectsMap
(compact [this]
(when modified
(do-compact data cache
(fn [data']
(set! (.-modified this) false)
(set! (.-data this) data'))))
this)
(get-data [this]
(compact this)
data)
(-hash-for-key [this key]
(if (c/-contains-key? cache key)
(c/-hash (c/-lookup cache key))
(c/-hash (c/-lookup this key))))
c/IWithMeta
(-with-meta [this new-meta]
(if (identical? new-meta meta)
this
(ObjectsMap. new-meta
cache
data
modified
hash)))
c/IMeta
(-meta [_] metadata)
c/ICloneable
(-clone [this]
(compact this)
(ObjectsMap. metadata {} data false nil))
c/IIterable
(-iterator [this]
(c/seq-iter this))
c/ICollection
(-conj [this entry]
(cond
(map-entry? entry)
(c/-assoc this (c/-key entry) (c/-val entry))
(vector? entry)
(c/-assoc this (c/-nth entry 0) (c/-nth entry 1))
:else
(loop [ret this es (seq entry)]
(if (nil? es)
ret
(let [e (first es)]
(if (vector? e)
(recur (c/-assoc ret (c/-nth e 0) (c/-nth e 1))
(next es))
(throw (js/Error. "conj on a map takes map entries or seqables of map entries"))))))))
c/IEmptyableCollection
(-empty [_]
(create))
c/IEquiv
(-equiv [this other]
(equiv-map this other))
c/IHash
(-hash [this]
(when-not hash
(set! hash (hash-unordered-coll this)))
hash)
c/ISeqable
(-seq [this]
(->> (keys data)
(map (fn [id] (new ObjectsMapEntry id this)))
(seq)))
c/ICounted
(-count [_]
(c/-count data))
c/ILookup
(-lookup [this k]
(or (c/-lookup cache k)
(if (c/-contains-key? data k)
(let [v (c/-lookup data k)
v (t/decode-str v)]
(set! (.-cache this) (c/-assoc cache k v))
v)
(do
(set! (.-cache this) (assoc cache key nil))
nil))))
(-lookup [this k not-found]
(if (c/-contains-key? data k)
(c/-lookup this k)
not-found))
c/IAssociative
(-assoc [_ k v]
(ObjectsMap. metadata
(c/-assoc cache k v)
(c/-assoc data k nil)
true
nil))
(-contains-key? [_ k]
(c/-contains-key? data k))
c/IFind
(-find [this k]
(when (c/-contains-key? data k)
(new ObjectsMapEntry k this)))
c/IMap
(-dissoc [_ k]
(ObjectsMap. metadata
(c/-dissoc cache k)
(c/-dissoc data k)
true
nil))
c/IKVReduce
(-kv-reduce [this f init]
(c/-kv-reduce data
(fn [init k _]
(f init k (c/-lookup this k)))
init))
c/IFn
(-invoke [this k]
(c/-lookup this k))
(-invoke [this k not-found]
(c/-lookup this k not-found))
c/IPrintWithWriter
(-pr-writer [this writer opts]
(c/pr-sequential-writer
writer
(fn [item w _]
(c/-write w (pr-str (c/-key item)))
(c/-write w \space)
(c/-write w (pr-str (c/-val item))))
"#penpot/objects-map {" ", " "}"
opts
(seq this))))
:clj
(deftype ObjectsMap [metadata cache
^:unsynchronized-mutable data
^:unsynchronized-mutable modified
^:unsynchronized-mutable hash]
Object
(hashCode [this]
(.hasheq ^clojure.lang.IHashEq this))
cp/Datafiable
(datafy [_]
{:data data
:cache cache
:modified modified
:hash hash})
IObjectsMap
(compact [this]
(locking this
(when modified
(do-compact data cache
(fn [data']
(set! (.-modified this) false)
(set! (.-data this) data')))))
this)
(get-data [this]
(compact this)
data)
(-hash-for-key [this key]
(if (contains? cache key)
(c/hash (get cache key))
(c/hash (get this key))))
json/JSONWriter
(-write [this writter options]
(json/-write (into {} this) writter options))
clojure.lang.IHashEq
(hasheq [this]
(when-not hash
(set! hash (Murmur3/hashUnordered this)))
hash)
clojure.lang.Seqable
(seq [this]
(RT/chunkIteratorSeq (.iterator ^Iterable this)))
java.lang.Iterable
(iterator [this]
(ObjectsMapIterator. (.iterator ^Iterable data) this))
clojure.lang.IPersistentCollection
(equiv [this other]
(and (instance? ObjectsMap other)
(= (count this) (count other))
(reduce-kv (fn [_ id _]
(let [this-val (get this id)
other-val (get other id)
result (= this-val other-val)]
(or result
(reduced false))))
true
data)))
clojure.lang.IPersistentMap
(cons [this o]
(if (map-entry? o)
(assoc this (key o) (val o))
(if (vector? o)
(assoc this (nth o 0) (nth o 1))
(throw (UnsupportedOperationException. "invalid arguments to cons")))))
(empty [_]
(create))
(containsKey [_ key]
(.containsKey ^clojure.lang.IPersistentMap data key))
(entryAt [this key]
(ObjectsMapEntry. this key))
(valAt [this key]
(or (get cache key)
(locking this
(if (contains? data key)
(let [value (get data key)
value (t/decode-str value)]
(set! (.-cache this) (assoc cache key value))
value)
(do
(set! (.-cache this) (assoc cache key nil))
nil)))))
(valAt [this key not-found]
(if (.containsKey ^clojure.lang.IPersistentMap data key)
(.valAt this key)
not-found))
(assoc [_ key val]
(ObjectsMap. metadata
(assoc cache key val)
(assoc data key nil)
true
nil))
(assocEx [_ _ _]
(throw (UnsupportedOperationException. "method not implemented")))
(without [_ key]
(ObjectsMap. metadata
(dissoc cache key)
(dissoc data key)
true
nil))
clojure.lang.Counted
(count [_]
(count data))))
#?(:cljs (es6-iterable ObjectsMap))
(defn- do-compact
[data cache update-fn]
(let [new-data
(persistent!
(reduce-kv (fn [data id obj]
(if (nil? obj)
(assoc! data id (t/encode-str (get cache id)))
data))
(transient data)
data))]
(update-fn new-data)
nil))
(defn from-data
[data]
(ObjectsMap. {} {}
data
false
nil))
(defn objects-map?
[o]
(instance? ObjectsMap o))
(defn create
([] (from-data {}))
([other]
(cond
(objects-map? other)
(-> other get-data from-data)
:else
(throw #?(:clj (UnsupportedOperationException. "invalid arguments")
:cljs (js/Error. "invalid arguments"))))))
(defn wrap
[objects]
(if (instance? ObjectsMap objects)
objects
(->> objects
(into (create))
(compact))))
#?(:clj
(fres/add-handlers!
{:name "penpot/objects-map/v2"
:class ObjectsMap
:wfn (fn [n w o]
(fres/write-tag! w n)
(fres/write-object! w (get-data o)))
:rfn (fn [r]
(-> r fres/read-object! from-data))}))
(t/add-handlers!
{:id "penpot/objects-map/v2"
:class ObjectsMap
:wfn get-data
:rfn from-data})

View File

@@ -41,7 +41,6 @@
[common-tests.types.components-test]
[common-tests.types.fill-test]
[common-tests.types.modifiers-test]
[common-tests.types.objects-map-test]
[common-tests.types.path-data-test]
[common-tests.types.shape-decode-encode-test]
[common-tests.types.shape-interactions-test]
@@ -91,10 +90,9 @@
'common-tests.time-test
'common-tests.types.absorb-assets-test
'common-tests.types.components-test
'common-tests.types.fill-test
'common-tests.types.modifiers-test
'common-tests.types.objects-map-test
'common-tests.types.path-data-test
'common-tests.types.fill-test
'common-tests.types.shape-decode-encode-test
'common-tests.types.shape-interactions-test
'common-tests.types.tokens-lib-test

View File

@@ -1,133 +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.objects-map-test
(:require
#?(:clj [app.common.fressian :as fres])
[app.common.json :as json]
[app.common.pprint :as pp]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.schema.test :as smt]
[app.common.transit :as transit]
[app.common.types.objects-map :as omap]
[app.common.types.path :as path]
[app.common.types.plugins :refer [schema:plugin-data]]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[clojure.datafy :refer [datafy]]
[clojure.test :as t]))
(t/deftest basic-operations
(t/testing "assoc"
(let [id (uuid/custom 0 1)
id' (uuid/custom 0 2)
obj (-> (omap/create) (assoc id {:foo 1}))]
(t/is (not= id id'))
(t/is (not (contains? obj id')))
(t/is (contains? obj id))))
(t/testing "assoc-with-non-uuid-keys"
(let [obj (-> (omap/create)
(assoc :a {:foo 1})
(assoc :b {:bar 1}))]
(t/is (not (contains? obj :c)))
(t/is (contains? obj :a))
(t/is (contains? obj :b))))
(t/testing "dissoc"
(let [id (uuid/custom 0 1)
obj (-> (omap/create) (assoc id {:foo 1}))]
(t/is (contains? obj id))
(let [obj (dissoc obj id)]
(t/is (not (contains? obj id))))))
(t/testing "seq"
(let [id (uuid/custom 0 1)
obj (-> (omap/create) (assoc id 1))]
(t/is (contains? obj id))
(let [[entry] (seq obj)]
(t/is (map-entry? entry))
(t/is (= (key entry) id))
(t/is (= (val entry) 1)))))
(t/testing "cons & count"
(let [obj (into (omap/create) [[uuid/zero 1]])]
(t/is (contains? obj uuid/zero))
(t/is (= 1 (count obj)))
(t/is (omap/objects-map? obj))))
(t/testing "wrap"
(let [obj1 (omap/wrap {})
tmp (omap/create)
obj2 (omap/wrap tmp)]
(t/is (omap/objects-map? obj1))
(t/is (omap/objects-map? obj2))
(t/is (identical? tmp obj2))
(t/is (= 0 (count obj1)))
(t/is (= 0 (count obj2))))))
(t/deftest internal-state
(t/testing "modified & compact"
(let [obj (-> (omap/create)
(assoc :a 1)
(assoc :b 2))]
(t/is (= 2 (count obj)))
(t/is (-> obj datafy :modified))
(let [obj (omap/compact obj)]
(t/is (not (-> obj datafy :modified))))))
(t/testing "create from other"
(let [obj1 (-> (omap/create)
(assoc :a {:foo 1})
(assoc :b {:bar 2}))
obj2 (omap/create obj1)]
(t/is (not (identical? obj1 obj2)))
(t/is (= obj1 obj2))
(t/is (= (hash obj1) (hash obj2)))
(t/is (= (get obj1 :a) (get obj2 :a)))
(t/is (= (get obj1 :b) (get obj2 :b))))))
(t/deftest creation-and-duplication
(smt/check!
(smt/for [data (->> (sg/map-of (sg/uuid) (sg/generator cts/schema:shape))
(sg/not-empty))]
(let [obj1 (omap/wrap data)
obj2 (omap/create obj1)]
(and (= (hash obj1) (hash obj2))
(= obj1 obj2))))
{:num 100}))
#?(:clj
(t/deftest fressian-encode-decode
(smt/check!
(smt/for [data (->> (sg/map-of (sg/uuid) (sg/generator cts/schema:shape))
(sg/not-empty)
(sg/fmap omap/wrap)
(sg/fmap (fn [o] {:objects o})))]
(let [res (-> data fres/encode fres/decode)]
(and (contains? res :objects)
(omap/objects-map? (:objects res))
(= res data))))
{:num 100})))
(t/deftest transit-encode-decode
(smt/check!
(smt/for [data (->> (sg/map-of (sg/uuid) (sg/generator cts/schema:shape))
(sg/not-empty)
(sg/fmap omap/wrap)
(sg/fmap (fn [o] {:objects o})))]
(let [res (-> data transit/encode-str transit/decode-str)]
;; (app.common.pprint/pprint data)
;; (app.common.pprint/pprint res)
(and (every? (fn [[k v]]
(= v (get-in data [:objects k])))
(:objects res))
(omap/objects-map? (:objects data))
(omap/objects-map? (:objects res)))))
{:num 100}))

View File

@@ -310,7 +310,6 @@ RUN set -ex; \
fonts-wqy-zenhei \
fonts-tlwg-loma-otf \
fonts-freefont-ttf \
poppler-utils \
\
libasound2t64 \
libatk-bridge2.0-0t64 \

View File

@@ -118,6 +118,7 @@ http {
location /api {
proxy_pass http://127.0.0.1:6060/api;
proxy_buffering off;
proxy_http_version 1.1;
}

View File

@@ -12,32 +12,27 @@ If you need more context you can look at the <a
href="https://community.penpot.app/t/self-hosting-penpot-i/2336" target="_blank">post
about self-hosting</a> in Penpot community.
<strong>The experience stays the same, whether you use
Penpot <a href="https://design.penpot.app" target="_blank">in the cloud</a>
or self-hosted.</strong>
**There is absolutely no difference between <a
href="https://design.penpot.app" target="_blank">our SaaS offer</a> for Penpot and your
self-hosted Penpot platform!**
<p class="advice">
Docker images are published shortly after the SaaS update:
<a href="https://community.penpot.app/t/why-do-self-hosted-versions-lag-behind-new-releases/9897" target="_blank">Why do self hosted versions lag behind new releases?</a>
Docker images are published shortly after the SaaS update:
<a
href="https://community.penpot.app/t/why-do-self-hosted-versions-lag-behind-new-releases/9897" target="_blank">Why do self hosted versions lag behind new releases?</a>
</p>
These are the main options to configure your Penpot instance:
There are three main options for creating a Penpot instance:
1. Deploy with [docker compose][2]
2. Use Kubernetes in its different flavors:
- Deploy the [official Helm Chart][3]
- Deploy in [Openshift][4]
- Deploy in [Rancher][5]
3. Other official options:
- [Elestio][6]
- [Truenas][7]
1. Using the platform of our partner <a href="https://elest.io/open-source/penpot" target="_blank">Elestio</a>.
2. Using <a href="https://docker.com" target="_blank">Docker</a> tool.
3. Using <a href="https://kubernetes.io/" target="_blank">Kubernetes</a>.
<p>
The recommended way is to use Elestio, since it's simpler, fully automatic and still greatly flexible.
Use Docker if you already know the tool, if need full control of the process or have extra requirements
and do not want to depend on any external provider, or need to do any special customization.
</p>
Or you can try [other options][1], offered by Penpot community.
[1]: /technical-guide/getting-started/unofficial-options/
[2]: /technical-guide/getting-started/docker/
[3]: /technical-guide/getting-started/kubernetes/
[4]: /technical-guide/getting-started/kubernetes/#using-openshift%3F
[5]: /technical-guide/getting-started/kubernetes/#using-rancher%3F
[6]: /technical-guide/getting-started/elestio/
[7]: https://apps.truenas.com/catalog/penpot/

View File

@@ -5,7 +5,12 @@ desc: Find guides for Docker, Kubernetes, and more in Penpot's Technical Guide f
# Unofficial self-host options
Other setups maintained by the community (not Penpot-supported):
There are some other options, **NOT SUPPORTED BY PENPOT**:
* Install with <a href="https://blog.linuxgrrl.com/2022/01/19/running-penpot-locally-docker-free-with-podman/" target="_blank">Podman</a> instead of Docker.
* Try the <a href="https://community.penpot.app/t/penpot-desktop-road-to-1-0/7296" target="_blank">Penpot Desktop app</a>.
* Install with <a href="https://community.penpot.app/t/how-to-develop-penpot-with-podman-penpotman/2113" target="_blank">Podman</a> instead of Docker.
* Try the under development <a href="https://github.com/author-more/penpot-desktop/releases/latest" target="_blank">Penpot Desktop app</a>.
* Try a simple Kubernetes Deployment option <a href="https://github.com/degola/penpot-kubernetes" target="_blank">penpot-kubernetes</a>.
* Penpot is available <a href="https://apps.yunohost.org/app/penpot">in the catalog</a> of apps installable on YunoHost instances.
* Or try a fully manual installation if you have a really specific use case.. For help, you can look at the [Architecture][1] section and the <a href="https://github.com/penpot/penpot/tree/develop/docker/images" target="_blank">Docker configuration files</a>.
[1]: /technical-guide/developer/architecture

View File

@@ -16,7 +16,7 @@
"inflation": "^2.1.0",
"ioredis": "^5.6.1",
"luxon": "^3.6.1",
"playwright": "^1.55.1",
"playwright": "^1.53.0",
"raw-body": "^3.0.0",
"svgo": "penpot/svgo#v3.1",
"xml-js": "^1.6.11",

View File

@@ -557,7 +557,7 @@ __metadata:
inflation: "npm:^2.1.0"
ioredis: "npm:^5.6.1"
luxon: "npm:^3.6.1"
playwright: "npm:^1.55.1"
playwright: "npm:^1.53.0"
raw-body: "npm:^3.0.0"
source-map-support: "npm:^0.5.21"
svgo: "penpot/svgo#v3.1"
@@ -1101,27 +1101,27 @@ __metadata:
languageName: node
linkType: hard
"playwright-core@npm:1.55.1":
version: 1.55.1
resolution: "playwright-core@npm:1.55.1"
"playwright-core@npm:1.53.0":
version: 1.53.0
resolution: "playwright-core@npm:1.53.0"
bin:
playwright-core: cli.js
checksum: 10c0/39837a8c1232ec27486eac8c3fcacc0b090acc64310f7f9004b06715370fc426f944e3610fe8c29f17cd3d68280ed72c75f660c02aa5b5cf0eb34bab0031308f
checksum: 10c0/fda0cf76115b15b1ca5cbc69e14185904e5c85e9e7cddb0a48121e69d681c638ac497e8a103985976cae260aa02e9c03ea27d6cd0b5f3d3ca914d4c7fd96f930
languageName: node
linkType: hard
"playwright@npm:^1.55.1":
version: 1.55.1
resolution: "playwright@npm:1.55.1"
"playwright@npm:^1.53.0":
version: 1.53.0
resolution: "playwright@npm:1.53.0"
dependencies:
fsevents: "npm:2.3.2"
playwright-core: "npm:1.55.1"
playwright-core: "npm:1.53.0"
dependenciesMeta:
fsevents:
optional: true
bin:
playwright: cli.js
checksum: 10c0/b84a97b0d764403df512f5bbb10c7343974e151a28202cc06f90883a13e8a45f4491a0597f0ae5fb03a026746cbc0d200f0f32195bfaa381aee5ca5770626771
checksum: 10c0/8d995114808b92f2005bd12ff5e494cdc3fa2d484f4d85a3e54be1fb99e88ae3e34b24792d83bb987462c73e553a0fa37a2a70264afbf67894b51c1498cf5a11
languageName: node
linkType: hard

View File

@@ -8,7 +8,6 @@
(:require
[app.common.data.macros :as dm]
[app.common.logging :as log]
[app.common.types.objects-map]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.auth :as da]

View File

@@ -52,7 +52,7 @@
(->> (rx/from changes)
(rx/merge-map (fn [[page-id changes]]
(log/debug :hint "update-indexes" :page-id page-id :changes (count changes))
(mw/ask! {:cmd :index/update
(mw/ask! {:cmd :index/update-page-index
:page-id page-id
:changes changes})))
(rx/catch (fn [cause]

View File

@@ -21,6 +21,7 @@
[app.common.types.component :as ctc]
[app.common.types.fills :as types.fills]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.changes :as dch]
@@ -42,6 +43,7 @@
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.fix-broken-shapes :as fbs]
[app.main.data.workspace.fix-deleted-fonts :as fdf]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.guides :as dwgu]
@@ -68,6 +70,7 @@
[app.main.features.pointer-map :as fpmap]
[app.main.repo :as rp]
[app.main.router :as rt]
[app.main.worker :as mw]
[app.render-wasm :as wasm]
[app.render-wasm.api :as api]
[app.util.dom :as dom]
@@ -155,9 +158,18 @@
(->> (fpmap/resolve-file file)
(rx/map :data)
(rx/map process-fills)
(rx/map
(fn [data]
(assoc file :data (d/removem (comp t/pointer? val) data))))))
(rx/mapcat
(fn [{:keys [pages-index] :as data}]
(->> (rx/from (seq pages-index))
(rx/mapcat
(fn [[id page]]
(let [page (update page :objects ctst/start-page-index)]
(->> (mw/ask! {:cmd :index/initialize-page-index :page page})
(rx/map (fn [_] [id page]))))))
(rx/reduce conj {})
(rx/map (fn [pages-index]
(let [data (assoc data :pages-index pages-index)]
(assoc file :data (d/removem (comp t/pointer? val) data))))))))))
(defn- check-libraries-synchronozation
[file-id libraries]
@@ -231,7 +243,8 @@
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dp/check-open-plugin)
(fdf/fix-deleted-fonts-for-local-library file-id)))))
(fdf/fix-deleted-fonts)
(fbs/fix-broken-shapes)))))
(defn- bundle-fetched
[{:keys [file file-id thumbnails] :as bundle}]
@@ -267,8 +280,6 @@
(ptk/reify ::fetch-bundle
ptk/WatchEvent
(watch [_ _ stream]
(log/debug :hint "fetch bundle" :file-id (dm/str file-id))
(let [stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)]
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
(get-file-object-thumbnails file-id))
@@ -277,7 +288,6 @@
(fn [[file thumbnails]]
(->> (resolve-file file)
(rx/map (fn [file]
(log/trace :hint "file resolved" :file-id file-id)
{:file file
:file-id file-id
:features features
@@ -347,10 +357,6 @@
(rx/map deref)
(rx/mapcat
(fn [{:keys [file]}]
(log/debug :hint "bundle fetched"
:team-id (dm/str team-id)
:file-id (dm/str file-id))
(rx/of (dpj/initialize-project (:project-id file))
(dwn/initialize team-id file-id)
(dwsl/initialize-shape-layout)

View File

@@ -83,7 +83,7 @@
(rx/of (dwsh/add-shape shape {:no-select? (= tool :curve)}))
(if (cfh/frame-shape? shape)
(rx/concat
(->> (mw/ask! {:cmd :index/query-selection
(->> (mw/ask! {:cmd :selection/query
:page-id page-id
:rect (:selrect shape)
:include-frames? true

View File

@@ -0,0 +1,56 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.fix-broken-shapes
(:require
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn- generate-broken-link-changes
[attr {:keys [objects id] :as container}]
(let [base {:type :fix-obj :fix :broken-children attr id}
contains? (partial contains? objects)
xform (comp
;; FIXME: Ensure all obj have id field (this is needed
;; because some bug adds an ephimeral shape with id ZERO,
;; with a single attr `:shapes` having a vector of ids
;; pointing to not existing shapes). That happens on
;; components. THIS IS A WORKAOURD
(map (fn [[id obj]]
(if (some? (:id obj))
obj
(assoc obj :id id))))
;; Remove all valid shapes
(remove (fn [obj]
(every? contains? (:shapes obj))))
(map (fn [obj]
(assoc base :id (:id obj)))))]
(sequence xform objects)))
(defn fix-broken-shapes
[]
(ptk/reify ::fix-broken-shapes
ptk/WatchEvent
(watch [it state _]
(let [fdata (dsh/lookup-file-data state)
changes (concat
(mapcat (partial generate-broken-link-changes :page-id)
(vals (:pages-index fdata)))
(mapcat (partial generate-broken-link-changes :component-id)
(vals (:components fdata))))]
(if (seq changes)
(rx/of (dch/commit-changes
{:origin it
:redo-changes (vec changes)
:undo-changes []
:save-undo? false}))
(rx/empty))))))

View File

@@ -14,9 +14,8 @@
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
;; This event will update the file so the texts with non existing
;; custom fonts try to be fixed. This can happen when:
;;
;; This event will update the file so the texts with non existing custom fonts try to be fixed.
;; This can happen when:
;; - Exporting/importing files to different teams or penpot instances
;; - Moving files from one team to another in the same instance
;; - Custom fonts are explicitly deleted in the team area
@@ -24,99 +23,112 @@
(defn- calculate-alternative-font-id
[value]
(let [fonts (deref fonts/fontsdb)]
(reduce-kv (fn [_ _ font]
(if (= (:family font) value)
(reduced (:id font))
nil))
nil
fonts)))
(->> (vals fonts)
(filter #(= (:family %) value))
(first)
:id)))
(defn- has-invalid-font-family?
[node]
(let [fonts (deref fonts/fontsdb)
font-family (:font-family node)]
(let [fonts (deref fonts/fontsdb)
font-family (:font-family node)
alternative-font-id (calculate-alternative-font-id font-family)]
(and (some? font-family)
(nil? (get fonts (:font-id node))))))
(nil? (get fonts (:font-id node)))
(some? alternative-font-id))))
(defn- shape-has-invalid-font-family??
(defn- should-fix-deleted-font-shape?
[shape]
(and (cfh/text-shape? shape)
(some has-invalid-font-family?
(txt/node-seq txt/is-text-node? (:content shape)))))
(let [text-nodes (txt/node-seq txt/is-text-node? (:content shape))]
(and (cfh/text-shape? shape)
(some has-invalid-font-family? text-nodes))))
(defn- should-fix-deleted-font-component?
[component]
(let [xf (comp (map val)
(filter should-fix-deleted-font-shape?))]
(first (sequence xf (:objects component)))))
(defn- fix-deleted-font
[node]
(if-let [alternative-font-id (calculate-alternative-font-id (:font-family node))]
(assoc node :font-id alternative-font-id)
node))
(let [alternative-font-id (calculate-alternative-font-id (:font-family node))]
(cond-> node
(some? alternative-font-id) (assoc :font-id alternative-font-id))))
(defn- fix-shape-content
(defn- fix-deleted-font-shape
[shape]
(txt/transform-nodes has-invalid-font-family? fix-deleted-font
(:content shape)))
(let [transform (partial txt/transform-nodes has-invalid-font-family? fix-deleted-font)]
(update shape :content transform)))
(defn- fix-typography
(defn- fix-deleted-font-component
[component]
(update component
:objects
(fn [objects]
(update-vals objects fix-deleted-font-shape))))
(defn fix-deleted-font-typography
[typography]
(if-let [alternative-font-id (calculate-alternative-font-id (:font-family typography))]
(assoc typography :font-id alternative-font-id)
typography))
(let [alternative-font-id (calculate-alternative-font-id (:font-family typography))]
(cond-> typography
(some? alternative-font-id) (assoc :font-id alternative-font-id))))
(defn- generate-page-changes
(defn- generate-deleted-font-shape-changes
[{:keys [objects id]}]
(reduce-kv (fn [changes shape-id shape]
(if (shape-has-invalid-font-family?? shape)
(conj changes {:type :mod-obj
:id shape-id
:page-id id
:operations [{:type :set
:attr :content
:val (fix-shape-content shape)}
{:type :set
:attr :position-data
:val nil}]})
changes))
[]
objects))
(sequence
(comp (map val)
(filter should-fix-deleted-font-shape?)
(map (fn [shape]
{:type :mod-obj
:id (:id shape)
:page-id id
:operations [{:type :set
:attr :content
:val (:content (fix-deleted-font-shape shape))}
{:type :set
:attr :position-data
:val nil}]})))
objects))
(defn- generate-library-changes
(defn- generate-deleted-font-components-changes
[fdata]
(reduce-kv (fn [changes _ typography]
(if (has-invalid-font-family? typography)
(conj changes {:type :mod-typography
:typography (fix-typography typography)})
changes))
[]
(:typographies fdata)))
(sequence
(comp (map val)
(filter should-fix-deleted-font-component?)
(map (fn [component]
{:type :mod-component
:id (:id component)
:objects (-> (fix-deleted-font-component component) :objects)})))
(:components fdata)))
(defn fix-deleted-fonts-for-local-library
"Looks the file local library for deleted fonts and emit changes if
invalid but fixable typographyes found."
[file-id]
(ptk/reify ::fix-deleted-fonts-for-local-library
(defn- generate-deleted-font-typography-changes
[fdata]
(sequence
(comp (map val)
(filter has-invalid-font-family?)
(map (fn [typography]
{:type :mod-typography
:typography (fix-deleted-font-typography typography)})))
(:typographies fdata)))
(defn fix-deleted-fonts
[]
(ptk/reify ::fix-deleted-fonts
ptk/WatchEvent
(watch [it state _]
(let [fdata (dsh/lookup-file-data state file-id)]
(when-let [changes (-> (generate-library-changes fdata)
(not-empty))]
(let [fdata (dsh/lookup-file-data state)
pages (:pages-index fdata)
shape-changes (mapcat generate-deleted-font-shape-changes (vals pages))
components-changes (generate-deleted-font-components-changes fdata)
typography-changes (generate-deleted-font-typography-changes fdata)
changes (concat shape-changes
components-changes
typography-changes)]
(if (seq changes)
(rx/of (dwc/commit-changes
{:origin it
:redo-changes changes
:redo-changes (vec changes)
:undo-changes []
:save-undo? false})))))))
;; FIXME: would be nice to not execute this code twice per page in the
;; same working session, maybe some local memoization can improve that
(defn fix-deleted-fonts-for-page
[file-id page-id]
(ptk/reify ::fix-deleted-fonts-for-page
ptk/WatchEvent
(watch [it state _]
(let [page (dsh/lookup-page state file-id page-id)]
(when-let [changes (-> (generate-page-changes page)
(not-empty))]
(rx/of (dwc/commit-changes
{:origin it
:redo-changes changes
:undo-changes []
:save-undo? false})))))))
:save-undo? false}))
(rx/empty))))))

View File

@@ -15,7 +15,6 @@
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.page :as ctp]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.changes :as dch]
@@ -24,14 +23,12 @@
[app.main.data.helpers :as dsh]
[app.main.data.persistence :as-alias dps]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.fix-deleted-fonts :as fdf]
[app.main.data.workspace.layout :as layout]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.thumbnails :as dwth]
[app.main.errors]
[app.main.features :as features]
[app.main.router :as rt]
[app.main.worker :as mw]
[app.render-wasm.shape :as wasm.shape]
[app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]]
@@ -59,21 +56,16 @@
(some? metadata) (cf/resolve-file-media metadata)
(some? fill-image) (cf/resolve-file-media fill-image))))))
(defn- get-page-cache
[state file-id page-id]
(dm/get-in state [:workspace-cache [file-id page-id]]))
(defn- initialize-page*
"Second phase of page initialization, once we know the page is
available in the state"
[file-id page-id]
[file-id page-id page]
(ptk/reify ::initialize-page*
ptk/UpdateEvent
(update [_ state]
(let [state (dsh/update-page state file-id page-id #(update % :objects ctst/start-page-index))
page (dsh/lookup-page state file-id page-id)
local (or (get-page-cache state file-id page-id) default-workspace-local)]
;; selection; when user abandon the current page, the selection is lost
(let [local (dm/get-in state [:workspace-cache [file-id page-id]] default-workspace-local)]
(-> state
(assoc :current-page-id page-id)
(assoc :workspace-local (assoc local :selected (d/ordered-set)))
@@ -83,16 +75,11 @@
(update :workspace-layout layout/load-layout-flags)
(update :workspace-global layout/load-layout-state))))
ptk/WatchEvent
(watch [_ state _]
(let [page (dsh/lookup-page state file-id page-id)
uris (into #{} xf:collect-file-media (:objects page))]
(rx/merge
(->> (rx/from uris)
(rx/map #(http/fetch-data-uri % false))
(rx/ignore))
(->> (mw/ask! {:cmd :index/initialize :page page})
(rx/ignore)))))))
ptk/EffectEvent
(effect [_ _ _]
(let [uris (into #{} xf:collect-file-media (:objects page))]
(->> (rx/from uris)
(rx/subs! #(http/fetch-data-uri % false)))))))
(defn initialize-page
[file-id page-id]
@@ -102,10 +89,9 @@
(ptk/reify ::initialize-page
ptk/WatchEvent
(watch [_ state _]
(if (dsh/lookup-page state file-id page-id)
(if-let [page (dsh/lookup-page state file-id page-id)]
(rx/concat
(rx/of (initialize-page* file-id page-id)
(fdf/fix-deleted-fonts-for-page file-id page-id)
(rx/of (initialize-page* file-id page-id page)
(dwth/watch-state-changes file-id page-id)
(dwl/watch-component-changes))
(let [profile (:profile state)

View File

@@ -344,7 +344,7 @@
(if (some? selrect)
(->> (ask-worker
{:cmd :index/query-selection
{:cmd :selection/query
:page-id page-id
:rect selrect
:include-frames? true

View File

@@ -8,8 +8,6 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as log]
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.uri :as u]
[app.config :as cf]
@@ -19,8 +17,6 @@
[beicon.v2.core :as rx]
[cuerdas.core :as str]))
(log/set-level! :info)
(defn handle-response
[{:keys [status body headers uri] :as response}]
(cond
@@ -130,21 +126,13 @@
(select-keys params query-params)
nil))
:response-type
(if stream? nil response-type)}
tpoint
(ct/tpoint-ms)]
(log/trc :hint "make request" :id id)
(if stream? nil response-type)}]
(->> (http/fetch request)
(rx/map http/response->map)
(rx/mapcat (fn [{:keys [headers body] :as response}]
(log/trc :hint "response received" :id id :elapsed (tpoint))
(let [ctype (get headers "content-type")
response-stream? (str/starts-with? ctype "text/event-stream")
tpoint (ct/tpoint-ms)]
response-stream? (str/starts-with? ctype "text/event-stream")]
(when (and response-stream? (not stream?))
(ex/raise :type :internal
@@ -160,8 +148,6 @@
(->> response
(http/process-response-type response-type)
(rx/map decode-fn)
(rx/tap (fn [_]
(log/trc :hint "response decoded" :id id :elapsed (tpoint))))
(rx/mapcat handle-response)))))))))
(defmulti cmd! (fn [id _] id))

View File

@@ -84,7 +84,7 @@
(let [value (get point coord)
vbox @refs/vbox
ranges [[(- value (/ 0.5 zoom)) (+ value (/ 0.5 zoom))]]]
(->> (mw/ask! {:cmd :index/query-snap
(->> (mw/ask! {:cmd :snaps/range-query
:page-id page-id
:frame-id frame-id
:axis coord
@@ -101,7 +101,7 @@
(mapv #(vector (- % snap-accuracy)
(+ % snap-accuracy))))
vbox @refs/vbox]
(->> (mw/ask! {:cmd :index/query-snap
(->> (mw/ask! {:cmd :snaps/range-query
:page-id page-id
:frame-id frame-id
:axis coord
@@ -217,7 +217,7 @@
(defn select-shapes-area
[page-id frame-id selected objects area]
(->> (mw/ask! {:cmd :index/query-selection
(->> (mw/ask! {:cmd :selection/query
:page-id page-id
:frame-id frame-id
:include-frames? true

View File

@@ -194,7 +194,7 @@
(if (mf/ref-val hover-disabled-ref)
(rx/of nil)
(->> (mw/ask-buffered!
{:cmd :index/query-selection
{:cmd :selection/query
:page-id page-id
:rect rect
:include-frames? true

View File

@@ -338,9 +338,8 @@
:else
(let [page (dsh/lookup-page @st/state)
shape (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
(update :content txt/change-text text)
(txt/change-text text)
(assoc :position-data nil))
changes
(-> (cb/empty-changes)
(cb/with-page page)

View File

@@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC
(ns app.worker.snap
(ns app.util.snap-data
"Data structure that holds and retrieves the data to make the snaps.
Internally is implemented with a balanced binary tree that queries by range.
https://en.wikipedia.org/wiki/Range_tree"

View File

@@ -9,13 +9,14 @@
[app.common.data.macros :as dm]
[app.common.logging :as log]
[app.common.schema :as sm]
[app.common.types.objects-map]
[app.util.object :as obj]
[app.worker.export]
[app.worker.impl :as impl]
[app.worker.import]
[app.worker.index]
[app.worker.messages :as wm]
[app.worker.selection]
[app.worker.snaps]
[app.worker.thumbnails]
[beicon.v2.core :as rx]
[promesa.core :as p]))

View File

@@ -9,69 +9,26 @@
(:require
[app.common.data.macros :as dm]
[app.common.files.changes :as ch]
[app.common.geom.rect :as grc]
[app.common.logging :as log]
[app.common.time :as ct]
[app.worker.impl :as impl]
[app.worker.selection :as selection]
[app.worker.snap :as snap]
[okulary.core :as l]))
(log/set-level! :info)
(defonce state (l/atom {:pages-index {}}))
(defmethod impl/handler :index/initialize
(defmethod impl/handler :index/initialize-page-index
[{:keys [page] :as message}]
(let [tpoint (ct/tpoint-ms)]
(try
(swap! state update :pages-index assoc (:id page) page)
(swap! state update ::selection selection/add-page page)
(swap! state update ::snap snap/add-page page)
(swap! state update :pages-index assoc (:id page) page)
(impl/handler (assoc message :cmd :selection/initialize-page-index))
(impl/handler (assoc message :cmd :snaps/initialize-page-index)))
(finally
(let [elapsed (tpoint)]
(log/dbg :hint "page indexed" :id (:id page) :elapsed elapsed ::log/sync? true))))
nil))
(defmethod impl/handler :index/update
(defmethod impl/handler :index/update-page-index
[{:keys [page-id changes] :as message}]
(let [tpoint (ct/tpoint-ms)]
(try
(let [old-page (dm/get-in @state [:pages-index page-id])
new-page (-> state
(swap! ch/process-changes changes false)
(dm/get-in [:pages-index page-id]))]
(swap! state update ::snap snap/update-page old-page new-page)
(swap! state update ::selection selection/update-page old-page new-page))
(finally
(let [elapsed (tpoint)]
(log/dbg :hint "page index updated" :id page-id :elapsed elapsed ::log/sync? true))))
nil))
;; FIXME: schema
(defmethod impl/handler :index/query-snap
[{:keys [page-id frame-id axis ranges bounds] :as message}]
(if-let [index (get @state ::snap)]
(let [match-bounds?
(fn [[_ data]]
(some #(or (= :guide (:type %))
(= :layout (:type %))
(grc/contains-point? bounds (:pt %))) data))
xform
(comp (mapcat #(snap/query index page-id frame-id axis %))
(distinct)
(filter match-bounds?))]
(into [] xform ranges))
[]))
;; FIXME: schema
(defmethod impl/handler :index/query-selection
[message]
(if-let [index (get @state ::selection)]
(selection/query index message)
[]))
(let [old-page (dm/get-in @state [:pages-index page-id])
new-page (-> state
(swap! ch/process-changes changes false)
(dm/get-in [:pages-index page-id]))
message (assoc message
:old-page old-page
:new-page new-page)]
(impl/handler (assoc message :cmd :selection/update-page-index))
(impl/handler (assoc message :cmd :snaps/update-page-index))))

View File

@@ -17,62 +17,46 @@
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]
[app.util.quadtree :as qdt]
[clojure.set :as set]))
[app.worker.impl :as impl]
[clojure.set :as set]
[okulary.core :as l]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; IMPL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FIXME: performance shape & rect static props
(def ^:const padding-percent 0.10)
(defn- index-shape
"A reducing function that ads a shape to the index"
[objects parents-index clip-index index shape]
(let [bounds
(cond
(and ^boolean (cfh/text-shape? shape)
^boolean (some? (:position-data shape))
^boolean (d/not-empty? (:position-data shape)))
(gst/shape->bounds shape)
(defonce state (l/atom {}))
:else
(grc/points->rect (:points shape)))
(defn make-index-shape
[objects parents-index clip-parents-index]
(fn [index shape]
(let [{:keys [x y width height]}
(cond
(and ^boolean (cfh/text-shape? shape)
^boolean (some? (:position-data shape))
^boolean (d/not-empty? (:position-data shape)))
(gst/shape->bounds shape)
bound
#js {:x (dm/get-prop bounds :x)
:y (dm/get-prop bounds :y)
:width (dm/get-prop bounds :width)
:height (dm/get-prop bounds :height)}
:else
(grc/points->rect (:points shape)))
shape-id
(dm/get-prop shape :id)
shape-bound #js {:x x :y y :width width :height height}
frame-id
(dm/get-prop shape :frame-id)
parents (get parents-index (:id shape))
clip-parents (get clip-parents-index (:id shape))
shape-type
(dm/get-prop shape :type)
frame (when (and (not= :frame (:type shape))
(not= (:frame-id shape) uuid/zero))
(get objects (:frame-id shape)))]
(qdt/insert index
(:id shape)
shape-bound
(assoc shape
:frame frame
:clip-parents clip-parents
:parents parents)))))
parents
(get parents-index shape-id)
clip-parents
(get clip-index shape-id)
frame
(when (and (not= :frame shape-type)
(not= frame-id uuid/zero))
(get objects frame-id))]
(qdt/insert index
shape-id
bound
(assoc shape
:frame frame
:clip-parents clip-parents
:parents parents))))
(defn- objects-bounds
(defn objects-bounds
"Calculates the bounds of the quadtree given a objects map."
[objects]
(-> objects
@@ -80,7 +64,7 @@
vals
gsh/shapes->rect))
(defn- add-padding-bounds
(defn add-padding-bounds
"Adds a padding to the bounds defined as a percent in the constant `padding-percent`.
For a value of 0.1 will add a 20% width increase (2 x padding)"
[bounds]
@@ -97,48 +81,41 @@
(defn- create-index
[objects]
(let [parents-index (cfi/generate-child-all-parents-index objects)
clip-index (cfi/create-clip-index objects parents-index)
root-shapes (cfh/get-immediate-children objects uuid/zero)
bounds (-> root-shapes gsh/shapes->rect add-padding-bounds)
(let [shapes (-> objects (dissoc uuid/zero) vals)
parents-index (cfi/generate-child-all-parents-index objects)
clip-parents-index (cfi/create-clip-index objects parents-index)
root-shapes (cfh/get-immediate-children objects uuid/zero)
bounds (-> root-shapes gsh/shapes->rect add-padding-bounds)
index-shape (make-index-shape objects parents-index clip-parents-index)
initial-quadtree (qdt/create (clj->js bounds))
index (reduce index-shape initial-quadtree shapes)]
index (reduce-kv #(index-shape objects parents-index clip-index %1 %3)
(qdt/create (clj->js bounds))
(dissoc objects uuid/zero))]
{:index index :bounds bounds}))
;; FIXME: optimize
(defn- update-index
[{index :index :as data} old-objects new-objects]
(let [object-changed?
(fn [id]
(not= (get old-objects id)
(get new-objects id)))
(let [changes? (fn [id]
(not= (get old-objects id)
(get new-objects id)))
changed-ids
(into #{}
(comp (filter #(not= % uuid/zero))
(filter object-changed?)
(mapcat #(into [%] (cfh/get-children-ids new-objects %))))
changed-ids (into #{}
(comp (filter #(not= % uuid/zero))
(filter changes?)
(mapcat #(into [%] (cfh/get-children-ids new-objects %))))
(set/union (set (keys old-objects))
(set (keys new-objects))))
(set/union (set (keys old-objects))
(set (keys new-objects))))
shapes (->> changed-ids (mapv #(get new-objects %)) (filterv (comp not nil?)))
parents-index (cfi/generate-child-all-parents-index new-objects shapes)
clip-parents-index (cfi/create-clip-index new-objects parents-index)
shapes
(->> changed-ids
(map #(get new-objects %))
(filterv (comp not nil?)))
new-index (qdt/remove-all index changed-ids)
parents-index
(cfi/generate-child-all-parents-index new-objects shapes)
clip-index
(cfi/create-clip-index new-objects parents-index)
index
(reduce #(index-shape new-objects parents-index clip-index %1 %2)
(qdt/remove-all index changed-ids)
shapes)]
index-shape (make-index-shape new-objects parents-index clip-parents-index)
index (reduce index-shape new-index shapes)]
(assoc data :index index)))
@@ -254,36 +231,35 @@
(map :id))
result)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PUBLIC API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn add-page
"Add a page index to the state"
[state {:keys [id objects] :as page}]
(assoc state id (create-index objects)))
(defmethod impl/handler :selection/initialize-page-index
[{:keys [page] :as message}]
(letfn [(add-page [state {:keys [id objects] :as page}]
(assoc state id (create-index objects)))]
(swap! state add-page page)
nil))
(defn update-page
"Update page index on the state"
[state old-page new-page]
(let [page-id (get old-page :id)]
(update state page-id
(fn [index]
(let [old-objects (:objects old-page)
new-objects (:objects new-page)
old-bounds (:bounds index)
new-bounds (objects-bounds new-objects)]
(defmethod impl/handler :selection/update-page-index
[{:keys [page-id old-page new-page] :as message}]
(swap! state update page-id
(fn [index]
(let [old-objects (:objects old-page)
new-objects (:objects new-page)
old-bounds (:bounds index)
new-bounds (objects-bounds new-objects)]
;; If the new bounds are contained within the old bounds
;; we can update the index. Otherwise we need to
;; re-create it.
(if (and (some? index)
(grc/contains-rect? old-bounds new-bounds))
(update-index index old-objects new-objects)
(create-index new-objects)))))))
;; If the new bounds are contained within the old bounds
;; we can update the index. Otherwise we need to
;; re-create it.
(if (and (some? index)
(grc/contains-rect? old-bounds new-bounds))
(update-index index old-objects new-objects)
(create-index new-objects)))))
nil)
(defn query
[index {:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?]
:or {full-frame? false include-frames? false clip-children? true using-selrect? false}}]
(when-let [index (get index page-id)]
(defmethod impl/handler :selection/query
[{:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?]
:or {full-frame? false include-frames? false clip-children? true using-selrect? false}
:as message}]
(when-let [index (get @state page-id)]
(query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?)))

View File

@@ -0,0 +1,40 @@
;; 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.worker.snaps
(:require
[app.common.geom.rect :as grc]
[app.util.snap-data :as sd]
[app.worker.impl :as impl]
[okulary.core :as l]))
(defonce state (l/atom {}))
;; Public API
(defmethod impl/handler :snaps/initialize-page-index
[{:keys [page] :as message}]
(swap! state sd/add-page page)
nil)
(defmethod impl/handler :snaps/update-page-index
[{:keys [old-page new-page] :as message}]
(swap! state sd/update-page old-page new-page)
nil)
(defmethod impl/handler :snaps/range-query
[{:keys [page-id frame-id axis ranges bounds] :as message}]
(let [match-bounds?
(fn [[_ data]]
(some #(or (= :guide (:type %))
(= :layout (:type %))
(grc/contains-point? bounds (:pt %))) data))]
(->> (into []
(comp (mapcat #(sd/query @state page-id frame-id axis %))
(distinct))
ranges)
(filter match-bounds?))))

View File

@@ -17,7 +17,7 @@
[frontend-tests.tokens.token-form-test]
[frontend-tests.util-range-tree-test]
[frontend-tests.util-simple-math-test]
[frontend-tests.worker-snap-test]))
[frontend-tests.util-snap-data-test]))
(enable-console-print!)
@@ -29,8 +29,6 @@
(defn init
[]
(t/run-tests
'frontend-tests.basic-shapes-test
'frontend-tests.data.workspace-colors-test
'frontend-tests.helpers-shapes-test
'frontend-tests.logic.comp-remove-swap-slots-test
'frontend-tests.logic.components-and-tokens
@@ -38,11 +36,13 @@
'frontend-tests.logic.frame-guides-test
'frontend-tests.logic.groups-test
'frontend-tests.plugins.context-shapes-test
'frontend-tests.tokens.import-export-test
'frontend-tests.util-range-tree-test
'frontend-tests.util-snap-data-test
'frontend-tests.util-simple-math-test
'frontend-tests.basic-shapes-test
'frontend-tests.data.workspace-colors-test
'frontend-tests.tokens.logic.token-actions-test
'frontend-tests.tokens.logic.token-data-test
'frontend-tests.tokens.import-export-test
'frontend-tests.tokens.style-dictionary-test
'frontend-tests.tokens.token-form-test
'frontend-tests.util-range-tree-test
'frontend-tests.util-simple-math-test
'frontend-tests.worker-snap-test))
'frontend-tests.tokens.token-form-test))

View File

@@ -4,12 +4,12 @@
;;
;; Copyright (c) KALEIDOS INC
(ns frontend-tests.worker-snap-test
(ns frontend-tests.util-snap-data-test
(:require
[app.common.files.builder :as fb]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.worker.snap :as snap]
[app.util.snap-data :as sd]
[cljs.pprint :refer [pprint]]
[cljs.test :as t :include-macros true]))
@@ -21,9 +21,9 @@
(fn []
(uuid/custom 123456789 (swap! counter inc)))))
(t/deftest create-index
(t/deftest test-create-index
(t/testing "Create empty data"
(let [data (snap/make-snap-data)]
(let [data (sd/make-snap-data)]
(t/is (some? data))))
(t/testing "Add empty page (only root-frame)"
@@ -32,8 +32,8 @@
(fb/add-page {:name "Page 1"})
(fb/get-current-page))
data (-> (snap/make-snap-data)
(snap/add-page page))]
data (-> (sd/make-snap-data)
(sd/add-page page))]
(t/is (some? data))))
(t/testing "Create simple shape on root"
@@ -48,10 +48,10 @@
:height 100}))
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
result-x (snap/query data (:id page) uuid/zero :x [0 100])]
result-x (sd/query data (:id page) uuid/zero :x [0 100])]
(t/is (some? data))
@@ -82,11 +82,11 @@
page (fb/get-current-page state)
;; frame-id (::fb/last-id file)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])]
(t/is (some? data))
(t/is (= (count result-zero-x) 3))
@@ -116,11 +116,11 @@
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])]
(t/is (some? data))
(t/is (= (count result-zero-x) 3))
@@ -137,13 +137,13 @@
frame-id (::fb/last-id state)
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x (snap/query data (:id page) frame-id :x [0 100])
result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
(t/is (some? data))
;; We can snap in the root
@@ -168,13 +168,13 @@
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x (snap/query data (:id page) frame-id :x [0 100])
result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
(t/is (some? data))
;; We can snap in the root
@@ -185,7 +185,7 @@
(t/is (= (count result-frame-x) 1))
(t/is (= (count result-frame-y) 0)))))
(t/deftest update-index
(t/deftest test-update-index
(t/testing "Create frame on root and then remove it."
(let [state (-> (fb/create-state)
(fb/add-file {:name "Test"})
@@ -200,17 +200,17 @@
shape-id (::fb/last-id state)
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
state (-> state
(fb/delete-shape shape-id))
new-page (fb/get-current-page state)
data (snap/update-page data page new-page)
data (sd/update-page data page new-page)
result-x (snap/query data (:id page) uuid/zero :x [0 100])
result-y (snap/query data (:id page) uuid/zero :y [0 100])]
result-x (sd/query data (:id page) uuid/zero :x [0 100])
result-y (sd/query data (:id page) uuid/zero :y [0 100])]
(t/is (some? data))
(t/is (= (count result-x) 0))
@@ -231,16 +231,16 @@
page (fb/get-current-page state)
;; frame-id (::fb/last-id state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
state (fb/delete-shape state shape-id)
new-page (fb/get-current-page state)
data (snap/update-page data page new-page)
data (sd/update-page data page new-page)
result-x (snap/query data (:id page) uuid/zero :x [0 100])
result-y (snap/query data (:id page) uuid/zero :y [0 100])]
result-x (sd/query data (:id page) uuid/zero :x [0 100])
result-y (sd/query data (:id page) uuid/zero :y [0 100])]
(t/is (some? data))
(t/is (= (count result-x) 0))
@@ -263,16 +263,16 @@
state (fb/close-board state)
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
state (fb/delete-shape state shape-id)
new-page (fb/get-current-page state)
data (snap/update-page data page new-page)
data (sd/update-page data page new-page)
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])]
(t/is (some? data))
(t/is (= (count result-zero-x) 3))
@@ -291,18 +291,18 @@
frame-id (::fb/last-id state)
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
new-page (-> (fb/delete-guide state guide-id)
(fb/get-current-page))
data (snap/update-page data page new-page)
data (sd/update-page data page new-page)
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x (snap/query data (:id page) frame-id :x [0 100])
result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
(t/is (some? data))
;; We can snap in the root
@@ -325,17 +325,17 @@
guide-id (::fb/last-id file)
page (fb/get-current-page file)
data (-> (snap/make-snap-data) (snap/add-page page))
data (-> (sd/make-snap-data) (sd/add-page page))
new-page (-> (fb/delete-guide file guide-id)
(fb/get-current-page))
data (snap/update-page data page new-page)
data (sd/update-page data page new-page)
result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x (snap/query data (:id page) frame-id :x [0 100])
result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100])
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])
result-frame-y (sd/query data (:id page) frame-id :y [0 100])]
(t/is (some? data))
;; We can snap in the root
(t/is (= (count result-zero-x) 0))
@@ -358,8 +358,8 @@
frame-id (::fb/last-id state)
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
state (fb/update-shape state frame-id
(fn [shape]
@@ -370,12 +370,12 @@
new-page (fb/get-current-page state)
data (snap/update-page data page new-page)
data (sd/update-page data page new-page)
result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
result-frame-x-1 (snap/query data (:id page) frame-id :x [0 100])
result-zero-x-2 (snap/query data (:id page) uuid/zero :x [200 300])
result-frame-x-2 (snap/query data (:id page) frame-id :x [200 300])]
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100])
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300])
result-frame-x-2 (sd/query data (:id page) frame-id :x [200 300])]
(t/is (some? data))
(t/is (= (count result-zero-x-1) 0))
@@ -396,8 +396,8 @@
shape-id (::fb/last-id state)
page (fb/get-current-page state)
data (-> (snap/make-snap-data)
(snap/add-page page))
data (-> (sd/make-snap-data)
(sd/add-page page))
state (fb/update-shape state shape-id
(fn [shape]
@@ -408,10 +408,10 @@
new-page (fb/get-current-page state)
;; FIXME: update
data (snap/update-page data page new-page)
data (sd/update-page data page new-page)
result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-x-2 (snap/query data (:id page) uuid/zero :x [200 300])]
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300])]
(t/is (some? data))
(t/is (= (count result-zero-x-1) 0))
@@ -432,22 +432,22 @@
frame-id (::fb/last-id state)
page (fb/get-current-page state)
data (-> (snap/make-snap-data) (snap/add-page page))
data (-> (sd/make-snap-data) (sd/add-page page))
new-page (-> (fb/update-guide state (assoc guide :position 150))
(fb/get-current-page))
data (snap/update-page data page new-page)
data (sd/update-page data page new-page)
result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y-1 (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x-1 (snap/query data (:id page) frame-id :x [0 100])
result-frame-y-1 (snap/query data (:id page) frame-id :y [0 100])
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100])
result-zero-y-1 (sd/query data (:id page) uuid/zero :y [0 100])
result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100])
result-frame-y-1 (sd/query data (:id page) frame-id :y [0 100])
result-zero-x-2 (snap/query data (:id page) uuid/zero :x [0 200])
result-zero-y-2 (snap/query data (:id page) uuid/zero :y [0 200])
result-frame-x-2 (snap/query data (:id page) frame-id :x [0 200])
result-frame-y-2 (snap/query data (:id page) frame-id :y [0 200])]
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [0 200])
result-zero-y-2 (sd/query data (:id page) uuid/zero :y [0 200])
result-frame-x-2 (sd/query data (:id page) frame-id :x [0 200])
result-frame-y-2 (sd/query data (:id page) frame-id :y [0 200])]
(t/is (some? data))

View File

@@ -1,15 +1,5 @@
# CHANGELOG
## 1.0.9
- Fix dependencies declaration on package.json
## 1.0.8
- Update penpot runtime
## 1.0.7
- Add the ability to provide refereron creating build context

View File

@@ -1,6 +1,6 @@
{
"name": "@penpot/library",
"version": "1.0.9",
"version": "1.0.7",
"license": "MPL-2.0",
"author": "Kaleidos INC",
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
@@ -35,11 +35,13 @@
"watch:test": "node --test --watch",
"watch": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs watch library"
},
"dependencies": {
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
"date-fns": "^4.1.0"
},
"devDependencies": {
"@types/node": "^22.12.0",
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
"concurrently": "^9.1.2",
"date-fns": "^4.1.0",
"nodemon": "^3.1.9",
"source-map-support": "^0.5.21"
}