mirror of
https://github.com/penpot/penpot.git
synced 2026-01-20 12:20:16 -05:00
Compare commits
35 Commits
2.2.0
...
bameda-doc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fef4d3bdbe | ||
|
|
042b3a71d8 | ||
|
|
eadae5e2cd | ||
|
|
7f9c4df284 | ||
|
|
9e3f8e7827 | ||
|
|
3a4e9ccc5a | ||
|
|
eb720b053a | ||
|
|
efc61241a0 | ||
|
|
cfad1d178f | ||
|
|
c24b2dadec | ||
|
|
9a3b5337d7 | ||
|
|
396cbb27b2 | ||
|
|
b4e6f8bc73 | ||
|
|
d88f28f5c2 | ||
|
|
e36cf1d963 | ||
|
|
a0bb5e5ef3 | ||
|
|
34cc211912 | ||
|
|
e95713c1df | ||
|
|
e189dc965d | ||
|
|
53f580ad40 | ||
|
|
cf0045681e | ||
|
|
762a883b39 | ||
|
|
a63ded1ba1 | ||
|
|
f812b28892 | ||
|
|
873c9b1903 | ||
|
|
edeb16bc26 | ||
|
|
90d947391a | ||
|
|
47cc80a93f | ||
|
|
1f8cfde1cf | ||
|
|
5f2ec595cb | ||
|
|
37a6446e32 | ||
|
|
be84b1cb01 | ||
|
|
298db46722 | ||
|
|
0c6b0598fa | ||
|
|
f2a2d772b0 |
12
CHANGES.md
12
CHANGES.md
@@ -1,5 +1,17 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.3.0
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
### :sparkles: New features
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
## 2.2.0
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
(ns user
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.fressian :as fres]
|
||||
[app.common.json :as json]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema :as sm]
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
|
||||
(defn ordered-map
|
||||
([] lkm/empty-linked-map)
|
||||
([a] (conj lkm/empty-linked-map a))
|
||||
([a & xs] (apply conj lkm/empty-linked-map a xs)))
|
||||
([k a] (assoc lkm/empty-linked-map k a))
|
||||
([k a & xs] (apply assoc lkm/empty-linked-map k a xs)))
|
||||
|
||||
(defn ordered-set?
|
||||
[o]
|
||||
@@ -564,6 +564,41 @@
|
||||
new-elems
|
||||
(remove p? after))))
|
||||
|
||||
(defn addm-at-index
|
||||
"Insert an element in an ordered map at an arbitrary index"
|
||||
[coll index key element]
|
||||
(assert (ordered-map? coll))
|
||||
(-> (ordered-map)
|
||||
(into (take index coll))
|
||||
(assoc key element)
|
||||
(into (drop index coll))))
|
||||
|
||||
(defn insertm-at-index
|
||||
"Insert a map {k v} of elements in an ordered map at an arbitrary index"
|
||||
[coll index new-elems]
|
||||
(assert (ordered-map? coll))
|
||||
(-> (ordered-map)
|
||||
(into (take index coll))
|
||||
(into new-elems)
|
||||
(into (drop index coll))))
|
||||
|
||||
(defn adds-at-index
|
||||
"Insert an element in an ordered set at an arbitrary index"
|
||||
[coll index element]
|
||||
(assert (ordered-set? coll))
|
||||
(-> (ordered-set)
|
||||
(into (take index coll))
|
||||
(conj element)
|
||||
(into (drop index coll))))
|
||||
|
||||
(defn inserts-at-index
|
||||
"Insert a list of elements in an ordered set at an arbitrary index"
|
||||
[coll index new-elems]
|
||||
(assert (ordered-set? coll))
|
||||
(-> (ordered-set)
|
||||
(into (take index coll))
|
||||
(into new-elems)
|
||||
(into (drop index coll))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Parsing / Conversion
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
java.time.Instant
|
||||
java.time.OffsetDateTime
|
||||
java.util.List
|
||||
linked.map.LinkedMap
|
||||
org.fressian.Reader
|
||||
org.fressian.StreamingWriter
|
||||
org.fressian.Writer
|
||||
@@ -109,6 +110,13 @@
|
||||
(clojure.lang.PersistentArrayMap. (.toArray kvs))
|
||||
(clojure.lang.PersistentHashMap/create (seq kvs)))))
|
||||
|
||||
(defn read-ordered-map
|
||||
[^Reader rdr]
|
||||
(let [kvs ^java.util.List (read-object! rdr)]
|
||||
(reduce #(assoc %1 (first %2) (second %2))
|
||||
(d/ordered-map)
|
||||
(partition-all 2 (seq kvs)))))
|
||||
|
||||
(def ^:dynamic *write-handler-lookup* nil)
|
||||
(def ^:dynamic *read-handler-lookup* nil)
|
||||
|
||||
@@ -225,6 +233,11 @@
|
||||
:wfn write-map-like
|
||||
:rfn read-map-like}
|
||||
|
||||
{:name "linked/map"
|
||||
:class LinkedMap
|
||||
:wfn write-map-like
|
||||
:rfn read-ordered-map}
|
||||
|
||||
{:name "clj/keyword"
|
||||
:class clojure.lang.Keyword
|
||||
:wfn write-named
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
[app.common.uri :as uri]
|
||||
[cognitect.transit :as t]
|
||||
[lambdaisland.uri :as luri]
|
||||
[linked.core :as lk]
|
||||
[linked.map :as lkm]
|
||||
[linked.set :as lks])
|
||||
#?(:clj
|
||||
(:import
|
||||
@@ -24,6 +24,7 @@
|
||||
java.time.Instant
|
||||
java.time.OffsetDateTime
|
||||
lambdaisland.uri.URI
|
||||
linked.map.LinkedMap
|
||||
linked.set.LinkedSet)))
|
||||
|
||||
(def write-handlers (atom nil))
|
||||
@@ -118,10 +119,15 @@
|
||||
{:id "u"
|
||||
:rfn parse-uuid})
|
||||
|
||||
{:id "ordered-map"
|
||||
:class #?(:clj LinkedMap :cljs lkm/LinkedMap)
|
||||
:wfn vec
|
||||
:rfn #(into lkm/empty-linked-map %)}
|
||||
|
||||
{:id "ordered-set"
|
||||
:class #?(:clj LinkedSet :cljs lks/LinkedSet)
|
||||
:wfn vec
|
||||
:rfn #(into (lk/set) %)}
|
||||
:rfn #(into lks/empty-linked-set %)}
|
||||
|
||||
{:id "duration"
|
||||
:class #?(:clj Duration :cljs lxn/Duration)
|
||||
|
||||
@@ -16,27 +16,27 @@ RUN set -ex; \
|
||||
echo "nameserver 8.8.8.8" > /etc/resolvconf/resolv.conf.d/tail; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy install --no-install-recommends \
|
||||
build-essential \
|
||||
openssh-client \
|
||||
redis-tools \
|
||||
locales \
|
||||
gnupg2 \
|
||||
ca-certificates \
|
||||
wget \
|
||||
sudo \
|
||||
tmux \
|
||||
vim \
|
||||
curl \
|
||||
bash \
|
||||
git \
|
||||
rlwrap \
|
||||
unzip \
|
||||
rsync \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
fakeroot \
|
||||
file \
|
||||
less \
|
||||
git \
|
||||
gnupg2 \
|
||||
jq \
|
||||
less \
|
||||
locales \
|
||||
nginx \
|
||||
openssh-client \
|
||||
redis-tools \
|
||||
rlwrap \
|
||||
rsync \
|
||||
sudo \
|
||||
tmux \
|
||||
unzip \
|
||||
url \
|
||||
vim \
|
||||
wget \
|
||||
; \
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
|
||||
locale-gen; \
|
||||
@@ -50,32 +50,28 @@ RUN set -ex; \
|
||||
RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy install --no-install-recommends \
|
||||
python3 \
|
||||
python3-tabulate \
|
||||
imagemagick \
|
||||
ghostscript \
|
||||
netpbm \
|
||||
poppler-utils \
|
||||
potrace \
|
||||
webp \
|
||||
woff-tools \
|
||||
woff2 \
|
||||
fontforge \
|
||||
fonts-liberation \
|
||||
gconf-service \
|
||||
ghostscript \
|
||||
imagemagick \
|
||||
libappindicator1 \
|
||||
libasound2 \
|
||||
libatk1.0-0 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
libcairo2 \
|
||||
libcups2 \
|
||||
libdbus-1-3 \
|
||||
libexpat1 \
|
||||
libfontconfig1 \
|
||||
libgbm1 \
|
||||
libgcc1 \
|
||||
libgconf-2-4 \
|
||||
libgdk-pixbuf2.0-0 \
|
||||
libglib2.0-0 \
|
||||
libgtk-3-0 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libx11-6 \
|
||||
@@ -92,10 +88,14 @@ RUN set -ex; \
|
||||
libxshmfence1 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
fonts-liberation \
|
||||
libappindicator1 \
|
||||
libnss3 \
|
||||
libgbm1 \
|
||||
netpbm \
|
||||
poppler-utils \
|
||||
potrace \
|
||||
python3 \
|
||||
python3-tabulate \
|
||||
webp \
|
||||
woff-tools \
|
||||
woff2 \
|
||||
xvfb \
|
||||
; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
FROM debian:bookworm
|
||||
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
|
||||
|
||||
ENV LANG='en_US.UTF-8' \
|
||||
LC_ALL='en_US.UTF-8' \
|
||||
JAVA_HOME="/opt/jdk" \
|
||||
@@ -13,20 +13,16 @@ RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qq upgrade; \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
nano \
|
||||
curl \
|
||||
tzdata \
|
||||
locales \
|
||||
ca-certificates \
|
||||
fontforge \
|
||||
imagemagick \
|
||||
webp \
|
||||
rlwrap \
|
||||
fontconfig \
|
||||
woff-tools \
|
||||
woff2 \
|
||||
locales \
|
||||
python3 \
|
||||
python3-tabulate \
|
||||
fontforge \
|
||||
rlwrap \
|
||||
webp \
|
||||
woff-tools \
|
||||
woff2 \
|
||||
; \
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
|
||||
locale-gen; \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:22.04
|
||||
FROM debian:bookworm
|
||||
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
@@ -13,11 +13,9 @@ RUN set -ex; \
|
||||
echo "nameserver 127.0.0.11" > /etc/resolvconf/resolv.conf.d/tail; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy --no-install-recommends install \
|
||||
curl \
|
||||
tzdata \
|
||||
locales \
|
||||
ca-certificates \
|
||||
fontconfig \
|
||||
locales \
|
||||
tzdata \
|
||||
xz-utils \
|
||||
; \
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
@@ -27,33 +25,32 @@ RUN set -ex; \
|
||||
RUN set -ex; \
|
||||
apt-get -qq update; \
|
||||
apt-get -qqy install \
|
||||
imagemagick \
|
||||
ghostscript \
|
||||
netpbm \
|
||||
poppler-utils \
|
||||
potrace \
|
||||
fonts-liberation \
|
||||
gconf-service \
|
||||
ghostscript \
|
||||
imagemagick \
|
||||
libasound2 \
|
||||
libatk1.0-0 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
libatomic1 \
|
||||
libcairo2 \
|
||||
libcups2 \
|
||||
libdbus-1-3 \
|
||||
libexpat1 \
|
||||
libfontconfig1 \
|
||||
libgbm1 \
|
||||
libgcc1 \
|
||||
libgconf-2-4 \
|
||||
libgdk-pixbuf2.0-0 \
|
||||
libglib2.0-0 \
|
||||
libgtk-3-0 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libx11-6 \
|
||||
libx11-xcb1 \
|
||||
libxcb1 \
|
||||
libxcb-dri3-0 \
|
||||
libxcomposite1 \
|
||||
libxcursor1 \
|
||||
libxdamage1 \
|
||||
@@ -65,9 +62,9 @@ RUN set -ex; \
|
||||
libxshmfence1 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
fonts-liberation \
|
||||
libnss3 \
|
||||
libgbm1 \
|
||||
netpbm \
|
||||
poppler-utils \
|
||||
potrace \
|
||||
; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ test("User goes to the Viewer Inspect code, code tab", async ({ page }) => {
|
||||
});
|
||||
|
||||
await viewerPage.showCode();
|
||||
await viewerPage.page.getByTestId("code").click();
|
||||
await viewerPage.page.getByRole("tab", { name: "code" }).click();
|
||||
|
||||
await expect(
|
||||
viewerPage.page.getByRole("button", { name: "Copy all code" }),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -115,20 +115,30 @@ export async function compileSassAll(worker) {
|
||||
return path.startsWith("app/main/ui/ds/");
|
||||
};
|
||||
|
||||
const isOldComponentSystemFile = (path) => {
|
||||
return path.startsWith("app/main/ui/components/");
|
||||
};
|
||||
|
||||
let files = (await fs.readdir(sourceDir, { recursive: true })).filter(
|
||||
isSassFile,
|
||||
);
|
||||
|
||||
const appFiles = files
|
||||
.filter((path) => !isDesignSystemFile(path))
|
||||
.filter((path) => !isOldComponentSystemFile(path))
|
||||
.map((path) => ph.join(sourceDir, path));
|
||||
|
||||
const dsFiles = files
|
||||
.filter(isDesignSystemFile)
|
||||
.map((path) => ph.join(sourceDir, path));
|
||||
|
||||
const oldComponentsFiles = files
|
||||
.filter(isOldComponentSystemFile)
|
||||
.map((path) => ph.join(sourceDir, path));
|
||||
|
||||
const procs = [compileSass(worker, "resources/styles/main-default.scss", {})];
|
||||
|
||||
for (let path of [...dsFiles, ...appFiles]) {
|
||||
for (let path of [...oldComponentsFiles, ...dsFiles, ...appFiles]) {
|
||||
const proc = limitFn(() => compileSass(worker, path, { modules: true }));
|
||||
procs.push(proc);
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
(def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI" "https://penpot.app/privacy"))
|
||||
(def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/"))
|
||||
(def grid-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/"))
|
||||
(def plugins-list-uri (obj/get global "penpotPluginsListUri" "https://penpot-docs-plugins.pages.dev/technical-guide/plugins/getting-started/#examples"))
|
||||
(def plugins-list-uri (obj/get global "penpotPluginsListUri" "https://penpot-docs-plugins.pages.dev/plugins/getting-started/#examples"))
|
||||
|
||||
(defn- normalize-uri
|
||||
[uri-str]
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
[app.main.data.websocket :as ws]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.plugins.register :as register]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as s]
|
||||
@@ -137,7 +138,9 @@
|
||||
(swap! s/storage assoc :profile profile)
|
||||
(i18n/set-locale! (:lang profile))
|
||||
(when (not= previous-email email)
|
||||
(set-current-team! nil)))))))
|
||||
(set-current-team! nil))
|
||||
|
||||
(register/init))))))
|
||||
|
||||
(defn- on-fetch-profile-exception
|
||||
[cause]
|
||||
|
||||
@@ -519,10 +519,8 @@
|
||||
@include bodySmallTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
// TODO: This fix is temporary, the error is caused by the
|
||||
// cascading order of the compiled css files.
|
||||
// https://tree.taiga.io/project/penpot/task/8658
|
||||
.custom-input-checkbox.custom-input-checkbox {
|
||||
|
||||
.custom-input-checkbox {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
[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.forms.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
|
||||
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg* raw-svg-list]]
|
||||
[app.main.ui.ds.foundations.typography :refer [typography-list]]
|
||||
@@ -33,6 +34,7 @@
|
||||
:Input input*
|
||||
:Loader loader*
|
||||
:RawSvg raw-svg*
|
||||
:Select select*
|
||||
:Text text*
|
||||
:TabSwitcher tab-switcher*
|
||||
:Toast toast*
|
||||
|
||||
@@ -9,4 +9,6 @@
|
||||
// TODO: create actual tokens once we have them from design
|
||||
$sz-16: px2rem(16);
|
||||
$sz-32: px2rem(32);
|
||||
$sz-36: px2rem(36);
|
||||
$sz-224: px2rem(224);
|
||||
$sz-400: px2rem(400);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.ds.forms.input
|
||||
(ns app.main.ui.ds.controls.input
|
||||
(:require-macros
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.style :as stl])
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
import * as InputStories from "./input.stories";
|
||||
|
||||
<Meta title="Forms/Input" />
|
||||
<Meta title="Controls/Input" />
|
||||
|
||||
# Input
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
// 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 "../_borders.scss" as *;
|
||||
@use "../_sizes.scss" as *;
|
||||
@use "../typography.scss" as *;
|
||||
@@ -11,7 +11,7 @@ const { Input } = Components;
|
||||
const { icons } = Components.meta;
|
||||
|
||||
export default {
|
||||
title: "Forms/Input",
|
||||
title: "Controls/Input",
|
||||
component: Components.Input,
|
||||
argTypes: {
|
||||
icon: {
|
||||
245
frontend/src/app/main/ui/ds/controls/select.cljs
Normal file
245
frontend/src/app/main/ui/ds/controls/select.cljs
Normal file
@@ -0,0 +1,245 @@
|
||||
;; 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.select
|
||||
(:require-macros
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
|
||||
[app.util.array :as array]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc option*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [id label icon aria-label on-click selected set-ref focused] :rest props}]
|
||||
[:> :li {:value id
|
||||
:class (stl/css-case :option true
|
||||
:option-with-icon (some? icon)
|
||||
:option-current focused)
|
||||
:aria-selected selected
|
||||
|
||||
:ref (fn [node]
|
||||
(set-ref node id))
|
||||
:role "option"
|
||||
:id id
|
||||
:on-click on-click
|
||||
:data-id id}
|
||||
|
||||
(when (some? icon)
|
||||
[:> icon*
|
||||
{:id icon
|
||||
:size "s"
|
||||
:class (stl/css :option-icon)
|
||||
:aria-hidden (when label true)
|
||||
:aria-label (when (not label) aria-label)}])
|
||||
|
||||
[:span {:class (stl/css :option-text)} label]
|
||||
(when selected
|
||||
[:> icon*
|
||||
{:id i/tick
|
||||
:size "s"
|
||||
:class (stl/css :option-check)
|
||||
:aria-hidden (when label true)}])])
|
||||
|
||||
(mf/defc options-dropdown*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [set-ref on-click options selected focused] :rest props}]
|
||||
(let [props (mf/spread-props props
|
||||
{:class (stl/css :option-list)
|
||||
:tab-index "-1"
|
||||
:role "listbox"})]
|
||||
[:> "ul" props
|
||||
(for [option ^js options]
|
||||
(let [id (obj/get option "id")
|
||||
label (obj/get option "label")
|
||||
aria-label (obj/get option "aria-label")
|
||||
icon (obj/get option "icon")]
|
||||
[:> option* {:selected (= id selected)
|
||||
:key id
|
||||
:id id
|
||||
:label label
|
||||
:icon icon
|
||||
:aria-label aria-label
|
||||
:set-ref set-ref
|
||||
:focused (= id focused)
|
||||
:on-click on-click}]))]))
|
||||
|
||||
(def ^:private schema:select-option
|
||||
[:and
|
||||
[:map {:title "option"}
|
||||
[:id :string]
|
||||
[:icon {:optional true}
|
||||
[:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:label {:optional true} :string]
|
||||
[:aria-label {:optional true} :string]]
|
||||
[:fn {:error/message "invalid data: missing required props"}
|
||||
(fn [option]
|
||||
(or (and (contains? option :icon)
|
||||
(or (contains? option :label)
|
||||
(contains? option :aria-label)))
|
||||
(contains? option :label)))]])
|
||||
|
||||
(def ^:private schema:select
|
||||
[:map
|
||||
[:disabled {:optional true} :boolean]
|
||||
[:class {:optional true} :string]
|
||||
[:icon {:optional true}
|
||||
[:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:default-selected {:optional true} :string]
|
||||
[:options [:vector {:min 1} schema:select-option]]])
|
||||
|
||||
(defn- get-option
|
||||
[options id]
|
||||
(or (array/find #(= id (obj/get % "id")) options)
|
||||
(aget options 0)))
|
||||
|
||||
(defn- get-selected-option-id
|
||||
[options default]
|
||||
(let [option (get-option options default)]
|
||||
(obj/get option "id")))
|
||||
|
||||
(defn- handle-focus-change
|
||||
[options focused* new-index options-nodes-refs]
|
||||
(let [option (aget options new-index)
|
||||
id (obj/get option "id")
|
||||
nodes (mf/ref-val options-nodes-refs)
|
||||
node (obj/get nodes id)]
|
||||
(reset! focused* id)
|
||||
(dom/scroll-into-view-if-needed! node)))
|
||||
|
||||
(defn- handle-selection
|
||||
[focused* selected* open*]
|
||||
(when-let [focused (deref focused*)]
|
||||
(reset! selected* focused))
|
||||
(reset! open* false)
|
||||
(reset! focused* nil))
|
||||
|
||||
(mf/defc select*
|
||||
{::mf/props :obj
|
||||
::mf/schema schema:select}
|
||||
[{:keys [disabled default-selected on-change options class] :rest props}]
|
||||
(let [open* (mf/use-state false)
|
||||
open (deref open*)
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps disabled)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(when-not disabled
|
||||
(swap! open* not))))
|
||||
|
||||
selected* (mf/use-state #(get-selected-option-id options default-selected))
|
||||
selected (deref selected*)
|
||||
|
||||
focused* (mf/use-state nil)
|
||||
focused (deref focused*)
|
||||
|
||||
on-option-click
|
||||
(mf/use-fn
|
||||
(mf/deps on-change)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
id (dom/get-data node "id")]
|
||||
(reset! selected* id)
|
||||
(reset! focused* nil)
|
||||
(reset! open* false)
|
||||
(when (fn? on-change)
|
||||
(on-change id)))))
|
||||
|
||||
options-nodes-refs (mf/use-ref nil)
|
||||
options-ref (mf/use-ref nil)
|
||||
|
||||
set-ref
|
||||
(mf/use-fn
|
||||
(fn [node id]
|
||||
(let [refs (or (mf/ref-val options-nodes-refs) #js {})
|
||||
refs (if node
|
||||
(obj/set! refs id node)
|
||||
(obj/unset! refs id))]
|
||||
(mf/set-ref-val! options-nodes-refs refs))))
|
||||
|
||||
on-blur
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [click-outside (nil? (.-relatedTarget event))]
|
||||
(when click-outside
|
||||
(reset! focused* nil)
|
||||
(reset! open* false)))))
|
||||
|
||||
on-key-down
|
||||
(mf/use-fn
|
||||
(mf/deps focused disabled)
|
||||
(fn [event]
|
||||
(when-not disabled
|
||||
(let [options (mf/ref-val options-ref)
|
||||
len (alength options)
|
||||
index (array/find-index #(= (deref focused*) (obj/get % "id")) options)]
|
||||
(dom/stop-propagation event)
|
||||
(cond
|
||||
(kbd/home? event)
|
||||
(handle-focus-change options focused* 0 options-nodes-refs)
|
||||
|
||||
(kbd/up-arrow? event)
|
||||
(handle-focus-change options focused* (mod (- index 1) len) options-nodes-refs)
|
||||
|
||||
(kbd/down-arrow? event)
|
||||
(handle-focus-change options focused* (mod (+ index 1) len) options-nodes-refs)
|
||||
|
||||
(or (kbd/space? event) (kbd/enter? event))
|
||||
(when (deref open*)
|
||||
(dom/prevent-default event)
|
||||
(handle-selection focused* selected* open*))
|
||||
|
||||
(kbd/esc? event)
|
||||
(do (reset! open* false)
|
||||
(reset! focused* nil)))))))
|
||||
|
||||
class (dm/str class " " (stl/css :select))
|
||||
|
||||
props (mf/spread-props props {:class class
|
||||
:role "combobox"
|
||||
:aria-controls "listbox"
|
||||
:aria-haspopup "listbox"
|
||||
:aria-activedescendant focused
|
||||
:aria-expanded open
|
||||
:on-key-down on-key-down
|
||||
:disabled disabled
|
||||
:on-click on-click
|
||||
:on-blur on-blur})
|
||||
|
||||
selected-option (get-option options selected)
|
||||
label (obj/get selected-option "label")
|
||||
icon (obj/get selected-option "icon")]
|
||||
|
||||
(mf/with-effect [options]
|
||||
(mf/set-ref-val! options-ref options))
|
||||
|
||||
[:div {:class (stl/css :select-wrapper)}
|
||||
[:> :button props
|
||||
[:span {:class (stl/css-case :select-header true
|
||||
:header-icon (some? icon))}
|
||||
(when icon
|
||||
[:> icon* {:id icon
|
||||
:size "s"
|
||||
:aria-hidden true}])
|
||||
[:span {:class (stl/css :header-label)}
|
||||
label]]
|
||||
[:> icon* {:id i/arrow
|
||||
:class (stl/css :arrow)
|
||||
:size "s"
|
||||
:aria-hidden true}]]
|
||||
(when open
|
||||
[:> options-dropdown* {:on-click on-option-click
|
||||
:options options
|
||||
:selected selected
|
||||
:focused focused
|
||||
:set-ref set-ref}])]))
|
||||
63
frontend/src/app/main/ui/ds/controls/select.mdx
Normal file
63
frontend/src/app/main/ui/ds/controls/select.mdx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
import * as SelectStories from "./select.stories";
|
||||
|
||||
<Meta title="Controls/Select" />
|
||||
|
||||
# Select
|
||||
|
||||
Select lets users choose one option from an options menu.
|
||||
|
||||
## Variants
|
||||
|
||||
**Text**: We will use this variant when there are enough space and icons don't add any useful context.
|
||||
|
||||
<Canvas of={SelectStories.Default} />
|
||||
|
||||
**Icon and text**: We will use this variant when there are enough space and icons add any useful context.
|
||||
<Canvas of={SelectStories.WithIcons} />
|
||||
|
||||
## Technical notes
|
||||
|
||||
### Icons
|
||||
|
||||
Each option of `select*` may accept an `icon`, which must contain an [icon ID](../foundations/assets/icon.mdx).
|
||||
These are available in the `app.main.ds.foundations.assets.icon` namespace.
|
||||
|
||||
|
||||
```clj
|
||||
(ns app.main.ui.foo
|
||||
(:require
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]))
|
||||
```
|
||||
|
||||
```clj
|
||||
[:> select*
|
||||
{:options [{ :label "Code"
|
||||
:id "option-code"
|
||||
:icon i/fill-content }
|
||||
{ :label "Design"
|
||||
:id "option-design"
|
||||
:icon i/pentool }
|
||||
{ :label "Menu"
|
||||
:id "option-menu" }
|
||||
]}]
|
||||
```
|
||||
|
||||
<Canvas of={SelectStories.WithIcons} />
|
||||
|
||||
## Usage guidelines (design)
|
||||
|
||||
### Where to use
|
||||
|
||||
Used in a wide range of applications in the app,
|
||||
to select among available text-based options,
|
||||
sometimes with icons that offers additional context.
|
||||
|
||||
### When to use
|
||||
|
||||
Consider using select when you have 5 or more options to choose from.
|
||||
|
||||
### Interaction / Behavior
|
||||
|
||||
When the user clicks on the clickable area, a list of
|
||||
options appears. When an option is chosen, the list is closed.
|
||||
147
frontend/src/app/main/ui/ds/controls/select.scss
Normal file
147
frontend/src/app/main/ui/ds/controls/select.scss
Normal file
@@ -0,0 +1,147 @@
|
||||
// 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 "../_borders.scss" as *;
|
||||
@use "../_sizes.scss" as *;
|
||||
@use "../typography.scss" as *;
|
||||
|
||||
.select-wrapper {
|
||||
--select-icon-fg-color: var(--color-foreground-secondary);
|
||||
--select-fg-color: var(--color-foreground-primary);
|
||||
--select-bg-color: var(--color-background-tertiary);
|
||||
--select-outline-color: none;
|
||||
--select-border-color: none;
|
||||
--select-dropdown-border-color: var(--color-background-quaternary);
|
||||
|
||||
&:hover {
|
||||
--select-bg-color: var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
@include use-typography("body-small");
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
gap: var(--sp-xxs);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.select {
|
||||
&:focus-visible {
|
||||
--select-outline-color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
--select-bg-color: var(--color-background-primary);
|
||||
--select-border-color: var(--color-background-quaternary);
|
||||
--select-fg-color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: var(--sp-xs);
|
||||
height: $sz-32;
|
||||
width: 100%;
|
||||
padding: var(--sp-s);
|
||||
border: none;
|
||||
border-radius: $br-8;
|
||||
outline: $b-1 solid var(--select-outline-color);
|
||||
border: $b-1 solid var(--select-border-color);
|
||||
background: var(--select-bg-color);
|
||||
color: var(--select-fg-color);
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
color: var(--select-icon-fg-color);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.select-header {
|
||||
display: grid;
|
||||
justify-items: start;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.header-label {
|
||||
@include use-typography("body-small");
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-inline-start: var(--sp-xxs);
|
||||
text-align: left;
|
||||
color: var(--select-fg-color);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
grid-template-columns: auto 1fr;
|
||||
color: var(--select-icon-fg-color);
|
||||
}
|
||||
|
||||
.option-list {
|
||||
--options-dropdown-bg-color: var(--color-background-tertiary);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: $sz-36;
|
||||
width: 100%;
|
||||
background-color: var(--options-dropdown-bg-color);
|
||||
border-radius: $br-8;
|
||||
border: $b-1 solid var(--select-dropdown-border-color);
|
||||
padding-block: var(--sp-xs);
|
||||
margin-block-end: 0;
|
||||
max-height: $sz-400;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.option {
|
||||
--select-option-fg-color: var(--color-foreground-primary);
|
||||
--select-option-bg-color: unset;
|
||||
|
||||
&:hover {
|
||||
--select-option-bg-color: var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
&[aria-selected="true"] {
|
||||
--select-option-bg-color: var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-items: start;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: var(--sp-xs);
|
||||
width: 100%;
|
||||
height: $sz-32;
|
||||
padding: var(--sp-s);
|
||||
border-radius: $br-8;
|
||||
outline: $b-1 solid var(--select-outline-color);
|
||||
outline-offset: -1px;
|
||||
background-color: var(--select-option-bg-color);
|
||||
}
|
||||
|
||||
.option-with-icon {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-inline-start: var(--sp-xxs);
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
color: var(--select-icon-fg-color);
|
||||
}
|
||||
|
||||
.option-current {
|
||||
--select-option-outline-color: var(--color-accent-primary);
|
||||
outline: $b-1 solid var(--select-option-outline-color);
|
||||
}
|
||||
65
frontend/src/app/main/ui/ds/controls/select.stories.jsx
Normal file
65
frontend/src/app/main/ui/ds/controls/select.stories.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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 { Select } = Components;
|
||||
|
||||
export default {
|
||||
title: "Controls/Select",
|
||||
component: Select,
|
||||
argTypes: {
|
||||
disabled: { control: "boolean" },
|
||||
},
|
||||
args: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{
|
||||
label: "Code",
|
||||
id: "option-code",
|
||||
},
|
||||
{
|
||||
label: "Design",
|
||||
id: "option-design",
|
||||
},
|
||||
{
|
||||
label: "Menu",
|
||||
id: "opeion-menu",
|
||||
},
|
||||
],
|
||||
defaultSelected: "option-code",
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
exclude: ["options", "defaultSelected"],
|
||||
},
|
||||
},
|
||||
render: ({ ...args }) => <Select {...args} />,
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
||||
export const WithIcons = {
|
||||
args: {
|
||||
options: [
|
||||
{
|
||||
label: "Code",
|
||||
id: "option-code",
|
||||
icon: "fill-content",
|
||||
},
|
||||
{
|
||||
label: "Design",
|
||||
id: "option-design",
|
||||
icon: "pentool",
|
||||
},
|
||||
{
|
||||
label: "Menu",
|
||||
id: "option-menu",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -372,7 +372,6 @@
|
||||
[:& fm/image-radio-buttons {:options start-options
|
||||
:img-width "159px"
|
||||
:img-height "120px"
|
||||
:class (stl/css :image-radio)
|
||||
:name :start-with
|
||||
:on-change on-start-change}]
|
||||
|
||||
|
||||
@@ -10,8 +10,12 @@
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.api :as api]
|
||||
[app.plugins.flex :as flex]
|
||||
[app.plugins.format :as format]
|
||||
[app.plugins.grid :as grid]
|
||||
[app.plugins.library :as library]
|
||||
[app.plugins.public-utils]
|
||||
[app.plugins.register :as register]
|
||||
[app.plugins.shape :as shape]
|
||||
[app.util.globals :refer [global]]
|
||||
[app.util.object :as obj]
|
||||
[beicon.v2.core :as rx]
|
||||
@@ -20,7 +24,6 @@
|
||||
(defn init-plugins-runtime!
|
||||
[]
|
||||
(when-let [init-runtime (obj/get global "initPluginsRuntime")]
|
||||
(register/init)
|
||||
(init-runtime (fn [plugin-id] (api/create-context plugin-id)))))
|
||||
|
||||
(defn initialize
|
||||
@@ -36,3 +39,10 @@
|
||||
(rx/tap init-plugins-runtime!)
|
||||
(rx/ignore)))))
|
||||
|
||||
;; Prevent circular dependency
|
||||
(set! flex/shape-proxy? shape/shape-proxy?)
|
||||
(set! grid/shape-proxy? shape/shape-proxy?)
|
||||
(set! format/shape-proxy shape/shape-proxy)
|
||||
|
||||
(set! shape/lib-typography-proxy? library/lib-typography-proxy?)
|
||||
(set! shape/lib-component-proxy library/lib-component-proxy)
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
ids (into #{} (map #(obj/get % "$id")) shapes)]
|
||||
(st/emit! (dwg/ungroup-shapes ids)))))
|
||||
|
||||
(createFrame
|
||||
(createBoard
|
||||
[_]
|
||||
(create-shape $plugin :frame))
|
||||
|
||||
|
||||
@@ -31,14 +31,22 @@
|
||||
"mixed"
|
||||
value))
|
||||
|
||||
;; export type PenpotPoint = { x: number; y: number };
|
||||
;; export type Point = { x: number; y: number };
|
||||
(defn format-point
|
||||
[{:keys [x y] :as point}]
|
||||
(when (some? point)
|
||||
(obj/without-empty
|
||||
#js {:x x :y y})))
|
||||
|
||||
;;export type PenpotBounds = {
|
||||
(defn shape-type
|
||||
[type]
|
||||
(case type
|
||||
:frame "board"
|
||||
:rect "rectangle"
|
||||
:circle "ellipse"
|
||||
(d/name type)))
|
||||
|
||||
;;export type Bounds = {
|
||||
;; x: number;
|
||||
;; y: number;
|
||||
;; width: number;
|
||||
@@ -50,7 +58,7 @@
|
||||
(obj/without-empty
|
||||
#js {:x x :y y :width width :height height})))
|
||||
|
||||
;; export interface PenpotColorShapeInfoEntry {
|
||||
;; export interface ColorShapeInfoEntry {
|
||||
;; readonly property: string;
|
||||
;; readonly index?: number;
|
||||
;; readonly shapeId: string;
|
||||
@@ -63,7 +71,7 @@
|
||||
:index index
|
||||
:shapeId (dm/str shape-id)})))
|
||||
|
||||
;; export type PenpotGradient = {
|
||||
;; export type Gradient = {
|
||||
;; type: 'linear' | 'radial';
|
||||
;; startX: number;
|
||||
;; startY: number;
|
||||
@@ -89,7 +97,7 @@
|
||||
:width width
|
||||
:stops (format-array format-stop stops)})))
|
||||
|
||||
;; export type PenpotImageData = {
|
||||
;; export type ImageData = {
|
||||
;; name?: string;
|
||||
;; width: number;
|
||||
;; height: number;
|
||||
@@ -108,7 +116,7 @@
|
||||
:id (format-id id)
|
||||
:keepAspectRatio keep-aspect-ratio})))
|
||||
|
||||
;; export interface PenpotColor {
|
||||
;; export interface Color {
|
||||
;; id?: string;
|
||||
;; name?: string;
|
||||
;; path?: string;
|
||||
@@ -116,8 +124,8 @@
|
||||
;; opacity?: number;
|
||||
;; refId?: string;
|
||||
;; refFile?: string;
|
||||
;; gradient?: PenpotGradient;
|
||||
;; image?: PenpotImageData;
|
||||
;; gradient?: Gradient;
|
||||
;; image?: ImageData;
|
||||
;; }
|
||||
(defn format-color
|
||||
[{:keys [id name path color opacity ref-id ref-file gradient image] :as color-data}]
|
||||
@@ -133,7 +141,7 @@
|
||||
:gradient (format-gradient gradient)
|
||||
:image (format-image image)})))
|
||||
|
||||
;; PenpotColor & PenpotColorShapeInfo
|
||||
;; Color & ColorShapeInfo
|
||||
(defn format-color-result
|
||||
[[color attrs]]
|
||||
(let [shapes-info (apply array (map format-shape-info attrs))
|
||||
@@ -142,7 +150,7 @@
|
||||
color))
|
||||
|
||||
|
||||
;; export interface PenpotShadow {
|
||||
;; export interface Shadow {
|
||||
;; id?: string;
|
||||
;; style?: 'drop-shadow' | 'inner-shadow';
|
||||
;; offsetX?: number;
|
||||
@@ -150,7 +158,7 @@
|
||||
;; blur?: number;
|
||||
;; spread?: number;
|
||||
;; hidden?: boolean;
|
||||
;; color?: PenpotColor;
|
||||
;; color?: Color;
|
||||
;; }
|
||||
(defn format-shadow
|
||||
[{:keys [id style offset-x offset-y blur spread hidden color] :as shadow}]
|
||||
@@ -170,13 +178,13 @@
|
||||
(when (some? shadows)
|
||||
(format-array format-shadow shadows)))
|
||||
|
||||
;;export interface PenpotFill {
|
||||
;;export interface Fill {
|
||||
;; fillColor?: string;
|
||||
;; fillOpacity?: number;
|
||||
;; fillColorGradient?: PenpotGradient;
|
||||
;; fillColorGradient?: Gradient;
|
||||
;; fillColorRefFile?: string;
|
||||
;; fillColorRefId?: string;
|
||||
;; fillImage?: PenpotImageData;
|
||||
;; fillImage?: ImageData;
|
||||
;;}
|
||||
(defn format-fill
|
||||
[{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-file fill-color-ref-id fill-image] :as fill}]
|
||||
@@ -201,7 +209,7 @@
|
||||
(some? fills)
|
||||
(format-array format-fill fills)))
|
||||
|
||||
;; export interface PenpotStroke {
|
||||
;; export interface Stroke {
|
||||
;; strokeColor?: string;
|
||||
;; strokeColorRefFile?: string;
|
||||
;; strokeColorRefId?: string;
|
||||
@@ -209,9 +217,9 @@
|
||||
;; strokeStyle?: 'solid' | 'dotted' | 'dashed' | 'mixed' | 'none' | 'svg';
|
||||
;; strokeWidth?: number;
|
||||
;; strokeAlignment?: 'center' | 'inner' | 'outer';
|
||||
;; strokeCapStart?: PenpotStrokeCap;
|
||||
;; strokeCapEnd?: PenpotStrokeCap;
|
||||
;; strokeColorGradient?: PenpotGradient;
|
||||
;; strokeCapStart?: StrokeCap;
|
||||
;; strokeCapEnd?: StrokeCap;
|
||||
;; strokeColorGradient?: Gradient;
|
||||
;; }
|
||||
(defn format-stroke
|
||||
[{:keys [stroke-color stroke-color-ref-file stroke-color-ref-id
|
||||
@@ -236,7 +244,7 @@
|
||||
(when (some? strokes)
|
||||
(format-array format-stroke strokes)))
|
||||
|
||||
;; export interface PenpotBlur {
|
||||
;; export interface Blur {
|
||||
;; id?: string;
|
||||
;; type?: 'layer-blur';
|
||||
;; value?: number;
|
||||
@@ -251,7 +259,7 @@
|
||||
:value value
|
||||
:hidden hidden})))
|
||||
|
||||
;; export interface PenpotExport {
|
||||
;; export interface Export {
|
||||
;; type: 'png' | 'jpeg' | 'svg' | 'pdf';
|
||||
;; scale: number;
|
||||
;; suffix: string;
|
||||
@@ -269,7 +277,7 @@
|
||||
(when (some? exports)
|
||||
(format-array format-export exports)))
|
||||
|
||||
;; export interface PenpotFrameGuideColumnParams {
|
||||
;; export interface GuideColumnParams {
|
||||
;; color: { color: string; opacity: number };
|
||||
;; type?: 'stretch' | 'left' | 'center' | 'right';
|
||||
;; size?: number;
|
||||
@@ -288,10 +296,10 @@
|
||||
:itemLength item-length
|
||||
:gutter gutter})))
|
||||
|
||||
;; export interface PenpotFrameGuideColumn {
|
||||
;; export interface GuideColumn {
|
||||
;; type: 'column';
|
||||
;; display: boolean;
|
||||
;; params: PenpotFrameGuideColumnParams;
|
||||
;; params: GuideColumnParams;
|
||||
;; }
|
||||
(defn format-frame-guide-column
|
||||
[{:keys [type display params] :as guide}]
|
||||
@@ -301,10 +309,10 @@
|
||||
:display display
|
||||
:params (format-frame-guide-column-params params)})))
|
||||
|
||||
;; export interface PenpotFrameGuideRow {
|
||||
;; export interface GuideRow {
|
||||
;; type: 'row';
|
||||
;; display: boolean;
|
||||
;; params: PenpotFrameGuideColumnParams;
|
||||
;; params: GuideColumnParams;
|
||||
;; }
|
||||
(defn format-frame-guide-row
|
||||
[{:keys [type display params] :as guide}]
|
||||
@@ -314,7 +322,7 @@
|
||||
:display display
|
||||
:params (format-frame-guide-column-params params)})))
|
||||
|
||||
;;export interface PenpotFrameGuideSquareParams {
|
||||
;;export interface GuideSquareParams {
|
||||
;; color: { color: string; opacity: number };
|
||||
;; size?: number;
|
||||
;;}
|
||||
@@ -325,10 +333,10 @@
|
||||
#js {:color (format-color color)
|
||||
:size size})))
|
||||
|
||||
;; export interface PenpotFrameGuideSquare {
|
||||
;; export interface GuideSquare {
|
||||
;; type: 'square';
|
||||
;; display: boolean;
|
||||
;; params: PenpotFrameGuideSquareParams;
|
||||
;; params: GuideSquareParams;
|
||||
;; }
|
||||
|
||||
(defn format-frame-guide-square
|
||||
@@ -352,7 +360,7 @@
|
||||
(when (some? guides)
|
||||
(format-array format-frame-guide guides)))
|
||||
|
||||
;;interface PenpotPathCommand {
|
||||
;;interface PathCommand {
|
||||
;; command:
|
||||
;; | 'M' | 'move-to'
|
||||
;; | 'Z' | 'close-path'
|
||||
@@ -407,10 +415,10 @@
|
||||
(when (some? content)
|
||||
(format-array format-command content)))
|
||||
|
||||
;; export type PenpotTrackType = 'flex' | 'fixed' | 'percent' | 'auto';
|
||||
;; export type TrackType = 'flex' | 'fixed' | 'percent' | 'auto';
|
||||
;;
|
||||
;; export interface PenpotTrack {
|
||||
;; type: PenpotTrackType;
|
||||
;; export interface Track {
|
||||
;; type: TrackType;
|
||||
;; value: number | null;
|
||||
;; }
|
||||
(defn format-track
|
||||
@@ -426,13 +434,13 @@
|
||||
(format-array format-track tracks)))
|
||||
|
||||
|
||||
;; export interface PenpotDissolve {
|
||||
;; export interface Dissolve {
|
||||
;; type: 'dissolve';
|
||||
;; duration: number;
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export interface PenpotSlide {
|
||||
;; export interface Slide {
|
||||
;; type: 'slide';
|
||||
;; way: 'in' | 'out';
|
||||
;; direction?:
|
||||
@@ -445,7 +453,7 @@
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export interface PenpotPush {
|
||||
;; export interface Push {
|
||||
;; type: 'push';
|
||||
;; direction?:
|
||||
;; | 'right'
|
||||
@@ -457,7 +465,7 @@
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export type PenpotAnimation = PenpotDissolve | PenpotSlide | PenpotPush;
|
||||
;; export type Animation = Dissolve | Slide | Push;
|
||||
|
||||
(defn format-animation
|
||||
[animation]
|
||||
@@ -485,24 +493,24 @@
|
||||
:easing (format-key (:easing animation))}
|
||||
nil))))
|
||||
|
||||
;;export type PenpotAction =
|
||||
;; | PenpotNavigateTo
|
||||
;; | PenpotOpenOverlay
|
||||
;; | PenpotToggleOverlay
|
||||
;; | PenpotCloseOverlay
|
||||
;; | PenpotPreviousScreen
|
||||
;; | PenpotOpenUrl;
|
||||
;;export type Action =
|
||||
;; | NavigateTo
|
||||
;; | OpenOverlay
|
||||
;; | ToggleOverlay
|
||||
;; | CloseOverlay
|
||||
;; | PreviousScreen
|
||||
;; | OpenUrl;
|
||||
;;
|
||||
;;export interface PenpotNavigateTo {
|
||||
;;export interface NavigateTo {
|
||||
;; type: 'navigate-to';
|
||||
;; destination: PenpotFrame;
|
||||
;; destination: Board;
|
||||
;; preserveScrollPosition?: boolean;
|
||||
;; animation: PenpotAnimation;
|
||||
;; animation: Animation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOverlayAction {
|
||||
;; destination: PenpotFrame;
|
||||
;; relativeTo?: PenpotShape;
|
||||
;;export interface OverlayAction {
|
||||
;; destination: Board;
|
||||
;; relativeTo?: Shape;
|
||||
;; position?:
|
||||
;; | 'manual'
|
||||
;; | 'center'
|
||||
@@ -512,31 +520,31 @@
|
||||
;; | 'bottom-left'
|
||||
;; | 'bottom-right'
|
||||
;; | 'bottom-center';
|
||||
;; manualPositionLocation?: PenpotPoint;
|
||||
;; manualPositionLocation?: Point;
|
||||
;; closeWhenClickOutside?: boolean;
|
||||
;; addBackgroundOverlay?: boolean;
|
||||
;; animation: PenpotAnimation;
|
||||
;; animation: Animation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOpenOverlay extends PenpotOverlayAction {
|
||||
;;export interface OpenOverlay extends OverlayAction {
|
||||
;; type: 'open-overlay';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotToggleOverlay extends PenpotOverlayAction {
|
||||
;;export interface ToggleOverlay extends OverlayAction {
|
||||
;; type: 'toggle-overlay';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotCloseOverlay {
|
||||
;;export interface CloseOverlay {
|
||||
;; type: 'close-overlay';
|
||||
;; destination?: PenpotFrame;
|
||||
;; animation: PenpotAnimation;
|
||||
;; destination?: Board;
|
||||
;; animation: Animation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotPreviousScreen {
|
||||
;;export interface PreviousScreen {
|
||||
;; type: 'previous-screen';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOpenUrl {
|
||||
;;export interface OpenUrl {
|
||||
;; type: 'open-url';
|
||||
;; url: string;
|
||||
;;}
|
||||
|
||||
@@ -413,8 +413,6 @@
|
||||
(defn lib-typography-proxy? [p]
|
||||
(instance? LibraryTypographyProxy p))
|
||||
|
||||
(set! shape/lib-typography-proxy? lib-typography-proxy?)
|
||||
|
||||
(defn lib-typography-proxy
|
||||
[plugin-id file-id id]
|
||||
(assert (uuid? file-id))
|
||||
@@ -758,8 +756,6 @@
|
||||
value (dm/str value " / " (:name component))]
|
||||
(st/emit! (dwl/rename-component id value)))))}))
|
||||
|
||||
(set! shape/lib-component-proxy lib-component-proxy)
|
||||
|
||||
(deftype Library [$plugin $id]
|
||||
Object
|
||||
|
||||
|
||||
@@ -29,17 +29,26 @@
|
||||
{:x (obj/get point "x")
|
||||
:y (obj/get point "y")}))
|
||||
|
||||
(defn parse-shape-type
|
||||
[type]
|
||||
(case type
|
||||
"board" :frame
|
||||
"boolean" :bool
|
||||
"rectangle" :rect
|
||||
"ellipse" :circle
|
||||
(parse-keyword type)))
|
||||
|
||||
;; {
|
||||
;; name?: string;
|
||||
;; nameLike?: string;
|
||||
;; type?:
|
||||
;; | 'frame'
|
||||
;; | 'board'
|
||||
;; | 'group'
|
||||
;; | 'bool'
|
||||
;; | 'rect'
|
||||
;; | 'boolean'
|
||||
;; | 'rectangle'
|
||||
;; | 'path'
|
||||
;; | 'text'
|
||||
;; | 'circle'
|
||||
;; | 'ellipse'
|
||||
;; | 'svg-raw'
|
||||
;; | 'image';
|
||||
;; }
|
||||
@@ -49,9 +58,9 @@
|
||||
(d/without-nils
|
||||
{:name (obj/get criteria "name")
|
||||
:name-like (obj/get criteria "nameLike")
|
||||
:type (-> (obj/get criteria "type") parse-keyword)})))
|
||||
:type (-> (obj/get criteria "type") parse-shape-type)})))
|
||||
|
||||
;;export type PenpotImageData = {
|
||||
;;export type ImageData = {
|
||||
;; name?: string;
|
||||
;; width: number;
|
||||
;; height: number;
|
||||
@@ -70,7 +79,7 @@
|
||||
:mtype (obj/get image-data "mtype")
|
||||
:keep-aspect-ratio (obj/get image-data "keepApectRatio")})))
|
||||
|
||||
;; export type PenpotGradient = {
|
||||
;; export type Gradient = {
|
||||
;; type: 'linear' | 'radial';
|
||||
;; startX: number;
|
||||
;; startY: number;
|
||||
@@ -100,7 +109,7 @@
|
||||
:stops (->> (obj/get gradient "stops")
|
||||
(mapv parse-gradient-stop))})))
|
||||
|
||||
;; export interface PenpotColor {
|
||||
;; export interface Color {
|
||||
;; id?: string;
|
||||
;; name?: string;
|
||||
;; path?: string;
|
||||
@@ -108,8 +117,8 @@
|
||||
;; opacity?: number;
|
||||
;; refId?: string;
|
||||
;; refFile?: string;
|
||||
;; gradient?: PenpotGradient;
|
||||
;; image?: PenpotImageData;
|
||||
;; gradient?: Gradient;
|
||||
;; image?: ImageData;
|
||||
;; }
|
||||
(defn parse-color
|
||||
[^js color]
|
||||
@@ -125,7 +134,7 @@
|
||||
:gradient (-> (obj/get color "gradient") parse-gradient)
|
||||
:image (-> (obj/get color "image") parse-image-data)})))
|
||||
|
||||
;; export interface PenpotShadow {
|
||||
;; export interface Shadow {
|
||||
;; id?: string;
|
||||
;; style?: 'drop-shadow' | 'inner-shadow';
|
||||
;; offsetX?: number;
|
||||
@@ -133,7 +142,7 @@
|
||||
;; blur?: number;
|
||||
;; spread?: number;
|
||||
;; hidden?: boolean;
|
||||
;; color?: PenpotColor;
|
||||
;; color?: Color;
|
||||
;; }
|
||||
(defn parse-shadow
|
||||
[^js shadow]
|
||||
@@ -153,13 +162,13 @@
|
||||
(when (some? shadows)
|
||||
(into [] (map parse-shadow) shadows)))
|
||||
|
||||
;;export interface PenpotFill {
|
||||
;;export interface Fill {
|
||||
;; fillColor?: string;
|
||||
;; fillOpacity?: number;
|
||||
;; fillColorGradient?: PenpotGradient;
|
||||
;; fillColorGradient?: Gradient;
|
||||
;; fillColorRefFile?: string;
|
||||
;; fillColorRefId?: string;
|
||||
;; fillImage?: PenpotImageData;
|
||||
;; fillImage?: ImageData;
|
||||
;;}
|
||||
(defn parse-fill
|
||||
[^js fill]
|
||||
@@ -177,7 +186,7 @@
|
||||
(when (some? fills)
|
||||
(into [] (map parse-fill) fills)))
|
||||
|
||||
;; export interface PenpotStroke {
|
||||
;; export interface Stroke {
|
||||
;; strokeColor?: string;
|
||||
;; strokeColorRefFile?: string;
|
||||
;; strokeColorRefId?: string;
|
||||
@@ -185,9 +194,9 @@
|
||||
;; strokeStyle?: 'solid' | 'dotted' | 'dashed' | 'mixed' | 'none' | 'svg';
|
||||
;; strokeWidth?: number;
|
||||
;; strokeAlignment?: 'center' | 'inner' | 'outer';
|
||||
;; strokeCapStart?: PenpotStrokeCap;
|
||||
;; strokeCapEnd?: PenpotStrokeCap;
|
||||
;; strokeColorGradient?: PenpotGradient;
|
||||
;; strokeCapStart?: StrokeCap;
|
||||
;; strokeCapEnd?: StrokeCap;
|
||||
;; strokeColorGradient?: Gradient;
|
||||
;; }
|
||||
(defn parse-stroke
|
||||
[^js stroke]
|
||||
@@ -209,7 +218,7 @@
|
||||
(when (some? strokes)
|
||||
(into [] (map parse-stroke) strokes)))
|
||||
|
||||
;; export interface PenpotBlur {
|
||||
;; export interface Blur {
|
||||
;; id?: string;
|
||||
;; type?: 'layer-blur';
|
||||
;; value?: number;
|
||||
@@ -225,7 +234,7 @@
|
||||
:hidden (obj/get blur "hidden")})))
|
||||
|
||||
|
||||
;; export interface PenpotExport {
|
||||
;; export interface Export {
|
||||
;; type: 'png' | 'jpeg' | 'svg' | 'pdf';
|
||||
;; scale: number;
|
||||
;; suffix: string;
|
||||
@@ -243,7 +252,7 @@
|
||||
(when (some? exports)
|
||||
(into [] (map parse-export) exports)))
|
||||
|
||||
;; export interface PenpotFrameGuideColumnParams {
|
||||
;; export interface GuideColumnParams {
|
||||
;; color: { color: string; opacity: number };
|
||||
;; type?: 'stretch' | 'left' | 'center' | 'right';
|
||||
;; size?: number;
|
||||
@@ -262,10 +271,10 @@
|
||||
:item-length (obj/get params "itemLength")
|
||||
:gutter (obj/get params "gutter")})))
|
||||
|
||||
;; export interface PenpotFrameGuideColumn {
|
||||
;; export interface GuideColumn {
|
||||
;; type: 'column';
|
||||
;; display: boolean;
|
||||
;; params: PenpotFrameGuideColumnParams;
|
||||
;; params: GuideColumnParams;
|
||||
;; }
|
||||
(defn parse-frame-guide-column
|
||||
[^js guide]
|
||||
@@ -275,10 +284,10 @@
|
||||
:display (obj/get guide "display")
|
||||
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
|
||||
|
||||
;; export interface PenpotFrameGuideRow {
|
||||
;; export interface GuideRow {
|
||||
;; type: 'row';
|
||||
;; display: boolean;
|
||||
;; params: PenpotFrameGuideColumnParams;
|
||||
;; params: GuideColumnParams;
|
||||
;; }
|
||||
|
||||
(defn parse-frame-guide-row
|
||||
@@ -289,7 +298,7 @@
|
||||
:display (obj/get guide "display")
|
||||
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
|
||||
|
||||
;;export interface PenpotFrameGuideSquareParams {
|
||||
;;export interface GuideSquareParams {
|
||||
;; color: { color: string; opacity: number };
|
||||
;; size?: number;
|
||||
;;}
|
||||
@@ -300,10 +309,10 @@
|
||||
{:color (-> (obj/get params "color") parse-color)
|
||||
:size (obj/get params "size")})))
|
||||
|
||||
;; export interface PenpotFrameGuideSquare {
|
||||
;; export interface GuideSquare {
|
||||
;; type: 'square';
|
||||
;; display: boolean;
|
||||
;; params: PenpotFrameGuideSquareParams;
|
||||
;; params: GuideSquareParams;
|
||||
;; }
|
||||
(defn parse-frame-guide-square
|
||||
[^js guide]
|
||||
@@ -331,7 +340,7 @@
|
||||
(when (some? guides)
|
||||
(into [] (map parse-frame-guide) guides)))
|
||||
|
||||
;;interface PenpotPathCommand {
|
||||
;;interface PathCommand {
|
||||
;; command:
|
||||
;; | 'M' | 'move-to'
|
||||
;; | 'Z' | 'close-path'
|
||||
@@ -401,13 +410,13 @@
|
||||
(when (some? content)
|
||||
(into [] (map parse-command) content)))
|
||||
|
||||
;; export interface PenpotDissolve {
|
||||
;; export interface Dissolve {
|
||||
;; type: 'dissolve';
|
||||
;; duration: number;
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export interface PenpotSlide {
|
||||
;; export interface Slide {
|
||||
;; type: 'slide';
|
||||
;; way: 'in' | 'out';
|
||||
;; direction?:
|
||||
@@ -420,7 +429,7 @@
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export interface PenpotPush {
|
||||
;; export interface Push {
|
||||
;; type: 'push';
|
||||
;; direction?:
|
||||
;; | 'right'
|
||||
@@ -432,7 +441,7 @@
|
||||
;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
;; }
|
||||
;;
|
||||
;; export type PenpotAnimation = PenpotDissolve | PenpotSlide | PenpotPush;
|
||||
;; export type Animation = Dissolve | Slide | Push;
|
||||
|
||||
(defn parse-animation
|
||||
[^js animation]
|
||||
@@ -441,44 +450,44 @@
|
||||
(d/without-nils
|
||||
(case animation-type
|
||||
:dissolve
|
||||
{:type animation-type
|
||||
{:animation-type animation-type
|
||||
:duration (obj/get animation "duration")
|
||||
:easing (-> (obj/get animation "easing") parse-keyword)}
|
||||
|
||||
:slide
|
||||
{:type animation-type
|
||||
{:animation-type animation-type
|
||||
:way (-> (obj/get animation "way") parse-keyword)
|
||||
:direction (-> (obj/get animation "direction") parse-keyword)
|
||||
:duration (obj/get animation "duration")
|
||||
:easing (-> (obj/get animation "easing") parse-keyword)
|
||||
:offset-effect (obj/get animation "offsetEffect")}
|
||||
:offset-effect (boolean (obj/get animation "offsetEffect"))}
|
||||
|
||||
:push
|
||||
{:type animation-type
|
||||
{:animation-type animation-type
|
||||
:direction (-> (obj/get animation "direction") parse-keyword)
|
||||
:duration (obj/get animation "duration")
|
||||
:easing (-> (obj/get animation "easing") parse-keyword)}
|
||||
|
||||
nil)))))
|
||||
|
||||
;;export type PenpotAction =
|
||||
;; | PenpotNavigateTo
|
||||
;; | PenpotOpenOverlay
|
||||
;; | PenpotToggleOverlay
|
||||
;; | PenpotCloseOverlay
|
||||
;; | PenpotPreviousScreen
|
||||
;; | PenpotOpenUrl;
|
||||
;;export type Action =
|
||||
;; | NavigateTo
|
||||
;; | OpenOverlay
|
||||
;; | ToggleOverlay
|
||||
;; | CloseOverlay
|
||||
;; | PreviousScreen
|
||||
;; | OpenUrl;
|
||||
;;
|
||||
;;export interface PenpotNavigateTo {
|
||||
;;export interface NavigateTo {
|
||||
;; type: 'navigate-to';
|
||||
;; destination: PenpotFrame;
|
||||
;; destination: Board;
|
||||
;; preserveScrollPosition?: boolean;
|
||||
;; animation: PenpotAnimation;
|
||||
;; animation: Animation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOverlayAction {
|
||||
;; destination: PenpotFrame;
|
||||
;; relativeTo?: PenpotShape;
|
||||
;;export interface OverlayAction {
|
||||
;; destination: Board;
|
||||
;; relativeTo?: Shape;
|
||||
;; position?:
|
||||
;; | 'manual'
|
||||
;; | 'center'
|
||||
@@ -488,31 +497,31 @@
|
||||
;; | 'bottom-left'
|
||||
;; | 'bottom-right'
|
||||
;; | 'bottom-center';
|
||||
;; manualPositionLocation?: PenpotPoint;
|
||||
;; manualPositionLocation?: Point;
|
||||
;; closeWhenClickOutside?: boolean;
|
||||
;; addBackgroundOverlay?: boolean;
|
||||
;; animation: PenpotAnimation;
|
||||
;; animation: Animation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOpenOverlay extends PenpotOverlayAction {
|
||||
;;export interface OpenOverlay extends OverlayAction {
|
||||
;; type: 'open-overlay';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotToggleOverlay extends PenpotOverlayAction {
|
||||
;;export interface ToggleOverlay extends OverlayAction {
|
||||
;; type: 'toggle-overlay';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotCloseOverlay {
|
||||
;;export interface CloseOverlay {
|
||||
;; type: 'close-overlay';
|
||||
;; destination?: PenpotFrame;
|
||||
;; animation: PenpotAnimation;
|
||||
;; destination?: Board;
|
||||
;; animation: Animation;
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotPreviousScreen {
|
||||
;;export interface PreviousScreen {
|
||||
;; type: 'previous-screen';
|
||||
;;}
|
||||
;;
|
||||
;;export interface PenpotOpenUrl {
|
||||
;;export interface OpenUrl {
|
||||
;; type: 'open-url';
|
||||
;; url: string;
|
||||
;;}
|
||||
@@ -554,10 +563,9 @@
|
||||
nil)))))
|
||||
|
||||
(defn parse-interaction
|
||||
[^js interaction]
|
||||
(when interaction
|
||||
(let [trigger (-> (obj/get interaction "trigger") parse-keyword)
|
||||
delay (obj/get interaction "trigger")
|
||||
action (-> (obj/get interaction "action") parse-action)]
|
||||
[trigger ^js action delay]
|
||||
(when (and (string? trigger) (some? action))
|
||||
(let [trigger (parse-keyword trigger)
|
||||
action (parse-action action)]
|
||||
(d/without-nils
|
||||
(d/patch-object {:event-type trigger :delay delay} action)))))
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.object :as obj]
|
||||
[app.util.storage :refer [storage]]))
|
||||
[beicon.v2.core :as rx]))
|
||||
|
||||
;; Stores the installed plugins information
|
||||
(defonce ^:private registry (atom {}))
|
||||
@@ -58,40 +60,14 @@
|
||||
:icon icon
|
||||
:permissions (into #{} (map str) permissions)}))
|
||||
|
||||
;; FIXME: LEGACY version of the load from store
|
||||
;; can be removed before deploying plugins to production
|
||||
;; Needs to be preserved for the beta users
|
||||
(defn legacy-load-from-store
|
||||
[]
|
||||
(let [parse-plugin-data
|
||||
(fn [^js data]
|
||||
{:plugin-id (obj/get data "plugin-id")
|
||||
:name (obj/get data "name")
|
||||
:description (obj/get data "description")
|
||||
:host (obj/get data "host")
|
||||
:code (obj/get data "code")
|
||||
:icon (obj/get data "icon")
|
||||
:permissions (into #{} (obj/get data "permissions"))})
|
||||
|
||||
ls (.-localStorage js/window)
|
||||
plugins-val (.getItem ls "plugins")]
|
||||
(when plugins-val
|
||||
(let [stored (->> (.parse js/JSON plugins-val)
|
||||
(map parse-plugin-data))]
|
||||
(reset! registry
|
||||
{:ids (->> stored (map :plugin-id))
|
||||
:data (d/index-by :plugin-id stored)})))))
|
||||
|
||||
(defn save-to-store
|
||||
[]
|
||||
(swap! storage assoc :plugins @registry))
|
||||
(->> (rp/cmd! :update-profile-props {:props {:plugins @registry}})
|
||||
(rx/subs! identity)))
|
||||
|
||||
(defn load-from-store
|
||||
[]
|
||||
(if (:plugins @storage)
|
||||
(reset! registry (:plugins @storage))
|
||||
(do (legacy-load-from-store)
|
||||
(save-to-store))))
|
||||
(reset! registry (get-in @st/state [:profile :props :plugins] {})))
|
||||
|
||||
(defn init
|
||||
[]
|
||||
|
||||
@@ -551,10 +551,10 @@
|
||||
|
||||
;; Interactions
|
||||
(addInteraction
|
||||
[self interaction]
|
||||
[self trigger action delay]
|
||||
(let [interaction
|
||||
(-> ctsi/default-interaction
|
||||
(d/patch-object (parser/parse-interaction interaction)))]
|
||||
(d/patch-object (parser/parse-interaction trigger action delay)))]
|
||||
(cond
|
||||
(not (sm/validate ::ctsi/interaction interaction))
|
||||
(u/display-not-valid :addInteraction interaction)
|
||||
@@ -576,12 +576,6 @@
|
||||
(defn shape-proxy? [p]
|
||||
(instance? ShapeProxy p))
|
||||
|
||||
;; Prevent circular dependency
|
||||
(do (set! flex/shape-proxy? shape-proxy?)
|
||||
(set! grid/shape-proxy? shape-proxy?))
|
||||
|
||||
(set! format/shape-proxy shape-proxy)
|
||||
|
||||
(crc/define-properties!
|
||||
ShapeProxy
|
||||
{:name js/Symbol.toStringTag
|
||||
@@ -611,7 +605,7 @@
|
||||
:get #(-> % u/proxy->shape :id str)}
|
||||
|
||||
{:name "type"
|
||||
:get #(-> % u/proxy->shape :type d/name)}
|
||||
:get #(-> % u/proxy->shape :type format/shape-type)}
|
||||
|
||||
{:name "name"
|
||||
:get #(-> % u/proxy->shape :name)
|
||||
@@ -662,6 +656,21 @@
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwsh/update-shapes [id] #(assoc % :hidden value))))))}
|
||||
|
||||
{:name "visible"
|
||||
:get #(-> % u/proxy->shape :hidden boolean not)
|
||||
:set
|
||||
(fn [self value]
|
||||
(cond
|
||||
(not (boolean? value))
|
||||
(u/display-not-valid :visible value)
|
||||
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/display-not-valid :visible "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwsh/update-shapes [id] #(assoc % :hidden (not value)))))))}
|
||||
|
||||
{:name "proportionLock"
|
||||
:get #(-> % u/proxy->shape :proportion-lock boolean)
|
||||
:set
|
||||
@@ -978,7 +987,7 @@
|
||||
parent-y (:y parent)]
|
||||
(st/emit! (dw/update-position id {:y (+ parent-y value)})))))}
|
||||
|
||||
{:name "frameX"
|
||||
{:name "boardX"
|
||||
:get (fn [self]
|
||||
(let [shape (u/proxy->shape self)
|
||||
frame-id (:parent-id shape)
|
||||
@@ -1001,7 +1010,7 @@
|
||||
frame-x (:x frame)]
|
||||
(st/emit! (dw/update-position id {:x (+ frame-x value)})))))}
|
||||
|
||||
{:name "frameY"
|
||||
{:name "boardY"
|
||||
:get (fn [self]
|
||||
(let [shape (u/proxy->shape self)
|
||||
frame-id (:parent-id shape)
|
||||
|
||||
@@ -175,7 +175,12 @@
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [font (fonts/get-font-data (obj/get self "fontId"))
|
||||
variant (fonts/find-variant font {:weight (dm/str value)})]
|
||||
weight (dm/str value)
|
||||
style (obj/get self "fontStyle")
|
||||
variant
|
||||
(or
|
||||
(fonts/find-variant font {:style style :weight weight})
|
||||
(fonts/find-variant font {:weight weight}))]
|
||||
(cond
|
||||
(nil? variant)
|
||||
(u/display-not-valid :fontWeight (dm/str "Font weight '" value "' not supported for the current font"))
|
||||
@@ -193,7 +198,12 @@
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [font (fonts/get-font-data (obj/get self "fontId"))
|
||||
variant (fonts/find-variant font {:weight (dm/str value)})]
|
||||
style (dm/str value)
|
||||
weight (obj/get self "fontWeight")
|
||||
variant
|
||||
(or
|
||||
(fonts/find-variant font {:weight weight :style style})
|
||||
(fonts/find-variant font {:style style}))]
|
||||
(cond
|
||||
(nil? variant)
|
||||
(u/display-not-valid :fontStyle (dm/str "Font style '" value "' not supported for the current font"))
|
||||
@@ -439,7 +449,12 @@
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")
|
||||
font (fonts/get-font-data (obj/get self "fontId"))
|
||||
variant (fonts/find-variant font {:weight (dm/str value)})]
|
||||
weight (dm/str value)
|
||||
style (obj/get self "fontStyle")
|
||||
variant
|
||||
(or
|
||||
(fonts/find-variant font {:style style :weight weight})
|
||||
(fonts/find-variant font {:weight weight}))]
|
||||
(cond
|
||||
(nil? variant)
|
||||
(u/display-not-valid :fontWeight (dm/str "Font weight '" value "' not supported for the current font"))
|
||||
@@ -456,7 +471,12 @@
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")
|
||||
font (fonts/get-font-data (obj/get self "fontId"))
|
||||
variant (fonts/find-variant font {:weight (dm/str value)})]
|
||||
style (dm/str value)
|
||||
weight (obj/get self "fontWeight")
|
||||
variant
|
||||
(or
|
||||
(fonts/find-variant font {:weight weight :style style})
|
||||
(fonts/find-variant font {:style style}))]
|
||||
(cond
|
||||
(nil? variant)
|
||||
(u/display-not-valid :fontStyle (dm/str "Font style '" value "' not supported for the current font"))
|
||||
|
||||
@@ -223,22 +223,22 @@
|
||||
(t/is (= (-> (. shape -strokes) (aget 0) (aget "strokeWidth")) 5))))
|
||||
|
||||
(t/testing "Relative properties"
|
||||
(let [frame (.createFrame context)]
|
||||
(set! (.-x frame) 100)
|
||||
(set! (.-y frame) 200)
|
||||
(t/is (= (.-x frame) 100))
|
||||
(t/is (= (.-y frame) 200))
|
||||
(.appendChild frame shape)
|
||||
(let [board (.createBoard context)]
|
||||
(set! (.-x board) 100)
|
||||
(set! (.-y board) 200)
|
||||
(t/is (= (.-x board) 100))
|
||||
(t/is (= (.-y board) 200))
|
||||
(.appendChild board shape)
|
||||
|
||||
(t/testing " - frameX"
|
||||
(set! (.-frameX shape) 10)
|
||||
(t/is (m/close? (.-frameX shape) 10))
|
||||
(t/testing " - boardX"
|
||||
(set! (.-boardX shape) 10)
|
||||
(t/is (m/close? (.-boardX shape) 10))
|
||||
(t/is (m/close? (.-x shape) 110))
|
||||
(t/is (m/close? (get-in @store (get-shape-path :x)) 110)))
|
||||
|
||||
(t/testing " - frameY"
|
||||
(set! (.-frameY shape) 20)
|
||||
(t/is (m/close? (.-frameY shape) 20))
|
||||
(t/testing " - boardY"
|
||||
(set! (.-boardY shape) 20)
|
||||
(t/is (m/close? (.-boardY shape) 20))
|
||||
(t/is (m/close? (.-y shape) 220))
|
||||
(t/is (m/close? (get-in @store (get-shape-path :y)) 220)))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user