mirror of
https://github.com/penpot/penpot.git
synced 2025-12-24 06:58:34 -05:00
Compare commits
30 Commits
revert-646
...
2.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99fb905070 | ||
|
|
faa68784af | ||
|
|
0748ef7267 | ||
|
|
9ca4fa752c | ||
|
|
ff9c8f5929 | ||
|
|
e4c563f917 | ||
|
|
2d3ad5a88f | ||
|
|
1334d733cd | ||
|
|
004a9f17d3 | ||
|
|
c87fa4f723 | ||
|
|
9378a5786f | ||
|
|
3224ba26f1 | ||
|
|
d33a5e6df1 | ||
|
|
b6be416c7b | ||
|
|
aaa57cb17f | ||
|
|
0b289153cb | ||
|
|
cf274099c4 | ||
|
|
6524e75770 | ||
|
|
9b80f7c9b3 | ||
|
|
bf76f328c8 | ||
|
|
ba25ce3098 | ||
|
|
f5f1316f0b | ||
|
|
79a164be6d | ||
|
|
ecb85778bc | ||
|
|
2cdc241e68 | ||
|
|
057bf9bf25 | ||
|
|
2ddcd0ce15 | ||
|
|
ca2891d441 | ||
|
|
37abb7b237 | ||
|
|
5fc2208c16 |
@@ -21,6 +21,7 @@
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix "at" icon to match all icons on app [Taiga #11136](https://tree.taiga.io/project/penpot/issue/11136)
|
||||
- Fix problem in viewer with the back button [Taiga #10907](https://tree.taiga.io/project/penpot/issue/10907)
|
||||
- Fix resize bar background on tokens panel [Taiga #10811](https://tree.taiga.io/project/penpot/issue/10811)
|
||||
- Fix shortcut for history version panel [Taiga #11006](https://tree.taiga.io/project/penpot/issue/11006)
|
||||
@@ -47,6 +48,13 @@
|
||||
- Fix cannot rename Design Token Sets when group of same name exists [Taiga Issue #10773](https://tree.taiga.io/project/penpot/issue/10773)
|
||||
- Fix problem when duplicating grid layout [Github #6391](https://github.com/penpot/penpot/issues/6391)
|
||||
- Fix issue that makes workspace shortcuts stop working [Taiga #11062](https://tree.taiga.io/project/penpot/issue/11062)
|
||||
- Fix problem while syncing library colors and typographies [Taiga #11068](https://tree.taiga.io/project/penpot/issue/11068)
|
||||
- Fix problem with path edition of shapes [Taiga #9496](https://tree.taiga.io/project/penpot/issue/9496)
|
||||
- Fix exception on paste invalid html [Taiga #11047](https://tree.taiga.io/project/penpot/issue/11047)
|
||||
- Fix share button being displayed with no permissions [Taiga #11086](https://tree.taiga.io/project/penpot/issue/11086)
|
||||
- Fix inline styles in code tab [Taiga Issue #7583](https://tree.taiga.io/project/penpot/issue/7583)
|
||||
- Fix exception on returning openapi.json
|
||||
- Fix json encoding of TokensLib [Taiga #10994](https://tree.taiga.io/project/penpot/issue/10994)
|
||||
|
||||
## 2.6.2
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<br />
|
||||
|
||||
[Penpot video](https://github.com/penpot/penpot/assets/5446186/b8ad0764-585e-4ddc-b098-9b4090d337cc)
|
||||
[Penpot video](https://github.com/user-attachments/assets/08b83119-c090-4a74-86ed-7bfbdda9a793)
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
(:refer-clojure :exclude [tap])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.transit :as t]
|
||||
[app.http.errors :as errors]
|
||||
@@ -54,18 +53,20 @@
|
||||
::yres/status 200
|
||||
::yres/body (yres/stream-body
|
||||
(fn [_ output]
|
||||
(binding [events/*channel* (sp/chan :buf buf :xf (keep encode))]
|
||||
(let [listener (events/start-listener
|
||||
(partial write! output)
|
||||
(partial pu/close! output))]
|
||||
(try
|
||||
(let [channel (sp/chan :buf buf :xf (keep encode))
|
||||
listener (events/start-listener
|
||||
channel
|
||||
(partial write! output)
|
||||
(partial pu/close! output))]
|
||||
(try
|
||||
(binding [events/*channel* channel]
|
||||
(let [result (handler)]
|
||||
(events/tap :end result))
|
||||
(catch Throwable cause
|
||||
(events/tap :error (errors/handle' cause request))
|
||||
(when-not (ex/instance? java.io.EOFException cause)
|
||||
(binding [l/*context* (errors/request->context request)]
|
||||
(l/err :hint "unexpected error on processing sse response" :cause cause))))
|
||||
(finally
|
||||
(sp/close! events/*channel*)
|
||||
(px/await! listener)))))))}))
|
||||
(events/tap :end result)))
|
||||
|
||||
(catch Throwable cause
|
||||
(let [result (errors/handle' cause request)]
|
||||
(events/tap channel :error result)))
|
||||
|
||||
(finally
|
||||
(sp/close! channel)
|
||||
(px/await! listener))))))}))
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
[:string {:max 250}]
|
||||
[::sm/one-of {:format "string"} valid-event-types]]]
|
||||
[:props
|
||||
[:map-of :keyword :any]]
|
||||
[:map-of :keyword ::sm/any]]
|
||||
[:context {:optional true}
|
||||
[:map-of :keyword :any]]])
|
||||
[:map-of :keyword ::sm/any]]])
|
||||
|
||||
(def schema:push-audit-events
|
||||
[:map {:title "push-audit-events"}
|
||||
|
||||
@@ -115,7 +115,8 @@
|
||||
|
||||
(db/update! pool :project
|
||||
{:modified-at (dt/now)}
|
||||
{:id project-id})
|
||||
{:id project-id}
|
||||
{::db/return-keys false})
|
||||
|
||||
result))
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
[:is-shared ::sm/boolean]
|
||||
[:project-id ::sm/uuid]
|
||||
[:created-at ::dt/instant]
|
||||
[:data {:optional true} :any]])
|
||||
[:data {:optional true} ::sm/any]])
|
||||
|
||||
(def schema:permissions-mixin
|
||||
[:map {:title "PermissionsMixin"}
|
||||
|
||||
@@ -80,9 +80,9 @@
|
||||
(def ^:private schema:create-font-variant
|
||||
[:map {:title "create-font-variant"}
|
||||
[:team-id ::sm/uuid]
|
||||
[:data [:map-of :string :any]]
|
||||
[:data [:map-of ::sm/text ::sm/any]]
|
||||
[:font-id ::sm/uuid]
|
||||
[:font-family :string]
|
||||
[:font-family ::sm/text]
|
||||
[:font-weight [::sm/one-of {:format "number"} valid-weight]]
|
||||
[:font-style [::sm/one-of {:format "string"} valid-style]]])
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.json :as json]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.desc-js-like :as smdj]
|
||||
@@ -19,7 +20,6 @@
|
||||
[app.http.sse :as-alias sse]
|
||||
[app.loggers.webhooks :as-alias webhooks]
|
||||
[app.rpc :as-alias rpc]
|
||||
[app.util.json :as json]
|
||||
[app.util.services :as sv]
|
||||
[app.util.template :as tmpl]
|
||||
[clojure.java.io :as io]
|
||||
@@ -86,7 +86,7 @@
|
||||
(fn [request]
|
||||
(let [params (:query-params request)
|
||||
pstyle (:type params "js")
|
||||
context (assoc context :param-style pstyle)]
|
||||
context (assoc @context :param-style pstyle)]
|
||||
|
||||
{::yres/status 200
|
||||
::yres/body (-> (io/resource "app/templates/api-doc.tmpl")
|
||||
@@ -178,8 +178,7 @@
|
||||
(fn [_]
|
||||
{::yres/status 200
|
||||
::yres/headers {"content-type" "application/json; charset=utf-8"}
|
||||
::yres/body (json/encode context)})
|
||||
|
||||
::yres/body (json/encode @context)})
|
||||
(fn [_]
|
||||
{::yres/status 404})))
|
||||
|
||||
@@ -209,7 +208,7 @@
|
||||
|
||||
(defmethod ig/init-key ::routes
|
||||
[_ {:keys [::rpc/methods] :as cfg}]
|
||||
[(let [context (prepare-doc-context methods)]
|
||||
[(let [context (delay (prepare-doc-context methods))]
|
||||
[["/_doc"
|
||||
{:handler (doc-handler context)
|
||||
:allowed-methods #{:get}}]
|
||||
@@ -217,7 +216,7 @@
|
||||
{:handler (doc-handler context)
|
||||
:allowed-methods #{:get}}]])
|
||||
|
||||
(let [context (prepare-openapi-context methods)]
|
||||
(let [context (delay (prepare-openapi-context methods))]
|
||||
[["/openapi"
|
||||
{:handler (openapi-handler)
|
||||
:allowed-methods #{:get}}]
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
to them. Mainly used in http.sse for progress reporting."
|
||||
(:refer-clojure :exclude [tap run!])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[promesa.exec :as px]
|
||||
@@ -18,33 +17,30 @@
|
||||
|
||||
(def ^:dynamic *channel* nil)
|
||||
|
||||
(defn channel
|
||||
[]
|
||||
(sp/chan :buf 32))
|
||||
|
||||
(defn tap
|
||||
[type data]
|
||||
(when-let [channel *channel*]
|
||||
(sp/put! channel [type data])
|
||||
nil))
|
||||
([type data]
|
||||
(when-let [channel *channel*]
|
||||
(sp/put! channel [type data])
|
||||
nil))
|
||||
([channel type data]
|
||||
(when channel
|
||||
(sp/put! channel [type data])
|
||||
nil)))
|
||||
|
||||
(defn start-listener
|
||||
[on-event on-close]
|
||||
|
||||
(dm/assert!
|
||||
"expected active events channel"
|
||||
(sp/chan? *channel*))
|
||||
[channel on-event on-close]
|
||||
(assert (sp/chan? channel) "expected active events channel")
|
||||
|
||||
(px/thread
|
||||
{:virtual true}
|
||||
(try
|
||||
(loop []
|
||||
(when-let [event (sp/take! *channel*)]
|
||||
(when-let [event (sp/take! channel)]
|
||||
(let [result (ex/try! (on-event event))]
|
||||
(if (ex/exception? result)
|
||||
(do
|
||||
(l/wrn :hint "unexpected exception" :cause result)
|
||||
(sp/close! *channel*))
|
||||
(sp/close! channel))
|
||||
(recur)))))
|
||||
(finally
|
||||
(on-close)))))
|
||||
@@ -55,7 +51,7 @@
|
||||
[f on-event]
|
||||
|
||||
(binding [*channel* (sp/chan :buf 32)]
|
||||
(let [listener (start-listener on-event (constantly nil))]
|
||||
(let [listener (start-listener *channel* on-event (constantly nil))]
|
||||
(try
|
||||
(f)
|
||||
(finally
|
||||
|
||||
@@ -47,14 +47,14 @@
|
||||
[:type [:= :assign]]
|
||||
;; NOTE: the full decoding is happening on the handler because it
|
||||
;; needs a proper context of the current shape and its type
|
||||
[:value [:map-of :keyword :any]]
|
||||
[:value [:map-of :keyword ::sm/any]]
|
||||
[:ignore-touched {:optional true} :boolean]
|
||||
[:ignore-geometry {:optional true} :boolean]]]
|
||||
[:set
|
||||
[:map {:title "SetOperation"}
|
||||
[:type [:= :set]]
|
||||
[:attr :keyword]
|
||||
[:val :any]
|
||||
[:val ::sm/any]
|
||||
[:ignore-touched {:optional true} :boolean]
|
||||
[:ignore-geometry {:optional true} :boolean]]]
|
||||
[:set-touched
|
||||
@@ -238,9 +238,9 @@
|
||||
[:component-id {:optional true} ::sm/uuid]
|
||||
[:ignore-touched {:optional true} :boolean]
|
||||
[:parent-id ::sm/uuid]
|
||||
[:shapes :any]
|
||||
[:shapes ::sm/any]
|
||||
[:index {:optional true} [:maybe :int]]
|
||||
[:after-shape {:optional true} :any]
|
||||
[:after-shape {:optional true} ::sm/any]
|
||||
[:component-swap {:optional true} :boolean]]]
|
||||
|
||||
[:reorder-children
|
||||
@@ -250,14 +250,14 @@
|
||||
[:component-id {:optional true} ::sm/uuid]
|
||||
[:ignore-touched {:optional true} :boolean]
|
||||
[:parent-id ::sm/uuid]
|
||||
[:shapes :any]]]
|
||||
[:shapes ::sm/any]]]
|
||||
|
||||
[:add-page
|
||||
[:map {:title "AddPageChange"}
|
||||
[:type [:= :add-page]]
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:name {:optional true} :string]
|
||||
[:page {:optional true} :any]]]
|
||||
[:page {:optional true} ::sm/any]]]
|
||||
|
||||
[:mod-page
|
||||
[:map {:title "ModPageChange"}
|
||||
@@ -327,14 +327,14 @@
|
||||
[:type [:= :add-component]]
|
||||
[:id ::sm/uuid]
|
||||
[:name :string]
|
||||
[:shapes {:optional true} [:vector {:gen/max 3} :any]]
|
||||
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
|
||||
[:path {:optional true} :string]]]
|
||||
|
||||
[:mod-component
|
||||
[:map {:title "ModCompoenentChange"}
|
||||
[:type [:= :mod-component]]
|
||||
[:id ::sm/uuid]
|
||||
[:shapes {:optional true} [:vector {:gen/max 3} :any]]
|
||||
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
|
||||
[:name {:optional true} :string]
|
||||
[:variant-id {:optional true} ::sm/uuid]
|
||||
[:variant-properties {:optional true} [:vector ::ctv/variant-property]]]]
|
||||
@@ -411,7 +411,7 @@
|
||||
[:set-tokens-lib
|
||||
[:map {:title "SetTokensLib"}
|
||||
[:type [:= :set-tokens-lib]]
|
||||
[:tokens-lib :any]]]
|
||||
[:tokens-lib ::sm/any]]]
|
||||
|
||||
[:set-token-set
|
||||
[:map {:title "SetTokenSetChange"}
|
||||
|
||||
@@ -25,18 +25,19 @@
|
||||
|
||||
;; Auxiliary functions to help create a set of changes (undo + redo)
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::changes}
|
||||
[:map {:title "changes"}
|
||||
[:redo-changes vector?]
|
||||
[:undo-changes seq?]
|
||||
[:origin {:optional true} any?]
|
||||
[:save-undo? {:optional true} boolean?]
|
||||
[:stack-undo? {:optional true} boolean?]
|
||||
[:undo-group {:optional true} any?]])
|
||||
(def schema:changes
|
||||
(sm/register!
|
||||
^{::sm/type ::changes}
|
||||
[:map {:title "changes"}
|
||||
[:redo-changes vector?]
|
||||
[:undo-changes seq?]
|
||||
[:origin {:optional true} ::sm/any]
|
||||
[:save-undo? {:optional true} boolean?]
|
||||
[:stack-undo? {:optional true} boolean?]
|
||||
[:undo-group {:optional true} ::sm/any]]))
|
||||
|
||||
(def check-changes!
|
||||
(sm/check-fn ::changes))
|
||||
(sm/check-fn schema:changes))
|
||||
|
||||
(defn empty-changes
|
||||
([origin page-id]
|
||||
|
||||
@@ -584,7 +584,7 @@
|
||||
(generate-sync-shape-direct changes file libraries container shape-id false)))
|
||||
|
||||
(defmethod generate-sync-shape :colors
|
||||
[_ changes library-id _ shape _ libraries _]
|
||||
[_ changes library-id _ shape libraries _]
|
||||
(shape-log :debug (:id shape) nil :msg "Sync colors of shape" :shape (:name shape))
|
||||
|
||||
;; Synchronize a shape that uses some colors of the library. The value of the
|
||||
@@ -595,7 +595,7 @@
|
||||
#(ctc/sync-shape-colors % library-id library-colors))))
|
||||
|
||||
(defmethod generate-sync-shape :typographies
|
||||
[_ changes library-id container shape _ libraries _]
|
||||
[_ changes library-id container shape libraries _]
|
||||
(shape-log :debug (:id shape) nil :msg "Sync typographies of shape" :shape (:name shape))
|
||||
|
||||
;; Synchronize a shape that uses some typographies of the library. The attributes
|
||||
|
||||
@@ -214,8 +214,7 @@
|
||||
|
||||
(defn lazy-validator
|
||||
[s]
|
||||
(let [s (schema s)
|
||||
vfn (delay (validator s))]
|
||||
(let [vfn (delay (validator s))]
|
||||
(fn [v] (@vfn v))))
|
||||
|
||||
(defn lazy-explainer
|
||||
@@ -318,11 +317,14 @@
|
||||
([params]
|
||||
(cond
|
||||
(map? params)
|
||||
(let [type (get params :type)]
|
||||
(let [mdata (meta params)
|
||||
type (or (get mdata ::id)
|
||||
(get mdata ::type)
|
||||
(get params :type))]
|
||||
(assert (qualified-keyword? type) "expected qualified keyword for `type`")
|
||||
(let [s (m/-simple-schema params)]
|
||||
(swap! sr/registry assoc type s)
|
||||
nil))
|
||||
s))
|
||||
|
||||
(vector? params)
|
||||
(let [mdata (meta params)
|
||||
@@ -330,11 +332,12 @@
|
||||
(get mdata ::type))]
|
||||
(assert (qualified-keyword? type) "expected qualified keyword to be on metadata")
|
||||
(swap! sr/registry assoc type params)
|
||||
nil)
|
||||
params)
|
||||
|
||||
(m/into-schema? params)
|
||||
(let [type (m/-type params)]
|
||||
(swap! sr/registry assoc type params))
|
||||
(swap! sr/registry assoc type params)
|
||||
params)
|
||||
|
||||
:else
|
||||
(throw (ex-info "Invalid Arguments" {}))))
|
||||
@@ -1042,6 +1045,8 @@
|
||||
{:title "agent"
|
||||
:description "instance of clojure agent"}}))
|
||||
|
||||
(register! ::any (mu/update-properties :any assoc :gen/gen sg/any))
|
||||
|
||||
;; ---- PREDICATES
|
||||
|
||||
(def valid-safe-number?
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.common.schema.desc-js-like
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as-alias sm]
|
||||
[cuerdas.core :as str]
|
||||
[malli.core :as m]
|
||||
[malli.util :as mu]))
|
||||
@@ -90,7 +91,7 @@
|
||||
(defmethod visit :int [_ schema _ _] (str "integer" (-titled schema) (-min-max-suffix-number schema)))
|
||||
(defmethod visit :double [_ schema _ _] (str "double" (-titled schema) (-min-max-suffix-number schema)))
|
||||
(defmethod visit :select-keys [_ schema _ options] (describe* (m/deref schema) options))
|
||||
(defmethod visit :and [_ s children _] (str (str/join ", and " children) (-titled s)))
|
||||
(defmethod visit :and [_ s children _] (str (str/join " && " children) (-titled s)))
|
||||
(defmethod visit :enum [_ s children _options] (str "enum" (-titled s) " of " (str/join ", " children)))
|
||||
(defmethod visit :maybe [_ _ children _] (str (first children) " nullable"))
|
||||
(defmethod visit :tuple [_ _ children _] (str "(" (str/join ", " children) ")"))
|
||||
@@ -106,7 +107,8 @@
|
||||
(defmethod visit :qualified-symbol [_ _ _ _] "qualified symbol")
|
||||
(defmethod visit :uuid [_ _ _ _] "uuid")
|
||||
(defmethod visit :boolean [_ _ _ _] "boolean")
|
||||
(defmethod visit :keyword [_ _ _ _] "keyword")
|
||||
(defmethod visit :keyword [_ _ _ _] "string")
|
||||
(defmethod visit :fn [_ _ _ _] "FN")
|
||||
|
||||
(defmethod visit :vector [_ _ children _]
|
||||
(str "[" (last children) "]"))
|
||||
@@ -123,10 +125,12 @@
|
||||
(defmethod visit :repeat [_ schema children _]
|
||||
(str "repeat " (-diamond (first children)) (-repeat-suffix schema)))
|
||||
|
||||
|
||||
(defmethod visit :set [_ schema children _]
|
||||
(str "set[" (first children) "]" (minmax-suffix schema)))
|
||||
|
||||
(defmethod visit ::sm/set [_ schema children _]
|
||||
(str "set[" (first children) "]" (minmax-suffix schema)))
|
||||
|
||||
(defmethod visit ::m/val [_ schema children _]
|
||||
(let [suffix (minmax-suffix schema)]
|
||||
(cond-> (first children)
|
||||
@@ -152,7 +156,6 @@
|
||||
(or (:title props)
|
||||
"*")))
|
||||
|
||||
|
||||
(defmethod visit :map
|
||||
[_ schema children {:keys [::level ::max-level] :as options}]
|
||||
(let [props (m/properties schema)
|
||||
@@ -172,13 +175,11 @@
|
||||
": " s)))
|
||||
(str/join ",\n"))
|
||||
|
||||
header (cond-> (if (zero? level)
|
||||
(str "type " title)
|
||||
(str title))
|
||||
header (cond-> (str "type " title)
|
||||
closed? (str "!")
|
||||
(some? title) (str " "))]
|
||||
|
||||
(str header "{\n" entries "\n" (pad "}" level))))))
|
||||
(str (pad header level) "{\n" entries "\n" (pad "}\n" level))))))
|
||||
|
||||
(defmethod visit :multi
|
||||
[_ s children {:keys [::level ::max-level] :as options}]
|
||||
@@ -205,18 +206,18 @@
|
||||
|
||||
(defmethod visit :merge
|
||||
[_ schema children _]
|
||||
(let [entries (str/join " , " children)
|
||||
(let [entries (str/join ",\n" children)
|
||||
props (m/properties schema)
|
||||
title (or (some-> (:title props) str/camel str/capital)
|
||||
"<untitled>")]
|
||||
(str "merge object " title " { " entries " }")))
|
||||
(str "merge type " title " { \n" entries "\n}\n")))
|
||||
|
||||
(defmethod visit :app.common.schema/one-of
|
||||
[_ _ children _]
|
||||
(defmethod visit ::sm/one-of
|
||||
[_ _ children _]
|
||||
(let [elems (last children)]
|
||||
(str "OneOf[" (->> elems
|
||||
(map d/name)
|
||||
(str/join ",")) "]")))
|
||||
(str "string oneOf (" (->> elems
|
||||
(map d/name)
|
||||
(str/join "|")) ")")))
|
||||
|
||||
(defmethod visit :schema [_ schema children options]
|
||||
(visit ::m/schema schema children options))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.schema.generators
|
||||
(:refer-clojure :exclude [set subseq uuid filter map let boolean])
|
||||
(:refer-clojure :exclude [set subseq uuid filter map let boolean vector keyword int double])
|
||||
#?(:cljs (:require-macros [app.common.schema.generators]))
|
||||
(:require
|
||||
[app.common.schema.registry :as sr]
|
||||
@@ -38,10 +38,6 @@
|
||||
([s opts]
|
||||
(mg/generator s (assoc opts :registry sr/default-registry))))
|
||||
|
||||
(defn filter
|
||||
[pred gen]
|
||||
(tg/such-that pred gen 100))
|
||||
|
||||
(defn small-double
|
||||
[& {:keys [min max] :or {min -100 max 100}}]
|
||||
(tg/double* {:min min, :max max, :infinite? false, :NaN? false}))
|
||||
@@ -61,7 +57,7 @@
|
||||
(defn word-keyword
|
||||
[]
|
||||
(->> (word-string)
|
||||
(tg/fmap keyword)))
|
||||
(tg/fmap c/keyword)))
|
||||
|
||||
(defn email
|
||||
[]
|
||||
@@ -100,12 +96,11 @@
|
||||
(c/map second))
|
||||
(c/map list bools elements)))))))
|
||||
|
||||
(def any tg/any)
|
||||
(def boolean tg/boolean)
|
||||
|
||||
(defn set
|
||||
[g]
|
||||
(tg/set g))
|
||||
(defn map-of
|
||||
([kg vg]
|
||||
(tg/map kg vg {:min-elements 1 :max-elements 3}))
|
||||
([kg vg opts]
|
||||
(tg/map kg vg opts)))
|
||||
|
||||
(defn elements
|
||||
[s]
|
||||
@@ -119,6 +114,10 @@
|
||||
[f g]
|
||||
(tg/fmap f g))
|
||||
|
||||
(defn filter
|
||||
[pred gen]
|
||||
(tg/such-that pred gen 100))
|
||||
|
||||
(defn mcat
|
||||
[f g]
|
||||
(tg/bind g f))
|
||||
@@ -126,3 +125,22 @@
|
||||
(defn tuple
|
||||
[& opts]
|
||||
(apply tg/tuple opts))
|
||||
|
||||
(defn vector
|
||||
[& opts]
|
||||
(apply tg/vector opts))
|
||||
|
||||
(defn set
|
||||
[g]
|
||||
(tg/set g))
|
||||
|
||||
;; Static Generators
|
||||
|
||||
(def boolean tg/boolean)
|
||||
(def text (word-string))
|
||||
(def double (small-double))
|
||||
(def int (small-int))
|
||||
(def keyword (word-keyword))
|
||||
|
||||
(def any
|
||||
(tg/one-of [text boolean double int keyword]))
|
||||
|
||||
@@ -97,7 +97,8 @@
|
||||
(defmethod visit :enum [_ _ children options] (merge (some-> (m/-infer children) (transform* options)) {:enum children}))
|
||||
(defmethod visit :maybe [_ _ children _] {:oneOf (conj children {:type "null"})})
|
||||
(defmethod visit :tuple [_ _ children _] {:type "array", :items children, :additionalItems false})
|
||||
(defmethod visit :re [_ schema _ options] {:type "string", :pattern (first (m/children schema options))})
|
||||
(defmethod visit :re [_ schema _ options]
|
||||
{:type "string", :pattern (str (first (m/children schema options)))})
|
||||
(defmethod visit :nil [_ _ _ _] {:type "null"})
|
||||
|
||||
(defmethod visit :string [_ schema _ _]
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
(.. r (toString 16) (padStart 2 "0"))
|
||||
(.. g (toString 16) (padStart 2 "0"))
|
||||
(.. b (toString 16) (padStart 2 "0"))))))
|
||||
sg/any))
|
||||
sg/int))
|
||||
|
||||
(defn rgb-color-string?
|
||||
[o]
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
[:map-of {:gen/max 10} ::sm/uuid :map]]
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]])
|
||||
|
||||
(def check-container!
|
||||
(def check-container
|
||||
(sm/check-fn ::container))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -74,13 +74,9 @@
|
||||
(defn get-shape
|
||||
[container shape-id]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid container"
|
||||
(check-container! container))
|
||||
|
||||
(dm/assert!
|
||||
"expected valid uuid for `shape-id`"
|
||||
(uuid? shape-id))
|
||||
(assert (check-container container))
|
||||
(assert (uuid? shape-id)
|
||||
"expected valid uuid for `shape-id`")
|
||||
|
||||
(-> container
|
||||
(get :objects)
|
||||
|
||||
@@ -16,54 +16,56 @@
|
||||
|
||||
(def node-types #{"root" "paragraph-set" "paragraph"})
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::content}
|
||||
[:map
|
||||
[:type [:= "root"]]
|
||||
[:key {:optional true} :string]
|
||||
[:children
|
||||
{:optional true}
|
||||
[:maybe
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:type [:= "paragraph-set"]]
|
||||
[:key {:optional true} :string]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:type [:= "paragraph"]]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:text :string]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])
|
||||
(def schema:content
|
||||
[:map
|
||||
[:type [:= "root"]]
|
||||
[:key {:optional true} :string]
|
||||
[:children
|
||||
{:optional true}
|
||||
[:maybe
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:type [:= "paragraph-set"]]
|
||||
[:key {:optional true} :string]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:type [:= "paragraph"]]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
[:text :string]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])
|
||||
|
||||
(sm/register! ::content schema:content)
|
||||
|
||||
(def valid-content?
|
||||
(sm/lazy-validator schema:content))
|
||||
|
||||
(sm/register!
|
||||
^{::sm/type ::position-data}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.common.types.tokens-lib
|
||||
(:require
|
||||
#?(:clj [app.common.fressian :as fres])
|
||||
#?(:clj [clojure.data.json :as json])
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
@@ -118,7 +119,7 @@
|
||||
[:map {:title "Token"}
|
||||
[:name cto/token-name-ref]
|
||||
[:type [::sm/one-of cto/token-types]]
|
||||
[:value :any]
|
||||
[:value ::sm/any]
|
||||
[:description {:optional true} :string]
|
||||
[:modified-at {:optional true} ::sm/inst]])
|
||||
|
||||
@@ -389,7 +390,8 @@
|
||||
[:description {:optional true} :string]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:tokens {:optional true
|
||||
:gen/gen (->> (sg/generator [:map-of ::sm/text schema:token])
|
||||
:gen/gen (->> (sg/map-of (sg/generator ::sm/text)
|
||||
(sg/generator schema:token))
|
||||
(sg/fmap #(into (d/ordered-map) %)))}
|
||||
[:and
|
||||
[:map-of {:gen/max 5
|
||||
@@ -910,6 +912,12 @@ Will return a value that matches this schema:
|
||||
"themes" (clj->js themes)
|
||||
"active-themes" (clj->js active-themes)))])
|
||||
|
||||
|
||||
#?@(:clj
|
||||
[json/JSONWriter
|
||||
(-write [this writter options] (json/-write (encode-dtcg this) writter options))])
|
||||
|
||||
|
||||
ITokenSets
|
||||
(add-set [_ token-set]
|
||||
(let [path (get-token-set-prefixed-path token-set)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="{{ description or metadata.description }}">
|
||||
<link rel="stylesheet" href="{{ '/css/index.css' | url }}">
|
||||
<link rel="stylesheet" href="{{ '/css/prism.css' | url }}">
|
||||
<link rel="shortcut icon" href="/img/favicon.png">
|
||||
@@ -15,7 +14,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||||
{% metagen
|
||||
title=title or metadata.title,
|
||||
desc=desc or metadata.desc,
|
||||
desc=desc or metadata.desc or description or metadata.description,
|
||||
url="https://help.penpot.app" + page.url,
|
||||
img="https://help.penpot.app/img/th-help-center.jpg",
|
||||
img_alt=alt,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 04· Code of Conduct
|
||||
desc: Learn about contributing to the Penpot project! This page outlines the Code of Conduct, reporting bugs, translations, core code contributions, & more.
|
||||
---
|
||||
|
||||
<h1 id="coc">Code of conduct</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 03· Core code contributions
|
||||
desc: Learn how to contribute to Penpot's open-source design collaboration platform. Find guidelines for bug reporting, code contributions & more.
|
||||
---
|
||||
|
||||
<h1 id="code-contributions">Core code contributions</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Contributing
|
||||
desc: Learn how to contribute to Penpot, the open-source design collaboration platform! Find guides on bug reporting, translations, code contributions, and more.
|
||||
eleventyNavigation:
|
||||
key: Contributing
|
||||
order: 3
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 05· Libraries & Templates
|
||||
desc: Contribute to Penpot's libraries & templates! Learn how to share your files and access resources. Try Penpot - It's free!
|
||||
---
|
||||
|
||||
<h1 id="libraries">Libraries & templates</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 01· Reporting bugs
|
||||
desc: Learn how to contribute to Penpot, the open-source design and prototyping platform! Find guidelines for reporting bugs, translations, & code contributions.
|
||||
---
|
||||
|
||||
<h1 id="reporting-bugs">Reporting bugs</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 02· Translations
|
||||
desc: Contribute to Penpot! Learn how to translate Penpot into your language using Weblate. Add new translations, languages, or edit existing ones today.
|
||||
---
|
||||
|
||||
<h1 id="translations">Translations</h1>
|
||||
|
||||
BIN
docs/img/abstraction-levels/abstraction-levels.png
Normal file
BIN
docs/img/abstraction-levels/abstraction-levels.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Help center
|
||||
desc: Find user guides, technical documentation, plugin info, FAQs, and contributing guidelines in Penpot's help center. Join the open-source community!
|
||||
layout: layouts/home.njk
|
||||
twitter: "@penpotapp"
|
||||
image: img/placeholder.png
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: layouts/plugins.njk
|
||||
title: 4. API
|
||||
desc: Create, deploy, and use the Penpot plugin API with our comprehensive documentation. Get started today and expand Penpot's capabilities.
|
||||
---
|
||||
|
||||
# Penpot plugins API
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
---
|
||||
layout: layouts/plugins-no-sidebar.njk
|
||||
title: Beta changelog
|
||||
desc: See the Penpot plugin API changelog for version 1.0! Find breaking changes, deprecations, new features, and updated documentation. Try Penpot for free.
|
||||
---
|
||||
|
||||
# Beta changelog
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: layouts/plugins.njk
|
||||
title: 2. Create a Plugin
|
||||
desc: Dive into Penpot plugin development! This guide covers creating plugins from scratch or using templates, libraries, API communication, & deployment.
|
||||
---
|
||||
|
||||
# Create a Plugin
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: layouts/plugins.njk
|
||||
title: 3. Deployment
|
||||
desc: Deploy your free Penpot plugins! Learn about Netlify, Cloudflare, Surge & Penpot submission in this guide. Build and share your creations.
|
||||
---
|
||||
|
||||
# Deployment
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: layouts/plugins.njk
|
||||
title: 5. Examples and templates
|
||||
desc: Learn to create shapes, text, layouts, components, themes, and interactive prototypes. Start building now! See Penpot plugins with examples & templates!
|
||||
---
|
||||
|
||||
# Examples and templates
|
||||
@@ -117,7 +118,6 @@ Just a friendly reminder that it's important to have the <b>comment permissions<
|
||||
|
||||
<a target="_blank" href="https://github.com/penpot/penpot-plugins-samples/tree/main/create-comments">Comments example</a>
|
||||
|
||||
|
||||
## 5.2. Templates
|
||||
|
||||
As we mentioned in the <a target="_blank" href="/plugins/create-a-plugin/">Create a plugin</a> section, we've got two great options for you to get started with your plugin.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: layouts/plugins.njk
|
||||
title: 6. FAQ
|
||||
desc: Find answers to common questions about plugin development, from choosing the right Node version to creating components. See Penpot plugins!
|
||||
---
|
||||
|
||||
# FAQ
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: layouts/plugins.njk
|
||||
title: 1. Getting started
|
||||
desc: Dive into Penpot plugins! Extend Penpot's functionality by automating tasks and adding new features using JavaScript, HTML, & CSS. Get started now!
|
||||
---
|
||||
|
||||
# Getting started
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
layout: layouts/plugins-home.njk
|
||||
title: Plugins
|
||||
desc: "Get started with Penpot Plugins: Installation, development, and deployment. Access API documentation, examples, templates, and FAQs."
|
||||
eleventyNavigation:
|
||||
key: Plugins
|
||||
order: 5
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 2. Penpot Configuration
|
||||
desc: Learn about self-hosting, configuration via environment variables, and authentication providers. Try Penpot - It's free! See Penpot's technical guide.
|
||||
---
|
||||
|
||||
# Penpot Configuration
|
||||
|
||||
359
docs/technical-guide/developer/abstraction-levels.md
Normal file
359
docs/technical-guide/developer/abstraction-levels.md
Normal file
@@ -0,0 +1,359 @@
|
||||
---
|
||||
title: 3.07. Abstraction levels
|
||||
---
|
||||
|
||||
# Code organization in abstraction levels
|
||||
|
||||
Initially, Penpot data model implementation was organized in a different way.
|
||||
We are currently in a process of reorganization. The objective is to have data
|
||||
manipulation code structured in abstraction layers, with well-defined
|
||||
boundaries, and a hierarchical structure (each level may only use same or
|
||||
lower levels, but not higher).
|
||||
|
||||

|
||||
|
||||
At this moment the namespace structure is already organized as described here,
|
||||
but there is much code that does not comply with these rules, and needs to be
|
||||
moved or refactored. We expect to be refactoring existing modules incrementally,
|
||||
each time we do an important functionality change.
|
||||
|
||||
## Basic data
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
data.cljc
|
||||
▾ src/app/data/
|
||||
macros.cljc
|
||||
```
|
||||
|
||||
A level for generic data structures or operations, that are not specifically part
|
||||
of the domain model (e.g. trees, strings, maps, iterators, etc.). Also may belong
|
||||
here some functions in <code>app.common.geom/</code> and <code>app.common.files.helpers.cljc</code>.
|
||||
|
||||
We need to create a new directory for this and move there all functions in this
|
||||
leve.
|
||||
|
||||
|
||||
## Abstract data types
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
▾ types/
|
||||
file.cljc
|
||||
page.cljc
|
||||
shape.cljc
|
||||
color.cljc
|
||||
component.cljc
|
||||
tokens_lib.cljc
|
||||
...
|
||||
```
|
||||
|
||||
Namespaces here represent a single data entity of the domain model, or a
|
||||
fragment of one, as an [Abstract Data Type](https://www.geeksforgeeks.org/abstract-data-types/).
|
||||
An ADT is a data component that is defined through a series of properties and
|
||||
operations, and that abstracts out the details of how it's implemented and what
|
||||
is the internal structure. This allows to simplify the logic of the client
|
||||
code, and also to make future changes in the ADT without affecting callers (if
|
||||
the abstract interface does not change).
|
||||
|
||||
Each structure in this module has:
|
||||
|
||||
* A **schema spec** that defines the structure of the type and its values:
|
||||
|
||||
```clojure
|
||||
(def schema:fill
|
||||
[:map {:title "Fill"}
|
||||
[:fill-color {:optional true} ::ctc/rgb-color]
|
||||
[:fill-opacity {:optional true} ::sm/safe-number]
|
||||
...)
|
||||
|
||||
(def schema:shape-base-attrs
|
||||
[:map {:title "ShapeMinimalRecord"}
|
||||
[:id ::sm/uuid]
|
||||
[:name :string]
|
||||
[:type [::sm/one-of shape-types]]
|
||||
[:selrect ::grc/rect]
|
||||
[:points schema:points]
|
||||
...)
|
||||
|
||||
(def schema:token-set-attrs
|
||||
[:map {:title "TokenSet"}
|
||||
[:name :string]
|
||||
[:description {:optional true} :string]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:tokens {:optional true} [:and
|
||||
[:map-of {:gen/max 5}
|
||||
:string
|
||||
schema:token]
|
||||
[:fn d/ordered-map?]]]])
|
||||
```
|
||||
|
||||
* A **protocol** that define the external interface to be used for this entity.
|
||||
|
||||
(NOTE: this is currently only implemented in some subsystems like Design Tokens
|
||||
and new path handling).
|
||||
|
||||
```clojure
|
||||
(defprotocol ITokenSet
|
||||
(update-name [_ set-name] "change a token set name while keeping the path")
|
||||
(add-token [_ token] "add a token at the end of the list")
|
||||
(update-token [_ token-name f] "update a token in the list")
|
||||
(delete-token [_ token-name] "delete a token from the list")
|
||||
(get-token [_ token-name] "return token by token-name")
|
||||
(get-tokens [_] "return an ordered sequence of all tokens in the set"))
|
||||
```
|
||||
|
||||
* A **custom data type** that implements this protocol. __Functions here are the only
|
||||
ones allowed to modify the internal structure of the type__.
|
||||
|
||||
Clojure allows us two kinds of custom data types:
|
||||
* [**`deftype`**](https://funcool.github.io/clojurescript-unraveled/#deftype). We'll
|
||||
use it when we want the internal structure to be completely opaque and
|
||||
data accessed through protocol functions. Clojure allows access to the
|
||||
attributes with the <code class="language-clojure">(.-attr)</code>
|
||||
syntax, but we prefer not to use it.
|
||||
* [**`defrecord`**](https://funcool.github.io/clojurescript-unraveled/#defrecord).
|
||||
We'll use it when we want the structure to be exposed as a plain clojure
|
||||
map, and thus allowing to read attributes with <code
|
||||
class="language-clojure">(:attr t)</code>, to use <code
|
||||
class="language-clojure">get</code>, <code
|
||||
class="language-clojure">keys</code>, <code
|
||||
class="language-clojure">vals</code>, etc. Note that this also allows
|
||||
modifying the object with <code class="language-clojure">assoc</code>,
|
||||
<code class="language-clojure">update</code>, etc. But in general we
|
||||
prefer to do all modification via protocol methods, because this way
|
||||
it's easier to track down where the failure is if an invalid structure
|
||||
is detected in a validation check, and add business logic like "update
|
||||
<code class="language-clojure">modified-at</code> whenever any other
|
||||
attribute is changed".
|
||||
|
||||
```clojure
|
||||
(defrecord TokenSet [name description modified-at tokens]
|
||||
ITokenSet
|
||||
(add-token [_ token]
|
||||
(let [token (check-token token)]
|
||||
(TokenSet. name
|
||||
description
|
||||
(dt/now)
|
||||
(assoc tokens (:name token) token))))
|
||||
```
|
||||
|
||||
|
||||
* **Additional helper functions** the protocol should be made as small and compact
|
||||
as possible. If we need functions for business logic or complex queries that
|
||||
do not need to directly access the internal structure, but can be implemented by
|
||||
only calling the abstract procotol, they should be created as standalone functions.
|
||||
At this level, they must be functions that operate only on instances of the given
|
||||
domain model entity. They must always ensure the internal integrity of the data.
|
||||
|
||||
```clojure
|
||||
(defn sets-tree-seq
|
||||
"Get tokens sets tree as a flat list"
|
||||
[token-sets]
|
||||
...)
|
||||
|
||||
> IMPORTANT SUMMARY
|
||||
> * Code in this level only knows about one domain model entity.
|
||||
> * All functions ensure the internal integrity of the data.
|
||||
> * For this, the schema is used, and the functions must check parameters
|
||||
> and output values as needed.
|
||||
> * No outside code should get any knowledge of the internal structure, so it
|
||||
> can be changed in the future without breaking cliente code.
|
||||
> * All modifications of the data should be done via protocol methods (even in
|
||||
> <code class="language-clojure">defrecords</code>). This allows a) more
|
||||
> control of the internal data dependencies, b) easier bug tracking of
|
||||
> corrupted data, and c) easier refactoring when the structure is modified.
|
||||
|
||||
Currently most of Penpot code does not follow those requirements, but it
|
||||
should do in new code or in any refactor.
|
||||
|
||||
## File operations
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
▾ files/
|
||||
helpers.cljc
|
||||
shapes_helpers.cljc
|
||||
...
|
||||
```
|
||||
|
||||
Functions that modify a file object (or a part of it) in place, returning the
|
||||
file object changed. They ensure the referential integrity within the file, or
|
||||
between a file and its libraries.
|
||||
|
||||
**These functions are used when we need to manipulate objects of different
|
||||
domain entities inside a file.**
|
||||
|
||||
```clojure
|
||||
(defn resolve-component
|
||||
"Retrieve the referenced component, from the local file or from a library"
|
||||
[shape file libraries & {:keys [include-deleted?] :or {include-deleted? False}}]
|
||||
(if (= (:component-file shape) (:id file))
|
||||
(ctkl/get-component (:data file) (:component-id shape) include-deleted?)
|
||||
(get-component libraries
|
||||
(:component-file shape)
|
||||
(:component-id shape)
|
||||
:include-deleted? include-deleted?)))
|
||||
|
||||
(defn delete-component
|
||||
"Mark a component as deleted and store the main instance shapes iside it, to
|
||||
be able to be recovered later."
|
||||
[file-data component-id skip-undelete? delta]
|
||||
(let [delta (or delta (gpt/point 0 0))]
|
||||
(if skip-undelete?
|
||||
(ctkl/delete-component file-data component-id)
|
||||
(-> file-data
|
||||
(ctkl/update-component component-id #(load-component-objects file-data % delta))
|
||||
(ctkl/mark-component-deleted component-id)))))
|
||||
```
|
||||
|
||||
> This module is still needing an important refactor. Mainly to take functions
|
||||
> from common.types and move them here.
|
||||
|
||||
### File validation and repair
|
||||
|
||||
There is a function in <code class="language-clojure">app.common.files.validate</code> that checks a file for
|
||||
referential and semantic integrity. It's called automatically when file changes
|
||||
are sent to backend, but may be invoked manually whenever it's needed.
|
||||
|
||||
## File changes objects
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
▾ files/
|
||||
changes_builder.cljc
|
||||
changes.cljc
|
||||
...
|
||||
```
|
||||
|
||||
This layer is how we adopt the [Event sourcing pattern](https://www.geeksforgeeks.org/event-sourcing-pattern/).
|
||||
Instead of directly modifying files, we create <code class="language-clojure">changes</code>
|
||||
objects, that represent one modification, and that can be serialized, stored,
|
||||
send to backend, logged, etc. Then it can be *materialized* by a **processing
|
||||
function**, that takes a file and a change, and returns the updated file.
|
||||
|
||||
This also allows an important feature: undoing changes.
|
||||
|
||||
Processing functions should not contain business logic or algorithms. Just
|
||||
adapt the change interface to the operations in **File** or **Abstract Data
|
||||
Types** levels.
|
||||
|
||||
There exists a <code class="language-clojure">changes-builder</code> module
|
||||
with helper functions to conveniently build changes objects, and to
|
||||
automatically calculate the reverse undo change.
|
||||
|
||||
> IMPORTANT RULES
|
||||
>
|
||||
> All changes must satisfy two properties:
|
||||
> * **[Idempotence](https://en.wikipedia.org/wiki/Idempotence)**. The event
|
||||
> sourcing architecture and multiuser capability may cause that the same
|
||||
> change may be applied more than once to a file. So changes must not, for
|
||||
> example, be like *increment counter* but rather *set counter to value x*.
|
||||
> * **Minimal scope**. To reduce conflicts, changes should only modify the
|
||||
> relevant part of the domain entity. This way, if a concurrent change on
|
||||
> the same entity arrives, from another user, and it modifies a different
|
||||
> part of the data, they may ve processed without overriding.
|
||||
|
||||
## Business logic
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
▾ logic/
|
||||
shapes.cljc
|
||||
libraries.cljc
|
||||
```
|
||||
|
||||
At this level there are functions that implement high level user actions, in an
|
||||
abstract way (independent of UI). Here may be complex business logic (eg. to
|
||||
create a component copy we must clone all shapes, assign new ids, relink
|
||||
parents, change the head structure to be a copy and link each shape in the copy
|
||||
with the corresponding one in the main).
|
||||
|
||||
They don't directly modify files, but generate changes objects, that may be
|
||||
executed in frontend or sent to backend.
|
||||
|
||||
Those functions may also be composed in even higher level actions. For example
|
||||
a "update shape attr" action may use "unapply token" actions when the attribute
|
||||
has an applied token.
|
||||
|
||||
```clojure
|
||||
(defn generate-instantiate-component
|
||||
"Generate changes to create a new instance from a component."
|
||||
([changes objects file-id component-id position page libraries]
|
||||
(generate-instantiate-component changes objects file-id component-id position page libraries nil nil nil {}))
|
||||
([changes objects file-id component-id position page libraries old-id parent-id frame-id
|
||||
{:keys [force-frame?]
|
||||
:or {force-frame? false}}]
|
||||
(let [component (ctf/get-component libraries file-id component-id)
|
||||
library (get libraries file-id)
|
||||
parent (when parent-id (get objects parent-id))
|
||||
|
||||
[...]
|
||||
|
||||
[new-shape new-shapes]
|
||||
(ctn/make-component-instance page
|
||||
component
|
||||
(:data library)
|
||||
position
|
||||
(cond-> {}
|
||||
force-frame?
|
||||
(assoc :force-frame-id frame-id)))
|
||||
|
||||
[...]
|
||||
|
||||
changes
|
||||
(reduce #(pcb/add-object %1 %2 {:ignore-touched true})
|
||||
changes
|
||||
(rest new-shapes))]
|
||||
|
||||
[new-shape changes])))
|
||||
```
|
||||
|
||||
## Data events
|
||||
|
||||
```text
|
||||
▾ frontend/
|
||||
▾ src/app/main/data/
|
||||
▾ dashboard/
|
||||
▾ viewer/
|
||||
▾ workspace/
|
||||
```
|
||||
|
||||
This is the intersection of the logic and the presentation in Penpot. Data
|
||||
events belong to the presentation interface and they manage the global state of
|
||||
the application. But they may also work on loaded files by using **File** or
|
||||
**Abstract Data Types** operations to query the data, and by creating and
|
||||
commiting **changes** via the **Business logic** generate functions.
|
||||
|
||||
**IMPORTANT: data events must not contain business logic theirselves**, or
|
||||
directly manipulate data structures. They only may modify or query the global
|
||||
state, and delegate all logic to lower level functions.
|
||||
|
||||
In current Penpot code, there is some quantity of business logic in data events,
|
||||
that should be progressively moved elsewhere as we keep refactoring.
|
||||
|
||||
```clojure
|
||||
(defn detach-component
|
||||
"Remove all references to components in the shape with the given id,
|
||||
and all its children, at the current page."
|
||||
[id]
|
||||
(dm/assert! (uuid? id))
|
||||
(ptk/reify ::detach-component
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
file-id (:current-file-id state)
|
||||
|
||||
fdata (dsh/lookup-file-data state file-id)
|
||||
libraries (dsh/lookup-libraries state)
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(cll/generate-detach-component id fdata page-id libraries))]
|
||||
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
```
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Backend app
|
||||
desc: Dive into self-hosting, configuration, developer insights (architecture, data model), integration, and troubleshooting. See Penpot's Technical Guide.
|
||||
---
|
||||
|
||||
# Backend app
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Common code
|
||||
desc: Learn about architecture, data models, and development environments. See Penpot's technical guide for developers. Dive into common code.
|
||||
---
|
||||
|
||||
# Common code
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Exporter app
|
||||
desc: Learn about self-hosting, configuration, architecture (backend, frontend), data model, and development environment. See Penpot's technical guide.
|
||||
---
|
||||
|
||||
# Exporter app
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Frontend app
|
||||
desc: Dive into the UI, data namespaces, ClojureScript, React, and worker app functionalities. View Penpot's frontend app architecture. Free to try!
|
||||
---
|
||||
|
||||
### Frontend app
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3.1. Architecture
|
||||
title: 3.01. Architecture
|
||||
desc: Dive into architecture, backend, frontend, data models, and development environments. Contribute and self-host for free! See Penpot's technical guide.
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 3.6. Backend Guide
|
||||
title: 3.06. Backend Guide
|
||||
---
|
||||
|
||||
# Backend guide #
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3.4. Common Guide
|
||||
title: 3.04. Common Guide
|
||||
desc: "View Penpot's technical guide: self-hosting, configuration, developer insights, architecture, data model, integration, and troubleshooting."
|
||||
---
|
||||
|
||||
# Common guide
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3.7. Data Guide
|
||||
title: 3.08. Data Guide
|
||||
desc: Learn about data structures, code organization, file operations, migrations, shape editing, and component syncing. See Penpot's technical guide. Try it free!
|
||||
---
|
||||
|
||||
# Data Guide
|
||||
@@ -29,217 +30,6 @@ all of this is important in general.
|
||||
Clojure (for example ending it with ? for boolean values), because this may
|
||||
cause problems when exporting.
|
||||
|
||||
## Code organization in abstraction levels
|
||||
|
||||
Initially, Penpot data model implementation was organized in a different way.
|
||||
We are currently in a process of reorganization. The objective is to have data
|
||||
manipulation code structured in abstraction layers, with well-defined
|
||||
boundaries.
|
||||
|
||||
At this moment the namespace structure is already organized as described here,
|
||||
but there is much code that does not comply with these rules, and needs to be
|
||||
moved or refactored. We expect to be refactoring existing modules incrementally,
|
||||
each time we do an important functionality change.
|
||||
|
||||
### Abstract data types
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
▾ types/
|
||||
file.cljc
|
||||
page.cljc
|
||||
shape.cljc
|
||||
color.cljc
|
||||
component.cljc
|
||||
...
|
||||
```
|
||||
|
||||
Namespaces here represent a single data structure, or a fragment of one, as an
|
||||
abstract data type. Each structure has:
|
||||
|
||||
* A **schema spec** that defines the structure of the type and its values:
|
||||
|
||||
```clojure
|
||||
(sm/define! ::fill
|
||||
[:map {:title "Fill"}
|
||||
[:fill-color {:optional true} ::ctc/rgb-color]
|
||||
[:fill-opacity {:optional true} ::sm/safe-number]
|
||||
...)
|
||||
|
||||
(sm/define! ::shape-attrs
|
||||
[:map {:title "ShapeAttrs"}
|
||||
[:name {:optional true} :string]
|
||||
[:selrect {:optional true} ::grc/rect]
|
||||
[:points {:optional true} ::points]
|
||||
[:blocked {:optional true} :boolean]
|
||||
[:fills {:optional true}
|
||||
[:vector {:gen/max 2} ::fill]]
|
||||
...)
|
||||
```
|
||||
|
||||
* **Helper functions** to create, query and manipulate the structure. Helpers
|
||||
at this level only are allowed to see the internal attributes of a type.
|
||||
Updaters receive an object of the type, and return a new object modified,
|
||||
also ensuring the internal integrity of the data after the change.
|
||||
|
||||
```clojure
|
||||
(defn setup-shape
|
||||
"A function that initializes the geometric data of the shape. The props must
|
||||
contain at least :x :y :width :height."
|
||||
[{:keys [type] :as props}]
|
||||
...)
|
||||
|
||||
(defn has-direction?
|
||||
[interaction]
|
||||
(#{:slide :push} (-> interaction :animation :animation-type)))
|
||||
|
||||
(defn set-direction
|
||||
[interaction direction]
|
||||
(dm/assert!
|
||||
"expected valid interaction map"
|
||||
(check-interaction! interaction))
|
||||
(dm/assert!
|
||||
"expected valid direction"
|
||||
(contains? direction-types direction))
|
||||
(dm/assert!
|
||||
"expected compatible interaction map"
|
||||
(has-direction? interaction))
|
||||
(update interaction :animation assoc :direction direction))
|
||||
```
|
||||
|
||||
> IMPORTANT: we should always use helper functions to access and modify these data
|
||||
> structures. Avoid direct attribute read or using functions like <code class="language-clojure">assoc</code> or
|
||||
> <code class="language-clojure">update</code>, even if the information is contained in a single attribute. This way
|
||||
> it will be much simpler to add validation checks or to modify the internal
|
||||
> representation of a type, and will be easier to search for places in the code
|
||||
> where this data item is used.
|
||||
>
|
||||
> Currently much of Penpot code does not follow this requirement, but it
|
||||
should do in new code or in any refactor.
|
||||
|
||||
### File operations
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
▾ files/
|
||||
helpers.cljc
|
||||
shapes_helpers.cljc
|
||||
...
|
||||
```
|
||||
|
||||
Functions that modify a file object (or a part of it) in place, returning the
|
||||
file object changed. They ensure the referential integrity within the file, or
|
||||
between a file and its libraries.
|
||||
|
||||
```clojure
|
||||
(defn resolve-component
|
||||
"Retrieve the referenced component, from the local file or from a library"
|
||||
[shape file libraries & {:keys [include-deleted?] :or {include-deleted? False}}]
|
||||
(if (= (:component-file shape) (:id file))
|
||||
(ctkl/get-component (:data file) (:component-id shape) include-deleted?)
|
||||
(get-component libraries
|
||||
(:component-file shape)
|
||||
(:component-id shape)
|
||||
:include-deleted? include-deleted?)))
|
||||
|
||||
(defn delete-component
|
||||
"Mark a component as deleted and store the main instance shapes inside it, to
|
||||
be able to be recovered later."
|
||||
[file-data component-id skip-undelete? Main-instance]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])]
|
||||
(if (or (not components-v2) skip-undelete?)
|
||||
(ctkl/delete-component file-data component-id)
|
||||
(let [set-main-instance ;; If there is a saved main-instance, restore it.
|
||||
#(if main-instance
|
||||
(assoc-in % [:objects (:main-instance-id %)] main-instance)
|
||||
%)]
|
||||
(-> file-data
|
||||
(ctkl/update-component component-id load-component-objects)
|
||||
(ctkl/update-component component-id set-main-instance)
|
||||
(ctkl/mark-component-deleted component-id))))))
|
||||
```
|
||||
|
||||
> This module is still needing an important refactor. Mainly to take functions
|
||||
> from common.types and move them here.
|
||||
|
||||
#### File validation and repair
|
||||
|
||||
There is a function in <code class="language-clojure">app.common.files.validate</code> that checks a file for
|
||||
referential and semantic integrity. It's called automatically when file changes
|
||||
are sent to backend, but may be invoked manually whenever it's needed.
|
||||
|
||||
### File changes objects
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
▾ files/
|
||||
changes_builder.cljc
|
||||
changes.cljc
|
||||
...
|
||||
```
|
||||
|
||||
Wrap the update functions in file operations module into <code class="language-clojure">changes</code> objects, that
|
||||
may be serialized, stored, sent to backend and executed to actually modify a file
|
||||
object. They should not contain business logic or algorithms. Only adapt the
|
||||
interface to the file operations or types.
|
||||
|
||||
```clojure
|
||||
(sm/define! ::changes
|
||||
[:map {:title "changes"}
|
||||
[:redo-changes vector?]
|
||||
[:undo-changes seq?]
|
||||
[:origin {:optional true} any?]
|
||||
[:save-undo? {:optional true} boolean?]
|
||||
[:stack-undo? {:optional true} boolean?]
|
||||
[:undo-group {:optional true} any?]])
|
||||
|
||||
(defmethod process-change :add-component
|
||||
[file-data params]
|
||||
(ctkl/add-component file-data params))
|
||||
```
|
||||
|
||||
### Business logic
|
||||
|
||||
```text
|
||||
▾ common/
|
||||
▾ src/app/common/
|
||||
▾ logic/
|
||||
shapes.cljc
|
||||
libraries.cljc
|
||||
```
|
||||
|
||||
Functions that implement semantic user actions, in an abstract way (independent
|
||||
of UI). They don't directly modify files, but generate changes objects, that
|
||||
may be executed in frontend or sent to backend.
|
||||
|
||||
```clojure
|
||||
(defn generate-instantiate-component
|
||||
"Generate changes to create a new instance from a component."
|
||||
[changes objects file-id component-id position page libraries old-id parent-id
|
||||
frame-id {:keys [force-frame?] :or {force-frame? False}}]
|
||||
(let [component (ctf/get-component libraries file-id component-id)
|
||||
parent (when parent-id (get objects parent-id))
|
||||
library (get libraries file-id)
|
||||
components-v2 (dm/get-in library [:data :options :components-v2])
|
||||
[new-shape new-shapes]º
|
||||
(ctn/make-component-instance page
|
||||
Component
|
||||
(:data library)
|
||||
Position
|
||||
Components-v2
|
||||
(cond-> {}
|
||||
force-frame? (assoc :force-frame-id frame-id)))
|
||||
changes (cond-> (pcb/add-object changes first-shape {:ignore-touched true})
|
||||
(some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id)))
|
||||
changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true})
|
||||
changes
|
||||
(rest new-shapes))]
|
||||
[new-shape changes]))
|
||||
```
|
||||
|
||||
## Data migrations
|
||||
|
||||
```text
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3.2. Data model
|
||||
title: 3.02. Data model
|
||||
desc: Learn about self-hosting, configuration, developer tools, data models, architecture, and integrations. View Penpot's technical guide. Free to use!
|
||||
---
|
||||
|
||||
# Penpot Data Model
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3.3. Dev environment
|
||||
title: 3.03. Dev environment
|
||||
desc: Dive into Penpot's development environment. Learn about self-hosting, configuration, developer tools, architecture, and more. See the Penpot Technical Guide!
|
||||
---
|
||||
|
||||
# Development environment
|
||||
@@ -95,7 +96,7 @@ npx shadow-cljs cljs-repl main
|
||||
### Storybook
|
||||
|
||||
The storybook local server is started on tmux **window 2** and will listen
|
||||
for changes in the styles, components or stories defined in the folders
|
||||
for changes in the styles, components or stories defined in the folders
|
||||
under the design system namespace: `app.main.ui.ds`.
|
||||
|
||||
You can open the broser on http://localhost:6006/ to see it.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3.5. Frontend Guide
|
||||
title: 3.05. Frontend Guide
|
||||
desc: "See Penpot's technical guide: self-hosting, configuration, developer insights (architecture, data model), frontend, backend, and integrations & more!"
|
||||
---
|
||||
|
||||
# Frontend Guide
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3. Developer Guide
|
||||
desc: Dive into architecture, data models, and more. Start building today! See Penpot's technical guide for self-hosting, configuration, and developer insights.
|
||||
---
|
||||
|
||||
# Developer Guide
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Assets storage
|
||||
desc: Learn about assets storage, API, object buckets, sharing, and garbage collection. See Penpot's technical guide for developers. Try Penpot - It's free.
|
||||
---
|
||||
|
||||
# Assets storage
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Authentication
|
||||
desc: Dive into Penpot today! Learn about self-hosting, configuration, developer insights, authentication, and more. View Penpot's technical guide. Try it free.
|
||||
---
|
||||
|
||||
# User authentication
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3.8. Penpot subsystems
|
||||
title: 3.09. Penpot subsystems
|
||||
desc: Learn about architecture, data models, and subsystems. View Penpot's technical guide for self-hosting, configuration, and development insights. Free!
|
||||
---
|
||||
|
||||
# Penpot subsystems
|
||||
@@ -12,4 +13,3 @@ implemented, over the whole app (backend, frontend or exporter), and points to
|
||||
the most relevant source files to look at to start exploring it. When some
|
||||
special considerations are needed (performance questions, limits, common
|
||||
"gotchas", historic reasons of some decisions, etc.) they are also noted.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 3.9. UI Guide
|
||||
title: 3.10. UI Guide
|
||||
desc: Learn UI development with React & Rumext, design system implementation, and performance considerations. See Penpot's technical guide. Free to use!
|
||||
---
|
||||
|
||||
# UI Guide
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 1.3 Install with Docker
|
||||
desc: This Penpot technical guide covers self-hosting, Docker installation, configuration, updates, backups, and proxy setup with NGINX and Caddy. Try Penpot!
|
||||
---
|
||||
|
||||
<p class="advice">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 1. Self-hosting Guide
|
||||
desc: Customize your Penpot instance today. Learn how to install with Elestio, Docker, or Kubernetes from the technical guide for self-hosting options.
|
||||
---
|
||||
|
||||
# Self-hosting Guide
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 1.4 Install with Kubernetes
|
||||
desc: Learn how to install and configure Penpot on your Kubernetes cluster using Helm. Our technical guide provides step-by-step instructions for setup.
|
||||
---
|
||||
|
||||
# Install with Kubernetes
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 1.1 Recommended storage
|
||||
desc: Learn recommended self-hosting settings, Docker & Kubernetes installs, configuration, and troubleshooting tips in Penpot's technical guide.
|
||||
---
|
||||
|
||||
# Recommended storage
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 1.5 Unofficial self-host options
|
||||
desc: Find guides for Docker, Kubernetes, and more in Penpot's Technical Guide for self-hosting options! Discover unofficial self-host options too.
|
||||
---
|
||||
|
||||
# Unofficial self-host options
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Technical Guide
|
||||
desc: Get self-hosting instructions, integration details, and developer resources. Troubleshoot issues easily. Try Penpot free! See Penpot's technical guide.
|
||||
eleventyNavigation:
|
||||
key: Technical Guide
|
||||
order: 4
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 4. Integration Guide
|
||||
desc: Connect Penpot with other apps using webhooks and access tokens! Learn from Penpot's integration guide for seamless workflows. Try Penpot - It's free.
|
||||
---
|
||||
|
||||
# Integration Guide
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 5. Troubleshooting Penpot
|
||||
desc: Troubleshoot Penpot like a pro! Our technical guide offers tips and tricks for diagnosing issues, reading logs, and resolving problems. Get started now!
|
||||
---
|
||||
|
||||
# Troubleshooting Penpot
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
title: 11· Components
|
||||
desc: Streamline your design workflow with Penpot's Components guide! Learn to create, duplicate, group, and manage reusable components.
|
||||
---
|
||||
|
||||
<h1 id="components">Components</h1>
|
||||
<p class="main-paragraph">Speed your workflow with the reusable power of components.</p>
|
||||
<p>A component is an object or group of objects that can be reused multiple times across files. This can help you maintain consistency across a group of designs.</p>
|
||||
<p>A component is an object or group of objects that can be reused multiple times across files. This can help you maintain consistency across a group of designs.</p>
|
||||
|
||||
<p>A component has two parts:</p>
|
||||
<ul>
|
||||
@@ -62,7 +63,7 @@ title: 11· Components
|
||||
|
||||
<h2 id="component-group">Group components</h2>
|
||||
<h3>Create component groups</h3>
|
||||
<p>At the Components section from the Assets library, there are two ways to create groups in a components library.</p>
|
||||
<p>At the Components section from the Assets library, there are two ways to create groups in a components library.</p>
|
||||
<ol>
|
||||
<li><strong>Using slashes (/):</strong> Select one component and rename it as follows: "FOLDER NAME/COMPONENT NAME". For example, "Buttons/Alert Button".</li>
|
||||
<li><strong>Using the "Group" option:</strong> Select one or more components at the Assets library, right click to show the menu and then select "Group".</li>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 17· Custom fonts
|
||||
desc: Penpot's guide on custom fonts! Upload, manage, and use custom fonts in Penpot! Enhance your designs with personalised typography.
|
||||
---
|
||||
|
||||
<h1 id="customfonts">Custom fonts</h1>
|
||||
@@ -13,9 +14,9 @@ title: 17· Custom fonts
|
||||
<h3>To upload a local font:</h3>
|
||||
<ol>
|
||||
<li>Press “Add custom font”.</li>
|
||||
<li>Inspect your local files to select one or more fonts that you want to upload. <strong>You can upload fonts with
|
||||
<li>Inspect your local files to select one or more fonts that you want to upload. <strong>You can upload fonts with
|
||||
the following formats: TTF, OTF and WOFF</strong>. Only one format will be needed.</li>
|
||||
<li>Change the font name if needed. The font name is the name that will be shown in the font list at the workspace.
|
||||
<li>Change the font name if needed. The font name is the name that will be shown in the font list at the workspace.
|
||||
It is also what Penpot uses to group fonts in families. You can always edit it later.</li>
|
||||
<li>Once ready, press upload. That's it. The font will be available at the font list of this team’s files.</li>
|
||||
</ol>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 07· Exporting objects
|
||||
desc: Learn how to export objects in Penpot, the free, open-source design collaboration tool. This guide covers export presets, file formats, and more!
|
||||
---
|
||||
|
||||
<h1 id="export">Exporting objects</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 08· Flexible Layouts
|
||||
desc: Master responsive web design with Penpot's flexible and grid layouts! Learn Flexbox and CSS Grid standards. Explore tutorials, properties, and more.
|
||||
---
|
||||
|
||||
<h1 id="layouts">Flexible Layouts</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 15· Import/export files
|
||||
desc: Learn how to import and export files in Penpot, the free, open-source design tool. Discover file formats, backups, sharing, and library management.
|
||||
---
|
||||
|
||||
<h1 id="import-export">Import and export files</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: User guide
|
||||
desc: Learn everything from interface basics to advanced features like prototyping and design sharing with Penpot's comprehensive user guide! Free access.
|
||||
eleventyNavigation:
|
||||
key: User guide
|
||||
order: 2
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 14· Inspect designs
|
||||
desc: Learn how to inspect designs in Penpot! This guide covers distances, properties, code snippets (CSS, SVG, HTML), & exporting assets for seamless collaboration.
|
||||
---
|
||||
|
||||
<h1 id="inspect">Inspect designs</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 01· Introduction
|
||||
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
|
||||
---
|
||||
|
||||
<h1 id="section-1">Introduction</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Tutorials & info
|
||||
desc: Begin with Penpot's comprehensive user guide! Get tutorials, learn interface basics, and master design features. Discover FAQs and more.
|
||||
---
|
||||
|
||||
<h1 id="section-1-1">Tutorials & info</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Quickstart
|
||||
desc: Start instantly with Penpot, the open-source design and prototyping platform! Access our free user guide to learn now.
|
||||
---
|
||||
|
||||
<h1 id="section-1-1">Quickstart</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Shortcuts
|
||||
desc: Get quickstart tips, shortcuts, and tutorials for Penpot! Learn interface basics and more with this free, open-source design tool.
|
||||
---
|
||||
|
||||
<h1 id="section-1-1">Shortcuts</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 04· Layer basics
|
||||
desc: Master layer basics with Penpot's user guide! Learn to create, manipulate, and organize layers for stunning designs. Try Penpot, it's free!
|
||||
---
|
||||
|
||||
<h1 id="layer-basics">Layer basics</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 09· Asset Libraries
|
||||
desc: Use Penpot's asset libraries for reusable design elements! Learn to create, manage, and share components, colors, and typography. Try Penpot - it's free!
|
||||
---
|
||||
|
||||
<h1 id="asset-libraries">Asset Libraries</h1>
|
||||
@@ -112,7 +113,7 @@ title: 09· Asset Libraries
|
||||
</figure>
|
||||
|
||||
<h4>Group assets</h4>
|
||||
<p>There are two ways to create groups in a library.</p>
|
||||
<p>There are two ways to create groups in a library.</p>
|
||||
<ol>
|
||||
<li><strong>With slashes (/):</strong> Select an asset and rename it as follows: "FOLDER NAME/ASSET NAME". For example, "Buttons/Alert Button".</li>
|
||||
<li><strong>With the "Group" option:</strong> Select one or more assets at the library, then right click to show the menu and then select "Group".</li>
|
||||
@@ -138,7 +139,7 @@ title: 09· Asset Libraries
|
||||
<h2 id="libraries">Libraries</h2>
|
||||
|
||||
<h3 id="file-libraries">File libraries</h3>
|
||||
<p>Each file has its own file library which is where the assets that belong to this file are stored.</p>
|
||||
<p>Each file has its own file library which is where the assets that belong to this file are stored.</p>
|
||||
<p>You have two ways to access the file library from the file <a href="/user-guide/the-interface/#interface-workspace">workspace</a>:</p>
|
||||
<ul>
|
||||
<li>Click the assets tab icon at the left sidebar.</li>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 05· Objects
|
||||
desc: "Work with Penpot's objects: boards, shapes, text, paths, and graphics. Learn to create, select, rename, and customize boards for optimal workflow."
|
||||
---
|
||||
|
||||
<h1 id="objects">Objects</h1>
|
||||
@@ -7,7 +8,7 @@ title: 05· Objects
|
||||
available in Penpot, and how to get the most of them.</p>
|
||||
|
||||
<h2 id="Boards">Boards</h2>
|
||||
<p>A Board is a layer typically used as a container for a design. Boards are useful if you want to design for a specific screen or print size. Boards can contain other boards. First level boards
|
||||
<p>A Board is a layer typically used as a container for a design. Boards are useful if you want to design for a specific screen or print size. Boards can contain other boards. First level boards
|
||||
are shown by default at the <a href="/user-guide/view-mode">View mode</a>, acting as screens of a design or pages of a document. Also, objects inside boards can be clipped. Boards are a powerful element at Penpot, opening up a ton of possibilities when creating and organizing your designs.</p>
|
||||
|
||||
<h3>Create boards</h3>
|
||||
@@ -80,7 +81,7 @@ are shown by default at the <a href="/user-guide/view-mode">View mode</a>, actin
|
||||
</figure>
|
||||
|
||||
<h3>Show in View mode</h3>
|
||||
<p>Boards offer the option to be shown as a separate board/screen in the <a href="/user-guide/the-interface/#interface-viewmode">View mode</a>. Use this setting to decide what boards should be shown as individual items in your presentations.</p>
|
||||
<p>Boards offer the option to be shown as a separate board/screen in the <a href="/user-guide/the-interface/#interface-viewmode">View mode</a>. Use this setting to decide what boards should be shown as individual items in your presentations.</p>
|
||||
<p><strong>Defaults</strong></p>
|
||||
<p>As it is very likely that the first level boards will be used as a screen and the interiors will not, there are different defaults for newly created boards.</p>
|
||||
<ul>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 18· Plugins
|
||||
desc: Extend Penpot's functionality with plugins! Install from PenpotHub or via URL. Learn to install, use, and create your own plugins.
|
||||
---
|
||||
|
||||
<h1 id="penpot-plugins">Penpot Plugins</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 12· Prototyping
|
||||
desc: This Penpot user guide explains how to prototype interactions, connect boards, use triggers/actions, and animations. Learn to build flows and more!
|
||||
---
|
||||
|
||||
<h1 id="prototype">Prototyping interactions</h1>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
title: 06· Styling
|
||||
desc: Style your designs with Penpot's options! Learn about color fills, gradients, strokes, shadows, blur, opacity, blend modes, and property copying.
|
||||
---
|
||||
|
||||
<h1 id="styling">Styling</h1>
|
||||
<p class="main-paragraph">Penpot has a variety of styling options for each object. When selected, the styling options are displayed in the design panel on the right.</p>
|
||||
|
||||
<h2 id="fill">Color fills</h2>
|
||||
<p>Color fills can be added to boards, shapes, texts and groups of layers.</p>
|
||||
<p>You can add as fills:</p>
|
||||
<p>Color fills can be added to boards, shapes, texts and groups of layers.</p>
|
||||
<p>You can add as fills:</p>
|
||||
<ul>
|
||||
<li>Custom colors (hex).</li>
|
||||
<li>Color <a href="/user-guide/libraries/#asset-types">assets</a>.</li>
|
||||
@@ -169,7 +170,7 @@ title: 06· Styling
|
||||
</figure>
|
||||
|
||||
<h2 id="blend">Opacity and blend</h2>
|
||||
<p>Set the overal opacity for layers and their blend mode.</p>
|
||||
<p>Set the overal opacity for layers and their blend mode.</p>
|
||||
<p>Blend allows you to control how a layer interacts with the layers beneath it, determining how pixels from the current layer are combined with pixels in the underlying layers. Use blend to achive various effects, such as shading, highlights, or creative visual styles.</p>
|
||||
<figure>
|
||||
<img alt="Layer blend and opacity" src="/img/styling/blend-opacity.webp"/>
|
||||
@@ -195,7 +196,7 @@ title: 06· Styling
|
||||
</ul>
|
||||
|
||||
<h2 id="copy-paste-properties">Copy/Paste properties</h2>
|
||||
<p>You can copy and apply properties, including fills, strokes, shadows, and others from one layer to another—or multiple layers with just a few clicks. You can do it using the layer's menu or shortcuts.</p>
|
||||
<p>You can copy and apply properties, including fills, strokes, shadows, and others from one layer to another—or multiple layers with just a few clicks. You can do it using the layer's menu or shortcuts.</p>
|
||||
|
||||
<figure>
|
||||
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/copy-properties.webp" height="auto">
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
title: 16· Teams
|
||||
desc: Manage teams and roles with Penpot's collaboration features! Learn how to manage teams, roles (Viewer, Editor, Admin, Owner), send invites and use webhooks.
|
||||
---
|
||||
|
||||
<h1 id="teams">Teams</h1>
|
||||
<p class="main-paragraph">A team is a group of members who collaborate on a collection of projects.
|
||||
<p class="main-paragraph">A team is a group of members who collaborate on a collection of projects.
|
||||
Team members are allowed to work with any project or file within the team. The actions that each team
|
||||
member is allowed to do depends on their permissions.</p>
|
||||
member is allowed to do depends on their permissions.</p>
|
||||
<p class="main-paragraph">At Penpot you can create and join as many teams as you need and add all necessary stakeholders with no team size limits.</p>
|
||||
|
||||
<h2 id="teams-management">Manage teams</h2>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 02· The interface
|
||||
desc: Discover Penpot's free user guide! Learn the interface, workspace basics, flexible layouts, and prototyping. Master Penpot today.
|
||||
---
|
||||
|
||||
<h1 id="the-interface">The interface</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 13· View mode
|
||||
desc: Present designs and share prototypes with Penpot's View mode! Play interactions, navigate boards, zoom, and toggle fullscreen. Try it for free!
|
||||
---
|
||||
|
||||
<h1 id="viewmode">View mode</h1>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 03· Workspace basics
|
||||
desc: Master Penpot's workspace basics! Learn interface navigation, zoom tools, dynamic alignment, rulers, guides, and shortcuts.
|
||||
---
|
||||
|
||||
<h1 id="workspace-basics">Workspace basics</h1>
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14.499" height="14.507" viewBox="700.734 827.762 14.499 14.507"><path d="M707.948 827.762c-.193 0-.386.007-.578.022-2.311.181-4.574 1.452-5.839 3.982-1.686 3.372-.52 6.821 1.835 8.787 2.354 1.966 5.955 2.497 8.974.233a.566.566 0 1 0-.68-.906c-2.611 1.959-5.563 1.478-7.568-.196-2.004-1.675-3.005-4.495-1.546-7.412 1.458-2.917 4.314-3.808 6.856-3.208 2.543.599 4.698 2.672 4.698 5.936v.667c0 .525-.176.847-.435 1.076-.258.23-.624.357-.998.357s-.741-.127-.999-.357c-.258-.229-.435-.551-.435-1.076v-3.334a.567.567 0 0 0-1.133 0v.215a3.215 3.215 0 0 0-2.1-.781 3.241 3.241 0 0 0-3.233 3.233 3.241 3.241 0 0 0 3.233 3.233 3.23 3.23 0 0 0 2.482-1.168c.122.199.267.377.433.525a2.63 2.63 0 0 0 1.752.643c.626 0 1.259-.206 1.751-.643.492-.437.815-1.115.815-1.923V835c0-3.773-2.586-6.336-5.572-7.04a7.405 7.405 0 0 0-1.713-.198ZM708 832.9c1.167 0 2.1.933 2.1 2.1a2.09 2.09 0 0 1-2.1 2.1 2.09 2.09 0 0 1-2.1-2.1c0-1.167.933-2.1 2.1-2.1Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10.667 5.333v3.334a2 2 0 1 0 4 0V8a6.667 6.667 0 1 0-2.614 5.293M10.667 8a2.667 2.667 0 1 1-5.334 0 2.667 2.667 0 0 1 5.334 0Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 282 B |
@@ -5,6 +5,7 @@
|
||||
set -ex
|
||||
|
||||
export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no};
|
||||
export INCLUDE_WASM=${BUILD_WASM:-yes};
|
||||
|
||||
export CURRENT_VERSION=$1;
|
||||
export BUILD_DATE=$(date -R);
|
||||
@@ -17,14 +18,18 @@ export TS=$(date +%s);
|
||||
export NODE_ENV=production;
|
||||
|
||||
corepack enable;
|
||||
corepack up || exit 1;
|
||||
corepack install || exit 1;
|
||||
yarn install || exit 1;
|
||||
|
||||
rm -rf resources/public;
|
||||
rm -rf target/dist;
|
||||
|
||||
yarn run build:app:main --config-merge "{:release-version \"${CURRENT_HASH}-${TS}\"}" $EXTRA_PARAMS || exit 1
|
||||
yarn run build:wasm || exit 1;
|
||||
|
||||
if [ "$INCLUDE_WASM" = "yes" ]; then
|
||||
yarn run build:wasm || exit 1;
|
||||
fi
|
||||
|
||||
yarn run build:app:libs || exit 1;
|
||||
yarn run build:app:assets || exit 1;
|
||||
|
||||
@@ -36,7 +41,10 @@ sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/render.html;
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/rasterizer.html;
|
||||
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/index.html;
|
||||
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/rasterizer.html;
|
||||
sed -i "s/version=develop/version=$CURRENT_VERSION/g" ./target/dist/js/render_wasm.js;
|
||||
|
||||
if [ "$INCLUDE_WASM" = "yes" ]; then
|
||||
sed -i "s/version=develop/version=$CURRENT_VERSION/g" ./target/dist/js/render_wasm.js;
|
||||
fi
|
||||
|
||||
if [ "$INCLUDE_STORYBOOK" = "yes" ]; then
|
||||
# build storybook
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.shape.text :as types.text]
|
||||
[app.common.types.typography :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
@@ -2192,27 +2193,27 @@
|
||||
(ptk/reify ::paste-html-text
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [root (dwtxt/create-root-from-html html)
|
||||
content (tc/dom->cljs root)
|
||||
(let [root (dwtxt/create-root-from-html html)
|
||||
content (tc/dom->cljs root)]
|
||||
(when (types.text/valid-content? content)
|
||||
(let [id (uuid/next)
|
||||
width (max 8 (min (* 7 (count text)) 700))
|
||||
height 16
|
||||
{:keys [x y]} (calculate-paste-position state)
|
||||
|
||||
id (uuid/next)
|
||||
width (max 8 (min (* 7 (count text)) 700))
|
||||
height 16
|
||||
{:keys [x y]} (calculate-paste-position state)
|
||||
|
||||
shape {:id id
|
||||
:type :text
|
||||
:name (txt/generate-shape-name text)
|
||||
:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:grow-type (if (> (count text) 100) :auto-height :auto-width)
|
||||
:content content}
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dwsh/create-and-add-shape :text x y shape)
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
shape {:id id
|
||||
:type :text
|
||||
:name (txt/generate-shape-name text)
|
||||
:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:grow-type (if (> (count text) 100) :auto-height :auto-width)
|
||||
:content content}
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dwsh/create-and-add-shape :text x y shape)
|
||||
(dwu/commit-undo-transaction undo-id))))))))
|
||||
|
||||
(defn- paste-text
|
||||
[text]
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-local dissoc :edition)
|
||||
(update :workspace-local dissoc :edition :edit-path)
|
||||
(update :workspace-drawing dissoc :tool :object :lock)
|
||||
(dissoc :workspace-grid-edition)))
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.modal :as md]
|
||||
[app.main.data.workspace.collapse :as dwc]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.specialized-panel :as-alias dwsp]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.data.workspace.zoom :as dwz]
|
||||
@@ -305,8 +306,9 @@
|
||||
(watch [_ state _]
|
||||
(let [params-without-board (-> (rt/get-params state)
|
||||
(dissoc :board-id))]
|
||||
(rx/of ::dwsp/interrupt)
|
||||
(rx/of (rt/nav :workspace params-without-board {::rt/replace true}))))
|
||||
(rx/of ::dwsp/interrupt
|
||||
(dwe/clear-edition-mode)
|
||||
(rt/nav :workspace params-without-board {::rt/replace true}))))
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
(swap! state* assoc :width width)))
|
||||
|
||||
[:div {:class (stl/css :palette-wrapper)
|
||||
:id "palette-wrapper"
|
||||
:style (calculate-palette-padding rulers?)
|
||||
:data-testid "palette"}
|
||||
(when-not workspace-read-only?
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "../ds/spacing.scss" as *;
|
||||
@use "../ds/z-index.scss" as *;
|
||||
@use "../ds/_sizes.scss" as *;
|
||||
|
||||
@import "refactor/common-refactor.scss";
|
||||
|
||||
.palette-wrapper {
|
||||
@@ -12,6 +16,12 @@
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
padding-bottom: $s-4;
|
||||
|
||||
/** Aligns AI Chat button **/
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.palettes {
|
||||
@@ -164,3 +174,27 @@
|
||||
padding-bottom: $s-8;
|
||||
}
|
||||
}
|
||||
|
||||
/** AI Chat button styles **/
|
||||
.help-btn {
|
||||
z-index: var(--z-index-panels);
|
||||
flex-shrink: 0;
|
||||
@extend .button-secondary;
|
||||
inline-size: $sz-40;
|
||||
block-size: $sz-40;
|
||||
border-radius: $br-circle;
|
||||
border: none;
|
||||
&.selected {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
&:hover {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-help {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
inline-size: var(--sp-xxl);
|
||||
block-size: var(--sp-xxl);
|
||||
}
|
||||
|
||||
@@ -129,6 +129,12 @@
|
||||
input-ref (mf/use-ref nil)
|
||||
|
||||
team (mf/deref refs/team)
|
||||
permissions (get team :permissions)
|
||||
|
||||
display-share-button?
|
||||
(and (not (:is-default team))
|
||||
(or (:is-admin permissions)
|
||||
(:is-owner permissions)))
|
||||
|
||||
nav-to-viewer
|
||||
(mf/use-fn
|
||||
@@ -216,7 +222,7 @@
|
||||
:on-click toggle-history}
|
||||
i/history]])
|
||||
|
||||
(when (not (:is-default team))
|
||||
(when display-share-button?
|
||||
[:a {:class (stl/css :viewer-btn)
|
||||
:title (tr "workspace.header.share")
|
||||
:on-click open-share-dialog}
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
indent))
|
||||
|
||||
(cfh/text-shape? shape)
|
||||
(let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))]
|
||||
(let [text-shape-html (rds/renderToStaticMarkup (mf/element text/text-shape #js {:shape shape :code? true}))
|
||||
text-shape-html (str/replace text-shape-html #"style\s*=\s*[\"'][^\"']*[\"']" "")]
|
||||
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
|
||||
indent
|
||||
(dm/str "shape " (d/name (:type shape)) " "
|
||||
|
||||
@@ -83,7 +83,10 @@ export function mapContentFragmentFromDocument(document, root, styleDefaults) {
|
||||
currentNode = nodeIterator.nextNode();
|
||||
}
|
||||
|
||||
fragment.appendChild(currentParagraph);
|
||||
if (currentParagraph) {
|
||||
fragment.appendChild(currentParagraph);
|
||||
}
|
||||
|
||||
if (fragment.children.length === 1) {
|
||||
const isContentInline = isContentFragmentFromDocumentInline(document);
|
||||
if (isContentInline) {
|
||||
|
||||
@@ -115,6 +115,7 @@ function build {
|
||||
--mount source=`pwd`,type=bind,target=/home/penpot/penpot \
|
||||
-e EXTERNAL_UID=$CURRENT_USER_ID \
|
||||
-e BUILD_STORYBOOK=$BUILD_STORYBOOK \
|
||||
-e BUILD_WASM=$BUILD_WASM \
|
||||
-e SHADOWCLJS_EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS \
|
||||
-e JAVA_OPTS="$JAVA_OPTS" \
|
||||
-w /home/penpot/penpot/$1 \
|
||||
|
||||
Reference in New Issue
Block a user