mirror of
https://github.com/penpot/penpot.git
synced 2025-12-23 22:48:40 -05:00
Compare commits
48 Commits
2.12.0-RC4
...
juanfran-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a08c2ca46e | ||
|
|
dde0fddd6f | ||
|
|
4637aced8c | ||
|
|
9dfe5b0865 | ||
|
|
33bcc9544a | ||
|
|
babd481b7f | ||
|
|
a9733c792d | ||
|
|
d04fdb5fbd | ||
|
|
81e0e4f222 | ||
|
|
f13b3c8737 | ||
|
|
a0f8559ffc | ||
|
|
416980f063 | ||
|
|
f76710296c | ||
|
|
d1379c55f6 | ||
|
|
b125c7b5a3 | ||
|
|
496d37795b | ||
|
|
9f6899007a | ||
|
|
641df77834 | ||
|
|
53aad7bc15 | ||
|
|
57297741f5 | ||
|
|
d63d692d34 | ||
|
|
fe72d0af82 | ||
|
|
ef68081d1d | ||
|
|
4ed49cdc5d | ||
|
|
50f9eedcdf | ||
|
|
efe74e62e8 | ||
|
|
456afe46de | ||
|
|
964ef799c2 | ||
|
|
d34b6b88b6 | ||
|
|
9a58f0e954 | ||
|
|
adaf8be56d | ||
|
|
2f1b99fa53 | ||
|
|
5080fcc594 | ||
|
|
ea2d3758f0 | ||
|
|
94c15916e2 | ||
|
|
ed0f3c3595 | ||
|
|
c2014a37b4 | ||
|
|
6611fbd13b | ||
|
|
b5a6867058 | ||
|
|
0f88253dd5 | ||
|
|
8e3996fbb0 | ||
|
|
67762d9450 | ||
|
|
7f62652870 | ||
|
|
78d31ab11a | ||
|
|
0a80c47901 | ||
|
|
77f1046fc8 | ||
|
|
553b73a83c | ||
|
|
00a45cb274 |
@@ -1,305 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
docker:
|
||||
- image: penpotapp/devenv:latest
|
||||
|
||||
working_directory: ~/repo
|
||||
resource_class: medium+
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: "fmt check"
|
||||
working_directory: "."
|
||||
command: |
|
||||
yarn install
|
||||
yarn run fmt:clj:check
|
||||
|
||||
- run:
|
||||
name: "lint clj common"
|
||||
working_directory: "."
|
||||
command: |
|
||||
yarn run lint:clj:common
|
||||
|
||||
- run:
|
||||
name: "lint clj frontend"
|
||||
working_directory: "."
|
||||
command: |
|
||||
yarn run lint:clj:frontend
|
||||
|
||||
- run:
|
||||
name: "lint clj backend"
|
||||
working_directory: "."
|
||||
command: |
|
||||
yarn run lint:clj:backend
|
||||
|
||||
- run:
|
||||
name: "lint clj exporter"
|
||||
working_directory: "."
|
||||
command: |
|
||||
yarn run lint:clj:exporter
|
||||
|
||||
- run:
|
||||
name: "lint clj library"
|
||||
working_directory: "."
|
||||
command: |
|
||||
yarn run lint:clj:library
|
||||
|
||||
test-common:
|
||||
docker:
|
||||
- image: penpotapp/devenv:latest
|
||||
|
||||
working_directory: ~/repo
|
||||
resource_class: medium+
|
||||
|
||||
environment:
|
||||
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "common/deps.edn"}}-{{ checksum "common/yarn.lock" }}
|
||||
|
||||
- run:
|
||||
name: "JVM tests"
|
||||
working_directory: "./common"
|
||||
command: |
|
||||
clojure -M:dev:test
|
||||
|
||||
- run:
|
||||
name: "NODE tests"
|
||||
working_directory: "./common"
|
||||
command: |
|
||||
yarn install
|
||||
yarn run test
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
- ~/.yarn
|
||||
- ~/.gitlibs
|
||||
- ~/.cache/ms-playwright
|
||||
key: v1-dependencies-{{ checksum "common/deps.edn"}}-{{ checksum "common/yarn.lock" }}
|
||||
|
||||
test-frontend:
|
||||
docker:
|
||||
- image: penpotapp/devenv:latest
|
||||
|
||||
working_directory: ~/repo
|
||||
resource_class: medium+
|
||||
|
||||
environment:
|
||||
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
|
||||
|
||||
- run:
|
||||
name: "install dependencies"
|
||||
working_directory: "./frontend"
|
||||
# We install playwright here because the dependent tasks
|
||||
# uses the same cache as this task so we prepopulate it
|
||||
command: |
|
||||
yarn install
|
||||
yarn run playwright install chromium --with-deps
|
||||
|
||||
- run:
|
||||
name: "lint scss on frontend"
|
||||
working_directory: "./frontend"
|
||||
command: |
|
||||
yarn run lint:scss
|
||||
|
||||
- run:
|
||||
name: "unit tests"
|
||||
working_directory: "./frontend"
|
||||
command: |
|
||||
yarn run test
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
- ~/.yarn
|
||||
- ~/.gitlibs
|
||||
- ~/.cache/ms-playwright
|
||||
key: v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
|
||||
|
||||
test-library:
|
||||
docker:
|
||||
- image: penpotapp/devenv:latest
|
||||
|
||||
working_directory: ~/repo
|
||||
resource_class: medium+
|
||||
|
||||
environment:
|
||||
JAVA_OPTS: -Xmx6g
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
|
||||
|
||||
- run:
|
||||
name: Install dependencies and build
|
||||
working_directory: "./library"
|
||||
command: |
|
||||
yarn install
|
||||
|
||||
- run:
|
||||
name: Build and Test
|
||||
working_directory: "./library"
|
||||
command: |
|
||||
./scripts/build
|
||||
yarn run test
|
||||
|
||||
test-components:
|
||||
docker:
|
||||
- image: penpotapp/devenv:latest
|
||||
|
||||
working_directory: ~/repo
|
||||
resource_class: medium+
|
||||
|
||||
environment:
|
||||
JAVA_OPTS: -Xmx6g -Xms2g
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
working_directory: "./frontend"
|
||||
command: |
|
||||
yarn install
|
||||
yarn run playwright install chromium
|
||||
|
||||
- run:
|
||||
name: Build Storybook
|
||||
working_directory: "./frontend"
|
||||
command: yarn run build:storybook
|
||||
|
||||
- run:
|
||||
name: Serve Storybook and run tests
|
||||
working_directory: "./frontend"
|
||||
command: |
|
||||
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
|
||||
"npx http-server storybook-static --port 6006 --silent" \
|
||||
"npx wait-on tcp:6006 && yarn test:storybook"
|
||||
|
||||
test-backend:
|
||||
docker:
|
||||
- image: penpotapp/devenv:latest
|
||||
- image: cimg/postgres:14.5
|
||||
environment:
|
||||
POSTGRES_USER: penpot_test
|
||||
POSTGRES_PASSWORD: penpot_test
|
||||
POSTGRES_DB: penpot_test
|
||||
- image: cimg/redis:7.0.5
|
||||
|
||||
working_directory: ~/repo
|
||||
resource_class: medium+
|
||||
|
||||
environment:
|
||||
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "backend/deps.edn" }}
|
||||
|
||||
- run:
|
||||
name: "tests"
|
||||
working_directory: "./backend"
|
||||
command: |
|
||||
clojure -M:dev:test --reporter kaocha.report/documentation
|
||||
|
||||
environment:
|
||||
PENPOT_TEST_DATABASE_URI: "postgresql://localhost/penpot_test"
|
||||
PENPOT_TEST_DATABASE_USERNAME: penpot_test
|
||||
PENPOT_TEST_DATABASE_PASSWORD: penpot_test
|
||||
PENPOT_TEST_REDIS_URI: "redis://localhost/1"
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
- ~/.gitlibs
|
||||
key: v1-dependencies-{{ checksum "backend/deps.edn" }}
|
||||
|
||||
test-render-wasm:
|
||||
docker:
|
||||
- image: penpotapp/devenv:latest
|
||||
|
||||
working_directory: ~/repo
|
||||
resource_class: medium+
|
||||
environment:
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: "fmt check"
|
||||
working_directory: "./render-wasm"
|
||||
command: |
|
||||
cargo fmt --check
|
||||
|
||||
- run:
|
||||
name: "lint"
|
||||
working_directory: "./render-wasm"
|
||||
command: |
|
||||
./lint
|
||||
|
||||
- run:
|
||||
name: "cargo tests"
|
||||
working_directory: "./render-wasm"
|
||||
command: |
|
||||
./test
|
||||
|
||||
workflows:
|
||||
penpot:
|
||||
jobs:
|
||||
- test-frontend:
|
||||
requires:
|
||||
- lint: success
|
||||
|
||||
- test-library:
|
||||
requires:
|
||||
- lint: success
|
||||
|
||||
- test-components:
|
||||
requires:
|
||||
- lint: success
|
||||
|
||||
- test-backend:
|
||||
requires:
|
||||
- lint: success
|
||||
|
||||
- test-common:
|
||||
requires:
|
||||
- lint: success
|
||||
|
||||
- lint
|
||||
- test-render-wasm
|
||||
21
.github/workflows/build-nitrate-module.yml
vendored
Normal file
21
.github/workflows/build-nitrate-module.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
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"
|
||||
16
.github/workflows/build-tag.yml
vendored
16
.github/workflows/build-tag.yml
vendored
@@ -21,6 +21,22 @@ jobs:
|
||||
with:
|
||||
gh_ref: ${{ github.ref_name }}
|
||||
|
||||
notify:
|
||||
name: Notifications
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build-docker
|
||||
|
||||
steps:
|
||||
- name: Notify Mattermost
|
||||
uses: mattermost/action-mattermost-notify@master
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||
TEXT: |
|
||||
🐳 *[PENPOT] Docker image available.*
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
|
||||
publish-final-tag:
|
||||
if: ${{ !contains(github.ref_name, '-RC') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && contains(github.ref_name, '.') }}
|
||||
needs: build-docker
|
||||
|
||||
13
.github/workflows/tests.yml
vendored
13
.github/workflows/tests.yml
vendored
@@ -159,17 +159,7 @@ jobs:
|
||||
- name: Build Bundle
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install
|
||||
yarn run build:app:assets
|
||||
yarn run build:app
|
||||
yarn run build:app:libs
|
||||
|
||||
- name: Build WASM
|
||||
working-directory: "./render-wasm"
|
||||
run: |
|
||||
./build release
|
||||
./scripts/build 0.0.0
|
||||
|
||||
- name: Store Bundle Cache
|
||||
uses: actions/cache@v4
|
||||
@@ -177,6 +167,7 @@ jobs:
|
||||
key: "integration-bundle-${{ github.sha }}"
|
||||
path: frontend/resources/public
|
||||
|
||||
|
||||
test-integration-1:
|
||||
name: "Integration Tests 1/4"
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
21
CHANGES.md
21
CHANGES.md
@@ -1,5 +1,26 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.13.0 (Unreleased)
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
### :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)
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
[app.common.schema :as sm]
|
||||
[clojure.core :as c]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[expound.alpha :as expound])
|
||||
[cuerdas.core :as str])
|
||||
#?(:clj
|
||||
(:import
|
||||
clojure.lang.IPersistentMap)))
|
||||
@@ -110,13 +109,6 @@
|
||||
(contains? data :explain))
|
||||
(explain (:explain data) opts)
|
||||
|
||||
(and (contains? data ::s/problems)
|
||||
(contains? data ::s/value)
|
||||
(contains? data ::s/spec))
|
||||
(binding [s/*explain-out* expound/printer]
|
||||
(with-out-str
|
||||
(s/explain-out (update data ::s/problems #(take (:length opts 10) %)))))
|
||||
|
||||
(contains? data ::sm/explain)
|
||||
(sm/humanize-explain (::sm/explain data) opts)))
|
||||
|
||||
|
||||
@@ -43,8 +43,6 @@
|
||||
"
|
||||
#?(:cljs (:require-macros [app.common.logging :as l]))
|
||||
(:require
|
||||
#?(:clj [clojure.edn :as edn]
|
||||
:cljs [cljs.reader :as edn])
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.pprint :as pp]
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.logic.variant-properties :as clvp]
|
||||
[app.common.path-names :as cpn]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
@@ -35,8 +34,7 @@
|
||||
[app.common.types.typography :as cty]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]
|
||||
[clojure.spec.alpha :as s]))
|
||||
[clojure.set :as set]))
|
||||
|
||||
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
|
||||
(log/set-level! :warn)
|
||||
@@ -473,10 +471,10 @@
|
||||
If an asset id is given, only shapes linked to this particular asset will
|
||||
be synchronized."
|
||||
[changes file-id asset-type asset-id library-id libraries current-file-id]
|
||||
(s/assert #{:colors :components :typographies} asset-type)
|
||||
(s/assert (s/nilable ::us/uuid) asset-id)
|
||||
(s/assert ::us/uuid file-id)
|
||||
(s/assert ::us/uuid library-id)
|
||||
(assert (contains? #{:colors :components :typographies} asset-type))
|
||||
(assert (or (nil? asset-id) (uuid? asset-id)))
|
||||
(assert (uuid? file-id))
|
||||
(assert (uuid? library-id))
|
||||
|
||||
(container-log :info asset-id
|
||||
:msg "Sync file with library"
|
||||
@@ -510,10 +508,10 @@
|
||||
If an asset id is given, only shapes linked to this particular asset will
|
||||
be synchronized."
|
||||
[changes file-id asset-type asset-id library-id libraries current-file-id]
|
||||
(s/assert #{:colors :components :typographies} asset-type)
|
||||
(s/assert (s/nilable ::us/uuid) asset-id)
|
||||
(s/assert ::us/uuid file-id)
|
||||
(s/assert ::us/uuid library-id)
|
||||
(assert (contains? #{:colors :components :typographies} asset-type))
|
||||
(assert (or (nil? asset-id) (uuid? asset-id)))
|
||||
(assert (uuid? file-id))
|
||||
(assert (uuid? library-id))
|
||||
|
||||
(container-log :info asset-id
|
||||
:msg "Sync local components with library"
|
||||
@@ -2441,11 +2439,13 @@
|
||||
(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}))
|
||||
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true}))
|
||||
[new-shape changes]
|
||||
(-> changes
|
||||
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
|
||||
@@ -2815,13 +2815,15 @@
|
||||
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
|
||||
|
||||
|
||||
;; If there is an alt-duplication of a variant, change its parent to root
|
||||
;; so the copy is made as a child of root
|
||||
;; If there is an alt-duplication we change to root
|
||||
;; For variants so the copy is made as a child of root
|
||||
;; This is because inside a variant-container can't be a copy
|
||||
;; For other shape this way the layout won't be changed when duplicated
|
||||
;; and if you move outside the layout will not change
|
||||
shapes (map (fn [shape]
|
||||
(if (and alt-duplication? (ctk/is-variant? shape))
|
||||
(assoc shape :parent-id uuid/zero :frame-id nil)
|
||||
shape))
|
||||
(cond-> shape
|
||||
alt-duplication?
|
||||
(assoc :parent-id uuid/zero :frame-id uuid/zero)))
|
||||
shapes)
|
||||
|
||||
|
||||
|
||||
@@ -123,8 +123,10 @@
|
||||
;; 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]
|
||||
:or {ignore-children-fn (constantly false)}}]
|
||||
ignore-children-fn
|
||||
ignore-mask]
|
||||
:or {ignore-children-fn (constantly false)
|
||||
ignore-mask false}}]
|
||||
(let [objects (pcb/get-objects changes)
|
||||
data (pcb/get-library-data changes)
|
||||
page-id (pcb/get-page-id changes)
|
||||
@@ -162,18 +164,20 @@
|
||||
lookup (d/getf objects)
|
||||
|
||||
groups-to-unmask
|
||||
(reduce (fn [group-ids id]
|
||||
;; When the shape to delete is the mask of a masked group,
|
||||
;; the mask condition must be removed, and it must be
|
||||
;; converted to a normal group.
|
||||
(let [obj (lookup id)
|
||||
parent (lookup (:parent-id obj))]
|
||||
(if (and (:masked-group parent)
|
||||
(= id (first (:shapes parent))))
|
||||
(conj group-ids (:id parent))
|
||||
group-ids)))
|
||||
#{}
|
||||
ids-to-delete)
|
||||
(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)
|
||||
[])
|
||||
|
||||
interacting-shapes
|
||||
(filter (fn [shape]
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
(:refer-clojure :exclude [deref merge parse-uuid parse-long parse-double parse-boolean type keys])
|
||||
#?(:cljs (:require-macros [app.common.schema :refer [ignoring]]))
|
||||
(:require
|
||||
#?(:clj [malli.dev.pretty :as mdp])
|
||||
#?(:clj [malli.dev.virhe :as v])
|
||||
[app.common.data :as d]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pprint :as pp]
|
||||
@@ -19,8 +21,6 @@
|
||||
[clojure.core :as c]
|
||||
[cuerdas.core :as str]
|
||||
[malli.core :as m]
|
||||
[malli.dev.pretty :as mdp]
|
||||
[malli.dev.virhe :as v]
|
||||
[malli.error :as me]
|
||||
[malli.generator :as mg]
|
||||
[malli.registry :as mr]
|
||||
@@ -245,27 +245,30 @@
|
||||
:level (d/nilv level 8)
|
||||
:length (d/nilv length 12)})))))
|
||||
|
||||
(defmethod v/-format ::schemaless-explain
|
||||
[_ explanation printer]
|
||||
{:body [:group
|
||||
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
|
||||
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer)]})
|
||||
#?(:clj
|
||||
(defmethod v/-format ::schemaless-explain
|
||||
[_ explanation printer]
|
||||
{:body [:group
|
||||
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
|
||||
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer)]}))
|
||||
|
||||
(defmethod v/-format ::explain
|
||||
[_ {:keys [schema] :as explanation} printer]
|
||||
{:body [:group
|
||||
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
|
||||
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer) :break :break
|
||||
(v/-block "Schema" (v/-visit schema printer) printer)]})
|
||||
#?(:clj
|
||||
(defmethod v/-format ::explain
|
||||
[_ {:keys [schema] :as explanation} printer]
|
||||
{:body [:group
|
||||
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
|
||||
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer) :break :break
|
||||
(v/-block "Schema" (v/-visit schema printer) printer)]}))
|
||||
|
||||
(defn pretty-explain
|
||||
"A helper that allows print a console-friendly output for the
|
||||
explain; should not be used for other purposes"
|
||||
[explain & {:keys [variant message]
|
||||
:or {variant ::explain
|
||||
message "Validation Error"}}]
|
||||
(let [explain (fn [] (me/with-error-messages explain))]
|
||||
((mdp/prettifier variant message explain default-options))))
|
||||
#?(:clj
|
||||
(defn pretty-explain
|
||||
"A helper that allows print a console-friendly output for the explain;
|
||||
should not be used for other purposes"
|
||||
[explain & {:keys [variant message]
|
||||
:or {variant ::explain
|
||||
message "Validation Error"}}]
|
||||
(let [explain (fn [] (me/with-error-messages explain))]
|
||||
((mdp/prettifier variant message explain default-options)))))
|
||||
|
||||
(defmacro ignoring
|
||||
[expr]
|
||||
@@ -281,9 +284,9 @@
|
||||
(defn check-fn
|
||||
"Create a predefined check function"
|
||||
[s & {:keys [hint type code]}]
|
||||
(let [s (schema s)
|
||||
validator* (delay (m/validator s))
|
||||
explainer* (delay (m/explainer s))
|
||||
(let [s (delay (schema s))
|
||||
validator* (delay (m/validator @s))
|
||||
explainer* (delay (m/explainer @s))
|
||||
hint (or ^boolean hint "check error")
|
||||
type (or ^boolean type :assertion)
|
||||
code (or ^boolean code :data-validation)]
|
||||
@@ -299,6 +302,13 @@
|
||||
::explain explain}))))
|
||||
value))))
|
||||
|
||||
(defn coercer
|
||||
[schema & {:as opts}]
|
||||
(let [decode-fn (lazy-decoder schema json-transformer)
|
||||
check-fn (check-fn schema opts)]
|
||||
(fn [data]
|
||||
(-> data decode-fn check-fn))))
|
||||
|
||||
(defn check
|
||||
"A helper intended to be used on assertions for validate/check the
|
||||
schema over provided data. Raises an assertion exception.
|
||||
@@ -993,6 +1003,9 @@
|
||||
(def valid-safe-number?
|
||||
(lazy-validator ::safe-number))
|
||||
|
||||
(def valid-safe-int?
|
||||
(lazy-validator ::safe-int))
|
||||
|
||||
(def valid-text?
|
||||
(validator ::text))
|
||||
|
||||
|
||||
@@ -1575,10 +1575,10 @@ Will return a value that matches this schema:
|
||||
(if (map? shadow)
|
||||
(let [legacy-shadow-type (get "type" shadow)]
|
||||
(-> shadow
|
||||
(set/rename-keys {"x" :offsetX
|
||||
"offsetX" :offsetX
|
||||
"y" :offsetY
|
||||
"offsetY" :offsetY
|
||||
(set/rename-keys {"x" :offset-x
|
||||
"offsetX" :offset-x
|
||||
"y" :offset-y
|
||||
"offsetY" :offset-y
|
||||
"blur" :blur
|
||||
"spread" :spread
|
||||
"color" :color
|
||||
@@ -1589,7 +1589,7 @@ Will return a value that matches this schema:
|
||||
(= "false" %) false
|
||||
(= legacy-shadow-type "innerShadow") true
|
||||
:else false))
|
||||
(select-keys [:offsetX :offsetY :blur :spread :color :inset])))
|
||||
(select-keys [:offset-x :offset-y :blur :spread :color :inset])))
|
||||
shadow))]
|
||||
(cond
|
||||
;; Reference value - keep as string
|
||||
@@ -1860,8 +1860,8 @@ Will return a value that matches this schema:
|
||||
(mapv (fn [shadow]
|
||||
(if (map? shadow)
|
||||
(-> shadow
|
||||
(set/rename-keys {:offsetX "offsetX"
|
||||
:offsetY "offsetY"
|
||||
(set/rename-keys {:offset-x "offsetX"
|
||||
:offset-y "offsetY"
|
||||
:blur "blur"
|
||||
:spread "spread"
|
||||
:color "color"
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
(defn parse
|
||||
[data]
|
||||
(cond
|
||||
(str/starts-with? data "%")
|
||||
(or (str/starts-with? data "%")
|
||||
(= data "develop"))
|
||||
{:full "develop"
|
||||
:branch "develop"
|
||||
:base "0.0.0"
|
||||
|
||||
@@ -1897,15 +1897,15 @@
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-single")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset false}]
|
||||
(t/is (= [{:offset-x "0", :offset-y "2px", :blur "4px", :spread "0", :color "#000", :inset false}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "multiple shadow token"
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-multiple")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset true}
|
||||
{:offsetX "0", :offsetY "8px", :blur "16px", :spread "0", :color "#000", :inset true}]
|
||||
(t/is (= [{:offset-x "0", :offset-y "2px", :blur "4px", :spread "0", :color "#000", :inset true}
|
||||
{:offset-x "0", :offset-y "8px", :blur "16px", :spread "0", :color "#000", :inset true}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "shadow token with reference"
|
||||
@@ -1918,7 +1918,7 @@
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-with-type")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offsetX "0", :offsetY "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}]
|
||||
(t/is (= [{:offset-x "0", :offset-y "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "shadow token with description"
|
||||
@@ -1937,14 +1937,14 @@
|
||||
(ctob/make-token
|
||||
{:name "shadow.single"
|
||||
:type :shadow
|
||||
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}]
|
||||
:value [{:offset-x "0" :offset-y "2px" :blur "4px" :spread "0" :color "#0000001A"}]
|
||||
:description "A single shadow"})
|
||||
"shadow.multiple"
|
||||
(ctob/make-token
|
||||
{:name "shadow.multiple"
|
||||
:type :shadow
|
||||
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}
|
||||
{:offsetX "0" :offsetY "8px" :blur "16px" :spread "0" :color "#0000001A"}]})
|
||||
:value [{:offset-x "0" :offset-y "2px" :blur "4px" :spread "0" :color "#0000001A"}
|
||||
{:offset-x "0" :offset-y "8px" :blur "16px" :spread "0" :color "#0000001A"}]})
|
||||
"shadow.ref"
|
||||
(ctob/make-token
|
||||
{:name "shadow.ref"
|
||||
@@ -1991,7 +1991,7 @@
|
||||
(ctob/make-token
|
||||
{:name "shadow.test"
|
||||
:type :shadow
|
||||
:value [{:offsetX "1" :offsetY "1" :blur "1" :spread "1" :color "red" :inset true}]
|
||||
:value [{:offset-x "1" :offset-y "1" :blur "1" :spread "1" :color "red" :inset true}]
|
||||
:description "Round trip test"})
|
||||
"shadow.ref"
|
||||
(ctob/make-token
|
||||
|
||||
@@ -25,48 +25,6 @@ RUN set -ex; \
|
||||
binutils \
|
||||
build-essential autoconf libtool pkg-config
|
||||
|
||||
|
||||
################################################################################
|
||||
## IMAGE MAGICK
|
||||
################################################################################
|
||||
|
||||
FROM base AS build-imagemagick
|
||||
|
||||
ENV IMAGEMAGICK_VERSION=7.1.1-47 \
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qq upgrade; \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
libltdl-dev \
|
||||
libpng-dev \
|
||||
libjpeg-dev \
|
||||
libtiff-dev \
|
||||
libwebp-dev \
|
||||
libopenexr-dev \
|
||||
libfftw3-dev \
|
||||
libzip-dev \
|
||||
liblcms2-dev \
|
||||
liblzma-dev \
|
||||
libzstd-dev \
|
||||
libheif-dev \
|
||||
librsvg2-dev \
|
||||
; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN set -eux; \
|
||||
curl -LfsSo /tmp/magick.tar.gz https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IMAGEMAGICK_VERSION}.tar.gz; \
|
||||
mkdir -p /tmp/magick; \
|
||||
cd /tmp/magick; \
|
||||
tar -xf /tmp/magick.tar.gz --strip-components=1; \
|
||||
./configure --prefix=/opt/imagick; \
|
||||
make -j 2; \
|
||||
make install; \
|
||||
rm -rf /opt/imagick/lib/libMagick++*; \
|
||||
rm -rf /opt/imagick/include; \
|
||||
rm -rf /opt/imagick/share;
|
||||
|
||||
################################################################################
|
||||
## NODE SETUP
|
||||
################################################################################
|
||||
@@ -417,7 +375,7 @@ ENV LANG='C.UTF-8' \
|
||||
RUSTUP_HOME="/opt/rustup" \
|
||||
PATH="/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH"
|
||||
|
||||
COPY --from=build-imagemagick /opt/imagick /opt/imagick
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
|
||||
COPY --from=setup-jvm /opt/jdk /opt/jdk
|
||||
COPY --from=setup-jvm /opt/clojure /opt/clojure
|
||||
COPY --from=setup-node /opt/node /opt/node
|
||||
|
||||
@@ -69,6 +69,11 @@ services:
|
||||
- PENPOT_LDAP_ATTRS_FULLNAME=cn
|
||||
- PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
|
||||
|
||||
networks:
|
||||
default:
|
||||
aliases:
|
||||
- main
|
||||
|
||||
minio:
|
||||
image: "minio/minio:RELEASE.2025-04-03T14-56-28Z"
|
||||
command: minio server /mnt/data --console-address ":9001"
|
||||
@@ -80,10 +85,6 @@ services:
|
||||
- MINIO_ROOT_USER=minioadmin
|
||||
- MINIO_ROOT_PASSWORD=minioadmin
|
||||
|
||||
ports:
|
||||
- 9000:9000
|
||||
- 9001:9001
|
||||
|
||||
networks:
|
||||
default:
|
||||
aliases:
|
||||
|
||||
@@ -38,11 +38,11 @@ http {
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 3;
|
||||
gzip_comp_level 6;
|
||||
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;
|
||||
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml application/wasm;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
@@ -223,16 +223,15 @@ http {
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
}
|
||||
|
||||
location ~ ^/(/|css|fonts|images|js|wasm|mjs|map) {
|
||||
location ~* \.(js|css|jpg|png|svg|ttf|woff|woff2|wasm)$ {
|
||||
add_header Cache-Control "public, max-age=604800" always; # 7 days
|
||||
}
|
||||
|
||||
location ~ ^/[^/]+/(.*)$ {
|
||||
return 301 " /404";
|
||||
}
|
||||
|
||||
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";
|
||||
add_header Cache-Control "no-store, no-cache, max-age=0" always;
|
||||
try_files $uri /index.html$is_args$args /index.html =404;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,18 @@ tmux select-window -t penpot:1
|
||||
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
|
||||
tmux send-keys -t penpot 'yarn run watch:app' enter
|
||||
|
||||
tmux new-window -t penpot:2 -n 'frontend storybook'
|
||||
tmux new-window -t penpot:2 -n 'frontend ts'
|
||||
tmux select-window -t penpot:2
|
||||
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
|
||||
tmux send-keys -t penpot 'yarn watch:ts' enter
|
||||
|
||||
tmux new-window -t penpot:3 -n 'frontend storybook'
|
||||
tmux select-window -t penpot:3
|
||||
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
|
||||
tmux send-keys -t penpot 'yarn run watch:storybook' enter
|
||||
|
||||
tmux new-window -t penpot:3 -n 'exporter'
|
||||
tmux select-window -t penpot:3
|
||||
tmux new-window -t penpot:4 -n 'exporter'
|
||||
tmux select-window -t penpot:4
|
||||
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
|
||||
tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l
|
||||
tmux send-keys -t penpot 'yarn run watch' enter
|
||||
@@ -45,8 +50,8 @@ tmux split-window -v
|
||||
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
|
||||
tmux send-keys -t penpot './scripts/wait-and-start.sh' enter
|
||||
|
||||
tmux new-window -t penpot:4 -n 'backend'
|
||||
tmux select-window -t penpot:4
|
||||
tmux new-window -t penpot:5 -n 'backend'
|
||||
tmux select-window -t penpot:5
|
||||
tmux send-keys -t penpot 'cd penpot/backend' enter C-l
|
||||
tmux send-keys -t penpot './scripts/start-dev' enter
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ 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/location.d/;
|
||||
|
||||
@@ -42,11 +42,11 @@ http {
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_static on;
|
||||
gzip_comp_level 4;
|
||||
gzip_comp_level 6;
|
||||
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;
|
||||
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml application/wasm;
|
||||
|
||||
proxy_buffer_size 16k;
|
||||
proxy_busy_buffers_size 24k; # essentially, proxy_buffer_size + 2 small buffers of 4k
|
||||
@@ -142,24 +142,15 @@ http {
|
||||
location / {
|
||||
include /etc/nginx/overrides/location.d/*.conf;
|
||||
|
||||
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 ~* \.(js|css|jpg|png|svg|ttf|woff|woff2|wasm)$ {
|
||||
add_header Cache-Control "public, max-age=604800" always; # 7 days
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@@ -11,4 +11,4 @@ node_modules/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/playwright/**/visual-specs/**/*.png
|
||||
|
||||
/ts/dist/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @type { import('@storybook/react-vite').StorybookConfig } */
|
||||
const config = {
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)", "../ts/src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
staticDirs: ["../resources/public"],
|
||||
addons: [
|
||||
"@storybook/addon-themes",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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 = [
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
:git/url "https://github.com/funcool/beicon.git"}
|
||||
|
||||
funcool/rumext
|
||||
{:git/tag "v2.24"
|
||||
:git/sha "17a0c94"
|
||||
{:git/tag "v2.25"
|
||||
:git/sha "27e5a1a"
|
||||
:git/url "https://github.com/funcool/rumext.git"}
|
||||
|
||||
instaparse/instaparse {:mvn/version "1.5.0"}
|
||||
@@ -42,7 +42,7 @@
|
||||
:dev
|
||||
{:extra-paths ["dev"]
|
||||
:extra-deps
|
||||
{thheller/shadow-cljs {:mvn/version "3.2.0"}
|
||||
{thheller/shadow-cljs {:mvn/version "3.2.2"}
|
||||
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
||||
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
||||
criterium/criterium {:mvn/version "RELEASE"}
|
||||
|
||||
23
frontend/eslint.config.js
Normal file
23
frontend/eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['ts/dist']),
|
||||
{
|
||||
files: ['ts/src/**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
@@ -17,7 +17,8 @@
|
||||
"@zip.js/zip.js@npm:^2.7.44": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"playwright": "1.52.0",
|
||||
"playwright-core": "1.52.0"
|
||||
"playwright-core": "1.52.0",
|
||||
"globals": "^16.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build:app:assets": "node ./scripts/build-app-assets.js",
|
||||
@@ -32,8 +33,9 @@
|
||||
"e2e:server": "node ./scripts/e2e-server.js",
|
||||
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
|
||||
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
|
||||
"fmt:js": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -w",
|
||||
"fmt:js:check": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js",
|
||||
"fmt:js": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -c ts/src/**/*.ts -w",
|
||||
"fmt:js:check": "yarn run prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -c ts/src/**/*.ts -c --check",
|
||||
"lint:ts": "eslint ts/",
|
||||
"lint:clj": "clj-kondo --parallel --lint src/",
|
||||
"lint:scss": "yarn run prettier -c resources/styles -c src/**/*.scss",
|
||||
"lint:scss:fix": "yarn run prettier -c resources/styles -c src/**/*.scss -w",
|
||||
@@ -45,11 +47,12 @@
|
||||
"translations": "node ./scripts/translations.js",
|
||||
"watch:app:assets": "node ./scripts/watch.js",
|
||||
"watch:app:libs": "node ./scripts/build-libs.js --watch",
|
||||
"watch:app:main": "clojure -M:dev:shadow-cljs watch main storybook",
|
||||
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
|
||||
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
||||
"watch:app": "yarn run clear:shadow-cache && yarn run build:app:worker && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
|
||||
"watch:app": "yarn run clear:shadow-cache && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
|
||||
"watch": "yarn run watch:app:assets",
|
||||
"watch:storybook": "yarn run build:storybook:assets && concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"",
|
||||
"watch:ts": "vite build --watch",
|
||||
"watch:storybook:assets": "node ./scripts/watch-storybook.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -59,11 +62,16 @@
|
||||
"@storybook/addon-vitest": "10.0.4",
|
||||
"@storybook/react-vite": "10.0.4",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/react": "^19.1.16",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@vitest/browser": "3.2.4",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"concurrently": "^9.2.1",
|
||||
"esbuild": "^0.25.9",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.22",
|
||||
"express": "^5.1.0",
|
||||
"fancy-log": "^2.0.0",
|
||||
"getopts": "^2.3.0",
|
||||
@@ -95,6 +103,7 @@
|
||||
"storybook": "10.0.4",
|
||||
"svg-sprite": "^2.0.4",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.45.0",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^3.2.0",
|
||||
"wasm-pack": "^0.13.1",
|
||||
@@ -108,7 +117,9 @@
|
||||
"@penpot/plugins-runtime": "1.3.2",
|
||||
"@penpot/svgo": "penpot/svgo#v3.1",
|
||||
"@penpot/text-editor": "portal:./text-editor",
|
||||
"@penpot/ts": "portal:./ts",
|
||||
"@tokens-studio/sd-transforms": "1.2.11",
|
||||
"@vitejs/plugin-react": "4.2.0",
|
||||
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
|
||||
"compression": "^1.8.1",
|
||||
"date-fns": "^4.1.0",
|
||||
|
||||
@@ -22,9 +22,9 @@ export default defineConfig({
|
||||
workers: 1,
|
||||
/* Timeout for expects (longer in CI) */
|
||||
|
||||
timeout: 60000,
|
||||
timeout: 80000,
|
||||
expect: {
|
||||
timeout: process.env.CI ? 30000 : 5000,
|
||||
timeout: process.env.CI ? 40000 : 5000,
|
||||
},
|
||||
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
|
||||
@@ -5947,8 +5947,8 @@
|
||||
"~:spread": "10",
|
||||
"~:color": "rgb(160, 73, 73)",
|
||||
"~:inset": true,
|
||||
"~:offsetX": "10",
|
||||
"~:offsetY": "10"
|
||||
"~:offset-x": "10",
|
||||
"~:offset-y": "10"
|
||||
}
|
||||
],
|
||||
"~:description": "",
|
||||
|
||||
@@ -73,7 +73,7 @@ export class BasePage {
|
||||
}
|
||||
|
||||
static async mockConfigFlags(page, flags) {
|
||||
const url = "**/js/config.js?ts=*";
|
||||
const url = "**/js/config.js*";
|
||||
return await page.route(url, (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
|
||||
@@ -303,7 +303,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.pressSequentially(".changed");
|
||||
|
||||
await tokensUpdateCreateModal.getByRole("button", {name: "Save"}).click();
|
||||
await tokensUpdateCreateModal.getByRole("button", { name: "Save" }).click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
@@ -1070,6 +1070,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
|
||||
// Fill in values for all fields and verify they persist when switching tabs
|
||||
await fontSizeField.fill("16");
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
const fontWeightField = tokensUpdateCreateModal.getByLabel(/Font Weight/i);
|
||||
const letterSpacingField =
|
||||
@@ -1238,8 +1239,12 @@ test.describe("Tokens: Apply token", () => {
|
||||
// Fill in the shadow values
|
||||
const offsetXInput = firstShadowFields.getByLabel("X");
|
||||
const offsetYInput = firstShadowFields.getByLabel("Y");
|
||||
const blurInput = firstShadowFields.getByLabel("Blur");
|
||||
const spreadInput = firstShadowFields.getByLabel("Spread");
|
||||
const blurInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Blur",
|
||||
});
|
||||
const spreadInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Spread",
|
||||
});
|
||||
|
||||
await offsetXInput.fill("2");
|
||||
await offsetYInput.fill("2");
|
||||
@@ -1260,9 +1265,10 @@ test.describe("Tokens: Apply token", () => {
|
||||
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
|
||||
|
||||
// Verify that a color value was set
|
||||
const colorInput = firstShadowFields.getByLabel("Color");
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
await expect(firstColorValue).toMatch(/^rgb(.*)$/);
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await expect(colorInput).toHaveValue(/^rgb(.*)$/);
|
||||
|
||||
// Wait for validation to complete
|
||||
await expect(
|
||||
@@ -1280,11 +1286,15 @@ test.describe("Tokens: Apply token", () => {
|
||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-0",
|
||||
);
|
||||
const colorInput = firstShadowFields.getByLabel("Color");
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
|
||||
// User adds a second shadow
|
||||
const addButton = firstShadowFields.getByTestId("shadow-add-button-0");
|
||||
const addButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Add Shadow",
|
||||
});
|
||||
await addButton.click();
|
||||
|
||||
const secondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
@@ -1293,8 +1303,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(secondShadowFields).toBeVisible();
|
||||
|
||||
// User adds a third shadow
|
||||
const addButton2 = secondShadowFields.getByTestId("shadow-add-button-1");
|
||||
await addButton2.click();
|
||||
await addButton.click();
|
||||
|
||||
const thirdShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-2",
|
||||
@@ -1304,9 +1313,15 @@ test.describe("Tokens: Apply token", () => {
|
||||
// User adds values for the third shadow
|
||||
const thirdOffsetXInput = thirdShadowFields.getByLabel("X");
|
||||
const thirdOffsetYInput = thirdShadowFields.getByLabel("Y");
|
||||
const thirdBlurInput = thirdShadowFields.getByLabel("Blur");
|
||||
const thirdSpreadInput = thirdShadowFields.getByLabel("Spread");
|
||||
const thirdColorInput = thirdShadowFields.getByLabel("Color");
|
||||
const thirdBlurInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Blur",
|
||||
});
|
||||
const thirdSpreadInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Spread",
|
||||
});
|
||||
const thirdColorInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
|
||||
await thirdOffsetXInput.fill("10");
|
||||
await thirdOffsetYInput.fill("10");
|
||||
@@ -1315,15 +1330,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
await thirdColorInput.fill("#FF0000");
|
||||
|
||||
// User removes the 2nd shadow
|
||||
const removeButton2 = secondShadowFields.getByTestId(
|
||||
"shadow-remove-button-1",
|
||||
);
|
||||
const removeButton2 = secondShadowFields.getByRole("button", {
|
||||
name: "Remove Shadow",
|
||||
});
|
||||
await removeButton2.click();
|
||||
|
||||
// Verify second shadow is removed
|
||||
await expect(
|
||||
secondShadowFields.getByTestId("shadow-add-button-3"),
|
||||
).not.toBeVisible();
|
||||
// Verify that we have only two shadow fields
|
||||
await expect(thirdShadowFields).not.toBeVisible();
|
||||
|
||||
// Verify that the first shadow kept its values
|
||||
const firstOffsetXValue = await firstShadowFields
|
||||
@@ -1333,13 +1346,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const firstBlurValue = await firstShadowFields
|
||||
.getByLabel("Blur")
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.inputValue();
|
||||
const firstSpreadValue = await firstShadowFields
|
||||
.getByLabel("Spread")
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.inputValue();
|
||||
const firstColorValueAfter = await firstShadowFields
|
||||
.getByLabel("Color")
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.inputValue();
|
||||
|
||||
await expect(firstOffsetXValue).toBe("2");
|
||||
@@ -1348,7 +1361,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(firstSpreadValue).toBe("0");
|
||||
await expect(firstColorValueAfter).toBe(firstColorValue);
|
||||
|
||||
// Verify that the third shadow (now second) kept its values
|
||||
// Verify that the second kept its values (after shadow 3)
|
||||
// After removing index 1, the third shadow becomes the second shadow at index 1
|
||||
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-1",
|
||||
@@ -1362,13 +1375,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const secondBlurValue = await newSecondShadowFields
|
||||
.getByLabel("Blur")
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.inputValue();
|
||||
const secondSpreadValue = await newSecondShadowFields
|
||||
.getByLabel("Spread")
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.inputValue();
|
||||
const secondColorValue = await newSecondShadowFields
|
||||
.getByLabel("Color")
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.inputValue();
|
||||
|
||||
await expect(secondOffsetXValue).toBe("10");
|
||||
@@ -1385,7 +1398,9 @@ test.describe("Tokens: Apply token", () => {
|
||||
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-1",
|
||||
);
|
||||
const colorInput = firstShadowFields.getByLabel("Color");
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
|
||||
// Switch to reference tab
|
||||
@@ -1413,13 +1428,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const restoredFirstBlur = await firstShadowFields
|
||||
.getByLabel("Blur")
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.inputValue();
|
||||
const restoredFirstSpread = await firstShadowFields
|
||||
.getByLabel("Spread")
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.inputValue();
|
||||
const restoredFirstColor = await firstShadowFields
|
||||
.getByLabel("Color")
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.inputValue();
|
||||
|
||||
await expect(restoredFirstOffsetX).toBe("2");
|
||||
@@ -1436,13 +1451,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const restoredSecondBlur = await newSecondShadowFields
|
||||
.getByLabel("Blur")
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.inputValue();
|
||||
const restoredSecondSpread = await newSecondShadowFields
|
||||
.getByLabel("Spread")
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.inputValue();
|
||||
const restoredSecondColor = await newSecondShadowFields
|
||||
.getByLabel("Color")
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.inputValue();
|
||||
|
||||
await expect(restoredSecondOffsetX).toBe("10");
|
||||
|
||||
@@ -17,23 +17,26 @@
|
||||
<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?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
<link id="theme" href="css/main.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
||||
<link href="css/ts-style.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
{{#isDebug}}
|
||||
<link href="css/debug.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
<link href="css/debug.css?version={{& version}}" 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 defer src="{{& config}}"></script>
|
||||
<script defer src="{{& polyfills}}"></script>
|
||||
<script src="{{& config}}"></script>
|
||||
<script src="{{& polyfills}}"></script>
|
||||
<script type="importmap">{{& importmap }}</script>
|
||||
{{/manifest}}
|
||||
|
||||
<script>
|
||||
window.penpotTranslations = JSON.parse({{& translations}});
|
||||
window.penpotVersion = "%version%";
|
||||
window.penpotBuildDate = "%buildDate%";
|
||||
</script>
|
||||
<!--cookie-consent-->
|
||||
</head>
|
||||
<body>
|
||||
@@ -44,9 +47,11 @@
|
||||
<section id="modal"></section>
|
||||
|
||||
{{# manifest}}
|
||||
<script defer src="js/libs.js?ts={{& ts}}"></script>
|
||||
<script defer src="{{& shared}}"></script>
|
||||
<script defer src="{{& main}}"></script>
|
||||
<script type="module" src="{{& libs}}"></script>
|
||||
<script type="module">
|
||||
import { init } from "{{& app_main}}";
|
||||
init();
|
||||
</script>
|
||||
{{/manifest}}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<link href="./css/ds.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
|
||||
<link href="./css/ds.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
@@ -9,7 +9,3 @@
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
window.penpotTranslations = JSON.parse({{& translations}});
|
||||
</script>
|
||||
|
||||
@@ -6,22 +6,24 @@
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
|
||||
<script>
|
||||
window.penpotVersion = "%version%";
|
||||
window.penpotBuildDate = "%buildDate%";
|
||||
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}}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
{{# manifest}}
|
||||
<script src="js/libs.js?ts={{& ts}}"></script>
|
||||
<script src="{{& shared}}"></script>
|
||||
<script src="{{& rasterizer}}"></script>
|
||||
<script type="module" src="{{& libs}}"></script>
|
||||
<script type="module">
|
||||
import { init } from "{{& rasterizer_main}}";
|
||||
init();
|
||||
</script>
|
||||
{{/manifest}}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,20 +7,24 @@
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
|
||||
<script>
|
||||
window.penpotVersion = "%version%";
|
||||
globalThis.penpotVersion = "{{& version}}";
|
||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||
</script>
|
||||
|
||||
{{# manifest}}
|
||||
<script src="{{& config}}"></script>
|
||||
<script src="{{& polyfills}}"></script>
|
||||
<script type="importmap">{{& importmap }}</script>
|
||||
{{/manifest}}
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
{{# manifest}}
|
||||
<script src="js/libs.js?ts={{& ts}}"></script>
|
||||
<script src="{{& shared}}"></script>
|
||||
<script src="{{& render}}"></script>
|
||||
<script type="module" src="{{& libs}}"></script>
|
||||
<script type="module">
|
||||
import { init } from "{{& render_main}}";
|
||||
init();
|
||||
</script>
|
||||
{{/manifest}}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -28,6 +28,8 @@ 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 =
|
||||
@@ -47,8 +49,7 @@ 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 {
|
||||
@@ -187,33 +188,34 @@ async function readManifestFile() {
|
||||
}
|
||||
|
||||
async function readShadowManifest() {
|
||||
const ts = Date.now();
|
||||
try {
|
||||
const content = await readManifestFile();
|
||||
const index = {
|
||||
app_main: "./js/main.js",
|
||||
render_main: "./js/render.js",
|
||||
rasterizer_main: "./js/rasterizer.js",
|
||||
|
||||
const index = {
|
||||
ts: ts,
|
||||
config: "js/config.js?ts=" + ts,
|
||||
polyfills: "js/polyfills.js?ts=" + ts,
|
||||
worker_main: "js/worker/main.js?ts=" + ts,
|
||||
};
|
||||
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,
|
||||
|
||||
for (let item of content) {
|
||||
index[item.name] = "js/" + item["output-name"];
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
return index;
|
||||
} catch (cause) {
|
||||
return {
|
||||
ts: ts,
|
||||
config: "js/config.js?ts=" + ts,
|
||||
polyfills: "js/polyfills.js?ts=" + ts,
|
||||
main: "js/main.js?ts=" + ts,
|
||||
shared: "js/shared.js?ts=" + ts,
|
||||
worker_main: "js/worker/main.js?ts=" + ts,
|
||||
rasterizer: "js/rasterizer.js?ts=" + ts,
|
||||
};
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
async function renderTemplate(path, context = {}, partials = {}) {
|
||||
@@ -253,7 +255,7 @@ const markedOptions = {
|
||||
|
||||
marked.use(markedOptions);
|
||||
|
||||
async function readTranslations() {
|
||||
export async function compileTranslations() {
|
||||
const langs = [
|
||||
"ar",
|
||||
"ca",
|
||||
@@ -290,9 +292,10 @@ async function readTranslations() {
|
||||
["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`;
|
||||
@@ -311,11 +314,6 @@ async function readTranslations() {
|
||||
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;
|
||||
@@ -325,9 +323,9 @@ async function readTranslations() {
|
||||
message = marked.parseInline(message);
|
||||
}
|
||||
|
||||
result[key][lang] = message;
|
||||
result[key] = message;
|
||||
} else {
|
||||
result[key][lang] = msgs.map((item) => {
|
||||
result[key] = msgs.map((item) => {
|
||||
if (isMarkdown) {
|
||||
return marked.parseInline(item);
|
||||
} else {
|
||||
@@ -336,22 +334,12 @@ async function readTranslations() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -403,14 +391,6 @@ 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;
|
||||
|
||||
@@ -432,13 +412,16 @@ 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",
|
||||
{
|
||||
manifest: manifest,
|
||||
translations: JSON.stringify(translations),
|
||||
isDebug,
|
||||
},
|
||||
context,
|
||||
partials,
|
||||
);
|
||||
|
||||
@@ -446,41 +429,30 @@ 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",
|
||||
{
|
||||
manifest: manifest,
|
||||
},
|
||||
context,
|
||||
partials,
|
||||
);
|
||||
await fs.writeFile("./.storybook/preview-body.html", content);
|
||||
|
||||
content = await renderTemplate(
|
||||
"resources/templates/preview-head.mustache",
|
||||
{
|
||||
manifest: manifest,
|
||||
translations: JSON.stringify(storybookTranslations),
|
||||
},
|
||||
context,
|
||||
partials,
|
||||
);
|
||||
await fs.writeFile("./.storybook/preview-head.html", content);
|
||||
|
||||
content = await renderTemplate("resources/templates/render.mustache", {
|
||||
manifest: manifest,
|
||||
translations: JSON.stringify(translations),
|
||||
});
|
||||
content = await renderTemplate("resources/templates/render.mustache", context);
|
||||
|
||||
await fs.writeFile("./resources/public/render.html", content);
|
||||
|
||||
content = await renderTemplate("resources/templates/rasterizer.mustache", {
|
||||
manifest: manifest,
|
||||
translations: JSON.stringify(translations),
|
||||
});
|
||||
content = await renderTemplate("resources/templates/rasterizer.mustache", context);
|
||||
|
||||
await fs.writeFile("./resources/public/rasterizer.html", content);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ yarn install || exit 1;
|
||||
rm -rf resources/public;
|
||||
rm -rf target/dist;
|
||||
|
||||
yarn run build:app:main --config-merge "{:release-version \"${CURRENT_HASH}-${TS}\"}" $EXTRA_PARAMS || exit 1
|
||||
mkdir -p resources/public;
|
||||
mkdir -p target/dist;
|
||||
|
||||
yarn run build:app:main $EXTRA_PARAMS || exit 1
|
||||
|
||||
if [ "$INCLUDE_WASM" = "yes" ]; then
|
||||
yarn run build:wasm || exit 1;
|
||||
@@ -35,18 +38,9 @@ fi
|
||||
yarn run build:app:libs || exit 1;
|
||||
yarn run build:app:assets || exit 1;
|
||||
|
||||
mkdir -p target/dist;
|
||||
rsync -avr resources/public/ target/dist/
|
||||
sed -i "s/render-wasm.js/render-wasm.js?version=$CURRENT_VERSION/g" ./resources/public/js/worker/main.js;
|
||||
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/index.html;
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/render.html;
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/rasterizer.html;
|
||||
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/index.html;
|
||||
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/rasterizer.html;
|
||||
|
||||
if [ "$INCLUDE_WASM" = "yes" ]; then
|
||||
sed -i "s/version=develop/version=$CURRENT_VERSION/g" ./target/dist/js/render_wasm.js;
|
||||
fi
|
||||
rsync -avr resources/public/ target/dist/;
|
||||
|
||||
if [ "$INCLUDE_STORYBOOK" = "yes" ]; then
|
||||
# build storybook
|
||||
|
||||
@@ -4,5 +4,6 @@ await h.compileStyles();
|
||||
await h.copyAssets();
|
||||
await h.copyWasmPlayground();
|
||||
await h.compileSvgSprites();
|
||||
await h.compileTranslations();
|
||||
await h.compileTemplates();
|
||||
await h.compilePolyfills();
|
||||
|
||||
@@ -31,9 +31,9 @@ const rebuildNotify = {
|
||||
const config = {
|
||||
entryPoints: ["target/index.js"],
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
format: "esm",
|
||||
banner: {
|
||||
js: '"use strict"; var global = globalThis;',
|
||||
js: '"use strict";\nvar global = globalThis;',
|
||||
},
|
||||
outfile: "resources/public/js/libs.js",
|
||||
plugins: [fixReactVirtualized, rebuildNotify],
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
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,6 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export OPTIONS="-A:dev -J-XX:-OmitStackTraceInFastThrow";
|
||||
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"
|
||||
|
||||
set -ex
|
||||
exec clojure $OPTIONS -M -m rebel-readline.main
|
||||
exec clojure $OPTIONS -M -e "$OPTIONS_EVAL" -m rebel-readline.main
|
||||
|
||||
@@ -52,6 +52,7 @@ 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();
|
||||
@@ -81,7 +82,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.compileTemplates();
|
||||
await h.compileTranslations();
|
||||
});
|
||||
|
||||
log.info("watch: assets (~)");
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
|
||||
:builds
|
||||
{:main
|
||||
{:target :browser
|
||||
{:target :esm
|
||||
:output-dir "resources/public/js/"
|
||||
:asset-path "/js"
|
||||
:devtools {:watch-dir "resources/public"
|
||||
:reload-strategy :full}
|
||||
:build-options {:manifest-name "manifest.json"}
|
||||
:module-loader true
|
||||
:modules
|
||||
{:shared
|
||||
{:entries []}
|
||||
@@ -20,42 +19,42 @@
|
||||
:main
|
||||
{:entries [app.main app.plugins.api]
|
||||
:depends-on #{:shared}
|
||||
:init-fn app.main/init}
|
||||
:exports {init app.main/init}}
|
||||
|
||||
:util-highlight
|
||||
{:entries [app.util.code-highlight]
|
||||
:depends-on #{:main}}
|
||||
:depends-on #{:shared}}
|
||||
|
||||
:main-auth
|
||||
{:entries [app.main.ui.auth
|
||||
app.main.ui.auth.verify-token]
|
||||
:depends-on #{:main}}
|
||||
:depends-on #{:shared}}
|
||||
|
||||
:main-viewer
|
||||
{:entries [app.main.ui.viewer]
|
||||
:depends-on #{:main :main-auth}}
|
||||
:depends-on #{:shared :main-auth}}
|
||||
|
||||
:main-workspace
|
||||
{:entries [app.main.ui.workspace]
|
||||
:depends-on #{:main}}
|
||||
:depends-on #{:shared}}
|
||||
|
||||
:main-dashboard
|
||||
{:entries [app.main.ui.dashboard]
|
||||
:depends-on #{:main}}
|
||||
:depends-on #{:shared}}
|
||||
|
||||
:main-settings
|
||||
{:entries [app.main.ui.settings]
|
||||
:depends-on #{:main}}
|
||||
:depends-on #{:shared}}
|
||||
|
||||
:render
|
||||
{:entries [app.render]
|
||||
:depends-on #{:shared}
|
||||
:init-fn app.render/init}
|
||||
:exports {init app.render/init}}
|
||||
|
||||
:rasterizer
|
||||
{:entries [app.rasterizer]
|
||||
:depends-on #{:shared}
|
||||
:init-fn app.rasterizer/init}}
|
||||
:exports {init app.rasterizer/init}}}
|
||||
|
||||
:js-options
|
||||
{:entry-keys ["module" "browser" "main"]
|
||||
@@ -75,11 +74,10 @@
|
||||
:compiler-options
|
||||
{:fn-invoke-direct true
|
||||
:optimizations #shadow/env ["PENPOT_BUILD_OPTIMIZATIONS" :as :keyword :default :advanced]
|
||||
:output-wrapper true
|
||||
:rename-prefix-namespace "PENPOT"
|
||||
:source-map true
|
||||
:elide-asserts true
|
||||
:anon-fn-naming-policy :off
|
||||
:cross-chunk-method-motion false
|
||||
:source-map-detail-level :all}}}
|
||||
|
||||
:worker
|
||||
|
||||
@@ -86,7 +86,6 @@
|
||||
(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))
|
||||
@@ -191,5 +190,4 @@
|
||||
|
||||
(defn resolve-static-asset
|
||||
[path]
|
||||
(let [uri (u/join public-uri path)]
|
||||
(assoc uri :query (dm/str "version=" (:full version)))))
|
||||
(u/join public-uri path))
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
(ns app.main
|
||||
(:require
|
||||
["@penpot/ts" :refer [setTranslation]]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.common.types.objects-map]
|
||||
@@ -38,6 +39,8 @@
|
||||
(log/setup! {:app :info})
|
||||
(log/set-level! :debug)
|
||||
|
||||
(setTranslation i18n/tr)
|
||||
|
||||
(when (= :browser cf/target)
|
||||
(log/inf :version (:full cf/version)
|
||||
:asserts *assert*
|
||||
@@ -92,7 +95,7 @@
|
||||
(defn ^:export init
|
||||
[]
|
||||
(mw/init!)
|
||||
(i18n/init! cf/translations)
|
||||
(i18n/init)
|
||||
(cur/init-styles)
|
||||
(thr/init!)
|
||||
(init-ui)
|
||||
@@ -114,11 +117,4 @@
|
||||
[]
|
||||
(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 1440
|
||||
:height 3120}
|
||||
:width 412
|
||||
:height 892}
|
||||
{:name "Google Pixel 6a/6"
|
||||
:width 1080
|
||||
:height 2400}
|
||||
:width 412
|
||||
:height 915}
|
||||
{:name "Google Pixel 4a/5"
|
||||
:width 393
|
||||
:height 851}
|
||||
{:name "Samsung Galaxy S22"
|
||||
:width 1080
|
||||
:height 2340}
|
||||
:width 360
|
||||
:height 780}
|
||||
{:name "Samsung Galaxy S20+"
|
||||
:width 384
|
||||
:height 854}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
(let [uagent (new ua/UAParser)]
|
||||
(merge
|
||||
{:app-version (:full cf/version)
|
||||
:locale @i18n/locale}
|
||||
:locale i18n/*current-locale*}
|
||||
(let [browser (.getBrowser uagent)]
|
||||
{:browser (obj/get browser "name")
|
||||
:browser-version (obj/get browser "version")})
|
||||
@@ -98,7 +98,9 @@
|
||||
(def context
|
||||
(atom (d/without-nils (collect-context))))
|
||||
|
||||
(add-watch i18n/locale ::events #(swap! context assoc :locale %4))
|
||||
(add-watch i18n/state "events"
|
||||
(fn [_ _ _ v]
|
||||
(swap! context assoc :locale (get v :locale))))
|
||||
|
||||
;; --- EVENT TRANSLATION
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.profile :refer [schema:profile]]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
@@ -54,11 +53,16 @@
|
||||
(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?
|
||||
@@ -484,7 +488,7 @@
|
||||
|
||||
(defn delete-access-token
|
||||
[{:keys [id] :as params}]
|
||||
(us/assert! ::us/uuid id)
|
||||
(assert (uuid? id))
|
||||
(ptk/reify ::delete-access-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
||||
@@ -59,9 +59,15 @@
|
||||
"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]
|
||||
(if-let [tc (tinycolor/valid-color value)]
|
||||
{:value value :unit (tinycolor/color-format tc)}
|
||||
{:errors [(wte/error-with-value :error.token/invalid-color 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)]}))))
|
||||
|
||||
(defn- numeric-string? [s]
|
||||
(and (string? s)
|
||||
@@ -120,7 +126,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 (cto/find-token-value-references value))
|
||||
(let [missing-references? (seq (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))]
|
||||
@@ -368,8 +374,8 @@
|
||||
(let [add-keyed-errors (fn [shadow-result k errors]
|
||||
(update shadow-result :errors concat
|
||||
(map #(assoc % :shadow-key k :shadow-index shadow-index) errors)))
|
||||
parsers {:offsetX parse-sd-token-general-value
|
||||
:offsetY parse-sd-token-general-value
|
||||
parsers {:offset-x parse-sd-token-general-value
|
||||
:offset-y parse-sd-token-general-value
|
||||
:blur parse-sd-token-shadow-blur
|
||||
:spread parse-sd-token-shadow-spread
|
||||
:color parse-sd-token-color-value
|
||||
@@ -389,35 +395,42 @@
|
||||
(defn- parse-sd-token-shadow-value
|
||||
"Parses shadow value and validates it."
|
||||
[value]
|
||||
(cond
|
||||
;; Reference value (string)
|
||||
(string? value) {:value value}
|
||||
(let [missing-references
|
||||
(when (string? value)
|
||||
(seq (cto/find-token-value-references value)))]
|
||||
(cond
|
||||
missing-references
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference missing-references)]
|
||||
:references missing-references}
|
||||
|
||||
(string? value)
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-shadow value)]}
|
||||
|
||||
;; Empty value
|
||||
(nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]}
|
||||
(nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]}
|
||||
|
||||
;; Invalid value
|
||||
(not (js/Array.isArray value)) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}
|
||||
(not (js/Array.isArray value)) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}
|
||||
|
||||
;; Array of shadows
|
||||
:else
|
||||
(let [converted (js->clj value :keywordize-keys true)
|
||||
:else
|
||||
(let [converted (js->clj value :keywordize-keys true)
|
||||
;; Parse each shadow with its index
|
||||
parsed-shadows (map-indexed
|
||||
(fn [idx shadow-map]
|
||||
(parse-single-shadow shadow-map idx))
|
||||
converted)
|
||||
parsed-shadows (map-indexed
|
||||
(fn [idx shadow-map]
|
||||
(parse-single-shadow shadow-map idx))
|
||||
converted)
|
||||
|
||||
;; Collect all errors from all shadows
|
||||
all-errors (mapcat :errors parsed-shadows)
|
||||
all-errors (mapcat :errors parsed-shadows)
|
||||
|
||||
;; Collect all values from shadows that have values
|
||||
all-values (into [] (keep :value parsed-shadows))]
|
||||
all-values (into [] (keep :value parsed-shadows))]
|
||||
|
||||
(if (seq all-errors)
|
||||
{:errors all-errors
|
||||
:value all-values}
|
||||
{:value all-values}))))
|
||||
(if (seq all-errors)
|
||||
{:errors all-errors
|
||||
:value all-values}
|
||||
{:value all-values})))))
|
||||
|
||||
(defn collect-shadow-errors [token shadow-index]
|
||||
(group-by :shadow-key
|
||||
|
||||
@@ -706,53 +706,58 @@
|
||||
(= 1 (count tree-root)))]
|
||||
|
||||
(cond
|
||||
;; Paste next to selected frame, if selected is itself or of the same size as the copied
|
||||
(and (selected-frame? state)
|
||||
(or (any-same-frame-from-selected? state (keys pobjects))
|
||||
(and only-one-root-shape?
|
||||
(frame-same-size? pobjects (first tree-root)))))
|
||||
(let [selected-frame-obj (get page-objects (first page-selected))
|
||||
parent-id (:parent-id base)
|
||||
paste-x (+ (:width selected-frame-obj) (:x selected-frame-obj) 50)
|
||||
paste-y (:y selected-frame-obj)
|
||||
delta (gpt/subtract (gpt/point paste-x paste-y) orig-pos)]
|
||||
|
||||
[parent-id delta index])
|
||||
|
||||
;; Paste inside selected frame otherwise
|
||||
(selected-frame? state)
|
||||
(let [selected-frame-obj (get page-objects (first page-selected))
|
||||
origin-frame-id (:frame-id first-selected-obj)
|
||||
origin-frame-object (get page-objects origin-frame-id)
|
||||
|
||||
(if (or (any-same-frame-from-selected? state (keys pobjects))
|
||||
(and only-one-root-shape?
|
||||
(frame-same-size? pobjects (first tree-root))))
|
||||
;; Paste next to selected frame, if selected is itself or of the same size as the copied
|
||||
(let [selected-frame-obj (get page-objects (first page-selected))
|
||||
parent-id (:parent-id base)
|
||||
paste-x (+ (:width selected-frame-obj) (:x selected-frame-obj) 50)
|
||||
paste-y (:y selected-frame-obj)
|
||||
delta (gpt/subtract (gpt/point paste-x paste-y) orig-pos)]
|
||||
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
|
||||
(min (- (:width frame-object) (:width wrapper))))
|
||||
|
||||
[parent-id delta index])
|
||||
margin-y (-> (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
|
||||
(min (- (:height frame-object) (:height wrapper))))
|
||||
|
||||
;; Paste inside selected frame otherwise
|
||||
(let [selected-frame-obj (get page-objects (first page-selected))
|
||||
origin-frame-id (:frame-id first-selected-obj)
|
||||
origin-frame-object (get page-objects origin-frame-id)
|
||||
;; Pasted objects mustn't exceed the selected frame x limit
|
||||
paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
|
||||
(+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
|
||||
(:x frame-object))
|
||||
|
||||
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
|
||||
(min (- (:width frame-object) (:width wrapper))))
|
||||
;; Pasted objects mustn't exceed the selected frame y limit
|
||||
paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
|
||||
(+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
|
||||
(:y frame-object))
|
||||
|
||||
margin-y (-> (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
|
||||
(min (- (:height frame-object) (:height wrapper))))
|
||||
delta (if (= origin-frame-id uuid/zero)
|
||||
;; When the origin isn't in a frame the result is pasted in the center.
|
||||
(gpt/subtract (gsh/shape->center frame-object) (grc/rect->center wrapper))
|
||||
;; When pasting from one frame to another frame the object
|
||||
;; position must be limited to container boundaries. If
|
||||
;; the pasted object doesn't fit we try to:
|
||||
;;
|
||||
;; - Align it to the limits on the x and y axis
|
||||
;; - Respect the distance of the object to the right
|
||||
;; and bottom in the original frame
|
||||
(gpt/point paste-x paste-y))
|
||||
|
||||
;; Pasted objects mustn't exceed the selected frame x limit
|
||||
paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
|
||||
(+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
|
||||
(:x frame-object))
|
||||
|
||||
;; Pasted objects mustn't exceed the selected frame y limit
|
||||
paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
|
||||
(+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
|
||||
(:y frame-object))
|
||||
|
||||
delta (if (= origin-frame-id uuid/zero)
|
||||
;; When the origin isn't in a frame the result is pasted in the center.
|
||||
(gpt/subtract (gsh/shape->center frame-object) (grc/rect->center wrapper))
|
||||
;; When pasting from one frame to another frame the object
|
||||
;; position must be limited to container boundaries. If
|
||||
;; the pasted object doesn't fit we try to:
|
||||
;;
|
||||
;; - Align it to the limits on the x and y axis
|
||||
;; - Respect the distance of the object to the right
|
||||
;; and bottom in the original frame
|
||||
(gpt/point paste-x paste-y))]
|
||||
[frame-id delta (dec (count (:shapes selected-frame-obj)))]))
|
||||
target-index
|
||||
(if (and (ctl/flex-layout? selected-frame-obj) (ctl/reverse? selected-frame-obj))
|
||||
(dec 0) ;; Before the first index 0
|
||||
(count (:shapes selected-frame-obj)))]
|
||||
[frame-id delta target-index])
|
||||
|
||||
(empty? page-selected)
|
||||
(let [frame-id (ctst/top-nested-frame page-objects position)
|
||||
|
||||
@@ -119,21 +119,6 @@
|
||||
(let [page (dsh/lookup-page state)]
|
||||
(rx/of (update-flow (:id page) flow-id #(assoc % :name name)))))))
|
||||
|
||||
(defn start-rename-flow
|
||||
[id]
|
||||
(dm/assert! (uuid? id))
|
||||
(ptk/reify ::start-rename-flow
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :flow-for-rename] id))))
|
||||
|
||||
(defn end-rename-flow
|
||||
[]
|
||||
(ptk/reify ::end-rename-flow
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :flow-for-rename))))
|
||||
|
||||
;; --- Interactions
|
||||
|
||||
(defn- connected-frame?
|
||||
|
||||
@@ -153,11 +153,11 @@
|
||||
(defn value->shadow
|
||||
"Transform a token shadow value into penpot shadow data structure"
|
||||
[value]
|
||||
(mapv (fn [{:keys [offsetX offsetY blur spread color inset]}]
|
||||
(mapv (fn [{:keys [offset-x offset-y blur spread color inset]}]
|
||||
{:id (random-uuid)
|
||||
:hidden false
|
||||
:offset-x offsetX
|
||||
:offset-y offsetY
|
||||
:offset-x offset-x
|
||||
:offset-y offset-y
|
||||
:blur blur
|
||||
:color (value->color color)
|
||||
:spread spread
|
||||
|
||||
@@ -108,6 +108,10 @@
|
||||
{:error/code :error.style-dictionary/invalid-token-value-shadow-spread
|
||||
:error/fn #(tr "workspace.tokens.shadow-spread-range")}
|
||||
|
||||
:error.style-dictionary/invalid-token-value-shadow
|
||||
{:error/code :error.style-dictionary/invalid-token-value-shadow
|
||||
:error/fn #(tr "workspace.tokens.invalid-token-value-shadow" %)}
|
||||
|
||||
:error/unknown
|
||||
{:error/code :error/unknown
|
||||
:error/fn #(tr "labels.unknown-error")}})
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
:letter-spacing "Letter Spacing"
|
||||
:text-case "Text Case"
|
||||
:text-decoration "Text Decoration"
|
||||
:offsetX "X"
|
||||
:offsetY "Y"
|
||||
:offset-x "X"
|
||||
:offset-y "Y"
|
||||
:blur "Blur"
|
||||
:spread "Spread"
|
||||
:color "Color"
|
||||
|
||||
@@ -31,27 +31,28 @@
|
||||
[app.main.ui.static :as static]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.modules :as mod]
|
||||
[app.util.theme :as theme]
|
||||
[beicon.v2.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def auth-page
|
||||
(mf/lazy-component app.main.ui.auth/auth))
|
||||
(mf/lazy #(mod/load 'app.main.ui.auth/auth-page*)))
|
||||
|
||||
(def verify-token-page
|
||||
(mf/lazy-component app.main.ui.auth.verify-token/verify-token))
|
||||
(def verify-token-page*
|
||||
(mf/lazy #(mod/load 'app.main.ui.auth.verify-token/verify-token-page*)))
|
||||
|
||||
(def viewer-page*
|
||||
(mf/lazy-component app.main.ui.viewer/viewer*))
|
||||
(mf/lazy #(mod/load 'app.main.ui.viewer/viewer-page*)))
|
||||
|
||||
(def dashboard-page*
|
||||
(mf/lazy-component app.main.ui.dashboard/dashboard*))
|
||||
(mf/lazy #(mod/load 'app.main.ui.dashboard/dashboard-page*)))
|
||||
|
||||
(def settings-page*
|
||||
(mf/lazy-component app.main.ui.settings/settings*))
|
||||
(mf/lazy #(mod/load 'app.main.ui.settings/settings-page*)))
|
||||
|
||||
(def workspace-page*
|
||||
(mf/lazy-component app.main.ui.workspace/workspace*))
|
||||
(mf/lazy #(mod/load 'app.main.ui.workspace/workspace-page*)))
|
||||
|
||||
(mf/defc workspace-legacy-redirect*
|
||||
{::mf/props :obj
|
||||
@@ -189,7 +190,7 @@
|
||||
[:? [:& auth-page {:route route}]]
|
||||
|
||||
:auth-verify-token
|
||||
[:? [:& verify-token-page {:route route}]]
|
||||
[:? [:& verify-token-page* {:route route}]]
|
||||
|
||||
(:settings-profile
|
||||
:settings-password
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc auth
|
||||
{::mf/props :obj}
|
||||
(mf/defc auth*
|
||||
[{:keys [route]}]
|
||||
(let [section (dm/get-in route [:data :name])
|
||||
is-register (or
|
||||
@@ -69,3 +68,9 @@
|
||||
|
||||
(when (= section :auth-register)
|
||||
[:& terms-register])]]))
|
||||
|
||||
|
||||
(mf/defc auth-page*
|
||||
{::mf/lazy-load true}
|
||||
[props]
|
||||
[:> auth* props])
|
||||
|
||||
@@ -61,9 +61,9 @@
|
||||
(rt/nav :auth-login)
|
||||
(ntf/warn (tr "errors.unexpected-token"))))
|
||||
|
||||
(mf/defc verify-token
|
||||
[{:keys [route] :as props}]
|
||||
(let [token (get-in route [:query-params :token])
|
||||
(mf/defc verify-token*
|
||||
[{:keys [route]}]
|
||||
(let [token (get-in route [:query-params :token])
|
||||
bad-token (mf/use-state false)]
|
||||
|
||||
(mf/with-effect []
|
||||
@@ -99,3 +99,8 @@
|
||||
[:> static/invalid-token {}]
|
||||
[:> loader* {:title (tr "labels.loading")
|
||||
:overlay true}])))
|
||||
|
||||
(mf/defc verify-token-page*
|
||||
{::mf/lazy-load true}
|
||||
[props]
|
||||
[:> verify-token* props])
|
||||
|
||||
@@ -8,24 +8,28 @@
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.util.modules :as modules]
|
||||
[cuerdas.core :as str]
|
||||
[promesa.core :as p]
|
||||
[rumext.v2 :as mf]
|
||||
[shadow.lazy :as lazy]))
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def highlight-fn
|
||||
(lazy/loadable app.util.code-highlight/highlight!))
|
||||
(delay (modules/load-fn 'app.util.code-highlight/highlight!)))
|
||||
|
||||
(mf/defc code-block
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [code type]}]
|
||||
(let [block-ref (mf/use-ref)
|
||||
code (str/trim code)]
|
||||
code (str/trim code)]
|
||||
|
||||
(mf/with-effect [code type]
|
||||
(when-let [node (mf/ref-val block-ref)]
|
||||
(p/let [highlight-fn (lazy/load highlight-fn)]
|
||||
(highlight-fn node))))
|
||||
(->> @highlight-fn
|
||||
(p/fmap (fn [f] (f)))
|
||||
(p/fnly (fn [f cause]
|
||||
(if cause
|
||||
(js/console.error cause)
|
||||
(f node)))))))
|
||||
|
||||
[:pre {:class (dm/str type " " (stl/css :code-display)) :ref block-ref} code]))
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||
[app.main.ui.formats :as fmt]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -46,7 +47,7 @@
|
||||
:disabled disabled)}
|
||||
|
||||
(if (some? icon)
|
||||
[:span {:class icon-class} icon]
|
||||
[:> icon* {:icon-id icon :class icon-class :aria-hidden true}]
|
||||
[:span {:class (stl/css :title-name)} value])
|
||||
|
||||
[:input {:id id
|
||||
|
||||
@@ -247,7 +247,6 @@
|
||||
(swap! storage/session dissoc :template))))))
|
||||
|
||||
(mf/defc dashboard*
|
||||
{::mf/props :obj}
|
||||
[{:keys [profile project-id team-id search-term plugin-url template section]}]
|
||||
(let [team (mf/deref refs/team)
|
||||
projects (mf/deref refs/projects)
|
||||
@@ -313,3 +312,8 @@
|
||||
:section section
|
||||
:search-term search-term
|
||||
:team team}]]]))
|
||||
|
||||
(mf/defc dashboard-page*
|
||||
{::mf/lazy-load true}
|
||||
[props]
|
||||
[:> dashboard* props])
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.main.ui.dashboard.projects
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
["@penpot/ts" :refer [TestTsxComponent]]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.time :as ct]
|
||||
[app.main.data.common :as dcm]
|
||||
@@ -50,8 +51,11 @@
|
||||
::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [can-edit]}]
|
||||
;; (js/console.log "xxxx" TestTsxComponent)
|
||||
(let [on-click (mf/use-fn #(st/emit! (dd/create-project)))]
|
||||
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
|
||||
[:div
|
||||
[:> TestTsxComponent]]
|
||||
[:div#dashboard-projects-title {:class (stl/css :dashboard-title)}
|
||||
[:h1 (tr "dashboard.projects-title")]]
|
||||
(when can-edit
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
(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*]]
|
||||
[app.main.ui.ds.controls.combobox :refer [combobox*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
@@ -43,8 +43,6 @@
|
||||
[app.util.i18n :as i18n]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(i18n/init! cf/translations)
|
||||
|
||||
(def default
|
||||
"A export used for storybook"
|
||||
(mf/object
|
||||
@@ -62,6 +60,7 @@
|
||||
:RawSvg raw-svg*
|
||||
:Select select*
|
||||
:Switch switch*
|
||||
:Checkbox checkbox*
|
||||
:Combobox combobox*
|
||||
:Text text*
|
||||
:TabSwitcher tab-switcher*
|
||||
@@ -78,6 +77,11 @@
|
||||
: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))
|
||||
|
||||
@@ -11,12 +11,18 @@
|
||||
%base-button {
|
||||
--button-bg-color: initial;
|
||||
--button-fg-color: initial;
|
||||
|
||||
--button-hover-bg-color: initial;
|
||||
--button-hover-fg-color: initial;
|
||||
|
||||
--button-active-bg-color: initial;
|
||||
--button-active-fg-color: initial;
|
||||
|
||||
--button-disabled-bg-color: initial;
|
||||
--button-disabled-fg-color: initial;
|
||||
|
||||
--button-border-color: var(--button-bg-color);
|
||||
|
||||
--button-focus-inner-ring-color: initial;
|
||||
--button-focus-outer-ring-color: initial;
|
||||
|
||||
@@ -38,8 +44,10 @@
|
||||
--button-fg-color: var(--button-hover-fg-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active,
|
||||
&[aria-pressed="true"] {
|
||||
--button-bg-color: var(--button-active-bg-color);
|
||||
--button-fg-color: var(--button-active-fg-color);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@@ -63,6 +71,7 @@
|
||||
--button-hover-fg-color: var(--color-background-secondary);
|
||||
|
||||
--button-active-bg-color: var(--color-accent-tertiary);
|
||||
--button-active-fg-color: var(--color-background-secondary);
|
||||
|
||||
--button-disabled-bg-color: var(--color-accent-primary-muted);
|
||||
--button-disabled-fg-color: var(--color-background-secondary);
|
||||
@@ -72,7 +81,8 @@
|
||||
--button-focus-inner-ring-color: var(--color-background-secondary);
|
||||
--button-focus-outer-ring-color: var(--color-accent-primary);
|
||||
|
||||
&:active {
|
||||
&:active,
|
||||
&[aria-pressed="true"] {
|
||||
box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
@@ -85,6 +95,7 @@
|
||||
--button-hover-fg-color: var(--color-accent-primary);
|
||||
|
||||
--button-active-bg-color: var(--color-background-quaternary);
|
||||
--button-active-fg-color: var(--color-accent-primary);
|
||||
|
||||
--button-disabled-bg-color: transparent;
|
||||
--button-disabled-fg-color: var(--color-foreground-secondary);
|
||||
@@ -103,6 +114,7 @@
|
||||
--button-hover-fg-color: var(--color-accent-primary);
|
||||
|
||||
--button-active-bg-color: var(--color-background-quaternary);
|
||||
--button-active-fg-color: var(--color-accent-primary);
|
||||
|
||||
--button-disabled-bg-color: transparent;
|
||||
--button-disabled-fg-color: var(--color-accent-primary-muted);
|
||||
@@ -121,6 +133,7 @@
|
||||
--button-hover-fg-color: var(--color-foreground-primary);
|
||||
|
||||
--button-active-bg-color: var(--color-accent-error);
|
||||
--button-active-fg-color: var(--color-foreground-primary);
|
||||
|
||||
--button-disabled-bg-color: var(--color-background-error);
|
||||
--button-disabled-fg-color: var(--color-accent-error);
|
||||
@@ -130,7 +143,8 @@
|
||||
--button-focus-inner-ring-color: var(--color-background-primary);
|
||||
--button-focus-outer-ring-color: var(--color-accent-primary);
|
||||
|
||||
&:active {
|
||||
&:active,
|
||||
&[aria-pressed="true"] {
|
||||
box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
45
frontend/src/app/main/ui/ds/controls/checkbox.cljs
Normal file
45
frontend/src/app/main/ui/ds/controls/checkbox.cljs
Normal file
@@ -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
|
||||
|
||||
(ns app.main.ui.ds.controls.checkbox
|
||||
(:require-macros
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:checkbox
|
||||
[:map
|
||||
[:id {:optional true} :string]
|
||||
[:label {:optional true} :string]
|
||||
[:checked {:optional true} :boolean]
|
||||
[:on-change {:optional true} fn?]
|
||||
[:disabled {:optional true} :boolean]])
|
||||
|
||||
(mf/defc checkbox*
|
||||
{::mf/schema schema:checkbox}
|
||||
[{:keys [id class label checked on-change disabled] :rest props}]
|
||||
(let [props
|
||||
(mf/spread-props props {:type "checkbox"
|
||||
:class (stl/css :checkbox-input)
|
||||
:id id
|
||||
:checked checked
|
||||
:on-change on-change
|
||||
:disabled disabled})]
|
||||
|
||||
[:div {:class [class (stl/css :checkbox)]}
|
||||
[:label {:for id
|
||||
:class (stl/css :checkbox-label)}
|
||||
[:div {:class (stl/css-case :checkbox-box true
|
||||
:checked checked
|
||||
:disabled disabled)}
|
||||
(when checked
|
||||
[:> icon* {:icon-id i/tick
|
||||
:size "s"}])]
|
||||
|
||||
[:div {:class (stl/css :checkbox-text)} label]
|
||||
|
||||
[:> :input props]]]))
|
||||
40
frontend/src/app/main/ui/ds/controls/checkbox.mdx
Normal file
40
frontend/src/app/main/ui/ds/controls/checkbox.mdx
Normal file
@@ -0,0 +1,40 @@
|
||||
{ /* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Copyright (c) KALEIDOS INC */ }
|
||||
|
||||
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
|
||||
import * as Checkbox from "./checkbox.stories";
|
||||
|
||||
<Meta title="Controls/Checkbox" />
|
||||
|
||||
# Checkbox
|
||||
|
||||
The `checkbox*` component is a toggle control. It allows users to switch between boolean states (`false` or `true`).
|
||||
|
||||
<Canvas of={Checkbox.Default} />
|
||||
|
||||
<Canvas of={Checkbox.Checked} />
|
||||
|
||||
## Anatomy
|
||||
|
||||
The checkbox component consists of three main parts:
|
||||
|
||||
- **Label** (optional): the text that describes what the checkbox controls. Clicking on this text also works for toggling.
|
||||
- **Box**: the box which shows the current state. Contains a check if the state is `true`.
|
||||
- **Native element**: the native HTML element which holds the state. It remains hidden to the user.
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
### When to Use
|
||||
|
||||
- For boolean settings that take effect immediately.
|
||||
- In preference panels and configuration screens.
|
||||
|
||||
### When Not to Use
|
||||
|
||||
- For actions that require confirmation (use buttons instead).
|
||||
- For multiple choice selections (use radio buttons or select).
|
||||
- For temporary states that need explicit "Apply" action.
|
||||
- For ternary states.
|
||||
86
frontend/src/app/main/ui/ds/controls/checkbox.scss
Normal file
86
frontend/src/app/main/ui/ds/controls/checkbox.scss
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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/_borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/typography.scss" as *;
|
||||
|
||||
.checkbox {
|
||||
--input-checkbox-border-color: var(--color-foreground-secondary);
|
||||
--input-checkbox-border-color-focus: var(--color-accent-primary);
|
||||
--input-checkbox-border-color-hover: var(--color-accent-primary-muted);
|
||||
--input-checkbox-foreground-color: var(--color-foreground-primary);
|
||||
--input-checkbox-background-color: var(--color-background-quaternary);
|
||||
|
||||
--input-checkbox-border-color-checked: var(--color-background-quaternary);
|
||||
--input-checkbox-foreground-color-checked: var(--color-background-primary);
|
||||
--input-checkbox-background-color-checked: var(--color-accent-primary);
|
||||
|
||||
--input-checkbox-foreground-color-disabled: var(--color-background-primary);
|
||||
--input-checkbox-background-color-disabled: var(--color-foreground-secondary);
|
||||
|
||||
--input-checkbox-text-color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sp-l) 1fr 0;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
.checkbox-box {
|
||||
border-color: var(--input-checkbox-border-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
.checkbox-box {
|
||||
border-color: var(--input-checkbox-border-color-focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: $sz-16;
|
||||
block-size: $sz-16;
|
||||
border-radius: $br-4;
|
||||
border: $b-1 solid var(--input-checkbox-border-color);
|
||||
color: var(--input-checkbox-foreground-color);
|
||||
background-color: var(--input-checkbox-background-color);
|
||||
|
||||
&.disabled {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
--input-checkbox-border-color: var(--input-checkbox-border-color-checked);
|
||||
--input-checkbox-foreground-color: var(--input-checkbox-foreground-color-checked);
|
||||
--input-checkbox-background-color: var(--input-checkbox-background-color-checked);
|
||||
|
||||
&.disabled {
|
||||
--input-checkbox-foreground-color: var(--input-checkbox-foreground-color-disabled);
|
||||
--input-checkbox-background-color: var(--input-checkbox-background-color-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-text {
|
||||
@include use-typography("body-small");
|
||||
padding-inline-start: var(--sp-s);
|
||||
color: var(--input-checkbox-text-color);
|
||||
}
|
||||
|
||||
.checkbox-input {
|
||||
&:focus {
|
||||
outline: 0;
|
||||
inline-size: 0;
|
||||
block-size: 0;
|
||||
}
|
||||
}
|
||||
76
frontend/src/app/main/ui/ds/controls/checkbox.stories.jsx
Normal file
76
frontend/src/app/main/ui/ds/controls/checkbox.stories.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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 { Checkbox } = Components;
|
||||
|
||||
export default {
|
||||
title: "Controls/Checkbox",
|
||||
component: Checkbox,
|
||||
argTypes: {
|
||||
label: {
|
||||
control: { type: "text" },
|
||||
description: "Label text displayed next to the checkbox",
|
||||
},
|
||||
checked: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is disabled",
|
||||
},
|
||||
},
|
||||
args: {
|
||||
checked: false,
|
||||
disabled: false,
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
exclude: ["id", "on-change"],
|
||||
},
|
||||
},
|
||||
render: ({ ...args }) => <Checkbox {...args} />,
|
||||
};
|
||||
|
||||
export const Default = {
|
||||
args: {
|
||||
label: "Toggle something",
|
||||
disabled: false,
|
||||
},
|
||||
render: ({ ...args }) => <Checkbox {...args} />,
|
||||
};
|
||||
|
||||
export const Checked = {
|
||||
args: {
|
||||
label: "Toggle something",
|
||||
checked: true,
|
||||
disabled: false,
|
||||
},
|
||||
render: ({ ...args }) => <Checkbox {...args} />,
|
||||
};
|
||||
|
||||
export const WithoutLabel = {
|
||||
args: {
|
||||
disabled: false,
|
||||
},
|
||||
render: ({ ...args }) => <Checkbox {...args} />,
|
||||
};
|
||||
|
||||
export const WithLongLabel = {
|
||||
args: {
|
||||
label:
|
||||
"This is a very long label that demonstrates how the checkbox component handles text wrapping and layout when the label content is extensive",
|
||||
disabled: false,
|
||||
},
|
||||
render: ({ ...args }) => (
|
||||
<div style={{ maxWidth: "300px" }}>
|
||||
<Checkbox {...args} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -26,14 +26,19 @@
|
||||
[:max-length {:optional true} :int]
|
||||
[:variant {:optional true} [:maybe [:enum "seamless" "dense" "comfortable"]]]
|
||||
[:hint-message {:optional true} [:maybe :string]]
|
||||
[:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]])
|
||||
[:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]
|
||||
[:hint-formated {:optional true} :boolean]])
|
||||
|
||||
(mf/defc input*
|
||||
{::mf/forward-ref true
|
||||
::mf/schema schema:input}
|
||||
[{:keys [id class label is-optional type max-length variant hint-message hint-type] :rest props} ref]
|
||||
[{:keys [id class label is-optional type max-length variant hint-message hint-type hint-formated] :rest props} ref]
|
||||
(let [id (or id (mf/use-id))
|
||||
variant (d/nilv variant "dense")
|
||||
hint-class (if (and (not= "error" hint-type)
|
||||
hint-formated)
|
||||
(stl/css :hint-formated)
|
||||
"")
|
||||
is-optional (d/nilv is-optional false)
|
||||
type (d/nilv type "text")
|
||||
max-length (d/nilv max-length max-input-length)
|
||||
@@ -56,6 +61,7 @@
|
||||
[:> input-field* props]
|
||||
(when has-hint
|
||||
[:> hint-message* {:id id
|
||||
:class hint-class
|
||||
:message hint-message
|
||||
:type hint-type}])]))
|
||||
|
||||
|
||||
@@ -12,3 +12,7 @@
|
||||
gap: var(--sp-xs);
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.hint-formated {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps disabled)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(when-not disabled
|
||||
(swap! is-open* not))))
|
||||
|
||||
@@ -53,10 +53,15 @@
|
||||
"true")
|
||||
:aria-describedby (when has-hint
|
||||
(str id "-hint"))
|
||||
:aria-labelledby tooltip-id
|
||||
:type (d/nilv type "text")
|
||||
:id id
|
||||
:max-length (d/nilv max-length max-input-length)})
|
||||
|
||||
props (if (and aria-label (not (some? icon)))
|
||||
(mf/spread-props props
|
||||
{:aria-label aria-label})
|
||||
(mf/spread-props props
|
||||
{:aria-labelledby tooltip-id}))
|
||||
inside-class (stl/css-case :input-wrapper true
|
||||
:has-hint has-hint
|
||||
:hint-type-hint (= hint-type "hint")
|
||||
|
||||
@@ -300,6 +300,7 @@
|
||||
"A collection of all icons"
|
||||
(collect-icons))
|
||||
|
||||
(def ^:private ^:const icon-size-l 32)
|
||||
(def ^:private ^:const icon-size-m 16)
|
||||
(def ^:private ^:const icon-size-s 12)
|
||||
|
||||
@@ -308,7 +309,7 @@
|
||||
[:class {:optional true} [:maybe :string]]
|
||||
[:icon-id [:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:size {:optional true}
|
||||
[:maybe [:enum "s" "m"]]]])
|
||||
[:maybe [:enum "s" "m" "l"]]]])
|
||||
|
||||
(mf/defc icon*
|
||||
{::mf/schema schema:icon}
|
||||
@@ -317,10 +318,14 @@
|
||||
{:class [class (stl/css :icon)]
|
||||
:width icon-size-m
|
||||
:height icon-size-m})
|
||||
size-px (if (= size "s")
|
||||
icon-size-s
|
||||
icon-size-m)
|
||||
offset (/ (- icon-size-m size-px) 2)]
|
||||
|
||||
size-px (cond (= size "l") icon-size-l
|
||||
(= size "s") icon-size-s
|
||||
:else icon-size-m)
|
||||
|
||||
offset (if (or (= size "s") (= size "m"))
|
||||
(/ (- icon-size-m size-px) 2)
|
||||
0)]
|
||||
|
||||
[:> :svg props
|
||||
[:use {:href (dm/str "#icon-" icon-id)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
.swatch {
|
||||
--border-color: var(--color-background-quaternary);
|
||||
--border-color: var(--color-accent-primary-muted);
|
||||
--border-radius: #{$br-4};
|
||||
--border-color-active: var(--color-foreground-primary);
|
||||
--border-color-active-inset: var(--color-background-primary);
|
||||
|
||||
@@ -34,10 +34,9 @@
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(fm/on-input-change form input-name value true))))
|
||||
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:default-value value})
|
||||
:value value})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
|
||||
@@ -78,3 +78,9 @@
|
||||
|
||||
:settings-notifications
|
||||
[:& notifications-page* {:profile profile}])]]]]))
|
||||
|
||||
(mf/defc settings-page*
|
||||
{::mf/lazy-load true}
|
||||
[props]
|
||||
[:> settings* props])
|
||||
|
||||
|
||||
@@ -624,7 +624,6 @@
|
||||
;; --- Component: Viewer
|
||||
|
||||
(mf/defc viewer*
|
||||
{::mf/props :obj}
|
||||
[{:keys [file-id share-id page-id] :as props}]
|
||||
(mf/with-effect [file-id page-id share-id]
|
||||
(let [params {:file-id file-id
|
||||
@@ -643,3 +642,8 @@
|
||||
[:> loader* {:title (tr "labels.loading")
|
||||
:overlay true}]))
|
||||
|
||||
|
||||
(mf/defc viewer-page*
|
||||
{::mf/lazy-load true}
|
||||
[props]
|
||||
[:> viewer* props])
|
||||
|
||||
@@ -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.create.modals]
|
||||
[app.main.ui.workspace.tokens.management.forms.modals]
|
||||
[app.main.ui.workspace.tokens.settings]
|
||||
[app.main.ui.workspace.tokens.themes.create-modal]
|
||||
[app.main.ui.workspace.viewport :refer [viewport*]]
|
||||
@@ -165,7 +165,7 @@
|
||||
(dsh/lookup-page state file-id page-id))))
|
||||
st/state))
|
||||
|
||||
(mf/defc workspace-page*
|
||||
(mf/defc workspace-inner*
|
||||
{::mf/private true}
|
||||
[{:keys [page-id file-id file layout wglobal]}]
|
||||
(let [page-ref (mf/with-memo [file-id page-id]
|
||||
@@ -252,10 +252,16 @@
|
||||
:touch-action "none"}}
|
||||
[:> context-menu*]
|
||||
(if (and file-loaded? page-id)
|
||||
[:> workspace-page*
|
||||
[:> workspace-inner*
|
||||
{:page-id page-id
|
||||
:file-id file-id
|
||||
:file file
|
||||
:wglobal wglobal
|
||||
:layout layout}]
|
||||
[:> workspace-loader*])]]]]]]))
|
||||
|
||||
(mf/defc workspace-page*
|
||||
{::mf/lazy-load true}
|
||||
[props]
|
||||
[:> workspace* props])
|
||||
|
||||
|
||||
@@ -418,11 +418,11 @@
|
||||
[:& radio-buttons {:selected color-style
|
||||
:on-change toggle-token-color
|
||||
:name "color-style"}
|
||||
[:& radio-button {:icon deprecated-icon/swatches
|
||||
[:& radio-button {:icon i/swatches
|
||||
:value :direct-color
|
||||
:title (tr "labels.color")
|
||||
:id "opt-color"}]
|
||||
[:& radio-button {:icon deprecated-icon/tokens
|
||||
[:& radio-button {:icon i/tokens
|
||||
:value :token-color
|
||||
:title (tr "workspace.colorpicker.color-tokens")
|
||||
:id "opt-token-color"}]])]
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.path-names :as cpn]
|
||||
[app.common.spec :as us]
|
||||
[app.common.thumbnails :as thc]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
@@ -38,7 +37,6 @@
|
||||
[app.util.i18n :as i18n :refer [c tr]]
|
||||
[app.util.strings :refer [matches-search]]
|
||||
[app.util.timers :as ts]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
@@ -97,10 +95,6 @@
|
||||
(str (str/slice (:path asset) (count path)))
|
||||
(cpn/merge-path-item (:name asset))))
|
||||
|
||||
(s/def ::asset-name ::us/not-empty-string)
|
||||
(s/def ::name-group-form
|
||||
(s/keys :req-un [::asset-name]))
|
||||
|
||||
(def initial-context-menu-state
|
||||
{:open? false :top nil :left nil})
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
[app.main.ui.workspace.sidebar.assets.groups :as grp]
|
||||
[app.util.dom :as dom]
|
||||
@@ -567,11 +566,11 @@
|
||||
[:& radio-buttons {:selected (if is-listing-thumbs "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "listing-style"}
|
||||
[:& radio-button {:icon deprecated-icon/view-as-list
|
||||
[:& radio-button {:icon i/view-as-list
|
||||
:value "list"
|
||||
:title (tr "workspace.assets.list-view")
|
||||
:id "opt-list"}]
|
||||
[:& radio-button {:icon deprecated-icon/flex-grid
|
||||
[:& radio-button {:icon i/flex-grid
|
||||
:value "grid"
|
||||
:title (tr "workspace.assets.grid-view")
|
||||
:id "opt-grid"}]]])
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
|
||||
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container]
|
||||
[app.main.ui.workspace.sidebar.options.page :as page]
|
||||
[app.main.ui.workspace.sidebar.options.shapes.bool :as bool]
|
||||
@@ -215,7 +215,7 @@
|
||||
(case options-mode
|
||||
:prototype
|
||||
[:div {:class (stl/css :element-options :interaction-options)}
|
||||
[:& interactions-menu {:shape (first shapes)}]]
|
||||
[:> interactions-menu* {:shape (first shapes)}]]
|
||||
|
||||
:inspect
|
||||
[:div {:class (stl/css :element-options :inspect-options)}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -99,10 +100,10 @@
|
||||
:name "frame-orientation"
|
||||
:wide true
|
||||
:class (stl/css :radio-buttons)}
|
||||
[:& radio-button {:icon deprecated-icon/size-vertical
|
||||
[:& radio-button {:icon i/size-vertical
|
||||
:value "vertical"
|
||||
:id "size-vertical"}]
|
||||
[:& radio-button {:icon deprecated-icon/size-horizontal
|
||||
[:& radio-button {:icon i/size-horizontal
|
||||
:value "horizontal"
|
||||
:id "size-horizontal"}]]]))
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -78,22 +79,22 @@
|
||||
:class (stl/css :boolean-radio-btn)
|
||||
:on-change on-change
|
||||
:name "bool-options"}
|
||||
[:& radio-button {:icon deprecated-icon/boolean-union
|
||||
[:& radio-button {:icon i/boolean-union
|
||||
:value "union"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.union") " (" (sc/get-tooltip :bool-union) ")")
|
||||
:id "bool-opt-union"}]
|
||||
[:& radio-button {:icon deprecated-icon/boolean-difference
|
||||
[:& radio-button {:icon i/boolean-difference
|
||||
:value "difference"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.difference") " (" (sc/get-tooltip :bool-difference) ")")
|
||||
:id "bool-opt-differente"}]
|
||||
[:& radio-button {:icon deprecated-icon/boolean-intersection
|
||||
[:& radio-button {:icon i/boolean-intersection
|
||||
:value "intersection"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :bool-intersection) ")")
|
||||
:id "bool-opt-intersection"}]
|
||||
[:& radio-button {:icon deprecated-icon/boolean-exclude
|
||||
[:& radio-button {:icon i/boolean-exclude
|
||||
:value "exclude"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :bool-exclude) ")")
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
[app.main.ui.workspace.sidebar.options.menus.variants-help-modal]
|
||||
[app.util.debug :as dbg]
|
||||
@@ -798,10 +797,10 @@
|
||||
[:& radio-buttons {:selected (if (:listing-thumbs? filters) "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "swap-listing-style"}
|
||||
[:& radio-button {:icon deprecated-icon/view-as-list
|
||||
[:& radio-button {:icon i/view-as-list
|
||||
:value "list"
|
||||
:id "swap-opt-list"}]
|
||||
[:& radio-button {:icon deprecated-icon/flex-grid
|
||||
[:& radio-button {:icon i/flex-grid
|
||||
:value "grid"
|
||||
:id "swap-opt-grid"}]]]
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
:data-value "bottom"
|
||||
:on-click on-constraint-button-clicked}
|
||||
[:span {:class (stl/css :resalted-area)}]]]]
|
||||
[:div {:class (stl/css :contraints-selects)}
|
||||
[:div {:class (stl/css :constraints-selects)}
|
||||
[:div {:class (stl/css :horizontal-select) :data-testid "constraint-h-select"}
|
||||
[:& select
|
||||
{:default-value (if (not= constraints-h :multiple) (d/nilv (d/name constraints-h) "scale") "")
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
}
|
||||
|
||||
.constraints-selects {
|
||||
@include deprecated.flexColumn;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.horizontal-select,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
@@ -52,29 +53,29 @@
|
||||
:name (dm/str "flex-align-items-" type)}
|
||||
[:& radio-button {:value "start"
|
||||
:icon (if is-col?
|
||||
deprecated-icon/align-self-row-left
|
||||
deprecated-icon/align-self-column-top)
|
||||
i/align-self-row-left
|
||||
i/align-self-column-top)
|
||||
:title "Align self start"
|
||||
:id (dm/str "align-self-start-" type)}]
|
||||
|
||||
[:& radio-button {:value "center"
|
||||
:icon (if is-col?
|
||||
deprecated-icon/align-self-row-center
|
||||
deprecated-icon/align-self-column-center)
|
||||
i/align-self-row-center
|
||||
i/align-self-column-center)
|
||||
:title "Align self center"
|
||||
:id (dm/str "align-self-center-" type)}]
|
||||
|
||||
[:& radio-button {:value "end"
|
||||
:icon (if is-col?
|
||||
deprecated-icon/align-self-row-right
|
||||
deprecated-icon/align-self-column-bottom)
|
||||
i/align-self-row-right
|
||||
i/align-self-column-bottom)
|
||||
:title "Align self end"
|
||||
:id (dm/str "align-self-end-" type)}]
|
||||
|
||||
[:& radio-button {:value "stretch"
|
||||
:icon (if is-col?
|
||||
deprecated-icon/align-self-row-stretch
|
||||
deprecated-icon/align-self-column-stretch)
|
||||
i/align-self-row-stretch
|
||||
i/align-self-column-stretch)
|
||||
:title "Align self stretch"
|
||||
:id (dm/str "align-self-stretch-" type)}]]]))
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,22 +4,51 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
@use "ds/mixins.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
@use "../../../sidebar/common/sidebar.scss" as sidebar;
|
||||
|
||||
.interactions-content {
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: deprecated.$s-8;
|
||||
padding-left: var(--sp-m);
|
||||
gap: var(--sp-s);
|
||||
padding-inline-start: var(--sp-m);
|
||||
}
|
||||
|
||||
.interaction-options {
|
||||
@include deprecated.flexColumn;
|
||||
.section {
|
||||
@include sidebar.option-grid-structure;
|
||||
}
|
||||
|
||||
.help-content {
|
||||
padding: deprecated.$s-32 0;
|
||||
width: deprecated.$s-200;
|
||||
.title {
|
||||
grid-column: span 8;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
padding-inline-start: var(--sp-xxs);
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: span 8;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
margin-block-start: var(--sp-xs);
|
||||
}
|
||||
|
||||
.content-interactions {
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.help {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxxl);
|
||||
inline-size: $sz-200;
|
||||
padding: var(--sp-xxxl) 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -27,130 +56,168 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: deprecated.$s-40;
|
||||
gap: deprecated.$s-12;
|
||||
gap: var(--sp-m);
|
||||
}
|
||||
|
||||
.interactions-help-icon {
|
||||
@include deprecated.flexCenter;
|
||||
width: deprecated.$s-48;
|
||||
height: deprecated.$s-48;
|
||||
border-radius: deprecated.$br-circle;
|
||||
background-color: var(--pill-background-color);
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
.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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.prototype-pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: px2rem(1);
|
||||
border-radius: $br-8;
|
||||
padding: var(--sp-s) var(--sp-m);
|
||||
block-size: $sz-32;
|
||||
padding: 0;
|
||||
|
||||
&.double {
|
||||
block-size: $sz-48;
|
||||
.prototype-pill-button {
|
||||
block-size: $sz-48;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.prototype-pill-input:focus) {
|
||||
outline: $b-1 solid var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.after {
|
||||
@include deprecated.bodySmallTypography;
|
||||
margin-top: deprecated.$s-1;
|
||||
.prototype-pill-button {
|
||||
&.left {
|
||||
border-end-end-radius: 0;
|
||||
border-start-end-radius: 0;
|
||||
}
|
||||
|
||||
&.right {
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.interactions-help {
|
||||
@include deprecated.bodySmallTypography;
|
||||
text-align: center;
|
||||
color: var(--title-foreground-color);
|
||||
}
|
||||
|
||||
.element-set {
|
||||
@include deprecated.flexColumn;
|
||||
}
|
||||
|
||||
.interactions-info {
|
||||
.prototype-pill-main {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
display: grid;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.trigger-name {
|
||||
.prototype-pill-center {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
block-size: 100%;
|
||||
padding: 0 var(--sp-s);
|
||||
background-color: var(--color-background-tertiary);
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.action-summary {
|
||||
color: var(--color-foreground-secondary);
|
||||
@include deprecated.textEllipsis;
|
||||
}
|
||||
|
||||
.groups {
|
||||
@include deprecated.flexColumn(deprecated.$s-12);
|
||||
}
|
||||
|
||||
.element-set-options-group-open {
|
||||
@include deprecated.flexColumn;
|
||||
}
|
||||
|
||||
.extended-options {
|
||||
@include deprecated.flexColumn;
|
||||
}
|
||||
|
||||
.property-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
.prototype-pill-info {
|
||||
display: grid;
|
||||
row-gap: deprecated.$s-16;
|
||||
margin-block: calc(#{deprecated.$s-16} - #{deprecated.$s-4});
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.property-row {
|
||||
@extend .attr-row;
|
||||
height: auto;
|
||||
&.big-row {
|
||||
height: 100%;
|
||||
}
|
||||
.interaction-name {
|
||||
@include deprecated.twoLineTextEllipsis;
|
||||
@include deprecated.bodySmallTypography;
|
||||
padding-left: deprecated.$s-4;
|
||||
width: deprecated.$s-92;
|
||||
margin: auto 0;
|
||||
grid-area: name;
|
||||
color: var(--title-foreground-color);
|
||||
}
|
||||
.select-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
grid-area: content;
|
||||
.easing-select {
|
||||
width: deprecated.$s-156;
|
||||
padding: 0 deprecated.$s-8;
|
||||
.dropdown-upwards {
|
||||
bottom: deprecated.$s-36;
|
||||
width: deprecated.$s-156;
|
||||
top: unset;
|
||||
}
|
||||
.prototype-pill-input {
|
||||
@include t.use-typography("body-small");
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
flex-grow: 1;
|
||||
margin: var(--sp-xxs) 0;
|
||||
padding: 0 0 0 var(--sp-s);
|
||||
margin: 0;
|
||||
background-color: var(--color-background-tertiary);
|
||||
color: var(--color-foreground-primary);
|
||||
display: grid;
|
||||
inline-size: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-quaternary);
|
||||
&:active {
|
||||
background-color: var(--color-background-quaternary);
|
||||
}
|
||||
}
|
||||
.input-element-wrapper {
|
||||
@extend .input-element;
|
||||
@include deprecated.bodySmallTypography;
|
||||
grid-area: content;
|
||||
}
|
||||
.buttons-wrapper {
|
||||
grid-area: content;
|
||||
.right svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.left svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.up svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
.inputs-wrapper {
|
||||
grid-area: content;
|
||||
@include deprecated.flexRow;
|
||||
.radio-btn {
|
||||
@extend .input-checkbox;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--color-background-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
.position-btns-wrapper {
|
||||
grid-area: content;
|
||||
.prototype-pill-name {
|
||||
@include t.use-typography("body-small");
|
||||
@include textEllipsis;
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.prototype-pill-description {
|
||||
@include t.use-typography("body-small");
|
||||
@include textEllipsis;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.interaction-row {
|
||||
@include sidebar.option-grid-structure;
|
||||
}
|
||||
|
||||
.interaction-row-label {
|
||||
grid-column: span 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.interaction-row-name {
|
||||
@include twoLineTextEllipsis;
|
||||
@include t.use-typography("body-small");
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.interaction-row-select {
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.interaction-row-checkbox {
|
||||
grid-column: 4 / span 5;
|
||||
min-block-size: $sz-32;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.interaction-row-input {
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.interaction-row-radio {
|
||||
grid-column: 4 / span 5;
|
||||
}
|
||||
|
||||
.interaction-row-position {
|
||||
grid-column: 4 / span 5;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"topleft top topright"
|
||||
@@ -158,191 +225,30 @@
|
||||
"bottomleft bottom bottomright";
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
width: deprecated.$s-84;
|
||||
height: deprecated.$s-84;
|
||||
border-radius: deprecated.$br-8;
|
||||
inline-size: calc($sz-32 * 3);
|
||||
block-size: calc($sz-32 * 3);
|
||||
border-radius: $br-8;
|
||||
background-color: var(--color-background-tertiary);
|
||||
.center-btn {
|
||||
|
||||
.center {
|
||||
grid-area: center;
|
||||
}
|
||||
.top-left-btn {
|
||||
.top-left {
|
||||
grid-area: topleft;
|
||||
}
|
||||
.top-right-btn {
|
||||
grid-area: topright;
|
||||
}
|
||||
.top-center-btn {
|
||||
.top-center {
|
||||
grid-area: top;
|
||||
}
|
||||
.bottom-left-btn {
|
||||
.top-right {
|
||||
grid-area: topright;
|
||||
}
|
||||
.bottom-left {
|
||||
grid-area: bottomleft;
|
||||
}
|
||||
.bottom-right-btn {
|
||||
grid-area: bottomright;
|
||||
}
|
||||
.bottom-center-btn {
|
||||
.bottom-center {
|
||||
grid-area: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.direction-btn {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-28;
|
||||
width: deprecated.$s-28;
|
||||
|
||||
&.active {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-option {
|
||||
@extend .input-checkbox;
|
||||
grid-area: content;
|
||||
line-height: 1.2;
|
||||
label {
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.interactions-summary {
|
||||
@extend .asset-element;
|
||||
height: deprecated.$s-44;
|
||||
padding: 0;
|
||||
gap: deprecated.$s-8;
|
||||
|
||||
.remove-btn {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extend-btn {
|
||||
@extend .button-tertiary;
|
||||
--button-tertiary-border-width: var(--expand-button-icon-border-width);
|
||||
height: 100%;
|
||||
width: deprecated.$s-28;
|
||||
border-end-end-radius: 0;
|
||||
border-start-end-radius: 0;
|
||||
padding: 0;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
}
|
||||
position: relative;
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-inline-end: deprecated.$s-1 solid var(--panel-background-color);
|
||||
}
|
||||
&.extended {
|
||||
@extend .button-icon-selected;
|
||||
--button-tertiary-border-width: var(--expand-button-icon-border-width-selected);
|
||||
}
|
||||
}
|
||||
|
||||
.corner-icon {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
width: deprecated.$s-12;
|
||||
height: deprecated.$s-12;
|
||||
}
|
||||
|
||||
.flow-element {
|
||||
@include deprecated.flexRow;
|
||||
}
|
||||
|
||||
.flow-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: deprecated.$s-2;
|
||||
border-radius: deprecated.$s-8;
|
||||
background-color: var(--input-details-color);
|
||||
height: deprecated.$s-32;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.flow-name-wrapper {
|
||||
@include deprecated.bodySmallTypography;
|
||||
@include deprecated.focusInput;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: deprecated.$s-4;
|
||||
flex-grow: 1;
|
||||
height: deprecated.$s-32;
|
||||
width: 100%;
|
||||
border-radius: deprecated.$br-8;
|
||||
padding: 0;
|
||||
margin-right: 0;
|
||||
background-color: var(--input-background-color);
|
||||
border: deprecated.$s-1 solid var(--input-border-color);
|
||||
color: var(--input-foreground-color);
|
||||
.start-flow-btn {
|
||||
@include deprecated.buttonStyle;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
padding: 0 deprecated.$s-2 0 deprecated.$s-8;
|
||||
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
|
||||
background-color: transparent;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
&:hover {
|
||||
stroke: var(--input-foreground-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow-input {
|
||||
@extend .input-base;
|
||||
@include deprecated.bodySmallTypography;
|
||||
background-color: transparent;
|
||||
height: deprecated.$s-28;
|
||||
}
|
||||
|
||||
.flow-input-wrapper {
|
||||
@include deprecated.bodySmallTypography;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: deprecated.$s-28;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
background-color: transparent;
|
||||
color: var(--input-foreground-color);
|
||||
border-radius: deprecated.$br-8;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--input-background-color-hover);
|
||||
border: deprecated.$s-1 solid var(--input-border-color-hover);
|
||||
&:active {
|
||||
background-color: var(--input-background-color-hover);
|
||||
.flow-input-wrapper {
|
||||
background-color: var(--input-background-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
background-color: var(--input-background-color-focus);
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
&:hover {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
}
|
||||
}
|
||||
|
||||
&.editing {
|
||||
background-color: var(--input-background-color-active);
|
||||
border: deprecated.$s-1 solid var(--input-border-color-active);
|
||||
.bottom-right {
|
||||
grid-area: bottomright;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
(defn- dir-icons-refactor
|
||||
[val]
|
||||
(case val
|
||||
:row deprecated-icon/grid-row
|
||||
:row-reverse deprecated-icon/row-reverse
|
||||
:column deprecated-icon/column
|
||||
:column-reverse deprecated-icon/column-reverse))
|
||||
:row i/grid-row
|
||||
:row-reverse i/row-reverse
|
||||
:column i/column
|
||||
:column-reverse i/column-reverse))
|
||||
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
@@ -111,63 +111,63 @@
|
||||
:align-items
|
||||
(if column?
|
||||
(case val
|
||||
:start deprecated-icon/align-items-column-start
|
||||
:end deprecated-icon/align-items-column-end
|
||||
:center deprecated-icon/align-items-column-center)
|
||||
:start i/align-items-column-start
|
||||
:end i/align-items-column-end
|
||||
:center i/align-items-column-center)
|
||||
(case val
|
||||
:start deprecated-icon/align-items-row-start
|
||||
:end deprecated-icon/align-items-row-end
|
||||
:center deprecated-icon/align-items-row-center))
|
||||
:start i/align-items-row-start
|
||||
:end i/align-items-row-end
|
||||
:center i/align-items-row-center))
|
||||
|
||||
:justify-content
|
||||
(if column?
|
||||
(case val
|
||||
:start deprecated-icon/justify-content-column-start
|
||||
:end deprecated-icon/justify-content-column-end
|
||||
:center deprecated-icon/justify-content-column-center
|
||||
:space-around deprecated-icon/justify-content-column-around
|
||||
:space-evenly deprecated-icon/justify-content-column-evenly
|
||||
:space-between deprecated-icon/justify-content-column-between)
|
||||
:start i/justify-content-column-start
|
||||
:end i/justify-content-column-end
|
||||
:center i/justify-content-column-center
|
||||
:space-around i/justify-content-column-around
|
||||
:space-evenly i/justify-content-column-evenly
|
||||
:space-between i/justify-content-column-between)
|
||||
(case val
|
||||
:start deprecated-icon/justify-content-row-start
|
||||
:end deprecated-icon/justify-content-row-end
|
||||
:center deprecated-icon/justify-content-row-center
|
||||
:space-around deprecated-icon/justify-content-row-around
|
||||
:space-evenly deprecated-icon/justify-content-row-evenly
|
||||
:space-between deprecated-icon/justify-content-row-between))
|
||||
:start i/justify-content-row-start
|
||||
:end i/justify-content-row-end
|
||||
:center i/justify-content-row-center
|
||||
:space-around i/justify-content-row-around
|
||||
:space-evenly i/justify-content-row-evenly
|
||||
:space-between i/justify-content-row-between))
|
||||
|
||||
:align-content
|
||||
(if column?
|
||||
(case val
|
||||
:start deprecated-icon/align-content-column-start
|
||||
:end deprecated-icon/align-content-column-end
|
||||
:center deprecated-icon/align-content-column-center
|
||||
:space-around deprecated-icon/align-content-column-around
|
||||
:space-evenly deprecated-icon/align-content-column-evenly
|
||||
:space-between deprecated-icon/align-content-column-between
|
||||
:start i/align-content-column-start
|
||||
:end i/align-content-column-end
|
||||
:center i/align-content-column-center
|
||||
:space-around i/align-content-column-around
|
||||
:space-evenly i/align-content-column-evenly
|
||||
:space-between i/align-content-column-between
|
||||
:stretch nil)
|
||||
|
||||
(case val
|
||||
:start deprecated-icon/align-content-row-start
|
||||
:end deprecated-icon/align-content-row-end
|
||||
:center deprecated-icon/align-content-row-center
|
||||
:space-around deprecated-icon/align-content-row-around
|
||||
:space-evenly deprecated-icon/align-content-row-evenly
|
||||
:space-between deprecated-icon/align-content-row-between
|
||||
:start i/align-content-row-start
|
||||
:end i/align-content-row-end
|
||||
:center i/align-content-row-center
|
||||
:space-around i/align-content-row-around
|
||||
:space-evenly i/align-content-row-evenly
|
||||
:space-between i/align-content-row-between
|
||||
:stretch nil))
|
||||
|
||||
:align-self
|
||||
(if column?
|
||||
(case val
|
||||
:auto deprecated-icon/remove-icon
|
||||
:start deprecated-icon/align-self-row-left
|
||||
:end deprecated-icon/align-self-row-right
|
||||
:center deprecated-icon/align-self-row-center)
|
||||
:auto i/remove
|
||||
:start i/align-self-row-left
|
||||
:end i/align-self-row-right
|
||||
:center i/align-self-row-center)
|
||||
(case val
|
||||
:auto deprecated-icon/remove-icon
|
||||
:start deprecated-icon/align-self-column-top
|
||||
:end deprecated-icon/align-self-column-bottom
|
||||
:center deprecated-icon/align-self-column-center))))
|
||||
:auto i/remove
|
||||
:start i/align-self-column-top
|
||||
:end i/align-self-column-bottom
|
||||
:center i/align-self-column-center))))
|
||||
|
||||
(defn get-layout-grid-icon
|
||||
[type val ^boolean column?]
|
||||
@@ -175,32 +175,32 @@
|
||||
:align-items
|
||||
(if column?
|
||||
(case val
|
||||
:auto deprecated-icon/remove-icon
|
||||
:start deprecated-icon/align-self-row-left
|
||||
:end deprecated-icon/align-self-row-right
|
||||
:center deprecated-icon/align-self-row-center)
|
||||
:auto i/remove
|
||||
:start i/align-self-row-left
|
||||
:end i/align-self-row-right
|
||||
:center i/align-self-row-center)
|
||||
(case val
|
||||
:auto deprecated-icon/remove-icon
|
||||
:start deprecated-icon/align-self-column-top
|
||||
:end deprecated-icon/align-self-column-bottom
|
||||
:center deprecated-icon/align-self-column-center))
|
||||
:auto i/remove
|
||||
:start i/align-self-column-top
|
||||
:end i/align-self-column-bottom
|
||||
:center i/align-self-column-center))
|
||||
|
||||
:justify-items
|
||||
(if (not column?)
|
||||
(case val
|
||||
:start deprecated-icon/align-content-column-start
|
||||
:center deprecated-icon/align-content-column-center
|
||||
:end deprecated-icon/align-content-column-end
|
||||
:space-around deprecated-icon/align-content-column-around
|
||||
:space-between deprecated-icon/align-content-column-between
|
||||
:stretch deprecated-icon/align-content-column-stretch)
|
||||
:start i/align-content-column-start
|
||||
:center i/align-content-column-center
|
||||
:end i/align-content-column-end
|
||||
:space-around i/align-content-column-around
|
||||
:space-between i/align-content-column-between
|
||||
:stretch i/align-content-column-stretch)
|
||||
(case val
|
||||
:start deprecated-icon/align-content-row-start
|
||||
:center deprecated-icon/align-content-row-center
|
||||
:end deprecated-icon/align-content-row-end
|
||||
:space-around deprecated-icon/align-content-row-around
|
||||
:space-between deprecated-icon/align-content-row-between
|
||||
:stretch deprecated-icon/align-content-row-stretch))))
|
||||
:start i/align-content-row-start
|
||||
:center i/align-content-row-center
|
||||
:end i/align-content-row-end
|
||||
:space-around i/align-content-row-around
|
||||
:space-between i/align-content-row-between
|
||||
:stretch i/align-content-row-stretch))))
|
||||
|
||||
(mf/defc direction-row-flex
|
||||
{::mf/props :obj
|
||||
|
||||
@@ -455,10 +455,10 @@
|
||||
:name "frame-orientation"
|
||||
:wide true
|
||||
:class (stl/css :radio-buttons)}
|
||||
[:& radio-button {:icon deprecated-icon/size-vertical
|
||||
[:& radio-button {:icon i/size-vertical
|
||||
:value "vert"
|
||||
:id "size-vertical"}]
|
||||
[:& radio-button {:icon deprecated-icon/size-horizontal
|
||||
[:& radio-button {:icon i/size-horizontal
|
||||
:value "horiz"
|
||||
:id "size-horizontal"}]]
|
||||
[:> icon-button*
|
||||
|
||||
@@ -53,19 +53,19 @@
|
||||
[:& radio-button {:value "left"
|
||||
:id "text-align-left"
|
||||
:title (tr "workspace.options.text-options.text-align-left")
|
||||
:icon deprecated-icon/text-align-left}]
|
||||
:icon i/text-align-left}]
|
||||
[:& radio-button {:value "center"
|
||||
:id "text-align-center"
|
||||
:title (tr "workspace.options.text-options.text-align-center")
|
||||
:icon deprecated-icon/text-align-center}]
|
||||
:icon i/text-align-center}]
|
||||
[:& radio-button {:value "right"
|
||||
:id "text-align-right"
|
||||
:title (tr "workspace.options.text-options.text-align-right")
|
||||
:icon deprecated-icon/text-align-right}]
|
||||
:icon i/text-align-right}]
|
||||
[:& radio-button {:value "justify"
|
||||
:id "text-align-justify"
|
||||
:title (tr "workspace.options.text-options.text-align-justify")
|
||||
:icon deprecated-icon/text-justify}]]]))
|
||||
:icon i/text-justify}]]]))
|
||||
|
||||
(mf/defc text-direction-options
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
@@ -88,12 +88,12 @@
|
||||
:type "checkbox"
|
||||
:id "ltr-text-direction"
|
||||
:title (tr "workspace.options.text-options.direction-ltr")
|
||||
:icon deprecated-icon/text-ltr}]
|
||||
:icon i/text-ltr}]
|
||||
[:& radio-button {:value "rtl"
|
||||
:type "checkbox"
|
||||
:id "rtl-text-direction"
|
||||
:title (tr "workspace.options.text-options.direction-rtl")
|
||||
:icon deprecated-icon/text-rtl}]]]))
|
||||
:icon i/text-rtl}]]]))
|
||||
|
||||
(mf/defc vertical-align
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
@@ -113,15 +113,15 @@
|
||||
[:& radio-button {:value "top"
|
||||
:id "vertical-text-align-top"
|
||||
:title (tr "workspace.options.text-options.align-top")
|
||||
:icon deprecated-icon/text-top}]
|
||||
:icon i/text-top}]
|
||||
[:& radio-button {:value "center"
|
||||
:id "vertical-text-align-center"
|
||||
:title (tr "workspace.options.text-options.align-middle")
|
||||
:icon deprecated-icon/text-middle}]
|
||||
:icon i/text-middle}]
|
||||
[:& radio-button {:value "bottom"
|
||||
:id "vertical-text-align-bottom"
|
||||
:title (tr "workspace.options.text-options.align-bottom")
|
||||
:icon deprecated-icon/text-bottom}]]]))
|
||||
:icon i/text-bottom}]]]))
|
||||
|
||||
(mf/defc grow-options
|
||||
[{:keys [ids values on-blur] :as props}]
|
||||
@@ -150,15 +150,15 @@
|
||||
[:& radio-button {:value "fixed"
|
||||
:id "text-fixed-grow"
|
||||
:title (tr "workspace.options.text-options.grow-fixed")
|
||||
:icon deprecated-icon/text-fixed}]
|
||||
:icon i/text-fixed}]
|
||||
[:& radio-button {:value "auto-width"
|
||||
:id "text-auto-width-grow"
|
||||
:title (tr "workspace.options.text-options.grow-auto-width")
|
||||
:icon deprecated-icon/text-auto-width}]
|
||||
:icon i/text-auto-width}]
|
||||
[:& radio-button {:value "auto-height"
|
||||
:id "text-auto-height-grow"
|
||||
:title (tr "workspace.options.text-options.grow-auto-height")
|
||||
:icon deprecated-icon/text-auto-height}]]]))
|
||||
:icon i/text-auto-height}]]]))
|
||||
|
||||
(mf/defc text-decoration-options
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
@@ -180,12 +180,12 @@
|
||||
:type "checkbox"
|
||||
:id "underline-text-decoration"
|
||||
:title (tr "workspace.options.text-options.underline" (sc/get-tooltip :underline))
|
||||
:icon deprecated-icon/text-underlined}]
|
||||
:icon i/text-underlined}]
|
||||
[:& radio-button {:value "line-through"
|
||||
:type "checkbox"
|
||||
:id "line-through-text-decoration"
|
||||
:title (tr "workspace.options.text-options.strikethrough" (sc/get-tooltip :line-through))
|
||||
:icon deprecated-icon/text-stroked}]]]))
|
||||
:icon i/text-stroked}]]]))
|
||||
|
||||
(mf/defc text-menu
|
||||
{::mf/wrap [mf/memo]}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -418,17 +419,17 @@
|
||||
[:& radio-buttons {:selected text-transform
|
||||
:on-change handle-change
|
||||
:name "text-transform"}
|
||||
[:& radio-button {:icon deprecated-icon/text-uppercase
|
||||
[:& radio-button {:icon i/text-uppercase
|
||||
:type "checkbox"
|
||||
:title (tr "inspect.attributes.typography.text-transform.uppercase")
|
||||
:value "uppercase"
|
||||
:id "text-transform-uppercase"}]
|
||||
[:& radio-button {:icon deprecated-icon/text-mixed
|
||||
[:& radio-button {:icon i/text-mixed
|
||||
:type "checkbox"
|
||||
:value "capitalize"
|
||||
:title (tr "inspect.attributes.typography.text-transform.capitalize")
|
||||
:id "text-transform-capitalize"}]
|
||||
[:& radio-button {:icon deprecated-icon/text-lowercase
|
||||
[:& radio-button {:icon i/text-lowercase
|
||||
:type "checkbox"
|
||||
:title (tr "inspect.attributes.typography.text-transform.lowercase")
|
||||
:value "lowercase"
|
||||
|
||||
@@ -1,226 +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.workspace.tokens.management.create.color
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[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.create.form-color-input-token :refer [form-color-input-token*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- token-value-error-fn
|
||||
[{:keys [value]}]
|
||||
(when (or (str/empty? value)
|
||||
(str/blank? value))
|
||||
(tr "workspace.tokens.empty-input")))
|
||||
|
||||
(defn- make-schema
|
||||
[tokens-tree]
|
||||
(sm/schema
|
||||
[:and
|
||||
[:map
|
||||
[:name
|
||||
[:and
|
||||
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||
(sm/update-properties cto/token-name-ref assoc :error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
||||
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
||||
#(not (cft/token-name-path-exists? % tokens-tree))]]]
|
||||
|
||||
[:value [::sm/text {:error/fn token-value-error-fn}]]
|
||||
|
||||
[:resolved-value ::sm/any]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
|
||||
[:fn {:error/field :value
|
||||
:error/fn #(tr "workspace.tokens.self-reference")}
|
||||
(fn [{:keys [name value]}]
|
||||
(when (and name value)
|
||||
(nil? (cto/token-value-self-reference? name value))))]]))
|
||||
|
||||
(mf/defc form*
|
||||
[{:keys [token validate-token action is-create selected-token-set-id tokens-tree-in-selected-set] :as props}]
|
||||
|
||||
(let [token
|
||||
(mf/with-memo [token]
|
||||
(or token {:type :color}))
|
||||
|
||||
token-type
|
||||
(get token :type)
|
||||
|
||||
token-properties
|
||||
(dwta/get-token-properties token)
|
||||
|
||||
token-title (str/lower (:title token-properties))
|
||||
|
||||
tokens
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
(cond-> tokens
|
||||
(and (:name token) (:value token))
|
||||
(assoc (:name token) token)))
|
||||
|
||||
schema
|
||||
(mf/with-memo [tokens-tree-in-selected-set]
|
||||
(make-schema tokens-tree-in-selected-set))
|
||||
|
||||
initial
|
||||
(mf/with-memo [token]
|
||||
{:name (:name token "")
|
||||
:value (:value token "")
|
||||
:description (:description token "")})
|
||||
|
||||
form
|
||||
(fm/use-form :schema schema
|
||||
:initial initial)
|
||||
|
||||
warning-name-change?
|
||||
(not= (get-in @form [:data :name])
|
||||
(:name initial))
|
||||
|
||||
on-cancel
|
||||
(mf/use-fn
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)))
|
||||
|
||||
on-delete-token
|
||||
(mf/use-fn
|
||||
(mf/deps selected-token-set-id token)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)
|
||||
(st/emit! (dwtl/delete-token selected-token-set-id (:id token)))))
|
||||
|
||||
handle-key-down-delete
|
||||
(mf/use-fn
|
||||
(mf/deps on-delete-token)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-delete-token e))))
|
||||
|
||||
handle-key-down-cancel
|
||||
(mf/use-fn
|
||||
(mf/deps on-cancel)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-cancel e))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(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])]
|
||||
(->> (validate-token {:token-value value
|
||||
:token-name name
|
||||
:token-description description
|
||||
:prev-token token
|
||||
:tokens tokens})
|
||||
(rx/subs!
|
||||
(fn [valid-token]
|
||||
(st/emit!
|
||||
(if is-create
|
||||
(dwtl/create-token (ctob/make-token {:name name
|
||||
:type token-type
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
|
||||
(dwtl/update-token (:id token)
|
||||
{:name name
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(modal/hide))))))))]
|
||||
|
||||
[:> fc/form* {:class (stl/css :form-wrapper)
|
||||
:form form
|
||||
:on-submit on-submit}
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(tr "workspace.tokens.create-token" token-type)]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-name"
|
||||
:name :name
|
||||
:label (tr "workspace.tokens.token-name")
|
||||
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:auto-focus true}]
|
||||
|
||||
(when (and warning-name-change? (= action "edit"))
|
||||
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
|
||||
[:> context-notification*
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> form-color-input-token*
|
||||
{:placeholder (tr "workspace.tokens.token-value-enter")
|
||||
:label (tr "workspace.tokens.token-value")
|
||||
:name :value
|
||||
:token token
|
||||
:tokens tokens}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-description"
|
||||
:name :description
|
||||
:label (tr "workspace.tokens.token-description")
|
||||
:placeholder (tr "workspace.tokens.token-description")
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:is-optional true}]]
|
||||
|
||||
[:div {:class (stl/css-case :button-row true
|
||||
:with-delete (= action "edit"))}
|
||||
(when (= action "edit")
|
||||
[:> button* {:on-click on-delete-token
|
||||
:on-key-down handle-key-down-delete
|
||||
:class (stl/css :delete-btn)
|
||||
:type "button"
|
||||
:icon i/delete
|
||||
:variant "secondary"}
|
||||
(tr "labels.delete")])
|
||||
|
||||
[:> button* {:on-click on-cancel
|
||||
:on-key-down handle-key-down-cancel
|
||||
:type "button"
|
||||
:id "token-modal-cancel"
|
||||
:variant "secondary"}
|
||||
(tr "labels.cancel")]
|
||||
|
||||
[:> fc/form-submit* {:variant "primary"
|
||||
:on-submit on-submit}
|
||||
(tr "labels.save")]]]]))
|
||||
@@ -1,225 +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.workspace.tokens.management.create.dimensions
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[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.create.form-input-token :refer [form-input-token*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- token-value-error-fn
|
||||
[{:keys [value]}]
|
||||
(when (or (str/empty? value)
|
||||
(str/blank? value))
|
||||
(tr "workspace.tokens.empty-input")))
|
||||
|
||||
(defn- make-schema
|
||||
[tokens-tree]
|
||||
(sm/schema
|
||||
[:and
|
||||
[:map
|
||||
[:name
|
||||
[:and
|
||||
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||
(sm/update-properties cto/token-name-ref assoc :error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
||||
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
||||
#(not (cft/token-name-path-exists? % tokens-tree))]]]
|
||||
|
||||
[:value [::sm/text {:error/fn token-value-error-fn}]]
|
||||
|
||||
[:resolved-value ::sm/any]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
|
||||
[:fn {:error/field :value
|
||||
:error/fn #(tr "workspace.tokens.self-reference")}
|
||||
(fn [{:keys [name value]}]
|
||||
(when (and name value)
|
||||
(nil? (cto/token-value-self-reference? name value))))]]))
|
||||
|
||||
(mf/defc form*
|
||||
[{:keys [token validate-token action is-create selected-token-set-id tokens-tree-in-selected-set] :as props}]
|
||||
|
||||
(let [token
|
||||
(mf/with-memo [token]
|
||||
(or token {:type :dimensions}))
|
||||
|
||||
token-type
|
||||
(get token :type)
|
||||
|
||||
token-properties
|
||||
(dwta/get-token-properties token)
|
||||
|
||||
token-title (str/lower (:title token-properties))
|
||||
|
||||
tokens
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
(cond-> tokens
|
||||
(and (:name token) (:value token))
|
||||
(assoc (:name token) token)))
|
||||
|
||||
schema
|
||||
(mf/with-memo [tokens-tree-in-selected-set]
|
||||
(make-schema tokens-tree-in-selected-set))
|
||||
|
||||
initial
|
||||
(mf/with-memo [token]
|
||||
{:name (:name token "")
|
||||
:value (:value token "")
|
||||
:description (:description token "")})
|
||||
|
||||
form
|
||||
(fm/use-form :schema schema
|
||||
:initial initial)
|
||||
|
||||
warning-name-change?
|
||||
(not= (get-in @form [:data :name])
|
||||
(:name initial))
|
||||
|
||||
on-cancel
|
||||
(mf/use-fn
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)))
|
||||
|
||||
on-delete-token
|
||||
(mf/use-fn
|
||||
(mf/deps selected-token-set-id token)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)
|
||||
(st/emit! (dwtl/delete-token selected-token-set-id (:id token)))))
|
||||
|
||||
handle-key-down-delete
|
||||
(mf/use-fn
|
||||
(mf/deps on-delete-token)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-delete-token e))))
|
||||
|
||||
handle-key-down-cancel
|
||||
(mf/use-fn
|
||||
(mf/deps on-cancel)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-cancel e))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(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])]
|
||||
(->> (validate-token {:token-value value
|
||||
:token-name name
|
||||
:token-description description
|
||||
:prev-token token
|
||||
:tokens tokens})
|
||||
(rx/subs!
|
||||
(fn [valid-token]
|
||||
(st/emit!
|
||||
(if is-create
|
||||
(dwtl/create-token (ctob/make-token {:name name
|
||||
:type token-type
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
|
||||
(dwtl/update-token (:id token)
|
||||
{:name name
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(modal/hide))))))))]
|
||||
|
||||
[:> fc/form* {:class (stl/css :form-wrapper)
|
||||
:form form
|
||||
:on-submit on-submit}
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(tr "workspace.tokens.create-token" token-type)]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-name"
|
||||
:name :name
|
||||
:label (tr "workspace.tokens.token-name")
|
||||
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:auto-focus true}]
|
||||
|
||||
(when (and warning-name-change? (= action "edit"))
|
||||
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
|
||||
[:> context-notification*
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> form-input-token*
|
||||
{:placeholder (tr "workspace.tokens.token-value-enter")
|
||||
:label (tr "workspace.tokens.token-value")
|
||||
:name :value
|
||||
:token token
|
||||
:tokens tokens}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-description"
|
||||
:name :description
|
||||
:label (tr "workspace.tokens.token-description")
|
||||
:placeholder (tr "workspace.tokens.token-description")
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:is-optional true}]]
|
||||
|
||||
[:div {:class (stl/css-case :button-row true
|
||||
:with-delete (= action "edit"))}
|
||||
(when (= action "edit")
|
||||
[:> button* {:on-click on-delete-token
|
||||
:on-key-down handle-key-down-delete
|
||||
:class (stl/css :delete-btn)
|
||||
:type "button"
|
||||
:icon i/delete
|
||||
:variant "secondary"}
|
||||
(tr "labels.delete")])
|
||||
|
||||
[:> button* {:on-click on-cancel
|
||||
:on-key-down handle-key-down-cancel
|
||||
:type "button"
|
||||
:id "token-modal-cancel"
|
||||
:variant "secondary"}
|
||||
(tr "labels.cancel")]
|
||||
|
||||
[:> fc/form-submit* {:variant "primary"
|
||||
:on-submit on-submit}
|
||||
(tr "labels.save")]]]]))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,105 +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/typography.scss" as t;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
|
||||
.form-wrapper {
|
||||
width: $sz-384;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.token-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.nested-input-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-m);
|
||||
}
|
||||
|
||||
.typography-inputs-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-m);
|
||||
}
|
||||
|
||||
.typography-inputs {
|
||||
border-inline-start: $b-1 solid var(--color-accent-primary-muted);
|
||||
padding-inline-start: var(--sp-m);
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
.title {
|
||||
@include t.use-typography("body-small");
|
||||
color: var(--color-foreground-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.warning-name-change-notification-wrapper {
|
||||
margin-block-start: var(--sp-l);
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: var(--sp-xs) $sz-6;
|
||||
margin-block-end: 0;
|
||||
color: var(--status-color-error-500);
|
||||
}
|
||||
|
||||
.resolved-value {
|
||||
--input-hint-color: var(--color-foreground-primary);
|
||||
color: var(--input-hint-color);
|
||||
}
|
||||
|
||||
.resolved-value-error {
|
||||
--input-hint-color: var(--status-color-error-500);
|
||||
}
|
||||
|
||||
.resolved-value-warning {
|
||||
--input-hint-color: var(--status-color-warning-500);
|
||||
}
|
||||
|
||||
.form-modal-title {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.font-select-wrapper {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
// This padding from the modal should be shared as a variable
|
||||
// Need to set this or the font-select will cause scroll
|
||||
bottom: var(--sp-xxxl);
|
||||
}
|
||||
@@ -1,245 +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.workspace.tokens.management.create.form-color-input-token
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.color :as cl]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.tinycolor :as tinycolor]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.utilities.swatch :refer [swatch*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.colorpicker :as colorpicker]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.core :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
(let [token
|
||||
{:value value
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
|
||||
tokens
|
||||
(-> tokens
|
||||
;; Remove previous token when renaming a token
|
||||
(dissoc (:name prev-token))
|
||||
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
|
||||
|
||||
(->> tokens
|
||||
(sd/resolve-tokens-interactive)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
|
||||
(if resolved-value
|
||||
(rx/of {:value resolved-value})
|
||||
(rx/of {:error (first errors)}))))))))
|
||||
|
||||
(defn- hex->color-obj
|
||||
[hex]
|
||||
(when-let [tc (tinycolor/valid-color hex)]
|
||||
(let [hex (tinycolor/->hex-string tc)
|
||||
alpha (tinycolor/alpha tc)
|
||||
[r g b] (cl/hex->rgb hex)
|
||||
[h s v] (cl/hex->hsv hex)]
|
||||
{:hex hex
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v
|
||||
:alpha alpha})))
|
||||
|
||||
(mf/defc ramp*
|
||||
[{:keys [color on-change]}]
|
||||
(let [wrapper-node-ref (mf/use-ref nil)
|
||||
dragging-ref (mf/use-ref false)
|
||||
|
||||
on-start-drag
|
||||
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
|
||||
|
||||
on-finish-drag
|
||||
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
|
||||
|
||||
internal-color*
|
||||
(mf/use-state #(hex->color-obj color))
|
||||
|
||||
internal-color
|
||||
(deref internal-color*)
|
||||
|
||||
on-change'
|
||||
(mf/use-fn
|
||||
(mf/deps on-change)
|
||||
(fn [{:keys [hex alpha] :as selector-color}]
|
||||
(let [dragging? (mf/ref-val dragging-ref)]
|
||||
(when-not (and dragging? hex)
|
||||
(reset! internal-color* selector-color)
|
||||
(on-change hex alpha)))))]
|
||||
(mf/use-effect
|
||||
(mf/deps color)
|
||||
(fn []
|
||||
;; Update internal color when user changes input value
|
||||
(when-let [color (tinycolor/valid-color color)]
|
||||
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
|
||||
(reset! internal-color* (hex->color-obj color))))))
|
||||
|
||||
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
|
||||
[:div {:ref wrapper-node-ref}
|
||||
[:> ramp-selector*
|
||||
{:color internal-color
|
||||
:on-start-drag on-start-drag
|
||||
:on-finish-drag on-finish-drag
|
||||
:on-change on-change'}]]))
|
||||
|
||||
(mf/defc form-color-input-token*
|
||||
[{:keys [name tokens token] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
resolved-input-name
|
||||
(mf/with-memo [input-name]
|
||||
(keyword (str "resolved-" (c/name input-name))))
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
resolved-value
|
||||
(get-in @form [:data resolved-input-name] "")
|
||||
|
||||
hex (if (tinycolor/valid-color resolved-value)
|
||||
(tinycolor/->hex-string (tinycolor/valid-color resolved-value))
|
||||
"#8f9da3")
|
||||
|
||||
alpha (if (tinycolor/valid-color resolved-value)
|
||||
(tinycolor/alpha (tinycolor/valid-color resolved-value))
|
||||
1)
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (:value token)]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
color-ramp-open* (mf/use-state false)
|
||||
color-ramp-open? (deref color-ramp-open*)
|
||||
|
||||
on-click-swatch
|
||||
(mf/use-fn
|
||||
(mf/deps color-ramp-open?)
|
||||
(fn []
|
||||
(let [open? (not color-ramp-open?)]
|
||||
(reset! color-ramp-open* open?))))
|
||||
|
||||
swatch
|
||||
(mf/html
|
||||
[:> swatch*
|
||||
{:background {:color hex :opacity alpha}
|
||||
:show-tooltip false
|
||||
:data-testid "token-form-color-bullet"
|
||||
:class (stl/css :slot-start)
|
||||
:on-click on-click-swatch}])
|
||||
|
||||
on-change-value
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name value)
|
||||
(fn [hex alpha]
|
||||
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
|
||||
prev-input-color (some-> value
|
||||
(tinycolor/valid-color))
|
||||
;; If the input is a reference we will take the format from the computed value
|
||||
prev-computed-color (when-not prev-input-color
|
||||
(some-> value (tinycolor/valid-color)))
|
||||
prev-format (some-> (or prev-input-color prev-computed-color)
|
||||
(tinycolor/color-format))
|
||||
to-rgba? (and
|
||||
(< alpha 1)
|
||||
(or (= prev-format "hex") (not prev-format)))
|
||||
to-hex? (and (not prev-format) (= alpha 1))
|
||||
format (cond
|
||||
to-rgba? "rgba"
|
||||
to-hex? "hex"
|
||||
prev-format prev-format
|
||||
:else "hex")
|
||||
color-value (-> (tinycolor/valid-color hex)
|
||||
(tinycolor/set-alpha (or alpha 1))
|
||||
(tinycolor/->string format))]
|
||||
(when (not= value color-value)
|
||||
(fm/on-input-change form input-name color-value true)
|
||||
(rx/push! resolve-stream color-value)))))
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [raw-value (-> event dom/get-target dom/get-input-value)
|
||||
value (if (tinycolor/hex-without-hash-prefix? raw-value)
|
||||
(dm/str "#" raw-value)
|
||||
raw-value)]
|
||||
(fm/on-input-change form input-name value true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
;; TODO: Review this value vs default-value
|
||||
:value (or value "")
|
||||
:hint-message (:message hint)
|
||||
:variant "comfortable"
|
||||
:slot-start swatch
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
((:error/fn error) (:error/value error))))))
|
||||
(rx/subs! (fn [{:keys [error value]}]
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:errors input-name] {:message error})
|
||||
(swap! form assoc-in [:errors resolved-input-name] {:message error})
|
||||
(swap! form update :data dissoc resolved-input-name)
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)]
|
||||
(swap! form update :errors dissoc input-name resolved-input-name)
|
||||
(swap! form update :data assoc resolved-input-name value)
|
||||
(reset! hint* {:message message :type "hint"}))))))]
|
||||
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:*
|
||||
[:> input* props]
|
||||
|
||||
(when color-ramp-open?
|
||||
[:> ramp* {:color value :on-change on-change-value}])]))
|
||||
@@ -1,118 +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.workspace.tokens.management.create.form-input-token
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.core :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
(let [token
|
||||
{:value value
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
|
||||
tokens
|
||||
(-> tokens
|
||||
;; Remove previous token when renaming a token
|
||||
(dissoc (:name prev-token))
|
||||
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
|
||||
|
||||
(->> tokens
|
||||
(sd/resolve-tokens-interactive)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
|
||||
(if resolved-value
|
||||
(rx/of {:value resolved-value})
|
||||
(rx/of {:error (first errors)}))))))))
|
||||
|
||||
(mf/defc form-input-token*
|
||||
[{:keys [name tokens token] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
resolved-input-name
|
||||
(mf/with-memo [input-name]
|
||||
(keyword (str "resolved-" (c/name input-name))))
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (:value token)]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(fm/on-input-change form input-name value true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:default-value value
|
||||
:hint-message (:message hint)
|
||||
:variant "comfortable"
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
((:error/fn error) (:error/value error))))))
|
||||
(rx/subs! (fn [{:keys [error value]}]
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:errors input-name] {:message error})
|
||||
(swap! form assoc-in [:errors resolved-input-name] {:message error})
|
||||
(swap! form update :data dissoc resolved-input-name)
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)]
|
||||
(swap! form update :errors dissoc input-name resolved-input-name)
|
||||
(swap! form update :data assoc resolved-input-name value)
|
||||
(reset! hint* {:message message :type "hint"}))))))]
|
||||
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:> input* props]))
|
||||
@@ -1,28 +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.workspace.tokens.management.create.input-token-color-bullet
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.data.workspace.tokens.color :as dwtc]
|
||||
[app.main.ui.components.color-bullet :refer [color-bullet]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema::input-token-color-bullet
|
||||
[:map
|
||||
[:color [:maybe :string]]
|
||||
[:on-click fn?]])
|
||||
|
||||
(mf/defc input-token-color-bullet*
|
||||
{::mf/props :obj
|
||||
::mf/schema schema::input-token-color-bullet}
|
||||
[{:keys [color on-click]}]
|
||||
[:div {:data-testid "token-form-color-bullet"
|
||||
:class (stl/css :input-token-color-bullet)
|
||||
:on-click on-click}
|
||||
(if-let [color' (dwtc/color-bullet-color color)]
|
||||
[:> color-bullet {:color color' :mini true}]
|
||||
[:div {:class (stl/css :input-token-color-bullet-placeholder)}])])
|
||||
@@ -1,28 +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/_borders.scss" as *;
|
||||
|
||||
.input-token-color-bullet {
|
||||
--bullet-size: var(--sp-l);
|
||||
--bullet-default-color: var(--color-foreground-secondary);
|
||||
--bullet-radius: var(--br-4);
|
||||
|
||||
margin-inline-end: var(--sp-s);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-token-color-bullet-placeholder {
|
||||
width: var(--bullet-size, --sp-l);
|
||||
height: var(--bullet-size, --sp-l);
|
||||
min-width: var(--bullet-size, --sp-l);
|
||||
min-height: var(--bullet-size, --sp-l);
|
||||
margin-top: 0;
|
||||
background-color: color-mix(in hsl, var(--bullet-default-color) 30%, transparent);
|
||||
border-radius: $br-4;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1,78 +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.workspace.tokens.management.create.input-tokens-value
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.data.workspace.tokens.warnings :as wtw]
|
||||
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
|
||||
[app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
|
||||
[app.main.ui.ds.controls.utilities.label :refer [label*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon-list]]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema::input-token
|
||||
[:map
|
||||
[:label {:optional true} [:maybe :string]]
|
||||
[:aria-label {:optional true} [:maybe :string]]
|
||||
[:placeholder {:optional true} :string]
|
||||
[:value {:optional true} [:maybe :string]]
|
||||
[:class {:optional true} :string]
|
||||
[:error {:optional true} :boolean]
|
||||
[:slot-start {:optional true} [:maybe some?]]
|
||||
[:icon {:optional true}
|
||||
[:maybe [:and :string [:fn #(contains? icon-list %)]]]]
|
||||
[:token-resolve-result {:optional true} :any]])
|
||||
|
||||
(mf/defc token-value-hint*
|
||||
[{:keys [result]}]
|
||||
(let [{:keys [errors warnings resolved-value]} result
|
||||
empty-message? (nil? result)
|
||||
|
||||
message (cond
|
||||
empty-message? (tr "workspace.tokens.resolved-value" "-")
|
||||
warnings (->> (wtw/humanize-warnings warnings)
|
||||
(str/join "\n"))
|
||||
errors (->> (wte/humanize-errors errors)
|
||||
(str/join "\n"))
|
||||
:else (tr "workspace.tokens.resolved-value" (dwtf/format-token-value (or resolved-value result))))
|
||||
type (cond
|
||||
empty-message? "hint"
|
||||
errors "error"
|
||||
warnings "warning"
|
||||
:else "hint")]
|
||||
[:> hint-message*
|
||||
{:id "token-value-hint"
|
||||
:message message
|
||||
:class (stl/css-case :resolved-value (not (or empty-message? (seq warnings) (seq errors))))
|
||||
:type type}]))
|
||||
|
||||
(mf/defc input-token*
|
||||
{::mf/forward-ref true
|
||||
::mf/schema schema::input-token}
|
||||
[{:keys [class label token-resolve-result] :rest props} ref]
|
||||
(let [error (not (nil? (:errors token-resolve-result)))
|
||||
id (mf/use-id)
|
||||
input-ref (mf/use-ref)
|
||||
props (mf/spread-props props {:id id
|
||||
:type "text"
|
||||
:class (stl/css :input)
|
||||
:variant "comfortable"
|
||||
:hint-type (when error "error")
|
||||
:ref (or ref input-ref)})]
|
||||
[:*
|
||||
[:div {:class (dm/str class " " (stl/css-case :wrapper true
|
||||
:input-error error))}
|
||||
(when label
|
||||
[:> label* {:for id} label])
|
||||
[:> input-field* props]]
|
||||
(when token-resolve-result
|
||||
[:> token-value-hint* {:result token-resolve-result}])]))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user