mirror of
https://github.com/penpot/penpot.git
synced 2026-01-09 14:58:59 -05:00
Compare commits
3 Commits
niwinz-dev
...
eva-replac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdcbde37c3 | ||
|
|
81c6cb52ca | ||
|
|
1dc274e6e2 |
21
.github/workflows/build-nitrate-module.yml
vendored
21
.github/workflows/build-nitrate-module.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: _NITRATE MODULE
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '36 5-20 * * 1-5'
|
||||
|
||||
jobs:
|
||||
build-bundle:
|
||||
uses: ./.github/workflows/build-bundle.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "nitrate-module"
|
||||
build_wasm: "yes"
|
||||
build_storybook: "yes"
|
||||
|
||||
build-docker:
|
||||
needs: build-bundle
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "nitrate-module"
|
||||
1
.github/workflows/build-tag.yml
vendored
1
.github/workflows/build-tag.yml
vendored
@@ -23,7 +23,6 @@ jobs:
|
||||
|
||||
notify:
|
||||
name: Notifications
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build-docker
|
||||
|
||||
steps:
|
||||
|
||||
10
CHANGES.md
10
CHANGES.md
@@ -8,17 +8,12 @@
|
||||
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
- Fix mask issues with component swap (by @dfelinto) [Github #7675](https://github.com/penpot/penpot/issues/7675)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
- Make i18n translation files load on-demand [Taiga #11474](https://tree.taiga.io/project/penpot/us/11474)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
|
||||
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
|
||||
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
|
||||
|
||||
|
||||
## 2.12.0 (Unreleased)
|
||||
@@ -82,7 +77,6 @@ example. It's still usable as before, we just removed the example.
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
|
||||
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [Github #7887](https://github.com/penpot/penpot/pull/7887)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
@@ -110,10 +104,6 @@ example. It's still usable as before, we just removed the example.
|
||||
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
|
||||
- Fix copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
|
||||
- Fix problem with plugins content attribute [Plugins #209](https://github.com/penpot/penpot-plugins/issues/209)
|
||||
- Fix U and E icon displayed in project list [Taiga #12806](https://tree.taiga.io/project/penpot/issue/12806)
|
||||
- Fix unpublish library modal not scrolling a long file list [Taiga #12285](https://tree.taiga.io/project/penpot/issue/12285)
|
||||
- Fix incorrect interaction betwen hower and scroll on assets sidebar [Taiga #12389](https://tree.taiga.io/project/penpot/issue/12389)
|
||||
- Fix switch variants with paths [Taiga #12841](https://tree.taiga.io/project/penpot/issue/12841)
|
||||
|
||||
## 2.11.1
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key
|
||||
export PENPOT_SECRET_KEY=super-secret-devenv-key
|
||||
export PENPOT_HOST=devenv
|
||||
export PENPOT_PUBLIC_URI=https://localhost:3449
|
||||
|
||||
export PENPOT_FLAGS="\
|
||||
$PENPOT_FLAGS \
|
||||
|
||||
@@ -106,17 +106,17 @@
|
||||
(let [content-part (MimeBodyPart.)
|
||||
alternative-mpart (MimeMultipart. "alternative")]
|
||||
|
||||
(when-let [content (get body "text/plain")]
|
||||
(let [text-part (MimeBodyPart.)]
|
||||
(.setText text-part ^String content ^String charset)
|
||||
(.addBodyPart alternative-mpart text-part)))
|
||||
|
||||
(when-let [content (get body "text/html")]
|
||||
(let [html-part (MimeBodyPart.)]
|
||||
(.setContent html-part ^String content
|
||||
(str "text/html; charset=" charset))
|
||||
(.addBodyPart alternative-mpart html-part)))
|
||||
|
||||
(when-let [content (get body "text/plain")]
|
||||
(let [text-part (MimeBodyPart.)]
|
||||
(.setText text-part ^String content ^String charset)
|
||||
(.addBodyPart alternative-mpart text-part)))
|
||||
|
||||
(.setContent content-part alternative-mpart)
|
||||
(.addBodyPart mixed-mpart content-part))
|
||||
|
||||
|
||||
@@ -79,6 +79,18 @@
|
||||
(remove #(contains? reserved-props (key %))))
|
||||
props))
|
||||
|
||||
(defn event-from-rpc-params
|
||||
"Create a base event skeleton with pre-filled some important
|
||||
data that can be extracted from RPC params object"
|
||||
[params]
|
||||
(let [context {:external-session-id (::rpc/external-session-id params)
|
||||
:external-event-origin (::rpc/external-event-origin params)
|
||||
:triggered-by (::rpc/handler-name params)}]
|
||||
{::type "action"
|
||||
::profile-id (::rpc/profile-id params)
|
||||
::ip-addr (::rpc/ip-addr params)
|
||||
::context (d/without-nils context)}))
|
||||
|
||||
(defn get-external-session-id
|
||||
[request]
|
||||
(when-let [session-id (yreq/get-header request "x-external-session-id")]
|
||||
@@ -87,24 +99,13 @@
|
||||
(str/blank? session-id))
|
||||
session-id)))
|
||||
|
||||
(defn- get-client-event-origin
|
||||
(defn- get-external-event-origin
|
||||
[request]
|
||||
(when-let [origin (yreq/get-header request "x-event-origin")]
|
||||
(when-not (or (= origin "null")
|
||||
(when-not (or (> (count origin) 256)
|
||||
(= origin "null")
|
||||
(str/blank? origin))
|
||||
(str/prune origin 200))))
|
||||
|
||||
(defn get-client-user-agent
|
||||
[request]
|
||||
(when-let [user-agent (yreq/get-header request "user-agent")]
|
||||
(str/prune user-agent 500)))
|
||||
|
||||
(defn- get-client-version
|
||||
[request]
|
||||
(when-let [origin (yreq/get-header request "x-frontend-version")]
|
||||
(when-not (or (= origin "null")
|
||||
(str/blank? origin))
|
||||
(str/prune origin 100))))
|
||||
origin)))
|
||||
|
||||
;; --- SPECS
|
||||
|
||||
@@ -133,33 +134,6 @@
|
||||
(def ^:private check-event
|
||||
(sm/check-fn schema:event))
|
||||
|
||||
(defn- prepare-context-from-request
|
||||
[request]
|
||||
(let [client-event-origin (get-client-event-origin request)
|
||||
client-version (get-client-version request)
|
||||
client-user-agent (get-client-user-agent request)
|
||||
session-id (get-external-session-id request)
|
||||
token-id (::actoken/id request)]
|
||||
(d/without-nils
|
||||
{:external-session-id session-id
|
||||
:access-token-id (some-> token-id str)
|
||||
:client-event-origin client-event-origin
|
||||
:client-user-agent client-user-agent
|
||||
:client-version client-version
|
||||
:version (:full cf/version)})))
|
||||
|
||||
(defn event-from-rpc-params
|
||||
"Create a base event skeleton with pre-filled some important
|
||||
data that can be extracted from RPC params object"
|
||||
[params]
|
||||
(let [context (some-> params meta ::http/request prepare-context-from-request)
|
||||
event {::type "action"
|
||||
::profile-id (or (::rpc/profile-id params) uuid/zero)
|
||||
::ip-addr (::rpc/ip-addr params)}]
|
||||
(cond-> event
|
||||
(some? context)
|
||||
(assoc ::context context))))
|
||||
|
||||
(defn prepare-event
|
||||
[cfg mdata params result]
|
||||
(let [resultm (meta result)
|
||||
@@ -174,10 +148,18 @@
|
||||
(merge (::props resultm))
|
||||
(dissoc :profile-id)
|
||||
(dissoc :type)))
|
||||
|
||||
(clean-props))
|
||||
|
||||
context (merge (::context resultm)
|
||||
(prepare-context-from-request request))
|
||||
token-id (::actoken/id request)
|
||||
context (-> (::context resultm)
|
||||
(assoc :external-session-id
|
||||
(get-external-session-id request))
|
||||
(assoc :external-event-origin
|
||||
(get-external-event-origin request))
|
||||
(assoc :access-token-id (some-> token-id str))
|
||||
(d/without-nils))
|
||||
|
||||
ip-addr (inet/parse-request request)]
|
||||
|
||||
{::type (or (::type resultm)
|
||||
|
||||
@@ -12,11 +12,8 @@
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.variant :as cfv]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.logging :as log]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.logic.variant-properties :as clvp]
|
||||
@@ -28,7 +25,6 @@
|
||||
[app.common.types.library :as ctl]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.path.segment :as segment]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
@@ -1878,44 +1874,6 @@
|
||||
roperations'
|
||||
uoperations')))))))
|
||||
|
||||
(defn- set-path-new-values
|
||||
[current-shape prev-shape transform]
|
||||
(let [new-content (segment/transform-content
|
||||
(:content current-shape)
|
||||
(gmt/transform-in (gpt/point 0 0) transform))
|
||||
new-points (-> (segment/content->selrect new-content)
|
||||
(grc/rect->points))
|
||||
points-center (gco/points->center new-points)
|
||||
new-selrect (gsh/calculate-selrect new-points points-center)
|
||||
shape (assoc current-shape
|
||||
:content new-content
|
||||
:points new-points
|
||||
:selrect new-selrect)
|
||||
|
||||
prev-center (segment/content-center (:content prev-shape))
|
||||
delta (gpt/subtract points-center (first new-points))
|
||||
new-pos (gpt/subtract prev-center delta)]
|
||||
(gsh/absolute-move shape new-pos)))
|
||||
|
||||
(defn- switch-path-change-value
|
||||
[prev-shape ;; The shape before the switch
|
||||
current-shape ;; The shape after the switch (a clean copy)
|
||||
ref-shape ;; The referenced shape on the main component
|
||||
;; before the switch
|
||||
attr]
|
||||
(let [old-width (-> ref-shape :selrect :width)
|
||||
new-width (-> prev-shape :selrect :width)
|
||||
|
||||
old-height (-> ref-shape :selrect :height)
|
||||
new-height (-> prev-shape :selrect :height)
|
||||
|
||||
transform (-> (gpt/point (/ new-width old-width)
|
||||
(/ new-height old-height))
|
||||
(gmt/scale-matrix))
|
||||
|
||||
shape (set-path-new-values current-shape prev-shape transform)]
|
||||
(get shape attr)))
|
||||
|
||||
|
||||
(defn- switch-text-change-value
|
||||
[prev-content ;; The :content of the text before the switch
|
||||
@@ -2067,10 +2025,6 @@
|
||||
(= :content attr)
|
||||
(touched attr-group))
|
||||
|
||||
path-change?
|
||||
(and (= :path (:type current-shape))
|
||||
(contains? #{:points :selrect :content} attr))
|
||||
|
||||
;; position-data is a special case because can be affected by :geometry-group and :content-group
|
||||
;; so, if the position-data changes but the geometry is touched we need to reset the position-data
|
||||
;; so it's calculated again
|
||||
@@ -2099,12 +2053,6 @@
|
||||
(:content origin-ref-shape)
|
||||
touched)
|
||||
|
||||
path-change?
|
||||
(switch-path-change-value previous-shape
|
||||
current-shape
|
||||
origin-ref-shape
|
||||
attr)
|
||||
|
||||
:else
|
||||
(get previous-shape attr)))
|
||||
|
||||
@@ -2491,13 +2439,11 @@
|
||||
(ctk/get-swap-slot))
|
||||
(constantly false))
|
||||
|
||||
;; In the cases where the swapped shape was the first element of the masked group it would make the group to loose the
|
||||
;; mask property as part of the sanitization check on generate-delete-shapes, passing "ignore-mask" to prevent this
|
||||
[all-parents changes]
|
||||
(-> changes
|
||||
(cls/generate-delete-shapes
|
||||
file page objects (d/ordered-set (:id shape))
|
||||
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true}))
|
||||
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn}))
|
||||
[new-shape changes]
|
||||
(-> changes
|
||||
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
|
||||
|
||||
@@ -123,10 +123,8 @@
|
||||
;; ignore-children-fn is used to ignore some descendants
|
||||
;; on the deletion process. It should receive a shape and
|
||||
;; return a boolean
|
||||
ignore-children-fn
|
||||
ignore-mask]
|
||||
:or {ignore-children-fn (constantly false)
|
||||
ignore-mask false}}]
|
||||
ignore-children-fn]
|
||||
:or {ignore-children-fn (constantly false)}}]
|
||||
(let [objects (pcb/get-objects changes)
|
||||
data (pcb/get-library-data changes)
|
||||
page-id (pcb/get-page-id changes)
|
||||
@@ -164,20 +162,18 @@
|
||||
lookup (d/getf objects)
|
||||
|
||||
groups-to-unmask
|
||||
(when-not ignore-mask
|
||||
(reduce (fn [group-ids id]
|
||||
;; When the shape to delete is the mask of a masked group,
|
||||
;; the mask condition must be removed, and it must be
|
||||
;; converted to a normal group.
|
||||
(let [obj (lookup id)
|
||||
parent (lookup (:parent-id obj))]
|
||||
(if (and (:masked-group parent)
|
||||
(= id (first (:shapes parent))))
|
||||
(conj group-ids (:id parent))
|
||||
group-ids)))
|
||||
#{}
|
||||
ids-to-delete)
|
||||
[])
|
||||
(reduce (fn [group-ids id]
|
||||
;; When the shape to delete is the mask of a masked group,
|
||||
;; the mask condition must be removed, and it must be
|
||||
;; converted to a normal group.
|
||||
(let [obj (lookup id)
|
||||
parent (lookup (:parent-id obj))]
|
||||
(if (and (:masked-group parent)
|
||||
(= id (first (:shapes parent))))
|
||||
(conj group-ids (:id parent))
|
||||
group-ids)))
|
||||
#{}
|
||||
ids-to-delete)
|
||||
|
||||
interacting-shapes
|
||||
(filter (fn [shape]
|
||||
|
||||
@@ -284,20 +284,7 @@
|
||||
(defn check-fn
|
||||
"Create a predefined check function"
|
||||
[s & {:keys [hint type code]}]
|
||||
(let [s #?(:clj
|
||||
(schema s)
|
||||
:cljs
|
||||
(try
|
||||
(schema s)
|
||||
(catch :default cause
|
||||
(let [data (ex-data cause)]
|
||||
(if (= :malli.core/invalid-schema (:type data))
|
||||
(throw (ex-info
|
||||
(str "Invalid schema\n"
|
||||
(pp/pprint-str (:data data)))
|
||||
{}))
|
||||
(throw cause))))))
|
||||
|
||||
(let [s (schema s)
|
||||
validator* (delay (m/validator s))
|
||||
explainer* (delay (m/explainer s))
|
||||
hint (or ^boolean hint "check error")
|
||||
@@ -317,7 +304,7 @@
|
||||
|
||||
(defn coercer
|
||||
[schema & {:as opts}]
|
||||
(let [decode-fn (lazy-decoder schema json-transformer)
|
||||
(let [decode-fn (decoder schema json-transformer)
|
||||
check-fn (check-fn schema opts)]
|
||||
(fn [data]
|
||||
(-> data decode-fn check-fn))))
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
(defn parse
|
||||
[data]
|
||||
(cond
|
||||
(or (str/starts-with? data "%")
|
||||
(= data "develop"))
|
||||
(str/starts-with? data "%")
|
||||
{:full "develop"
|
||||
:branch "develop"
|
||||
:base "0.0.0"
|
||||
|
||||
@@ -395,8 +395,6 @@ COPY files/tmux.conf /root/.tmux.conf
|
||||
COPY files/sudoers /etc/sudoers
|
||||
|
||||
COPY files/Caddyfile /home/
|
||||
COPY files/selfsigned.crt /home/
|
||||
COPY files/selfsigned.key /home/
|
||||
COPY files/start-tmux.sh /home/start-tmux.sh
|
||||
COPY files/start-tmux-back.sh /home/start-tmux-back.sh
|
||||
COPY files/entrypoint.sh /home/entrypoint.sh
|
||||
|
||||
@@ -33,8 +33,6 @@ services:
|
||||
- 3447:3447
|
||||
- 3448:3448
|
||||
- 3449:3449
|
||||
- 3449:3449/udp
|
||||
- 3450:3450
|
||||
- 6006:6006
|
||||
- 6060:6060
|
||||
- 6061:6061
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
|
||||
localhost:3449 {
|
||||
reverse_proxy localhost:4449
|
||||
tls /home/selfsigned.crt /home/selfsigned.key
|
||||
}
|
||||
|
||||
http://localhost:3450 {
|
||||
reverse_proxy localhost:4449
|
||||
}
|
||||
tls internal
|
||||
reverse_proxy localhost:4449
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
nginx
|
||||
caddy start -c /home/Caddyfile
|
||||
tail -f /dev/null;
|
||||
nginx;
|
||||
caddy run -c /home/Caddyfile;
|
||||
|
||||
@@ -38,11 +38,11 @@ http {
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_comp_level 3;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
|
||||
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml application/wasm;
|
||||
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
@@ -223,19 +223,16 @@ http {
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
}
|
||||
|
||||
location ~* \.(jpg|png|svg|ttf|woff|woff2)$ {
|
||||
add_header Cache-Control "public, max-age=604800" always; # 7 days
|
||||
}
|
||||
|
||||
location ~* \.(js|css|wasm)$ {
|
||||
add_header Cache-Control "no-store" always;
|
||||
location ~ ^/(/|css|fonts|images|js|wasm|mjs|map) {
|
||||
}
|
||||
|
||||
location ~ ^/[^/]+/(.*)$ {
|
||||
return 301 " /404";
|
||||
}
|
||||
|
||||
add_header Cache-Control "no-store" always;
|
||||
add_header Cache-Control "no-store";
|
||||
# This header is what we need to use on prod
|
||||
# add_header Cache-Control "public, must-revalidate, max-age=0";
|
||||
try_files $uri /index.html$is_args$args /index.html =404;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDuzCCAqOgAwIBAgIUa3THJQSn1+ErK65g1jDL0tjUkBYwDQYJKoZIhvcNAQEL
|
||||
BQAwXzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBUxvY2FsMQ4wDAYDVQQHDAVMb2Nh
|
||||
bDEOMAwGA1UECgwFTG9jYWwxDDAKBgNVBAsMA0RldjESMBAGA1UEAwwJbG9jYWxo
|
||||
b3N0MB4XDTI1MTIwMjA4MjUyM1oXDTI2MTIwMjA4MjUyM1owXzELMAkGA1UEBhMC
|
||||
VVMxDjAMBgNVBAgMBUxvY2FsMQ4wDAYDVQQHDAVMb2NhbDEOMAwGA1UECgwFTG9j
|
||||
YWwxDDAKBgNVBAsMA0RldjESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyVIlfpIPE+QyL/q7IQOilEA7wEOZ6wbsh2Fr
|
||||
59H1gSLFvgoCxI6RVUkQ/MFRnw/r1ZbAqRpc2xAl5a9Ml14q20Zlj6dAHsWX6O2J
|
||||
EwNsD18dQmX3BncnjV3yCZM2iQcMFKuXG4KQNdIQNNvdIgtlrHYp0ohS9s3XC7cj
|
||||
KxNrm/pW9EAXfn9AYDd/qER090L2E4ipP9m/5l3MjinNc4l2kpH9rLOgb79H0RLt
|
||||
PK3/KP8ErZhAvzdmDBAdM5Z5K37b+TfB/kSVNUKL6qyw5CCjlShERLhBNprlnRfz
|
||||
tHNIQ1RHq3qJJN19ZnJrLqICuQ5ztvj7hBDiOSV0LnmyKgXr6wIDAQABo28wbTAd
|
||||
BgNVHQ4EFgQUPL8WGf6z/wB8TimJBx1zybsIeikwHwYDVR0jBBgwFoAUPL8WGf6z
|
||||
/wB8TimJBx1zybsIeikwDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2Nh
|
||||
bGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBACMMVyR3kbNxnzuUc2lahKH4
|
||||
cPXVWOsvCvnDtjzm41XmKjUJTbtjn3p5d/ZmLbZ4zzIQULfWXO3XG/HevkvVo0g6
|
||||
6pJXTXc6C6ZhFG0rIYMcPPzmGmalDV5n+lUaCVx5XbFFxvRQ7893auwhRATdwGs+
|
||||
xiMyYbE2w9otKqyDItmJZJ5nW6vmXJ42YHxlXF18u9U88xqtOSMd5xZahbsmw7Gg
|
||||
A4/o4TPoAX5QfA306sL443WaczsF7bmsTf9qcYa/3xxQkP5Seyqx8ePWpS22qysE
|
||||
jG6XPpymxb6sb2mVaFBAzhEMb/eBvE9nRAopxmB7uV4TbqC51K/U3uo6jFX4Jbw=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJUiV+kg8T5DIv
|
||||
+rshA6KUQDvAQ5nrBuyHYWvn0fWBIsW+CgLEjpFVSRD8wVGfD+vVlsCpGlzbECXl
|
||||
r0yXXirbRmWPp0AexZfo7YkTA2wPXx1CZfcGdyeNXfIJkzaJBwwUq5cbgpA10hA0
|
||||
290iC2WsdinSiFL2zdcLtyMrE2ub+lb0QBd+f0BgN3+oRHT3QvYTiKk/2b/mXcyO
|
||||
Kc1ziXaSkf2ss6Bvv0fREu08rf8o/wStmEC/N2YMEB0zlnkrftv5N8H+RJU1Qovq
|
||||
rLDkIKOVKEREuEE2muWdF/O0c0hDVEereokk3X1mcmsuogK5DnO2+PuEEOI5JXQu
|
||||
ebIqBevrAgMBAAECggEABqtE+LNn8nW9v98jcc2IBjc2g4D5yVJaZYWxqGVJJ7T6
|
||||
Lfhw7Qf4AoZAHM9en9FMM7Ahw7hO2SboynoLJHyHGOp1FNQqiJptFNdBkjKr0rqI
|
||||
4pk0HK+3zLQO/4gz50gne0vP3qZtlorV5Jpf8e/Et3jWm9XOQcTB2e6AKL4k827B
|
||||
dv4Tld+/7PoZVXjahfrUWuIZr5mzyF1eUkD8sPOpdr3HJxSueqsOMjbG8XMRqCQ+
|
||||
5eCWWSW5yPQlMr7M7cXM+a0k73Xn1sKl7fP3/9byji25zxGUaMu5RA1kw0Oqseid
|
||||
RXuRxGphGZgnx1aFxDAPg3FtmGch7/Cc6WfqboOL0QKBgQD4GZO1gGaE8cg4lvuo
|
||||
ZUX2YJu6UJuNOmuhfvG3ui4WO9PHy3btc2q+3kutSuBcyIjhi+qbXasBcX/QOOJF
|
||||
udyTZc5PopNkJojS4JdXAZCiu5sKI3lp4DIt9qNISlXGgrJgdxGUO+DzarBctXdn
|
||||
BSwXFw5hcjJjl7wsPGQl1tBTQwKBgQDPuz5MEM5ZeUe9CT5sQDq/ld0u4aL5AHmx
|
||||
aaA2gzDgd9l2R5wHX6wLzjoVWXOmeqaYzJopt2JN4iXrtbjWkyePgZeZMyWoyJ/v
|
||||
clW9bi8HM9f9EpPr7czSj9sLUnsjd9cuTD+JuXK//jRGbRpw7r7nWtLHImjj6d2v
|
||||
APZRq0v2OQKBgBcESG/OObSbubeGSlKVEqiIzem7ELNJeDLDVCl3XE8zvbILbj0Z
|
||||
OA39EYhCKg5xjEFgeaNwTS0VGoZ2wIc3dv81sq4wpvvjl035CBFKU+DFBt0p7Vml
|
||||
MwKQnxVV0B9agLHyWe8mnvf2LeZr72ffUvfRa8QelA4pRYvVDnV0OF+BAoGAW6rM
|
||||
+tQPuvwB5DFIEozlX9XKHP4E5MyI5vktceDCmMtKcx92gup9CVif2Pv4ROaqzZK8
|
||||
FNyPzL6W7UTrpASb2H/fXgNsAudFbGyP2V/d8Ne34D1qeRoe4GwKxRxIqoYftpZ/
|
||||
E096i66pcsqCeINiSsWRbb6JesmgwbEzAScOBkECgYEA6O/Dibc9PaqRpaiE6Qut
|
||||
S3W/Rr1Pd1jbN4rOVI2TFCgMJQmc6jOdq2fCntR9acsa8HPx+djOlXTUBPKBZ/Ae
|
||||
p8umRdXVWcNMnwWVWHt7tsEuR/gYkxQ5xjXeS1VDPnEre9+EaevMBuVs8HdRsKQO
|
||||
uzvNGeAFEfqwIqn7CFQ+ndU=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -7,10 +7,8 @@ RUN set -ex; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
|
||||
mkdir -p /opt/data/assets; \
|
||||
chown -R penpot:penpot /opt/data; \
|
||||
mkdir -p /etc/nginx/overrides/main.d/; \
|
||||
mkdir -p /etc/nginx/overrides/http.d/; \
|
||||
mkdir -p /etc/nginx/overrides/server.d/; \
|
||||
mkdir -p /etc/nginx/overrides/assets.d/; \
|
||||
mkdir -p /etc/nginx/overrides/location.d/;
|
||||
|
||||
ARG BUNDLE_PATH="./bundle-frontend/"
|
||||
|
||||
@@ -42,11 +42,11 @@ http {
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_static on;
|
||||
gzip_comp_level 6;
|
||||
gzip_comp_level 4;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
|
||||
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml application/wasm;
|
||||
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml;
|
||||
|
||||
proxy_buffer_size 16k;
|
||||
proxy_busy_buffers_size 24k; # essentially, proxy_buffer_size + 2 small buffers of 4k
|
||||
@@ -110,8 +110,6 @@ http {
|
||||
recursive_error_pages on;
|
||||
proxy_intercept_errors on;
|
||||
error_page 301 302 307 = @handle_redirect;
|
||||
|
||||
include /etc/nginx/overrides/assets.d/*.conf;
|
||||
}
|
||||
|
||||
location /internal/assets {
|
||||
@@ -144,15 +142,24 @@ http {
|
||||
location / {
|
||||
include /etc/nginx/overrides/location.d/*.conf;
|
||||
|
||||
location ~* \.(js|css|jpg|png|svg|ttf|woff|woff2|wasm)$ {
|
||||
add_header Cache-Control "public, max-age=604800" always; # 7 days
|
||||
location ~ ^/js/config.js$ {
|
||||
add_header Cache-Control "no-store, no-cache, max-age=0" always;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|jpg|svg|png|mjs|map)$ {
|
||||
add_header Cache-Control "max-age=604800" always; # 7 days
|
||||
}
|
||||
|
||||
location ~ ^/(/|css|fonts|images|js|wasm|mjs|map) {
|
||||
}
|
||||
|
||||
location ~ ^/[^/]+/(.*)$ {
|
||||
return 301 " /404";
|
||||
}
|
||||
|
||||
add_header Last-Modified $date_gmt;
|
||||
add_header Cache-Control "no-store, no-cache, max-age=0" always;
|
||||
if_modified_since off;
|
||||
try_files $uri /index.html$is_args$args /index.html =404;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,19 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/your-account">
|
||||
<h2>Your account →</h2>
|
||||
<p>Access your account settings and manage personal access tokens</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/teams">
|
||||
<h2>Teams →</h2>
|
||||
<p>Create and manage your teams</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/comments/">
|
||||
<h2>Comments →</h2>
|
||||
<p>Give and receive feedback right over your designs</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -10,31 +10,31 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/assets">
|
||||
<h2>Assets →</h2>
|
||||
<p>Store elements and styles to easily reuse them</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/libraries">
|
||||
<h2>Libraries →</h2>
|
||||
<p>Organize and manage your stored elements with Libraries</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/components">
|
||||
<h2>Components →</h2>
|
||||
<p>Speed your design workflow with reusable components</p>
|
||||
<p>Speed your design workflow</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/variants">
|
||||
<h2>Variants →</h2>
|
||||
<p>Group components into a single, customizable one</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/design-tokens">
|
||||
<h2>Design Tokens →</h2>
|
||||
<p>Synchronize visual elements across your designs</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -5,7 +5,7 @@ desc: Use Penpot's libraries for reusable design elements! Learn to create, mana
|
||||
---
|
||||
<h1 id="libraries">Libraries</h1>
|
||||
|
||||
<p class="main-paragraph">Libraries may include components, colors and typographies. Learn how to create and manage them to better organize the pieces of your designs and speed your workflow.</p>
|
||||
<p class="main-paragraph">Libraries may include components, graphics, colors and typographies. Learn how to create and manage them to better organize the pieces of your designs and speed your workflow.</p>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -10,31 +10,31 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<li>
|
||||
<a href="/user-guide/designing/workspace-basics">
|
||||
<h2>Workspace basics →</h2>
|
||||
<p>Get to know the Workspace, where designs are created</p>
|
||||
<p>Workspace basics</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/layers">
|
||||
<h2>Layers →</h2>
|
||||
<p>Objects available in Penpot and how to get the most of them</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/color-stroke/">
|
||||
<h2>Color & Strokes→</h2>
|
||||
<p>Styling options available for each layer</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/text-typo">
|
||||
<h2>Text & Typography→</h2>
|
||||
<p>Styling text content & using custom fonts</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/flexible-layouts">
|
||||
<h2>Flexible layouts →</h2>
|
||||
<p>Create designs that adapt automatically</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -10,13 +10,13 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<li>
|
||||
<a href="/user-guide/export-import/export-import-files/">
|
||||
<h2>Export/Import Penpot files →</h2>
|
||||
<p>How to export and import your Penpot files</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/export-import/exporting-layers/">
|
||||
<h2>Exporting layers →</h2>
|
||||
<p>How to export elements from your design into different file formats</p>
|
||||
<p>Exporting layers</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -16,7 +16,7 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<li>
|
||||
<a href="/user-guide/first-steps/the-interface">
|
||||
<h2>Interface tour →</h2>
|
||||
<p>Take a tour of Penpot's main areas</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -28,7 +28,7 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<li>
|
||||
<a href="/user-guide/first-steps/info">
|
||||
<h2>Tutorials & info →</h2>
|
||||
<p>Useful resources to better understand Penpot</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -22,49 +22,49 @@ eleventyNavigation:
|
||||
<li>
|
||||
<a href="/user-guide/designing/layers/">
|
||||
<h2>Layers</h2>
|
||||
<p>Objects available in Penpot and how to get the most of them</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/flexible-layouts/">
|
||||
<h2>Flexible layouts</h2>
|
||||
<p>Create designs that adapt automatically</p>
|
||||
<p>Create designs that adapt automatically.</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/components/">
|
||||
<h2>Components</h2>
|
||||
<p>Speed your design workflow with reusable components</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/variants/">
|
||||
<h2>Variants</h2>
|
||||
<p>Group components into a single, customizable one</p>
|
||||
<p>Penpot's main areas and features</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/design-tokens/">
|
||||
<h2>Design Tokens</h2>
|
||||
<p>Synchronize visual elements across your designs</p>
|
||||
<p>Penpot's main areas and features</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/dev-tools/#inspect-design">
|
||||
<h2>Inspect design</h2>
|
||||
<p>Get production-ready code</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/prototyping-testing/prototyping/">
|
||||
<h2>Prototyping</h2>
|
||||
<p>Build interactive prototypes to mimic your product behaviour</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/libraries/">
|
||||
<h2>Libraries</h2>
|
||||
<p>Organize and manage your stored elements with Libraries</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -10,13 +10,13 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<li>
|
||||
<a href="/user-guide/prototyping-testing/prototyping">
|
||||
<h2>Prototyping →</h2>
|
||||
<p>Build interactive prototypes to mimic your product behaviour</p>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/prototyping-testing/testing-view-mode">
|
||||
<h2>Testing: View mode →</h2>
|
||||
<p>Test your designs and play the interactions</p>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -7,5 +7,4 @@ bb -i '(babashka.wait/wait-for-port "localhost" 9630)';
|
||||
bb -i '(babashka.wait/wait-for-path "target/app.js")';
|
||||
sleep 2;
|
||||
|
||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
exec node target/app.js
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
(def browser-pool-factory
|
||||
(letfn [(create []
|
||||
(p/let [opts #js {:args #js ["--allow-insecure-localhost" "--font-render-hinting=none"]}
|
||||
(p/let [opts #js {:args #js ["--font-render-hinting=none"]}
|
||||
browser (.launch pw/chromium opts)
|
||||
id (swap! pool-browser-id inc)]
|
||||
(l/info :origin "factory" :action "create" :browser-id id)
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
(p/fmap (fn [resource]
|
||||
(assoc exchange :response/body resource)))
|
||||
(p/merr (fn [cause]
|
||||
(l/error :hint "unexpected error on single export"
|
||||
(l/error :hint "unexpected error on export multiple"
|
||||
:cause cause)
|
||||
(p/rejected cause))))))
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
(redis/pub! topic data))))
|
||||
|
||||
on-error (fn [cause]
|
||||
(l/error :hint "unexpected error on multiple export" :cause cause)
|
||||
(l/error :hint "unexpected error on multiple exportation" :cause cause)
|
||||
(if wait
|
||||
(p/rejected cause)
|
||||
(redis/pub! topic {:type :export-update
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { withThemeByClassName } from "@storybook/addon-themes";
|
||||
|
||||
|
||||
import Components from "@target/components";
|
||||
import translations from "@public/translation.en.js";
|
||||
Components.setDefaultTranslations(translations);
|
||||
|
||||
import '../resources/public/css/ds.css';
|
||||
|
||||
export const decorators = [
|
||||
|
||||
@@ -73,7 +73,7 @@ export class BasePage {
|
||||
}
|
||||
|
||||
static async mockConfigFlags(page, flags) {
|
||||
const url = "**/js/config.js*";
|
||||
const url = "**/js/config.js";
|
||||
return await page.route(url, (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 821 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 676 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB |
@@ -746,6 +746,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
@include flexCenter;
|
||||
height: $s-48;
|
||||
width: $s-48;
|
||||
border-radius: $br-circle;
|
||||
background-color: var(--empty-message-background-color);
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
height: $s-28;
|
||||
width: $s-28;
|
||||
stroke: var(--empty-message-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.attr-title {
|
||||
div {
|
||||
margin-left: 0;
|
||||
|
||||
@@ -17,25 +17,23 @@
|
||||
<meta name="twitter:site" content="@penpotapp">
|
||||
<meta name="twitter:creator" content="@penpotapp">
|
||||
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
|
||||
<link id="theme" href="css/main.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
||||
<link id="theme" href="css/main.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
{{#isDebug}}
|
||||
<link href="css/debug.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
||||
<link href="css/debug.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
{{/isDebug}}
|
||||
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
|
||||
<script type="module">
|
||||
globalThis.penpotVersion = "{{& version}}";
|
||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
|
||||
</script>
|
||||
|
||||
{{# manifest}}
|
||||
<script>window.penpotWorkerURI="{{& worker_main}}"</script>
|
||||
<script src="{{& config}}"></script>
|
||||
<script src="{{& polyfills}}"></script>
|
||||
<script type="importmap">{{& importmap }}</script>
|
||||
{{/manifest}}
|
||||
|
||||
<script type="module">
|
||||
globalThis.penpotTranslations = JSON.parse({{& translations}});
|
||||
globalThis.penpotVersion = "%version%";
|
||||
globalThis.penpotBuildDate = "%buildDate%";
|
||||
</script>
|
||||
<!--cookie-consent-->
|
||||
</head>
|
||||
<body>
|
||||
@@ -48,7 +46,7 @@
|
||||
{{# manifest}}
|
||||
<script type="module" src="{{& libs}}"></script>
|
||||
<script type="module">
|
||||
import { init } from "{{& app_main}}";
|
||||
import { init } from "{{& main}}";
|
||||
init();
|
||||
</script>
|
||||
{{/manifest}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<link href="./css/ds.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
||||
<link href="./css/ds.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
@@ -9,3 +9,7 @@
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
window.penpotTranslations = JSON.parse({{& translations}});
|
||||
</script>
|
||||
|
||||
@@ -6,22 +6,22 @@
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
|
||||
<script>
|
||||
globalThis.penpotVersion = "{{& version}}";
|
||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
|
||||
window.penpotVersion = "%version%";
|
||||
window.penpotBuildDate = "%buildDate%";
|
||||
</script>
|
||||
|
||||
{{# manifest}}
|
||||
<script>window.penpotWorkerURI="{{& worker_main}}"</script>
|
||||
<script src="{{& config}}"></script>
|
||||
<script src="{{& polyfills}}"></script>
|
||||
<script type="importmap">{{& importmap }}</script>
|
||||
{{/manifest}}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
{{# manifest}}
|
||||
<script type="module" src="{{& libs}}"></script>
|
||||
<script type="module">
|
||||
import { init } from "{{& rasterizer_main}}";
|
||||
import { init } from "{{& rasterizer}}";
|
||||
init();
|
||||
</script>
|
||||
{{/manifest}}
|
||||
|
||||
@@ -7,14 +7,12 @@
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
|
||||
<script>
|
||||
globalThis.penpotVersion = "{{& version}}";
|
||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||
window.penpotVersion = "%version%";
|
||||
</script>
|
||||
|
||||
{{# manifest}}
|
||||
<script src="{{& config}}"></script>
|
||||
<script src="{{& polyfills}}"></script>
|
||||
<script type="importmap">{{& importmap }}</script>
|
||||
{{/manifest}}
|
||||
</head>
|
||||
<body>
|
||||
@@ -22,7 +20,7 @@
|
||||
{{# manifest}}
|
||||
<script type="module" src="{{& libs}}"></script>
|
||||
<script type="module">
|
||||
import { init } from "{{& render_main}}";
|
||||
import { init } from "{{& render}}";
|
||||
init();
|
||||
</script>
|
||||
{{/manifest}}
|
||||
|
||||
@@ -28,8 +28,6 @@ export function startWorker() {
|
||||
}
|
||||
|
||||
export const isDebug = process.env.NODE_ENV !== "production";
|
||||
export const CURRENT_VERSION = process.env.CURRENT_VERSION || "develop";
|
||||
export const BUILD_DATE = process.env.BUILD_DATE || "" + new Date();
|
||||
|
||||
async function findFiles(basePath, predicate, options = {}) {
|
||||
predicate =
|
||||
@@ -49,7 +47,8 @@ async function findFiles(basePath, predicate, options = {}) {
|
||||
function syncDirs(originPath, destPath) {
|
||||
const command = `rsync -ar --delete ${originPath} ${destPath}`;
|
||||
|
||||
return new Promise((resolve, reject) => {proc.exec(command, (cause, stdout) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
proc.exec(command, (cause, stdout) => {
|
||||
if (cause) {
|
||||
reject(cause);
|
||||
} else {
|
||||
@@ -181,41 +180,44 @@ export async function watch(baseDir, predicate, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
async function readManifestFile(resource) {
|
||||
const manifestPath = "resources/public/" + resource;
|
||||
async function readManifestFile() {
|
||||
const manifestPath = "resources/public/js/manifest.json";
|
||||
let content = await fs.readFile(manifestPath, { encoding: "utf8" });
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
async function readShadowManifest() {
|
||||
const index = {
|
||||
app_main: "./js/main.js",
|
||||
render_main: "./js/render.js",
|
||||
rasterizer_main: "./js/rasterizer.js",
|
||||
const ts = Date.now();
|
||||
try {
|
||||
const content = await readManifestFile();
|
||||
|
||||
config: "./js/config.js?version=" + CURRENT_VERSION,
|
||||
polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION,
|
||||
libs: "./js/libs.js?version=" + CURRENT_VERSION,
|
||||
worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION,
|
||||
const index = {
|
||||
ts: ts,
|
||||
config: "./js/config.js",
|
||||
polyfills: "./js/polyfills.js",
|
||||
worker_main: "./js/worker/main.js",
|
||||
libs: "./js/libs.js",
|
||||
};
|
||||
|
||||
importmap: JSON.stringify({
|
||||
"imports": {
|
||||
"./js/shared.js": "./js/shared.js?version=" + CURRENT_VERSION,
|
||||
"./js/main.js": "./js/main.js?version=" + CURRENT_VERSION,
|
||||
"./js/render.js": "./js/render.js?version=" + CURRENT_VERSION,
|
||||
"./js/render-wasm.js": "./js/render-wasm.js?version=" + CURRENT_VERSION,
|
||||
"./js/rasterizer.js": "./js/rasterizer.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-dashboard.js": "./js/main-dashboard.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-auth.js": "./js/main-auth.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-viewer.js": "./js/main-viewer.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-settings.js": "./js/main-settings.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-workspace.js": "./js/main-workspace.js?version=" + CURRENT_VERSION,
|
||||
"./js/util-highlight.js": "./js/util-highlight.js?version=" + CURRENT_VERSION
|
||||
}
|
||||
})
|
||||
};
|
||||
for (let item of content) {
|
||||
index[item.name] = "./js/" + item["output-name"] + "";
|
||||
}
|
||||
|
||||
return index;
|
||||
return index;
|
||||
} catch (cause) {
|
||||
const index = {
|
||||
ts: ts,
|
||||
config: "./js/config.js",
|
||||
polyfills: "./js/polyfills.js",
|
||||
main: "./js/main.js",
|
||||
shared: "./js/shared.js",
|
||||
worker_main: "./js/worker/main.js",
|
||||
rasterizer: "./js/rasterizer.js",
|
||||
libs: "./js/libs.js",
|
||||
};
|
||||
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
async function renderTemplate(path, context = {}, partials = {}) {
|
||||
@@ -255,7 +257,7 @@ const markedOptions = {
|
||||
|
||||
marked.use(markedOptions);
|
||||
|
||||
export async function compileTranslations() {
|
||||
async function readTranslations() {
|
||||
const langs = [
|
||||
"ar",
|
||||
"ca",
|
||||
@@ -292,10 +294,9 @@ export async function compileTranslations() {
|
||||
["uk", "ukr_UA"],
|
||||
"ha",
|
||||
];
|
||||
const result = {};
|
||||
|
||||
for (let lang of langs) {
|
||||
const result = {};
|
||||
|
||||
let filename = `${lang}.po`;
|
||||
if (l.isArray(lang)) {
|
||||
filename = `${lang[1]}.po`;
|
||||
@@ -314,6 +315,11 @@ export async function compileTranslations() {
|
||||
for (let key of Object.keys(trdata)) {
|
||||
if (key === "") continue;
|
||||
const comments = trdata[key].comments || {};
|
||||
|
||||
if (l.isNil(result[key])) {
|
||||
result[key] = {};
|
||||
}
|
||||
|
||||
const isMarkdown = l.includes(comments.flag, "markdown");
|
||||
|
||||
const msgs = trdata[key].msgstr;
|
||||
@@ -323,9 +329,9 @@ export async function compileTranslations() {
|
||||
message = marked.parseInline(message);
|
||||
}
|
||||
|
||||
result[key] = message;
|
||||
result[key][lang] = message;
|
||||
} else {
|
||||
result[key] = msgs.map((item) => {
|
||||
result[key][lang] = msgs.map((item) => {
|
||||
if (isMarkdown) {
|
||||
return marked.parseInline(item);
|
||||
} else {
|
||||
@@ -334,12 +340,22 @@ export async function compileTranslations() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const esm = `export default ${JSON.stringify(result, null, 0)};\n`;
|
||||
const outputDir = "resources/public/js/";
|
||||
const outputFile = ph.join(outputDir, "translation." + lang + ".js");
|
||||
await fs.writeFile(outputFile, esm);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function filterTranslations(translations, langs = [], keyFilter) {
|
||||
const filteredEntries = Object.entries(translations)
|
||||
.filter(([translationKey, _]) => keyFilter(translationKey))
|
||||
.map(([translationKey, value]) => {
|
||||
const langEntries = Object.entries(value).filter(([lang, _]) =>
|
||||
langs.includes(lang),
|
||||
);
|
||||
return [translationKey, Object.fromEntries(langEntries)];
|
||||
});
|
||||
|
||||
return Object.fromEntries(filteredEntries);
|
||||
}
|
||||
|
||||
async function generateSvgSprite(files, prefix) {
|
||||
@@ -391,6 +407,14 @@ async function generateTemplates() {
|
||||
const isDebug = process.env.NODE_ENV !== "production";
|
||||
await fs.mkdir("./resources/public/", { recursive: true });
|
||||
|
||||
let translations = await readTranslations();
|
||||
const storybookTranslations = JSON.stringify(
|
||||
filterTranslations(translations, ["en"], (key) =>
|
||||
key.startsWith("labels."),
|
||||
),
|
||||
);
|
||||
translations = JSON.stringify(translations);
|
||||
|
||||
const manifest = await readShadowManifest();
|
||||
let content;
|
||||
|
||||
@@ -412,16 +436,13 @@ async function generateTemplates() {
|
||||
"../public/images/sprites/assets.svg": assetsSprite,
|
||||
};
|
||||
|
||||
const context = {
|
||||
manifest: manifest,
|
||||
version: CURRENT_VERSION,
|
||||
build_date: BUILD_DATE,
|
||||
isDebug,
|
||||
};
|
||||
|
||||
content = await renderTemplate(
|
||||
"resources/templates/index.mustache",
|
||||
context,
|
||||
{
|
||||
manifest: manifest,
|
||||
translations: JSON.stringify(translations),
|
||||
isDebug,
|
||||
},
|
||||
partials,
|
||||
);
|
||||
|
||||
@@ -429,30 +450,41 @@ async function generateTemplates() {
|
||||
|
||||
content = await renderTemplate(
|
||||
"resources/templates/challenge.mustache",
|
||||
context,
|
||||
{},
|
||||
partials,
|
||||
);
|
||||
await fs.writeFile("./resources/public/challenge.html", content);
|
||||
|
||||
content = await renderTemplate(
|
||||
"resources/templates/preview-body.mustache",
|
||||
context,
|
||||
{
|
||||
manifest: manifest,
|
||||
},
|
||||
partials,
|
||||
);
|
||||
await fs.writeFile("./.storybook/preview-body.html", content);
|
||||
|
||||
content = await renderTemplate(
|
||||
"resources/templates/preview-head.mustache",
|
||||
context,
|
||||
{
|
||||
manifest: manifest,
|
||||
translations: JSON.stringify(storybookTranslations),
|
||||
},
|
||||
partials,
|
||||
);
|
||||
await fs.writeFile("./.storybook/preview-head.html", content);
|
||||
|
||||
content = await renderTemplate("resources/templates/render.mustache", context);
|
||||
content = await renderTemplate("resources/templates/render.mustache", {
|
||||
manifest: manifest,
|
||||
translations: JSON.stringify(translations),
|
||||
});
|
||||
|
||||
await fs.writeFile("./resources/public/render.html", content);
|
||||
|
||||
content = await renderTemplate("resources/templates/rasterizer.mustache", context);
|
||||
content = await renderTemplate("resources/templates/rasterizer.mustache", {
|
||||
manifest: manifest,
|
||||
translations: JSON.stringify(translations),
|
||||
});
|
||||
|
||||
await fs.writeFile("./resources/public/rasterizer.html", content);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,16 @@ fi
|
||||
yarn run build:app:libs || exit 1;
|
||||
yarn run build:app:assets || exit 1;
|
||||
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./resources/public/index.html;
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./resources/public/render.html;
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./resources/public/rasterizer.html;
|
||||
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./resources/public/index.html;
|
||||
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./resources/public/rasterizer.html;
|
||||
|
||||
if [ "$INCLUDE_WASM" = "yes" ]; then
|
||||
sed -i "s/version=develop/version=$CURRENT_VERSION/g" ./resources/public/js/render_wasm.js;
|
||||
fi
|
||||
|
||||
rsync -avr resources/public/ target/dist/;
|
||||
|
||||
if [ "$INCLUDE_STORYBOOK" = "yes" ]; then
|
||||
|
||||
@@ -4,6 +4,5 @@ await h.compileStyles();
|
||||
await h.copyAssets();
|
||||
await h.copyWasmPlayground();
|
||||
await h.compileSvgSprites();
|
||||
await h.compileTranslations();
|
||||
await h.compileTemplates();
|
||||
await h.compilePolyfills();
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import * as h from "./_helpers.js";
|
||||
|
||||
await fs.mkdir("resources/public/js", {recursive: true});
|
||||
|
||||
await h.compileStorybookStyles();
|
||||
await h.copyAssets();
|
||||
await h.compileSvgSprites();
|
||||
await h.compileTranslations();
|
||||
await h.compileTemplates();
|
||||
await h.compilePolyfills();
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export JAVA_OPTS="\
|
||||
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
||||
-Djdk.attach.allowAttachSelf \
|
||||
-Dlog4j2.configurationFile=log4j2-devenv-repl.xml \
|
||||
-Djdk.tracePinnedThreads=full \
|
||||
-XX:+EnableDynamicAgentLoading \
|
||||
-XX:-OmitStackTraceInFastThrow \
|
||||
-XX:+UnlockDiagnosticVMOptions \
|
||||
-XX:+DebugNonSafepoints \
|
||||
--sun-misc-unsafe-memory-access=allow \
|
||||
--enable-preview \
|
||||
--enable-native-access=ALL-UNNAMED";
|
||||
|
||||
export OPTIONS="-A:dev"
|
||||
export OPTIONS="-A:dev -J-XX:-OmitStackTraceInFastThrow";
|
||||
|
||||
set -ex
|
||||
exec clojure $OPTIONS -M -e "$OPTIONS_EVAL" -m rebel-readline.main
|
||||
exec clojure $OPTIONS -M -m rebel-readline.main
|
||||
|
||||
@@ -52,7 +52,6 @@ await fs.mkdir("./resources/public/css/", { recursive: true });
|
||||
await compileSassAll();
|
||||
await h.copyAssets();
|
||||
await h.copyWasmPlayground();
|
||||
await h.compileTranslations();
|
||||
await h.compileSvgSprites();
|
||||
await h.compileTemplates();
|
||||
await h.compilePolyfills();
|
||||
@@ -82,7 +81,7 @@ h.watch("resources/templates", null, async function (path) {
|
||||
log.info("watch: translations (~)");
|
||||
h.watch("translations", null, async function (path) {
|
||||
log.info("changed:", path);
|
||||
await h.compileTranslations();
|
||||
await h.compileTemplates();
|
||||
});
|
||||
|
||||
log.info("watch: assets (~)");
|
||||
|
||||
@@ -23,28 +23,28 @@
|
||||
|
||||
:util-highlight
|
||||
{:entries [app.util.code-highlight]
|
||||
:depends-on #{:shared}}
|
||||
:depends-on #{:main}}
|
||||
|
||||
:main-auth
|
||||
{:entries [app.main.ui.auth
|
||||
app.main.ui.auth.verify-token]
|
||||
:depends-on #{:shared}}
|
||||
:depends-on #{:main}}
|
||||
|
||||
:main-viewer
|
||||
{:entries [app.main.ui.viewer]
|
||||
:depends-on #{:shared :main-auth}}
|
||||
:depends-on #{:main :main-auth}}
|
||||
|
||||
:main-workspace
|
||||
{:entries [app.main.ui.workspace]
|
||||
:depends-on #{:shared}}
|
||||
:depends-on #{:main}}
|
||||
|
||||
:main-dashboard
|
||||
{:entries [app.main.ui.dashboard]
|
||||
:depends-on #{:shared}}
|
||||
:depends-on #{:main}}
|
||||
|
||||
:main-settings
|
||||
{:entries [app.main.ui.settings]
|
||||
:depends-on #{:shared}}
|
||||
:depends-on #{:main}}
|
||||
|
||||
:render
|
||||
{:entries [app.render]
|
||||
@@ -81,7 +81,7 @@
|
||||
:source-map-detail-level :all}}}
|
||||
|
||||
:worker
|
||||
{:target :browser
|
||||
{:target :esm
|
||||
:output-dir "resources/public/js/worker/"
|
||||
:asset-path "/js/worker"
|
||||
:devtools {:browser-inject :main
|
||||
@@ -92,7 +92,6 @@
|
||||
{:main
|
||||
{:entries [app.worker]
|
||||
:web-worker true
|
||||
:prepend-js "importScripts('/js/worker/render.js');"
|
||||
:depends-on #{}}}
|
||||
|
||||
:js-options
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
(def default-theme "default")
|
||||
(def default-language "en")
|
||||
|
||||
(def translations (obj/get global "penpotTranslations"))
|
||||
(def themes (obj/get global "penpotThemes"))
|
||||
|
||||
(def build-date (parse-build-date global))
|
||||
@@ -126,7 +127,7 @@
|
||||
public-uri))
|
||||
|
||||
(def worker-uri
|
||||
(obj/get global "penpotWorkerURI" "/js/worker/main.js"))
|
||||
(obj/get global "penpotWorkerURI" "/js/worker.js"))
|
||||
|
||||
(defn external-feature-flag
|
||||
[flag value]
|
||||
@@ -188,11 +189,7 @@
|
||||
(true? thumbnail?) (u/join (dm/str id "/thumbnail"))
|
||||
(false? thumbnail?) (u/join (dm/str id)))))))
|
||||
|
||||
(defn resolve-href
|
||||
[resource]
|
||||
(let [version (get version :full)
|
||||
href (-> public-uri
|
||||
(u/ensure-path-slash)
|
||||
(u/join resource)
|
||||
(get :path))]
|
||||
(str href "?version=" version)))
|
||||
(defn resolve-static-asset
|
||||
[path]
|
||||
(let [uri (u/join public-uri path)]
|
||||
(assoc uri :query (dm/str "version=" (:full version)))))
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
(defn ^:export init
|
||||
[]
|
||||
(mw/init!)
|
||||
(i18n/init)
|
||||
(i18n/init! cf/translations)
|
||||
(cur/init-styles)
|
||||
(thr/init!)
|
||||
(init-ui)
|
||||
@@ -114,4 +114,11 @@
|
||||
[]
|
||||
(reinit))
|
||||
|
||||
;; Reload the UI when the language changes
|
||||
(add-watch
|
||||
i18n/locale "locale"
|
||||
(fn [_ _ old-value current-value]
|
||||
(when (not= old-value current-value)
|
||||
(reinit))))
|
||||
|
||||
(set! (.-stackTraceLimit js/Error) 50)
|
||||
|
||||
@@ -148,17 +148,17 @@
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "Google Pixel 7 Pro"
|
||||
:width 412
|
||||
:height 892}
|
||||
:width 1440
|
||||
:height 3120}
|
||||
{:name "Google Pixel 6a/6"
|
||||
:width 412
|
||||
:height 915}
|
||||
:width 1080
|
||||
:height 2400}
|
||||
{:name "Google Pixel 4a/5"
|
||||
:width 393
|
||||
:height 851}
|
||||
{:name "Samsung Galaxy S22"
|
||||
:width 360
|
||||
:height 780}
|
||||
:width 1080
|
||||
:height 2340}
|
||||
{:name "Samsung Galaxy S20+"
|
||||
:width 384
|
||||
:height 854}
|
||||
|
||||
@@ -67,8 +67,8 @@
|
||||
[]
|
||||
(let [uagent (new ua/UAParser)]
|
||||
(merge
|
||||
{:version (:full cf/version)
|
||||
:locale i18n/*current-locale*}
|
||||
{:app-version (:full cf/version)
|
||||
:locale @i18n/locale}
|
||||
(let [browser (.getBrowser uagent)]
|
||||
{:browser (obj/get browser "name")
|
||||
:browser-version (obj/get browser "version")})
|
||||
@@ -98,9 +98,7 @@
|
||||
(def context
|
||||
(atom (d/without-nils (collect-context))))
|
||||
|
||||
(add-watch i18n/state "events"
|
||||
(fn [_ _ _ v]
|
||||
(swap! context assoc :locale (get v :locale))))
|
||||
(add-watch i18n/locale ::events #(swap! context assoc :locale %4))
|
||||
|
||||
;; --- EVENT TRANSLATION
|
||||
|
||||
|
||||
@@ -53,16 +53,11 @@
|
||||
(assoc :profile-id id)
|
||||
(assoc :profile profile)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [profile (:profile state)]
|
||||
(->> (rx/from (i18n/set-locale (:lang profile)))
|
||||
(rx/ignore))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [profile (:profile state)]
|
||||
(swap! storage/user assoc :profile profile)
|
||||
(i18n/set-locale! (:lang profile))
|
||||
(plugins.register/init)))))
|
||||
|
||||
(def profile-fetched?
|
||||
|
||||
@@ -59,15 +59,9 @@
|
||||
"Parses `value` of a color `sd-token` into a map like `{:value 1 :unit \"px\"}`.
|
||||
If the value is not parseable and/or has missing references returns a map with `:errors`."
|
||||
[value]
|
||||
(let [missing-references (seq (cto/find-token-value-references value))]
|
||||
(if-let [tc (tinycolor/valid-color value)]
|
||||
{:value value :unit (tinycolor/color-format tc)}
|
||||
(cond
|
||||
missing-references
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference missing-references)]
|
||||
:references missing-references}
|
||||
:else
|
||||
{:errors [(wte/error-with-value :error.token/invalid-color value)]}))))
|
||||
(if-let [tc (tinycolor/valid-color value)]
|
||||
{:value value :unit (tinycolor/color-format tc)}
|
||||
{:errors [(wte/error-with-value :error.token/invalid-color value)]}))
|
||||
|
||||
(defn- numeric-string? [s]
|
||||
(and (string? s)
|
||||
@@ -126,7 +120,7 @@
|
||||
If the `value` is not parseable and/or has missing references returns a map with `:errors`.
|
||||
If the `value` is parseable but is out of range returns a map with `warnings`."
|
||||
[value]
|
||||
(let [missing-references? (seq (seq (cto/find-token-value-references value)))
|
||||
(let [missing-references? (seq (cto/find-token-value-references value))
|
||||
parsed-value (cft/parse-token-value value)
|
||||
out-of-scope (not (<= 0 (:value parsed-value) 1))
|
||||
references (seq (cto/find-token-value-references value))]
|
||||
|
||||
@@ -351,31 +351,19 @@
|
||||
(on-success))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
||||
(def ^:private schema:create-invitation
|
||||
[:and
|
||||
[:map
|
||||
[:emails {:optional true} [::sm/set ::sm/email]]
|
||||
[:invitations {:optional true}
|
||||
[:vector
|
||||
[:map
|
||||
[:email ::sm/email]
|
||||
[:role [::sm/one-of ctt/valid-roles]]]]]
|
||||
[:team-id ::sm/uuid]
|
||||
[:resend? {:optional true} ::sm/boolean]]
|
||||
[:fn (fn [attrs]
|
||||
(or (contains? attrs :emails)
|
||||
(contains? attrs :invitations)))]])
|
||||
|
||||
(def ^:private check-create-invitations-params
|
||||
(sm/check-fn schema:create-invitation))
|
||||
|
||||
(defn create-invitations
|
||||
"Unified function to create invitations. Supports two parameter formats:
|
||||
1. {:emails #{...} :role :admin :team-id uuid} - single role for all emails
|
||||
2. {:invitations [{:email ... :role ...}] :team-id uuid} - individual roles per email"
|
||||
[{:keys [emails role team-id invitations resend?] :as params}]
|
||||
(check-create-invitations-params params)
|
||||
|
||||
(assert (uuid? team-id))
|
||||
;; Validate input format - must have either emails+role OR invitations
|
||||
(assert (or (and emails role (sm/check-set-of-emails emails) (keyword? role))
|
||||
(and invitations
|
||||
(sm/check-set-of-emails (map :email invitations))
|
||||
(every? #(contains? ctt/valid-roles (:role %)) invitations)))
|
||||
"Must provide either emails+role or invitations with individual roles")
|
||||
|
||||
(ptk/reify ::create-invitations
|
||||
ev/Event
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
[app.common.path-names :as cpn]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.comments :as dcmt]
|
||||
@@ -553,6 +551,7 @@
|
||||
component-id (:component-id shape)
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
|
||||
(when valid?
|
||||
(if (ctc/is-variant-container? shape)
|
||||
;; Rename the full variant when it is a variant container
|
||||
@@ -567,43 +566,6 @@
|
||||
(dwl/rename-component component-id clean-name))
|
||||
(dwu/commit-undo-transaction undo-id))))))))))
|
||||
|
||||
(defn rename-shape-or-variant
|
||||
([id name]
|
||||
(rename-shape-or-variant nil nil id name))
|
||||
([file-id page-id id name]
|
||||
(ptk/reify ::rename-shape-or-variant
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (d/nilv file-id (:current-file-id state))
|
||||
page-id (d/nilv page-id (:current-page-id state))
|
||||
|
||||
file-data (dsh/lookup-file-data state file-id)
|
||||
shape
|
||||
(-> (dsh/lookup-page-objects state file-id page-id)
|
||||
(get id))
|
||||
|
||||
is-variant? (ctc/is-variant? shape)
|
||||
variant-id (when is-variant? (:variant-id shape))
|
||||
variant-name (when is-variant? (:variant-name shape))
|
||||
component-id (:component-id shape)
|
||||
component (ctkl/get-component file-data (:component-id shape))
|
||||
variant-properties (:variant-properties component)]
|
||||
(cond
|
||||
(and variant-name (ctv/valid-properties-formula? name))
|
||||
(rx/of (dwva/update-properties-names-and-values
|
||||
component-id variant-id variant-properties (ctv/properties-formula->map name))
|
||||
(dwva/remove-empty-properties variant-id)
|
||||
(dwva/update-error component-id))
|
||||
|
||||
variant-name
|
||||
(rx/of (dwva/update-properties-names-and-values
|
||||
component-id variant-id variant-properties {})
|
||||
(dwva/remove-empty-properties variant-id)
|
||||
(dwva/update-error component-id name))
|
||||
|
||||
:else
|
||||
(rx/of (end-rename-shape id name))))))))
|
||||
|
||||
;; --- Update Selected Shapes attrs
|
||||
|
||||
(defn update-selected-shapes
|
||||
|
||||
@@ -128,16 +128,14 @@
|
||||
related-components (cfv/find-variant-components data objects variant-id)
|
||||
|
||||
props (-> related-components last :variant-properties)
|
||||
valid-pos? (> (count props) pos)
|
||||
prop-name (when valid-pos? (-> props (nth pos) :name))
|
||||
prop-name (-> props (nth pos) :name)
|
||||
|
||||
changes (when valid-pos?
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/with-library-data data)
|
||||
(clvp/generate-update-property-name variant-id pos new-name)))
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/with-library-data data)
|
||||
(clvp/generate-update-property-name variant-id pos new-name))
|
||||
undo-id (js/Symbol)]
|
||||
(when (and valid-pos? (not= prop-name new-name))
|
||||
(when (not= prop-name new-name)
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
[app.main.ui.components.dropdown :refer [dropdown-content*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as ug]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.timers :as tm]
|
||||
@@ -54,13 +53,14 @@
|
||||
(def ^:private valid-option?
|
||||
(sm/lazy-validator schema:option))
|
||||
|
||||
(mf/defc context-menu-inner*
|
||||
[{:keys [on-close options selectable selected
|
||||
(mf/defc context-menu*
|
||||
[{:keys [show on-close options selectable selected
|
||||
top left fixed min-width origin width]
|
||||
:as props}]
|
||||
|
||||
(assert (every? valid-option? options) "expected valid options")
|
||||
(assert (fn? on-close) "missing `on-close` prop")
|
||||
(assert (boolean? show) "missing `show` prop")
|
||||
(assert (vector? options) "missing `options` prop")
|
||||
|
||||
(let [width (d/nilv width "initial")
|
||||
@@ -80,15 +80,14 @@
|
||||
offset-x (get state :offset-x)
|
||||
offset-y (get state :offset-y)
|
||||
levels (get state :levels)
|
||||
internal-id (mf/use-id)
|
||||
|
||||
on-local-close
|
||||
(mf/use-fn
|
||||
(mf/deps on-close)
|
||||
(fn []
|
||||
(swap! state* assoc :levels [{:parent nil :options options}])
|
||||
(when (fn? on-close)
|
||||
(on-close))))
|
||||
(swap! state* assoc :levels [{:parent nil
|
||||
:options options}])
|
||||
(on-close)))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-close on-local-close})
|
||||
@@ -217,22 +216,11 @@
|
||||
(swap! state* assoc :levels [{:parent nil
|
||||
:options options}]))
|
||||
|
||||
(mf/with-effect [internal-id]
|
||||
(ug/dispatch! (ug/event "penpot:context-menu:open" #js {:id internal-id})))
|
||||
|
||||
(mf/with-effect [internal-id on-local-close]
|
||||
(letfn [(on-event [event]
|
||||
(when-let [detail (unchecked-get event "detail")]
|
||||
(when (not= internal-id (unchecked-get detail "id"))
|
||||
(on-local-close event))))]
|
||||
(ug/listen "penpot:context-menu:open" on-event)
|
||||
(partial ug/unlisten "penpot:context-menu:open" on-event)))
|
||||
|
||||
(mf/with-effect [ids]
|
||||
(tm/schedule-on-idle
|
||||
#(dom/focus! (dom/get-element (first ids)))))
|
||||
|
||||
(when (some? levels)
|
||||
(when (and show (some? levels))
|
||||
[:> dropdown-content* props
|
||||
(let [level (peek levels)
|
||||
options (:options level)
|
||||
@@ -241,7 +229,7 @@
|
||||
[:div {:class (stl/css-case
|
||||
:is-selectable selectable
|
||||
:context-menu true
|
||||
:is-open true
|
||||
:is-open show
|
||||
:fixed fixed)
|
||||
:style {:top (+ top offset-y)
|
||||
:left (+ left offset-x)}
|
||||
@@ -253,7 +241,7 @@
|
||||
:role "menu"
|
||||
:ref check-menu-offscreen}
|
||||
|
||||
(when parent
|
||||
(when-let [parent (:parent level)]
|
||||
[:*
|
||||
[:li {:id "go-back-sub-option"
|
||||
:class (stl/css :context-menu-item)
|
||||
@@ -268,7 +256,7 @@
|
||||
|
||||
[:li {:class (stl/css :separator)}]])
|
||||
|
||||
(for [[index option] (d/enumerate options)]
|
||||
(for [[index option] (d/enumerate (:options level))]
|
||||
(let [name (:name option)
|
||||
id (:id option)
|
||||
sub-options (:options option)
|
||||
@@ -309,12 +297,3 @@
|
||||
:data-testid id}
|
||||
name
|
||||
[:span {:class (stl/css :submenu-icon)} deprecated-icon/arrow]])]))))]])])))
|
||||
|
||||
(mf/defc context-menu*
|
||||
{::mf/private true}
|
||||
[{:keys [show] :as props}]
|
||||
|
||||
(assert (boolean? show) "expected `show` prop to be a boolean")
|
||||
|
||||
(when ^boolean show
|
||||
[:> context-menu-inner* props]))
|
||||
|
||||
@@ -151,16 +151,14 @@
|
||||
|
||||
(mf/defc menu-team-icon*
|
||||
[{:keys [subscription-type]}]
|
||||
[:span {:class (stl/css :subscription-icon-wrapper)}
|
||||
[:> icon* {:icon-id (case subscription-type
|
||||
"unlimited" i/character-u
|
||||
"enterprise" i/character-e)
|
||||
:class (stl/css :subscription-icon)
|
||||
:size "s"
|
||||
:title (if (= subscription-type "unlimited")
|
||||
(tr "subscription.dashboard.power-up.unlimited-plan")
|
||||
(tr "subscription.dashboard.power-up.enterprise-plan"))
|
||||
:data-testid "subscription-icon"}]])
|
||||
[:span {:class (stl/css :subscription-icon)
|
||||
:title (if (= subscription-type "unlimited")
|
||||
(tr "subscription.dashboard.power-up.unlimited-plan")
|
||||
(tr "subscription.dashboard.power-up.enterprise-plan"))
|
||||
:data-testid "subscription-icon"}
|
||||
(case subscription-type
|
||||
"unlimited" i/character-u
|
||||
"enterprise" i/character-e)])
|
||||
|
||||
(mf/defc main-menu-power-up*
|
||||
[{:keys [close-sub-menu]}]
|
||||
|
||||
@@ -144,20 +144,20 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.subscription-icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--color-background-primary);
|
||||
border-radius: $br-6;
|
||||
border: 1.75px solid var(--color-foreground-secondary);
|
||||
block-size: var(--sp-xl);
|
||||
inline-size: var(--sp-xl);
|
||||
}
|
||||
|
||||
.subscription-icon {
|
||||
@extend .button-icon;
|
||||
background: var(--color-background-primary);
|
||||
stroke: var(--color-foreground-secondary);
|
||||
border-radius: 6px;
|
||||
border: 1.75px solid var(--color-foreground-secondary);
|
||||
stroke-width: 2.25px;
|
||||
width: var(--sp-xl);
|
||||
height: var(--sp-xl);
|
||||
|
||||
svg {
|
||||
block-size: var(--sp-m);
|
||||
inline-size: var(--sp-m);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
|
||||
@@ -106,14 +106,14 @@
|
||||
(when (not= 0 count-libraries)
|
||||
(if (pos? (count references))
|
||||
[:*
|
||||
(when (and (string? scd-msg) (not= scd-msg ""))
|
||||
[:p {:class (stl/css :modal-scd-msg)} scd-msg])
|
||||
|
||||
[:ul {:class (stl/css :element-list)}
|
||||
(for [[file-id file-name] references]
|
||||
[:li {:class (stl/css :list-item)
|
||||
:key (dm/str file-id)}
|
||||
[:span "- " file-name]])]
|
||||
[:div
|
||||
(when (and (string? scd-msg) (not= scd-msg ""))
|
||||
[:h3 {:class (stl/css :modal-scd-msg)} scd-msg])
|
||||
[:ul {:class (stl/css :element-list)}
|
||||
(for [[file-id file-name] references]
|
||||
[:li {:class (stl/css :list-item)
|
||||
:key (dm/str file-id)}
|
||||
[:span "- " file-name]])]]
|
||||
(when (and (string? hint) (not= hint ""))
|
||||
[:> context-notification* {:level :info
|
||||
:appearance :ghost}
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/basic-rules.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.modal-overlay {
|
||||
@extend .modal-overlay-base;
|
||||
@@ -16,19 +15,14 @@
|
||||
|
||||
.modal-container {
|
||||
@extend .modal-container-base;
|
||||
display: grid;
|
||||
gap: var(--sp-xxl);
|
||||
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
max-height: 100%;
|
||||
.modal-header {
|
||||
margin-bottom: deprecated.$s-24;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
@include t.use-typography("headline-medium");
|
||||
@include deprecated.headlineMediumTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
@@ -37,16 +31,13 @@
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
@include t.use-typography("body-small");
|
||||
display: grid;
|
||||
gap: var(--sp-s);
|
||||
@include deprecated.bodySmallTypography;
|
||||
margin-bottom: deprecated.$s-24;
|
||||
}
|
||||
|
||||
.element-list {
|
||||
@include t.use-typography("body-large");
|
||||
@include deprecated.bodyLargeTypography;
|
||||
color: var(--modal-text-foreground-color);
|
||||
overflow-y: scroll;
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
@@ -64,14 +55,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modal-scd-msg {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
.modal-scd-msg,
|
||||
.modal-subtitle,
|
||||
.modal-msg {
|
||||
@include t.use-typography("body-large");
|
||||
@include deprecated.bodyLargeTypography;
|
||||
color: var(--modal-text-foreground-color);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
(ns app.main.ui.ds
|
||||
(:require
|
||||
[app.config :as cf]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.checkbox :refer [checkbox*]]
|
||||
@@ -32,7 +33,6 @@
|
||||
[app.main.ui.ds.product.avatar :refer [avatar*]]
|
||||
[app.main.ui.ds.product.cta :refer [cta*]]
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.ds.product.milestone :refer [milestone*]]
|
||||
@@ -44,6 +44,8 @@
|
||||
[app.util.i18n :as i18n]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(i18n/init! cf/translations)
|
||||
|
||||
(def default
|
||||
"A export used for storybook"
|
||||
(mf/object
|
||||
@@ -57,7 +59,6 @@
|
||||
:HintMessage hint-message*
|
||||
:InputWithMeta input-with-meta*
|
||||
:EmptyPlaceholder empty-placeholder*
|
||||
:EmptyState empty-state*
|
||||
:Loader loader*
|
||||
:RawSvg raw-svg*
|
||||
:Select select*
|
||||
@@ -79,11 +80,6 @@
|
||||
:Milestone milestone*
|
||||
:MilestoneGroup milestone-group*
|
||||
:Date date*
|
||||
|
||||
:set-default-translations
|
||||
(fn [data]
|
||||
(i18n/set-translations "en" data))
|
||||
|
||||
;; meta / misc
|
||||
:meta
|
||||
{:icons (clj->js (sort icon-list))
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.ds.product.empty-state
|
||||
(:require-macros
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:empty-state
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:icon [:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:text :string]])
|
||||
|
||||
(mf/defc empty-state*
|
||||
{::mf/schema schema:empty-state}
|
||||
[{:keys [class icon text] :rest props}]
|
||||
(let [props (mf/spread-props props {:class [class (stl/css :group)]})]
|
||||
[:> :div props
|
||||
[:div {:class (stl/css :icon-wrapper)}
|
||||
[:> icon* {:icon-id icon
|
||||
:size "l"
|
||||
:class (stl/css :icon)}]]
|
||||
[:div {:class (stl/css :text)} text]]))
|
||||
@@ -1,35 +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 */ }
|
||||
|
||||
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
|
||||
import * as EmptyState from "./empty_state.stories";
|
||||
|
||||
<Meta title="Product/EmptyState" />
|
||||
|
||||
# EmptyState
|
||||
|
||||
An indication to the user that there is no data to display in the current view; often includes instructions on what to do next.
|
||||
|
||||
<Canvas of={EmptyState.Default} />
|
||||
|
||||
## Technical notes
|
||||
|
||||
The icon and the text are mandatory, and a class is optional.
|
||||
|
||||
```clj
|
||||
[:> empty-state* {:icon i/at
|
||||
:text "This is an empty state"}]
|
||||
```
|
||||
|
||||
## Usage guidelines (design)
|
||||
|
||||
### Where to use
|
||||
|
||||
Use in areas of the app where users can add data or elements, but where there is currently no available information.
|
||||
|
||||
### Interaction / Behavior
|
||||
|
||||
There is no interaction.
|
||||
@@ -1,36 +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
|
||||
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/spacing.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--sp-m);
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
inline-size: $sz-48;
|
||||
block-size: $sz-48;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--color-foreground-secondary);
|
||||
inline-size: $sz-32;
|
||||
block-size: $sz-32;
|
||||
}
|
||||
|
||||
.text {
|
||||
@include t.use-typography("body-small");
|
||||
text-align: center;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
@@ -1,32 +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
|
||||
|
||||
import * as React from "react";
|
||||
import Components from "@target/components";
|
||||
|
||||
const { EmptyState } = Components;
|
||||
const { icons } = Components.meta;
|
||||
|
||||
export default {
|
||||
title: "Product/EmptyState",
|
||||
component: Components.EmptyState,
|
||||
argTypes: {
|
||||
icon: {
|
||||
options: icons,
|
||||
control: { type: "select" },
|
||||
},
|
||||
text: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
icon: "help",
|
||||
text: "This is an empty state",
|
||||
},
|
||||
render: ({ ...args }) => <EmptyState {...args} />,
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
@@ -173,7 +173,7 @@
|
||||
[:id {:optional true} :string]
|
||||
[:offset {:optional true} :int]
|
||||
[:delay {:optional true} :int]
|
||||
[:content [:or fn? :string map?]]
|
||||
[:content [:or fn? :string]]
|
||||
[:placement {:optional true}
|
||||
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]])
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
:text [:visibility :geometry :text :shadow :blur :stroke :layout-element]
|
||||
:variant [:variant :geometry :fill :stroke :shadow :blur :layout :layout-element]})
|
||||
|
||||
(mf/defc attributes*
|
||||
(mf/defc attributes
|
||||
[{:keys [page-id file-id shapes frame from libraries share-id objects color-space]}]
|
||||
(let [shapes (hooks/use-equal-memo shapes)
|
||||
first-shape (first shapes)
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
embed-images? (replace-map images-data))]
|
||||
(str/format page-template style-code markup-code)))
|
||||
|
||||
(mf/defc code*
|
||||
(mf/defc code
|
||||
[{:keys [shapes frame on-expand from]}]
|
||||
(let [style-type* (mf/use-state "css")
|
||||
markup-type* (mf/use-state "html")
|
||||
|
||||
@@ -12,13 +12,12 @@
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.inspect.attributes :refer [attributes*]]
|
||||
[app.main.ui.inspect.code :refer [code*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.inspect.attributes :refer [attributes]]
|
||||
[app.main.ui.inspect.code :refer [code]]
|
||||
[app.main.ui.inspect.selection-feedback :refer [resolve-shapes]]
|
||||
[app.main.ui.inspect.styles :refer [styles-tab*]]
|
||||
[app.util.dom :as dom]
|
||||
@@ -123,7 +122,8 @@
|
||||
(fn []
|
||||
(if (seq shapes)
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "inspect-mode-click-element"}))
|
||||
(handle-change-tab (if (contains? cf/flags :inspect-styles) :styles :info)))))
|
||||
(handle-change-tab (if (contains? cf/flags :inspect-styles) :styles :info)))
|
||||
(reset! color-space* "hex")))
|
||||
|
||||
[:aside {:class (stl/css-case :settings-bar-right true
|
||||
:viewer-code (= from :viewer))}
|
||||
@@ -189,49 +189,52 @@
|
||||
:libraries libraries
|
||||
:file-id file-id}]
|
||||
:computed
|
||||
[:> attributes* {:color-space color-space
|
||||
:page-id page-id
|
||||
:objects objects
|
||||
:file-id file-id
|
||||
:frame frame
|
||||
:shapes shapes
|
||||
:from from
|
||||
:libraries libraries
|
||||
:share-id share-id}]
|
||||
[:& attributes {:color-space color-space
|
||||
:page-id page-id
|
||||
:objects objects
|
||||
:file-id file-id
|
||||
:frame frame
|
||||
:shapes shapes
|
||||
:from from
|
||||
:libraries libraries
|
||||
:share-id share-id}]
|
||||
|
||||
:code
|
||||
[:> code* {:frame frame
|
||||
:shapes shapes
|
||||
:on-expand handle-expand
|
||||
:from from}])]
|
||||
[:& code {:frame frame
|
||||
:shapes shapes
|
||||
:on-expand handle-expand
|
||||
:from from}])]
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
:selected (name @section)
|
||||
:on-change handle-change-tab
|
||||
:class (stl/css :viewer-tab-switcher)}
|
||||
(case @section
|
||||
:info
|
||||
[:> attributes* {:page-id page-id
|
||||
:objects objects
|
||||
:file-id file-id
|
||||
:frame frame
|
||||
:shapes shapes
|
||||
:from from
|
||||
:libraries libraries
|
||||
:share-id share-id}]
|
||||
[:& attributes {:page-id page-id
|
||||
:objects objects
|
||||
:file-id file-id
|
||||
:frame frame
|
||||
:shapes shapes
|
||||
:from from
|
||||
:libraries libraries
|
||||
:share-id share-id}]
|
||||
|
||||
:code
|
||||
[:> code* {:frame frame
|
||||
:shapes shapes
|
||||
:on-expand handle-expand
|
||||
:from from}])])]]
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css :empty)}
|
||||
[:> empty-state* {:icon i/code
|
||||
:text (tr "inspect.empty.select")}]
|
||||
[:> empty-state* {:icon i/help
|
||||
:text (tr "inspect.empty.help")}]]
|
||||
[:div {:class (stl/css :empty-button)}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click navigate-to-help}
|
||||
(tr "inspect.empty.more")]]])]))
|
||||
[:& code {:frame frame
|
||||
:shapes shapes
|
||||
:on-expand handle-expand
|
||||
:from from}])])]]
|
||||
[:div {:class (stl/css :empty)}
|
||||
[:div {:class (stl/css :code-info)}
|
||||
[:span {:class (stl/css :placeholder-icon)}
|
||||
deprecated-icon/code]
|
||||
[:span {:class (stl/css :placeholder-label)}
|
||||
(tr "inspect.empty.select")]]
|
||||
[:div {:class (stl/css :help-info)}
|
||||
[:span {:class (stl/css :placeholder-icon)}
|
||||
deprecated-icon/help]
|
||||
[:span {:class (stl/css :placeholder-label)}
|
||||
(tr "inspect.empty.help")]]
|
||||
[:button {:class (stl/css :more-info-btn)
|
||||
:on-click navigate-to-help}
|
||||
(tr "inspect.empty.more-info")]])]))
|
||||
|
||||
@@ -81,14 +81,36 @@
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxxl);
|
||||
margin: var(--sp-xxxl) auto;
|
||||
inline-size: $sz-200;
|
||||
align-items: center;
|
||||
gap: $sz-40;
|
||||
padding-top: $sz-24;
|
||||
}
|
||||
|
||||
.empty-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.code-info,
|
||||
.help-info {
|
||||
@include deprecated.flexColumn;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: var(--sp-m);
|
||||
margin-inline-end: var(--sp-s);
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
@extend .empty-icon;
|
||||
}
|
||||
|
||||
.placeholder-label {
|
||||
@include deprecated.bodySmallTypography;
|
||||
text-align: center;
|
||||
inline-size: px2rem(200);
|
||||
color: var(--empty-message-foreground-color);
|
||||
}
|
||||
|
||||
.more-info-btn {
|
||||
@extend .button-secondary;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
block-size: $sz-32;
|
||||
padding: var(--sp-s) var(--sp-xxl);
|
||||
}
|
||||
|
||||
.inspect-tab-switcher {
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
(swap! shorthands* assoc (:panel shorthand) (:property shorthand))))]
|
||||
[:ol {:class (stl/css :styles-tab) :aria-label (tr "labels.styles")}
|
||||
;; TOKENS PANEL
|
||||
(when (or (seq active-themes) (seq active-sets))
|
||||
(when (or active-themes active-sets)
|
||||
[:li
|
||||
[:> style-box* {:panel :token}
|
||||
[:> tokens-panel* {:theme-paths active-themes :set-names active-sets}]]])
|
||||
|
||||
@@ -6,15 +6,6 @@
|
||||
|
||||
@use "ds/typography.scss" as *;
|
||||
|
||||
// TODO: this must be a custom property in the design system
|
||||
:global(.light) {
|
||||
--low-emphasis-background: #fafafa;
|
||||
}
|
||||
|
||||
:global(.default) {
|
||||
--low-emphasis-background: #121214;
|
||||
}
|
||||
|
||||
.style-box {
|
||||
--title-gap: var(--sp-xs);
|
||||
--title-padding: var(--sp-s);
|
||||
@@ -22,9 +13,12 @@
|
||||
--arrow-color: var(--color-foreground-secondary);
|
||||
--box-border-color: var(--color-background-primary);
|
||||
|
||||
// TODO: this must be a custom property in the design system
|
||||
--lowEmphasis-background: #121214;
|
||||
|
||||
padding-block: var(--sp-s);
|
||||
padding-inline: var(--sp-m);
|
||||
background-color: var(--low-emphasis-background);
|
||||
background-color: var(--lowEmphasis-background);
|
||||
|
||||
border-block-end: 2px solid var(--box-border-color);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
[app.main.ui.releases.v2-1]
|
||||
[app.main.ui.releases.v2-10]
|
||||
[app.main.ui.releases.v2-11]
|
||||
[app.main.ui.releases.v2-12]
|
||||
[app.main.ui.releases.v2-2]
|
||||
[app.main.ui.releases.v2-3]
|
||||
[app.main.ui.releases.v2-4]
|
||||
@@ -103,4 +102,4 @@
|
||||
|
||||
(defmethod rc/render-release-notes "0.0"
|
||||
[params]
|
||||
(rc/render-release-notes (assoc params :version "2.12")))
|
||||
(rc/render-release-notes (assoc params :version "2.11")))
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.releases.v2-12
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.releases.common :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defmethod c/render-release-notes "2.12"
|
||||
[{:keys [slide klass next finish navigate version]}]
|
||||
(mf/html
|
||||
(case slide
|
||||
:start
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.12-slide-0.jpg"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot 2.12 is here!"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"What’s new in Penpot?"]
|
||||
|
||||
[:div {:class (stl/css :version-tag)}
|
||||
(dm/str "Version " version)]]
|
||||
|
||||
[:div {:class (stl/css :features-block)}
|
||||
[:span {:class (stl/css :feature-title)}
|
||||
"Better tokens visibility and more!"]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"This release focuses on making your everyday workflow feel clearer, faster and more intuitive. Tokens are now easier to see and apply, appearing directly where you work and giving the designs better context during code inspection. Variants gain a more natural flow thanks to simple boolean toggles that remove friction when switching states. And PDF export becomes more flexible, letting you choose exactly which boards to share so your files match the story you want to tell."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Together, these enhancements bring greater control and fluidity to your entire design process."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Let’s dive in!"]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:button {:class (stl/css :next-btn)
|
||||
:on-click next} "Continue"]]]]]]
|
||||
|
||||
0
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.12-tokens-sidebar.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Better tokens visibility"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Better tokens visibility"]]
|
||||
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Design systems should be both powerful and effortless to use. This release brings tokens closer to where you work, making them easier to apply and easier to understand."]
|
||||
|
||||
[:span {:class (stl/css :feature-title)}
|
||||
"Apply color tokens right from the sidebar"]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Your color tokens now appear directly in the properties sidebar, making it faster to apply or unapply tokens from the design tab. No more digging: now you can use tokens within your design flow."]
|
||||
|
||||
[:span {:class (stl/css :feature-title)}
|
||||
"See token names in the Inspect panel"]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Developers now get a clearer context during handoff. The Inspect panel shows the actual token used in your design, in a similar way to how styles are displayed. This small detail reduces ambiguity, aligns everyone on the same language, and strengthens collaboration across the team."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 3}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
1
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.12-variants.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Simpler boolean variants"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Simpler boolean variants"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Variants are central to building flexible, scalable components. With this release, boolean properties become far easier to work with."]
|
||||
|
||||
[:span {:class (stl/css :feature-title)}
|
||||
"A simple toggle for boolean values"]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Binary states now use a clean toggle, to be able to switch visually, instead of a dropdown. This makes adjusting component states more intuitive and speeds up working with multiple instances."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"It’s a subtle improvement, but it removes friction you feel hundreds of times a week, and makes component work flow more naturally."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 3}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
|
||||
2
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.12-export-pdf.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Smarter PDF export"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Smarter PDF export"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Exporting your work is now more precise and flexible."]
|
||||
|
||||
[:span {:class (stl/css :feature-title)}
|
||||
"Select specific boards when exporting"]
|
||||
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"You’re now in control of which boards make it into your PDF. Share just the final screens, just a flow, just the workshop materials. This streamlined export flow adapts to the way real teams work: share the story you want to tell, with exactly the boards you need."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 3}]
|
||||
|
||||
[:button {:on-click finish
|
||||
:class (stl/css :next-btn)} "Let's go"]]]]]])))
|
||||
|
||||
@@ -1,102 +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
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.modal-overlay {
|
||||
@extend .modal-overlay-base;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
display: grid;
|
||||
grid-template-columns: deprecated.$s-324 1fr;
|
||||
height: deprecated.$s-500;
|
||||
width: deprecated.$s-888;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--modal-background-color);
|
||||
border: deprecated.$s-2 solid var(--modal-border-color);
|
||||
}
|
||||
|
||||
.start-image {
|
||||
width: deprecated.$s-324;
|
||||
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: deprecated.$s-40;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr deprecated.$s-32;
|
||||
gap: deprecated.$s-24;
|
||||
|
||||
a {
|
||||
color: var(--button-primary-background-color-rest);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: grid;
|
||||
gap: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.version-tag {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.headlineSmallTypography;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-96;
|
||||
background-color: var(--communication-tag-background-color);
|
||||
color: var(--communication-tag-foreground-color);
|
||||
border-radius: deprecated.$br-8;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
@include deprecated.headlineLargeTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.features-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: deprecated.$s-16;
|
||||
width: deprecated.$s-440;
|
||||
}
|
||||
|
||||
.feature {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
@include deprecated.bodyLargeTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
@include deprecated.bodyMediumTypography;
|
||||
margin: 0;
|
||||
color: var(--modal-text-foreground-color);
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
@include deprecated.bodyMediumTypography;
|
||||
color: var(--modal-text-foreground-color);
|
||||
list-style: disc;
|
||||
display: grid;
|
||||
gap: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-areas: "bullets button";
|
||||
}
|
||||
|
||||
.next-btn {
|
||||
@extend .button-primary;
|
||||
width: deprecated.$s-100;
|
||||
justify-self: flex-end;
|
||||
grid-area: button;
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
[app.main.ui.workspace.coordinates :as coordinates]
|
||||
[app.main.ui.workspace.libraries]
|
||||
[app.main.ui.workspace.nudge]
|
||||
[app.main.ui.workspace.palette :refer [palette*]]
|
||||
[app.main.ui.workspace.palette :refer [palette]]
|
||||
[app.main.ui.workspace.plugins]
|
||||
[app.main.ui.workspace.sidebar :refer [sidebar*]]
|
||||
[app.main.ui.workspace.sidebar.history :refer [history-toolbox*]]
|
||||
@@ -35,7 +35,7 @@
|
||||
[app.main.ui.workspace.tokens.export.modal]
|
||||
[app.main.ui.workspace.tokens.import]
|
||||
[app.main.ui.workspace.tokens.import.modal]
|
||||
[app.main.ui.workspace.tokens.management.forms.modals]
|
||||
[app.main.ui.workspace.tokens.management.create.modals]
|
||||
[app.main.ui.workspace.tokens.settings]
|
||||
[app.main.ui.workspace.tokens.themes.create-modal]
|
||||
[app.main.ui.workspace.viewport :refer [viewport*]]
|
||||
@@ -84,8 +84,8 @@
|
||||
node-ref (use-resize-observer on-resize)]
|
||||
[:*
|
||||
(when (not ^boolean hide-ui?)
|
||||
[:> palette* {:layout layout
|
||||
:on-change-size on-resize-palette}])
|
||||
[:& palette {:layout layout
|
||||
:on-change-palette-size on-resize-palette}])
|
||||
|
||||
[:section
|
||||
{:key (dm/str "workspace-" page-id)
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
(let [{:keys [modal title]} (get dwta/token-properties :color)
|
||||
window-size (dom/get-window-size)
|
||||
left-sidebar (dom/get-element "left-sidebar-aside")
|
||||
x-size (dom/get-data left-sidebar "width")
|
||||
x-size (dom/get-data left-sidebar "left-sidebar-width")
|
||||
modal-height 392
|
||||
x (- (int x-size) 30)
|
||||
y (- (/ (:height window-size) 2) (/ modal-height 2))]
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -161,5 +160,6 @@
|
||||
:key (:page-id tgroup)}])]
|
||||
|
||||
[:div {:class (stl/css :thread-group-placeholder)}
|
||||
[:> empty-state* {:icon i/comments
|
||||
:text (tr "labels.no-comments-available")}]])]]))
|
||||
[:span {:class (stl/css :placeholder-icon)} deprecated-icon/comments]
|
||||
[:span {:class (stl/css :placeholder-label)}
|
||||
(tr "labels.no-comments-available")]])]]))
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.comments-section {
|
||||
@@ -129,9 +128,29 @@
|
||||
}
|
||||
|
||||
.thread-group-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxxl);
|
||||
margin: var(--sp-xxxl) auto;
|
||||
inline-size: $sz-200;
|
||||
@include deprecated.flexColumn;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin-top: deprecated.$s-36;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-48;
|
||||
width: deprecated.$s-48;
|
||||
border-radius: deprecated.$br-circle;
|
||||
background-color: var(--empty-message-background-color);
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
height: deprecated.$s-28;
|
||||
width: deprecated.$s-28;
|
||||
stroke: var(--empty-message-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-label {
|
||||
@include deprecated.bodySmallTypography;
|
||||
text-align: center;
|
||||
width: deprecated.$s-184;
|
||||
color: var(--empty-message-foreground-color);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.color :as uc]
|
||||
@@ -50,6 +49,9 @@
|
||||
(def ^:private add-icon
|
||||
(deprecated-icon/icon-xref :add (stl/css :add-icon)))
|
||||
|
||||
(def ^:private library-icon
|
||||
(deprecated-icon/icon-xref :library (stl/css :library-icon)))
|
||||
|
||||
(defn- get-library-summary
|
||||
"Given a library data return a summary representation of this library"
|
||||
[data]
|
||||
@@ -491,8 +493,9 @@
|
||||
[:div {:class (stl/css :update-section)}
|
||||
(if (empty? libs-assets)
|
||||
[:div {:class (stl/css :section-list-empty)}
|
||||
[:> empty-state* {:icon i/library
|
||||
:text (tr "workspace.libraries.no-libraries-need-sync")}]]
|
||||
[:span {:class (stl/css :empty-state-icon)}
|
||||
library-icon]
|
||||
(tr "workspace.libraries.no-libraries-need-sync")]
|
||||
[:*
|
||||
[:div {:class (stl/css :section-title)} (tr "workspace.libraries.library-updates")]
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@
|
||||
|
||||
// empty state
|
||||
.section-list-empty {
|
||||
@include t.use-typography("body-medium");
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
justify-items: center;
|
||||
@@ -199,6 +200,17 @@
|
||||
text-align: center;
|
||||
height: fit-content;
|
||||
margin-block: var(--sp-l);
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: $sz-48;
|
||||
height: $sz-48;
|
||||
border-radius: $br-circle;
|
||||
background-color: var(--pill-background-color);
|
||||
}
|
||||
|
||||
.library-icon {
|
||||
|
||||
@@ -33,13 +33,12 @@
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private ref:viewport
|
||||
(def viewport
|
||||
(l/derived :vport refs/workspace-local))
|
||||
|
||||
(defn- calculate-palette-style
|
||||
[rulers?]
|
||||
(defn calculate-palette-padding [rulers?]
|
||||
(let [left-sidebar (dom/get-element "left-sidebar-aside")
|
||||
left-sidebar-size (-> (dom/get-data left-sidebar "width")
|
||||
left-sidebar-size (-> (dom/get-data left-sidebar "left-sidebar-width")
|
||||
(d/parse-integer))
|
||||
rulers-width (if rulers? 22 0)
|
||||
min-left-sidebar-width left-sidebar-default-width
|
||||
@@ -49,46 +48,36 @@
|
||||
#js {"paddingLeft" (dm/str calculate-padding-left "px")
|
||||
"paddingRight" "322px"}))
|
||||
|
||||
(mf/defc palette*
|
||||
[{:keys [layout on-change-size]}]
|
||||
(let [color-palette? (:colorpalette layout)
|
||||
text-palette? (:textpalette layout)
|
||||
hide-palettes? (:hide-palettes layout)
|
||||
(mf/defc palette
|
||||
[{:keys [layout on-change-palette-size]}]
|
||||
(let [color-palette? (:colorpalette layout)
|
||||
text-palette? (:textpalette layout)
|
||||
hide-palettes? (:hide-palettes layout)
|
||||
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
container (mf/use-ref nil)
|
||||
state* (mf/use-state {:show-menu false})
|
||||
state (deref state*)
|
||||
show-menu? (:show-menu state)
|
||||
selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent)
|
||||
selected-text* (mf/use-state :file)
|
||||
selected-text (deref selected-text*)
|
||||
on-select (mf/use-fn #(reset! selected %))
|
||||
rulers? (mf/deref refs/rulers?)
|
||||
{:keys [on-pointer-down on-lost-pointer-capture on-pointer-move parent-ref size]}
|
||||
(r/use-resize-hook :palette 72 54 80 :y true :bottom on-change-palette-size)
|
||||
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
container (mf/use-ref nil)
|
||||
|
||||
state* (mf/use-state #(-> {:show-menu false}))
|
||||
state (deref state*)
|
||||
show-menu? (:show-menu state)
|
||||
|
||||
selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent)
|
||||
|
||||
selected-text* (mf/use-state :file)
|
||||
selected-text (deref selected-text*)
|
||||
|
||||
on-select (mf/use-fn #(reset! selected %))
|
||||
|
||||
rulers? (mf/deref refs/rulers?)
|
||||
vport (mf/deref ref:viewport)
|
||||
vport-width (get vport :width)
|
||||
|
||||
{:keys [on-pointer-down
|
||||
on-lost-pointer-capture
|
||||
on-pointer-move
|
||||
parent-ref
|
||||
size]}
|
||||
(r/use-resize-hook :palette 72 54 80 :y true :bottom on-change-size)
|
||||
vport (mf/deref viewport)
|
||||
vport-width (:width vport)
|
||||
|
||||
on-resize
|
||||
(mf/use-fn
|
||||
(mf/use-callback
|
||||
(fn [_]
|
||||
(let [dom (mf/ref-val container)
|
||||
width (obj/get dom "clientWidth")]
|
||||
(swap! state* assoc :width width))))
|
||||
|
||||
on-close-menu
|
||||
(mf/use-fn
|
||||
(mf/use-callback
|
||||
(fn [_]
|
||||
(swap! state* assoc :show-menu false)))
|
||||
|
||||
@@ -111,7 +100,7 @@
|
||||
(reset! selected-text* (:id lib)))))
|
||||
|
||||
toggle-palettes
|
||||
(mf/use-fn
|
||||
(mf/use-callback
|
||||
(fn [_]
|
||||
(r/set-resize-type! :top)
|
||||
(dom/add-class! (dom/get-element-by-class "color-palette") "fade-out-down")
|
||||
@@ -142,9 +131,7 @@
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))
|
||||
(dom/blur! node))))
|
||||
|
||||
any-palette?
|
||||
(or color-palette? text-palette?)
|
||||
|
||||
any-palette? (or color-palette? text-palette?)
|
||||
size-classname
|
||||
(cond
|
||||
(<= size 64) (stl/css :small-palette)
|
||||
@@ -155,16 +142,16 @@
|
||||
(let [key1 (events/listen js/window "resize" on-resize)]
|
||||
#(events/unlistenByKey key1)))
|
||||
|
||||
(mf/with-layout-effect []
|
||||
(let [dom (mf/ref-val parent-ref)
|
||||
(mf/use-layout-effect
|
||||
#(let [dom (mf/ref-val parent-ref)
|
||||
width (obj/get dom "clientWidth")]
|
||||
(swap! state* assoc :width width)))
|
||||
|
||||
[:div {:class (stl/css :palette-wrapper)
|
||||
:id "palette-wrapper"
|
||||
:style (calculate-palette-style rulers?)
|
||||
:style (calculate-palette-padding rulers?)
|
||||
:data-testid "palette"}
|
||||
(when-not ^boolean read-only?
|
||||
(when-not workspace-read-only?
|
||||
[:div {:ref parent-ref
|
||||
:class (dm/str size-classname " " (stl/css-case :palettes true
|
||||
:wide any-palette?
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
[app.main.ui.workspace.left-header :refer [left-header*]]
|
||||
[app.main.ui.workspace.right-header :refer [right-header*]]
|
||||
[app.main.ui.workspace.sidebar.assets :refer [assets-toolbox*]]
|
||||
[app.main.ui.workspace.sidebar.collapsable-button :refer [collapsed-button*]]
|
||||
[app.main.ui.workspace.sidebar.debug :refer [debug-panel*]]
|
||||
[app.main.ui.workspace.sidebar.debug-shape-info :refer [debug-shape-info*]]
|
||||
[app.main.ui.workspace.sidebar.history :refer [history-toolbox*]]
|
||||
@@ -43,34 +44,19 @@
|
||||
|
||||
;; --- Left Sidebar (Component)
|
||||
|
||||
(def ^:private toggle-collapse-left-sidebar
|
||||
(partial st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||
(defn- on-collapse-left-sidebar
|
||||
[]
|
||||
(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||
|
||||
(mf/defc collapse-button*
|
||||
{::mf/private true}
|
||||
[]
|
||||
;; NOTE: This custom button may be replace by an action button when this variant is designed
|
||||
[:button {:class (stl/css :collapse-sidebar-button)
|
||||
:on-click toggle-collapse-left-sidebar}
|
||||
:on-click on-collapse-left-sidebar}
|
||||
[:> icon* {:icon-id i/arrow
|
||||
:size "s"
|
||||
:aria-label (tr "workspace.sidebar.collapse")}]])
|
||||
|
||||
(mf/defc collapsed-button*
|
||||
{::mf/memo true
|
||||
::mf/private true}
|
||||
[]
|
||||
[:div {:id "left-sidebar-aside"
|
||||
:data-width "0"
|
||||
:class (stl/css :collapsed-sidebar)}
|
||||
[:div {:class (stl/css :collapsed-title)}
|
||||
[:button {:class (stl/css :collapsed-button)
|
||||
:title (tr "workspace.sidebar.expand")
|
||||
:on-click toggle-collapse-left-sidebar}
|
||||
[:> icon* {:icon-id i/arrow
|
||||
:size "s"
|
||||
:aria-label (tr "workspace.sidebar.expand")}]]]])
|
||||
|
||||
(mf/defc layers-content*
|
||||
{::mf/private true
|
||||
::mf/memo true}
|
||||
@@ -111,7 +97,6 @@
|
||||
|
||||
[:> layers-toolbox* {:size-parent width}]]))
|
||||
|
||||
|
||||
(mf/defc left-sidebar*
|
||||
{::mf/memo true}
|
||||
[{:keys [layout file page-id tokens-lib active-tokens resolved-active-tokens]}]
|
||||
@@ -176,7 +161,7 @@
|
||||
[:aside {:ref parent-ref
|
||||
:id "left-sidebar-aside"
|
||||
:data-testid "left-sidebar"
|
||||
:data-width (str width)
|
||||
:data-left-sidebar-width (str width)
|
||||
:class aside-class
|
||||
:style {:--left-sidebar-width (dm/str width "px")}}
|
||||
|
||||
|
||||
@@ -116,44 +116,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.collapsed-sidebar {
|
||||
@include deprecated.flexCenter;
|
||||
position: absolute;
|
||||
top: deprecated.$s-48;
|
||||
left: 0;
|
||||
padding: deprecated.$s-4;
|
||||
border-radius: deprecated.$br-8;
|
||||
background: var(--color-background-primary);
|
||||
margin-inline-start: var(--sp-m);
|
||||
}
|
||||
.collapsed-title {
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-36;
|
||||
width: deprecated.$s-24;
|
||||
border-radius: deprecated.$br-8;
|
||||
background: var(--color-background-secondary);
|
||||
}
|
||||
.collapsed-button {
|
||||
@include deprecated.buttonStyle;
|
||||
height: deprecated.$s-24;
|
||||
width: deprecated.$s-16;
|
||||
padding: 0;
|
||||
border-radius: deprecated.$br-5;
|
||||
svg {
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
&:hover {
|
||||
svg {
|
||||
stroke: var(--icon-foreground-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.versions-tab {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -56,8 +56,9 @@
|
||||
(update file :data dissoc :pages-index))
|
||||
refs/file))
|
||||
|
||||
(mf/defc assets-local-library*
|
||||
{::mf/private true}
|
||||
(mf/defc assets-local-library
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [filters]}]
|
||||
(let [file (mf/deref ref:local-library)]
|
||||
[:> file-library*
|
||||
@@ -67,7 +68,7 @@
|
||||
:filters filters}]))
|
||||
|
||||
(defn- toggle-values
|
||||
[v a b]
|
||||
[v [a b]]
|
||||
(if (= v a) b a))
|
||||
|
||||
(mf/defc assets-toolbox*
|
||||
@@ -96,7 +97,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ordering)
|
||||
(fn []
|
||||
(let [new-value (toggle-values ordering :asc :desc)]
|
||||
(let [new-value (toggle-values ordering [:asc :desc])]
|
||||
(swap! filters* assoc :ordering new-value)
|
||||
(dwa/set-current-assets-ordering! new-value))))
|
||||
|
||||
@@ -104,7 +105,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps list-style)
|
||||
(fn []
|
||||
(let [new-value (toggle-values list-style :thumbs :list)]
|
||||
(let [new-value (toggle-values list-style [:thumbs :list])]
|
||||
(swap! filters* assoc :list-style new-value)
|
||||
(dwa/set-current-assets-list-style! new-value))))
|
||||
|
||||
@@ -208,5 +209,5 @@
|
||||
[:& (mf/provider cmm/assets-toggle-ordering) {:value toggle-ordering}
|
||||
[:& (mf/provider cmm/assets-toggle-list-style) {:value toggle-list-style}
|
||||
[:*
|
||||
[:> assets-local-library* {:filters filters}]
|
||||
[:& assets-local-library {:filters filters}]
|
||||
[:> assets-libraries* {:filters filters}]]]]]]))
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
cursor: pointer;
|
||||
|
||||
.title-menu {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
.title-menu {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
;; 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.ui.workspace.sidebar.collapsable-button
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc collapsed-button*
|
||||
{::mf/memo true}
|
||||
[]
|
||||
(let [on-click (mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))]
|
||||
[:div {:id "left-sidebar-aside"
|
||||
:data-size "0"
|
||||
:class (stl/css :collapsed-sidebar)}
|
||||
[:div {:class (stl/css :collapsed-title)}
|
||||
[:button {:class (stl/css :collapsed-button)
|
||||
:title (tr "workspace.sidebar.expand")
|
||||
:on-click on-click}
|
||||
[:> icon* {:icon-id i/arrow
|
||||
:size "s"
|
||||
:aria-label (tr "workspace.sidebar.expand")}]]]]))
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.collapsed-sidebar {
|
||||
@include deprecated.flexCenter;
|
||||
position: absolute;
|
||||
top: deprecated.$s-48;
|
||||
left: 0;
|
||||
padding: deprecated.$s-4;
|
||||
border-radius: deprecated.$br-8;
|
||||
background: var(--color-background-primary);
|
||||
margin-inline-start: var(--sp-m);
|
||||
}
|
||||
.collapsed-title {
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-36;
|
||||
width: deprecated.$s-24;
|
||||
border-radius: deprecated.$br-8;
|
||||
background: var(--color-background-secondary);
|
||||
}
|
||||
.collapsed-button {
|
||||
@include deprecated.buttonStyle;
|
||||
height: deprecated.$s-24;
|
||||
width: deprecated.$s-16;
|
||||
padding: 0;
|
||||
border-radius: deprecated.$br-5;
|
||||
svg {
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
&:hover {
|
||||
svg {
|
||||
stroke: var(--icon-foreground-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,6 @@
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr] :as i18n]
|
||||
@@ -329,8 +327,8 @@
|
||||
[:div {:class (stl/css :history-toolbox)}
|
||||
(if (empty? entries)
|
||||
[:div {:class (stl/css :history-entry-empty)}
|
||||
[:> empty-state* {:icon i/history
|
||||
:text (tr "workspace.undo.empty")}]]
|
||||
[:div {:class (stl/css :history-entry-empty-icon)} deprecated-icon/history]
|
||||
[:div {:class (stl/css :history-entry-empty-msg)} (tr "workspace.undo.empty")]]
|
||||
[:ul {:class (stl/css :history-entries)}
|
||||
(for [[idx-entry entry] (->> entries (map-indexed vector) reverse)] #_[i (range 0 10)]
|
||||
[:& history-entry {:key (str "entry-" idx-entry)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.history-toolbox {
|
||||
@@ -31,11 +30,23 @@
|
||||
}
|
||||
|
||||
.history-entry-empty {
|
||||
display: flex;
|
||||
@include deprecated.flexCenter;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxxl);
|
||||
margin: var(--sp-xxxl) auto;
|
||||
inline-size: $sz-200;
|
||||
gap: deprecated.$s-16;
|
||||
padding: deprecated.$s-28 deprecated.$s-16;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.history-entry-empty-icon {
|
||||
@extend .empty-icon;
|
||||
svg {
|
||||
margin-left: calc(-1 * deprecated.$s-2);
|
||||
}
|
||||
}
|
||||
|
||||
.history-entry-empty-msg {
|
||||
@include deprecated.bodySmallTypography;
|
||||
color: var(--empty-message-foreground-color);
|
||||
}
|
||||
|
||||
.history-entries {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.variants :as dwv]
|
||||
[app.main.store :as st]
|
||||
[app.util.debug :as dbg]
|
||||
[app.util.dom :as dom]
|
||||
@@ -68,7 +69,15 @@
|
||||
name (str/trim (dom/get-value name-input))]
|
||||
(on-stop-edit)
|
||||
(reset! edition* false)
|
||||
(st/emit! (dw/rename-shape-or-variant shape-id name)))))
|
||||
(if variant-name
|
||||
(if (ctv/valid-properties-formula? name)
|
||||
(st/emit! (dwv/update-properties-names-and-values component-id variant-id variant-properties (ctv/properties-formula->map name))
|
||||
(dwv/remove-empty-properties variant-id)
|
||||
(dwv/update-error component-id))
|
||||
(st/emit! (dwv/update-properties-names-and-values component-id variant-id variant-properties {})
|
||||
(dwv/remove-empty-properties variant-id)
|
||||
(dwv/update-error component-id name)))
|
||||
(st/emit! (dw/end-rename-shape shape-id name))))))
|
||||
|
||||
cancel-edit
|
||||
(mf/use-fn
|
||||
|
||||
@@ -686,7 +686,7 @@
|
||||
(str/upper (tr "workspace.assets.local-library"))
|
||||
(dm/get-in libraries [current-library-id :name]))
|
||||
|
||||
current-lib-data (mf/with-memo [libraries current-library-id]
|
||||
current-lib-data (mf/with-memo [libraries]
|
||||
(get-in libraries [current-library-id :data]))
|
||||
|
||||
current-lib-counts (mf/with-memo [current-lib-data]
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
[app.main.ui.ds.controls.checkbox :refer [checkbox*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
@@ -776,11 +775,29 @@
|
||||
(when (= (count interactions) 0)
|
||||
[:div {:class (stl/css :section)}
|
||||
[:div {:class (stl/css :content)}
|
||||
[:div {:class (stl/css :empty)}
|
||||
[:div {:class (stl/css :help)}
|
||||
|
||||
(when framed-shape?
|
||||
[:> empty-state* {:icon i/add
|
||||
:text (tr "workspace.options.add-interaction")}])
|
||||
[:> empty-state* {:icon i/interaction
|
||||
:text (tr "workspace.options.select-a-shape")}]
|
||||
[:> empty-state* {:icon i/play
|
||||
:text (tr "workspace.options.use-play-button")}]]]])]))
|
||||
[:div {:class (stl/css :help-group)}
|
||||
[:div {:class (stl/css :help-icon)}
|
||||
[:> icon* {:icon-id i/add
|
||||
:size "l"
|
||||
:class (stl/css :help-icon-inner)}]]
|
||||
[:div {:class (stl/css :help-text)}
|
||||
(tr "workspace.options.add-interaction")]])
|
||||
|
||||
[:div {:class (stl/css :help-group)}
|
||||
[:div {:class (stl/css :help-icon)}
|
||||
[:> icon* {:icon-id i/interaction
|
||||
:size "l"
|
||||
:class (stl/css :help-icon-inner)}]]
|
||||
[:div {:class (stl/css :help-text)}
|
||||
(tr "workspace.options.select-a-shape")]]
|
||||
|
||||
[:div {:class (stl/css :help-group)}
|
||||
[:div {:class (stl/css :help-icon)}
|
||||
[:> icon* {:icon-id i/play
|
||||
:size "l"
|
||||
:class (stl/css :help-icon-inner)}]]
|
||||
[:div {:class (stl/css :help-text)}
|
||||
(tr "workspace.options.use-play-button")]]]]])]))
|
||||
|
||||
@@ -43,12 +43,40 @@
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.empty {
|
||||
.help {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxxl);
|
||||
margin: var(--sp-xxxl) auto;
|
||||
inline-size: $sz-200;
|
||||
padding: var(--sp-xxxl) 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.help-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--sp-m);
|
||||
}
|
||||
|
||||
.help-text {
|
||||
@include t.use-typography("body-small");
|
||||
text-align: center;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
inline-size: $sz-48;
|
||||
block-size: $sz-48;
|
||||
}
|
||||
|
||||
.help-icon-inner {
|
||||
color: var(--color-foreground-secondary);
|
||||
inline-size: $sz-32;
|
||||
block-size: $sz-32;
|
||||
}
|
||||
|
||||
.interaction-item {
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.dashboard.subscription :refer [get-subscription-type]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.ds.product.cta :refer [cta*]]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.ds.product.milestone :refer [milestone*]]
|
||||
[app.main.ui.ds.product.milestone-group :refer [milestone-group*]]
|
||||
[app.util.dom :as dom]
|
||||
@@ -386,9 +385,8 @@
|
||||
|
||||
(cond
|
||||
(= status :loading)
|
||||
[:div {:class (stl/css :versions-empty)}
|
||||
[:> empty-state* {:icon i/clock
|
||||
:text (tr "workspace.versions.loading")}]]
|
||||
[:div {:class (stl/css :versions-entry-empty)}
|
||||
[:div {:class (stl/css :versions-entry-empty-msg)} (tr "workspace.versions.loading")]]
|
||||
|
||||
(= status :loaded)
|
||||
[:*
|
||||
@@ -400,9 +398,9 @@
|
||||
:icon i/pin}]]
|
||||
|
||||
(if (empty? data)
|
||||
[:div {:class (stl/css :versions-empty)}
|
||||
[:> empty-state* {:icon i/history
|
||||
:text (tr "workspace.versions.empty")}]]
|
||||
[:div {:class (stl/css :versions-entry-empty)}
|
||||
[:div {:class (stl/css :versions-entry-empty-icon)} [:> icon* {:icon-id i/history}]]
|
||||
[:div {:class (stl/css :versions-entry-empty-msg)} (tr "workspace.versions.empty")]]
|
||||
|
||||
[:ul {:class (stl/css :versions-entries)}
|
||||
(for [entry entries]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
@@ -16,12 +15,21 @@
|
||||
grid-template-rows: auto auto 1fr;
|
||||
}
|
||||
|
||||
.versions-empty {
|
||||
.versions-entry-empty {
|
||||
align-items: center;
|
||||
color: var(--color-foreground-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxxl);
|
||||
margin: var(--sp-xxxl) auto;
|
||||
inline-size: $sz-200;
|
||||
font-size: deprecated.$fs-12;
|
||||
gap: deprecated.$s-8;
|
||||
padding: deprecated.$s-16;
|
||||
}
|
||||
|
||||
.versions-entry-empty-icon {
|
||||
background: var(--color-background-tertiary);
|
||||
border-radius: 50%;
|
||||
padding: deprecated.$s-8;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.version-save-version {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.forms.generic-form
|
||||
(ns app.main.ui.workspace.tokens.management.create.border-radius
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.files.tokens :as cft]
|
||||
@@ -23,8 +23,7 @@
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
||||
[app.main.ui.workspace.tokens.management.forms.validators :refer [default-validate-token]]
|
||||
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -39,25 +38,8 @@
|
||||
(str/blank? value))
|
||||
(tr "workspace.tokens.empty-input")))
|
||||
|
||||
|
||||
(defn get-value-for-validator
|
||||
[active-tab value value-subfield form-type]
|
||||
|
||||
(case form-type
|
||||
:indexed
|
||||
(if (= active-tab :reference)
|
||||
(:reference value)
|
||||
(value-subfield value))
|
||||
|
||||
:composite
|
||||
(if (= active-tab :reference)
|
||||
(get value :reference)
|
||||
value)
|
||||
|
||||
value))
|
||||
|
||||
(defn- default-make-schema
|
||||
[tokens-tree _]
|
||||
(defn- make-schema
|
||||
[tokens-tree]
|
||||
(sm/schema
|
||||
[:and
|
||||
[:map
|
||||
@@ -80,37 +62,14 @@
|
||||
(nil? (cto/token-value-self-reference? name value))))]]))
|
||||
|
||||
(mf/defc form*
|
||||
[{:keys [token
|
||||
validator
|
||||
action
|
||||
is-create
|
||||
selected-token-set-id
|
||||
tokens-tree-in-selected-set
|
||||
token-type
|
||||
make-schema
|
||||
input-component
|
||||
initial
|
||||
type
|
||||
value-subfield
|
||||
input-value-placeholder] :as props}]
|
||||
[{:keys [token validate-token action is-create selected-token-set-id tokens-tree-in-selected-set] :as props}]
|
||||
|
||||
(let [make-schema (or make-schema default-make-schema)
|
||||
input-component (or input-component token.controls/input*)
|
||||
validate-token (or validator default-validate-token)
|
||||
|
||||
active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite))
|
||||
active-tab (deref active-tab*)
|
||||
|
||||
on-toggle-tab
|
||||
(mf/use-fn
|
||||
(mf/deps)
|
||||
(fn [new-tab]
|
||||
(let [new-tab (keyword new-tab)]
|
||||
(reset! active-tab* new-tab))))
|
||||
|
||||
token
|
||||
(let [token
|
||||
(mf/with-memo [token]
|
||||
(or token {:type token-type}))
|
||||
(or token {:type :border-radius}))
|
||||
|
||||
token-type
|
||||
(get token :type)
|
||||
|
||||
token-properties
|
||||
(dwta/get-token-properties token)
|
||||
@@ -130,15 +89,14 @@
|
||||
(assoc (:name token) token)))
|
||||
|
||||
schema
|
||||
(mf/with-memo [tokens-tree-in-selected-set active-tab]
|
||||
(make-schema tokens-tree-in-selected-set active-tab))
|
||||
(mf/with-memo [tokens-tree-in-selected-set]
|
||||
(make-schema tokens-tree-in-selected-set))
|
||||
|
||||
initial
|
||||
(mf/with-memo [token]
|
||||
(or initial
|
||||
{:name (:name token "")
|
||||
:value (:value token "")
|
||||
:description (:description token "")}))
|
||||
{:name (:name token "")
|
||||
:value (:value token "")
|
||||
:description (:description token "")})
|
||||
|
||||
form
|
||||
(fm/use-form :schema schema
|
||||
@@ -178,13 +136,12 @@
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(mf/deps validate-token token tokens token-type value-subfield type active-tab)
|
||||
(mf/deps validate-token token tokens token-type)
|
||||
(fn [form _event]
|
||||
(let [name (get-in @form [:clean-data :name])
|
||||
description (get-in @form [:clean-data :description])
|
||||
value (get-in @form [:clean-data :value])
|
||||
value-for-validation (get-value-for-validator active-tab value value-subfield type)]
|
||||
(->> (validate-token {:token-value value-for-validation
|
||||
value (get-in @form [:clean-data :value])]
|
||||
(->> (validate-token {:token-value value
|
||||
:token-name name
|
||||
:token-description description
|
||||
:prev-token token
|
||||
@@ -230,16 +187,12 @@
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-component
|
||||
{:placeholder (or input-value-placeholder (tr "workspace.tokens.token-value-enter"))
|
||||
[:> input-token*
|
||||
{:placeholder (tr "workspace.tokens.token-value-enter")
|
||||
:label (tr "workspace.tokens.token-value")
|
||||
:name :value
|
||||
:form form
|
||||
:token token
|
||||
:tokens tokens
|
||||
:tab active-tab
|
||||
:subfield value-subfield
|
||||
:toggle on-toggle-tab}]]
|
||||
:tokens tokens}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-description"
|
||||
@@ -8,16 +8,18 @@
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
|
||||
.inputs-wrapper {
|
||||
.form-wrapper {
|
||||
width: $sz-384;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.token-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-m);
|
||||
border-inline-start: $b-1 solid var(--color-accent-primary-muted);
|
||||
padding-inline-start: var(--sp-m);
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
@@ -28,9 +30,29 @@
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include t.use-typography("body-small");
|
||||
.form-modal-title {
|
||||
@include t.use-typography("headline-medium");
|
||||
color: var(--color-foreground-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
justify-content: end;
|
||||
gap: var(--sp-m);
|
||||
padding-block-start: var(--sp-s);
|
||||
}
|
||||
|
||||
.with-delete {
|
||||
grid-template-columns: 1fr auto auto;
|
||||
}
|
||||
|
||||
.warning-name-change-notification-wrapper {
|
||||
margin-block-start: var(--sp-l);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
justify-self: start;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user