Compare commits

...

35 Commits

Author SHA1 Message Date
David Barragán Merino
fef4d3bdbe wip 2024-09-11 18:08:36 +02:00
Alejandro Alonso
042b3a71d8 Merge remote-tracking branch 'origin/staging' into develop 2024-09-11 12:46:04 +02:00
Alejandro Alonso
eadae5e2cd Merge remote-tracking branch 'origin/staging' into develop 2024-09-11 12:05:45 +02:00
Alejandro Alonso
7f9c4df284 Merge remote-tracking branch 'origin/staging' into develop 2024-09-11 11:34:35 +02:00
Alejandro Alonso
9e3f8e7827 Merge remote-tracking branch 'origin/staging' into develop 2024-09-09 11:09:53 +02:00
Eva Marco
3a4e9ccc5a 👷 Fix CI error 2024-09-09 10:32:50 +02:00
Belén Albeza
eb720b053a Merge pull request #5057 from penpot/eva-fix-css-compilation
🔧 Rearrange css files for compilation
2024-09-06 14:45:52 +02:00
Alejandro Alonso
efc61241a0 Merge remote-tracking branch 'origin/staging' into develop 2024-09-06 13:50:37 +02:00
Andrey Antukh
cfad1d178f Merge pull request #5068 from penpot/alotor-plugins-install-profile
 Change installation data to profile
2024-09-06 12:05:52 +02:00
alonso.torres
c24b2dadec Change installation data to profile 2024-09-06 11:10:32 +02:00
Andrey Antukh
9a3b5337d7 Merge pull request #5062 from penpot/alotor-plugins-fix-interactions
🐛 Fix plugins add interaction
2024-09-05 16:22:46 +02:00
alonso.torres
396cbb27b2 🐛 Fix plugins add interaction 2024-09-05 16:00:04 +02:00
Alejandro
b4e6f8bc73 Merge pull request #5061 from penpot/niwinz-challenge
 Add support for optional human challenge
2024-09-05 15:49:50 +02:00
Andrey Antukh
d88f28f5c2 Add support for optional human challenge 2024-09-05 15:35:39 +02:00
Eva Marco
e36cf1d963 🐛 Fix onboarding slide after rearrange 2024-09-05 14:46:49 +02:00
Eva Marco
a0bb5e5ef3 ♻️ Remove unnecesary code 2024-09-05 09:41:11 +02:00
Eva Marco
34cc211912 🔧 Rearrange css files for compilation 2024-09-05 09:39:43 +02:00
Eva Marco
e95713c1df 🐛 Fix visual integration test 2024-09-05 09:39:43 +02:00
Alejandro Alonso
e189dc965d Merge remote-tracking branch 'origin/staging' into develop 2024-09-05 09:37:16 +02:00
Belén Albeza
53f580ad40 Merge pull request #5017 from penpot/eva-add-select-to-ds
 Add select component to the DS
2024-09-04 15:51:10 +02:00
Andrey Antukh
cf0045681e Merge pull request #5054 from penpot/alotor-plugins-fixes
Update API types
2024-09-04 14:16:01 +02:00
alonso.torres
762a883b39 🐛 Fix problem with font weight and style 2024-09-04 13:52:48 +02:00
alonso.torres
a63ded1ba1 Change type names in plugins 2024-09-04 13:29:56 +02:00
alonso.torres
f812b28892 ⬆️ Update plugin dependencies 2024-09-04 12:38:50 +02:00
Andrey Antukh
873c9b1903 Merge pull request #5050 from penpot/hiru-ordered-maps
🔧 Add serializable ordered collections
2024-09-04 12:26:21 +02:00
Alejandro Alonso
edeb16bc26 Merge remote-tracking branch 'origin/staging' into develop 2024-09-04 12:02:31 +02:00
Alejandro Alonso
90d947391a Merge remote-tracking branch 'origin/staging' into develop 2024-09-04 08:59:05 +02:00
Andrés Moya
47cc80a93f 🔧 Add serializable ordered collections 2024-09-03 23:35:53 +02:00
Andrey Antukh
1f8cfde1cf Merge pull request #5046 from penpot/alotor-plugins-fixes
Plugins small fixes
2024-09-03 14:36:59 +02:00
Alejandro Alonso
5f2ec595cb 📎 Update changelog 2024-09-03 13:15:48 +02:00
alonso.torres
37a6446e32 🐛 Fix problem with font style 2024-09-03 13:10:28 +02:00
alonso.torres
be84b1cb01 🐛 Change place for circular dependency workaround 2024-09-03 13:10:28 +02:00
Eva Marco
298db46722 Add documentation to select on storybook 2024-09-02 16:56:53 +02:00
Eva Marco
0c6b0598fa Add new select ds component to storybook 2024-08-29 14:14:12 +02:00
Eva Marco
f2a2d772b0 Add new select component to the ds 2024-08-29 14:14:08 +02:00
34 changed files with 2898 additions and 2203 deletions

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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/*;

View File

@@ -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; \

View File

@@ -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/*;

View File

@@ -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" }),

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}

View File

@@ -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]

View File

@@ -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]

View File

@@ -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;
}

View File

@@ -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*

View File

@@ -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);

View File

@@ -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])

View File

@@ -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

View File

@@ -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 *;

View File

@@ -11,7 +11,7 @@ const { Input } = Components;
const { icons } = Components.meta;
export default {
title: "Forms/Input",
title: "Controls/Input",
component: Components.Input,
argTypes: {
icon: {

View 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}])]))

View 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.

View 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);
}

View 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",
},
],
},
};

View File

@@ -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}]

View File

@@ -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)

View File

@@ -227,7 +227,7 @@
ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwg/ungroup-shapes ids)))))
(createFrame
(createBoard
[_]
(create-shape $plugin :frame))

View File

@@ -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;
;;}

View File

@@ -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

View File

@@ -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)))))

View File

@@ -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
[]

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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)))