Merge remote-tracking branch 'penpot/develop' into token-studio-develop

This commit is contained in:
Florian Schroedl 2024-05-22 13:46:26 +02:00
commit cbfcc50563
27 changed files with 3021 additions and 2806 deletions

View file

@ -1,5 +1,11 @@
# CHANGELOG # CHANGELOG
## 2.0.3
### :bug: Bugs fixed
- Fix chrome scrollbar styling [Taiga Issue #7852](https://tree.taiga.io/project/penpot/issue/7852)
## 2.0.2 ## 2.0.2
### :sparkles: Enhancements ### :sparkles: Enhancements
@ -10,6 +16,7 @@
### :bug: Bugs fixed ### :bug: Bugs fixed
- Fix color palette sorting [Taiga Issue #7458](https://tree.taiga.io/project/penpot/issue/7458) - Fix color palette sorting [Taiga Issue #7458](https://tree.taiga.io/project/penpot/issue/7458)
- Fix style scoping problem with imported SVG [Taiga #7671](https://tree.taiga.io/project/penpot/issue/7671)
## 2.0.1 ## 2.0.1

View file

@ -274,6 +274,13 @@
(catch #?(:clj Throwable :cljs :default) _cause (catch #?(:clj Throwable :cljs :default) _cause
[0 0 0]))) [0 0 0])))
(defn hex->lum
[color]
(let [[r g b] (hex->rgb color)]
(mth/sqrt (+ (* 0.241 r)
(* 0.691 g)
(* 0.068 b)))))
(defn- int->hex (defn- int->hex
"Convert integer to hex string" "Convert integer to hex string"
[v] [v]
@ -455,3 +462,19 @@
:else :else
[r g (inc b)])) [r g (inc b)]))
(defn reduce-range
[value range]
(/ (mth/floor (* value range)) range))
(defn sort-colors
[a b]
(let [[ah _ av] (hex->hsv (:color a))
[bh _ bv] (hex->hsv (:color b))
ah (reduce-range (/ ah 60) 8)
bh (reduce-range (/ bh 60) 8)
av (/ av 255)
bv (/ bv 255)
a (+ (* ah 100) (* av 10))
b (+ (* bh 100) (* bv 10))]
(compare a b)))

View file

@ -3,8 +3,8 @@ LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV NODE_VERSION=v20.11.1 \ ENV NODE_VERSION=v20.13.1 \
CLOJURE_VERSION=1.11.1.1435 \ CLOJURE_VERSION=1.11.3.1463 \
CLJKONDO_VERSION=2024.03.13 \ CLJKONDO_VERSION=2024.03.13 \
BABASHKA_VERSION=1.3.189 \ BABASHKA_VERSION=1.3.189 \
CLJFMT_VERSION=0.12.0 \ CLJFMT_VERSION=0.12.0 \
@ -105,12 +105,12 @@ RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \ ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \ case "${ARCH}" in \
aarch64|arm64) \ aarch64|arm64) \
ESUM='3ce6a2b357e2ef45fd6b53d6587aa05bfec7771e7fb982f2c964f6b771b7526a'; \ ESUM='7d3ab0e8eba95bd682cfda8041c6cb6fa21e09d0d9131316fd7c96c78969de31'; \
BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.2_13.tar.gz'; \ BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.3%2B9/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.3_9.tar.gz'; \
;; \ ;; \
amd64|x86_64) \ amd64|x86_64) \
ESUM='454bebb2c9fe48d981341461ffb6bf1017c7b7c6e15c6b0c29b959194ba3aaa5'; \ ESUM='fffa52c22d797b715a962e6c8d11ec7d79b90dd819b5bc51d62137ea4b22a340'; \
BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz'; \ BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.3%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.3_9.tar.gz'; \
;; \ ;; \
*) \ *) \
echo "Unsupported arch: ${ARCH}"; \ echo "Unsupported arch: ${ARCH}"; \

View file

@ -17,8 +17,8 @@ export default defineConfig({
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
/* Retry on CI only */ /* Retry on CI only */
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests by default; can be overriden with --workers */
workers: process.env.CI ? 1 : undefined, workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html", reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */

File diff suppressed because it is too large Load diff

View file

@ -28,9 +28,16 @@ body {
* { * {
box-sizing: border-box; box-sizing: border-box;
scrollbar-width: thin;
// transition: all .4s ease; // transition: all .4s ease;
} }
@-moz-document url-prefix() {
* {
scrollbar-width: auto;
}
}
.global-zeroclipboard-container { .global-zeroclipboard-container {
transition: none; transition: none;

View file

@ -6,10 +6,14 @@
// SCROLLBAR // SCROLLBAR
.new-scrollbar { .new-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(170, 181, 186, 0.3) transparent; scrollbar-color: rgba(170, 181, 186, 0.3) transparent;
&:hover { &:hover {
scrollbar-color: rgba(170, 181, 186, 0.7) transparent; scrollbar-color: rgba(170, 181, 186, 0.7) transparent;
} }
// These rules do not apply in chrome - 121 or higher
// We keep them to preserve backward compatibility.
::-webkit-scrollbar { ::-webkit-scrollbar {
background-color: transparent; background-color: transparent;
cursor: pointer; cursor: pointer;

View file

@ -19,19 +19,22 @@ function getCoreCount() {
return os.cpus().length; return os.cpus().length;
} }
// const __filename = url.fileURLToPath(import.meta.url);
export const dirname = url.fileURLToPath(new URL(".", import.meta.url)); export const dirname = url.fileURLToPath(new URL(".", import.meta.url));
export function startWorker() { export function startWorker() {
return wpool.pool(dirname + "/_worker.js", { return wpool.pool(dirname + "/_worker.js", {
maxWorkers: getCoreCount() maxWorkers: getCoreCount(),
}); });
} }
async function findFiles(basePath, predicate, options={}) { async function findFiles(basePath, predicate, options = {}) {
predicate = predicate ?? function() { return true; } predicate =
predicate ??
function () {
return true;
};
let files = await fs.readdir(basePath, {recursive: options.recursive ?? false}) let files = await fs.readdir(basePath, { recursive: options.recursive ?? false });
files = files.map((path) => ph.join(basePath, path)); files = files.map((path) => ph.join(basePath, path));
return files; return files;
@ -42,8 +45,11 @@ function syncDirs(originPath, destPath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
proc.exec(command, (cause, stdout) => { proc.exec(command, (cause, stdout) => {
if (cause) { reject(cause); } if (cause) {
else { resolve(); } reject(cause);
} else {
resolve();
}
}); });
}); });
} }
@ -71,28 +77,30 @@ export async function compileSassAll(worker) {
const limitFn = pLimit(4); const limitFn = pLimit(4);
const sourceDir = "src"; const sourceDir = "src";
let files = await fs.readdir(sourceDir, { recursive: true }) let files = await fs.readdir(sourceDir, { recursive: true });
files = files.filter((path) => path.endsWith(".scss")); files = files.filter((path) => path.endsWith(".scss"));
files = files.map((path) => ph.join(sourceDir, path)); files = files.map((path) => ph.join(sourceDir, path));
// files = files.slice(0, 10);
const procs = [ const procs = [
compileSass(worker, "resources/styles/main-default.scss", {}), compileSass(worker, "resources/styles/main-default.scss", {}),
compileSass(worker, "resources/styles/debug.scss", {}) compileSass(worker, "resources/styles/debug.scss", {}),
]; ];
for (let path of files) { for (let path of files) {
const proc = limitFn(() => compileSass(worker, path, {modules: true})); const proc = limitFn(() => compileSass(worker, path, { modules: true }));
procs.push(proc); procs.push(proc);
} }
const result = await Promise.all(procs); const result = await Promise.all(procs);
return result.reduce((acc, item, index) => { return result.reduce(
acc.index[item.outputPath] = item.css; (acc, item, index) => {
acc.items.push(item.outputPath); acc.index[item.outputPath] = item.css;
return acc; acc.items.push(item.outputPath);
}, {index:{}, items: []}); return acc;
},
{ index: {}, items: [] },
);
} }
function compare(a, b) { function compare(a, b) {
@ -106,7 +114,7 @@ function compare(a, b) {
} }
export function concatSass(data) { export function concatSass(data) {
const output = [] const output = [];
for (let path of data.items) { for (let path of data.items) {
output.push(data.index[path]); output.push(data.index[path]);
@ -118,10 +126,9 @@ export function concatSass(data) {
export async function watch(baseDir, predicate, callback) { export async function watch(baseDir, predicate, callback) {
predicate = predicate ?? (() => true); predicate = predicate ?? (() => true);
const watcher = new Watcher(baseDir, { const watcher = new Watcher(baseDir, {
persistent: true, persistent: true,
recursive: true recursive: true,
}); });
watcher.on("change", (path) => { watcher.on("change", (path) => {
@ -133,7 +140,7 @@ export async function watch(baseDir, predicate, callback) {
async function readShadowManifest() { async function readShadowManifest() {
try { try {
const manifestPath = "resources/public/js/manifest.json" const manifestPath = "resources/public/js/manifest.json";
let content = await fs.readFile(manifestPath, { encoding: "utf8" }); let content = await fs.readFile(manifestPath, { encoding: "utf8" });
content = JSON.parse(content); content = JSON.parse(content);
@ -148,7 +155,6 @@ async function readShadowManifest() {
return index; return index;
} catch (cause) { } catch (cause) {
// log.error("error on reading manifest (using default)", cause);
return { return {
config: "js/config.js", config: "js/config.js",
polyfills: "js/polyfills.js", polyfills: "js/polyfills.js",
@ -160,14 +166,14 @@ async function readShadowManifest() {
} }
} }
async function renderTemplate(path, context={}, partials={}) { async function renderTemplate(path, context = {}, partials = {}) {
const content = await fs.readFile(path, {encoding: "utf-8"}); const content = await fs.readFile(path, { encoding: "utf-8" });
const ts = Math.floor(new Date()); const ts = Math.floor(new Date());
context = Object.assign({}, context, { context = Object.assign({}, context, {
ts: ts, ts: ts,
isDebug: process.env.NODE_ENV !== "production" isDebug: process.env.NODE_ENV !== "production",
}); });
return mustache.render(content, context, partials); return mustache.render(content, context, partials);
@ -214,9 +220,8 @@ async function readTranslations() {
// this happens when file does not matches correct // this happens when file does not matches correct
// iso code for the language. // iso code for the language.
["ja_jp", "jpn_JP"], ["ja_jp", "jpn_JP"],
// ["fi", "fin_FI"],
["uk", "ukr_UA"], ["uk", "ukr_UA"],
"ha" "ha",
]; ];
const result = {}; const result = {};
@ -261,10 +266,6 @@ async function readTranslations() {
} }
}); });
} }
// if (key === "modals.delete-font.title") {
// console.dir(trdata[key], {depth:10});
// console.dir(result[key], {depth:10});
// }
} }
} }
@ -274,13 +275,13 @@ async function readTranslations() {
async function generateSvgSprite(files, prefix) { async function generateSvgSprite(files, prefix) {
const spriter = new SVGSpriter({ const spriter = new SVGSpriter({
mode: { mode: {
symbol: { inline: true } symbol: { inline: true },
} },
}); });
for (let path of files) { for (let path of files) {
const name = `${prefix}${ph.basename(path)}` const name = `${prefix}${ph.basename(path)}`;
const content = await fs.readFile(path, {encoding: "utf-8"}); const content = await fs.readFile(path, { encoding: "utf-8" });
spriter.add(name, name, content); spriter.add(name, name, content);
} }
@ -302,6 +303,7 @@ async function generateSvgSprites() {
} }
async function generateTemplates() { async function generateTemplates() {
const isDebug = process.env.NODE_ENV !== "production";
await fs.mkdir("./resources/public/", { recursive: true }); await fs.mkdir("./resources/public/", { recursive: true });
const translations = await readTranslations(); const translations = await readTranslations();
@ -315,13 +317,19 @@ async function generateTemplates() {
"../public/images/sprites/symbol/cursors.svg": cursorsSprite, "../public/images/sprites/symbol/cursors.svg": cursorsSprite,
}; };
const pluginRuntimeUri = (process.env.PENPOT_PLUGIN_DEV === "true") ? "http://localhost:4200" : "./plugins-runtime"; const pluginRuntimeUri =
process.env.PENPOT_PLUGIN_DEV === "true" ? "http://localhost:4200" : "./plugins-runtime";
content = await renderTemplate("resources/templates/index.mustache", { content = await renderTemplate(
manifest: manifest, "resources/templates/index.mustache",
translations: JSON.stringify(translations), {
pluginRuntimeUri, manifest: manifest,
}, partials); translations: JSON.stringify(translations),
pluginRuntimeUri,
isDebug,
},
partials,
);
await fs.writeFile("./resources/public/index.html", content); await fs.writeFile("./resources/public/index.html", content);
@ -351,7 +359,7 @@ export async function compileStyles() {
const worker = startWorker(); const worker = startWorker();
const start = process.hrtime(); const start = process.hrtime();
log.info("init: compile styles") log.info("init: compile styles");
let result = await compileSassAll(worker); let result = await compileSassAll(worker);
result = concatSass(result); result = concatSass(result);
@ -365,7 +373,7 @@ export async function compileStyles() {
export async function compileSvgSprites() { export async function compileSvgSprites() {
const start = process.hrtime(); const start = process.hrtime();
log.info("init: compile svgsprite") log.info("init: compile svgsprite");
await generateSvgSprites(); await generateSvgSprites();
const end = process.hrtime(start); const end = process.hrtime(start);
log.info("done: compile svgsprite", `(${ppt(end)})`); log.info("done: compile svgsprite", `(${ppt(end)})`);
@ -373,7 +381,7 @@ export async function compileSvgSprites() {
export async function compileTemplates() { export async function compileTemplates() {
const start = process.hrtime(); const start = process.hrtime();
log.info("init: compile templates") log.info("init: compile templates");
await generateTemplates(); await generateTemplates();
const end = process.hrtime(start); const end = process.hrtime(start);
log.info("done: compile templates", `(${ppt(end)})`); log.info("done: compile templates", `(${ppt(end)})`);
@ -381,13 +389,12 @@ export async function compileTemplates() {
export async function compilePolyfills() { export async function compilePolyfills() {
const start = process.hrtime(); const start = process.hrtime();
log.info("init: compile polyfills") log.info("init: compile polyfills");
const files = await findFiles("resources/polyfills/", isJsFile); const files = await findFiles("resources/polyfills/", isJsFile);
let result = []; let result = [];
for (let path of files) { for (let path of files) {
const content = await fs.readFile(path, {encoding:"utf-8"}); const content = await fs.readFile(path, { encoding: "utf-8" });
result.push(content); result.push(content);
} }
@ -400,7 +407,7 @@ export async function compilePolyfills() {
export async function copyAssets() { export async function copyAssets() {
const start = process.hrtime(); const start = process.hrtime();
log.info("init: copy assets") log.info("init: copy assets");
await syncDirs("resources/images/", "resources/public/images/"); await syncDirs("resources/images/", "resources/public/images/");
await syncDirs("resources/fonts/", "resources/public/fonts/"); await syncDirs("resources/fonts/", "resources/public/fonts/");
@ -409,4 +416,3 @@ export async function copyAssets() {
const end = process.hrtime(start); const end = process.hrtime(start);
log.info("done: copy assets", `(${ppt(end)})`); log.info("done: copy assets", `(${ppt(end)})`);
} }

View file

@ -58,7 +58,6 @@
width: 100%; width: 100%;
} }
.demo-account,
.go-back { .go-back {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -67,7 +66,6 @@
border-block-start: none; border-block-start: none;
} }
.demo-account-link,
.go-back-link { .go-back-link {
@extend .button-secondary; @extend .button-secondary;
@include uppercaseTitleTipography; @include uppercaseTitleTipography;
@ -81,7 +79,8 @@
.register, .register,
.account, .account,
.recovery-request { .recovery-request,
.demo-account {
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: $s-8; gap: $s-8;
@ -90,7 +89,8 @@
.register-text, .register-text,
.account-text, .account-text,
.recovery-text { .recovery-text,
.demo-account-text {
@include smallTitleTipography; @include smallTitleTipography;
text-align: right; text-align: right;
color: var(--title-foreground-color); color: var(--title-foreground-color);
@ -99,7 +99,8 @@
.register-link, .register-link,
.account-link, .account-link,
.recovery-link, .recovery-link,
.forgot-pass-link { .forgot-pass-link,
.demo-account-link {
@include smallTitleTipography; @include smallTitleTipography;
text-align: left; text-align: left;
background-color: transparent; background-color: transparent;

View file

@ -158,7 +158,7 @@
[:* [:*
(when-let [message @error] (when-let [message @error]
[:& context-notification [:& context-notification
{:type :warning {:type :error
:content message :content message
:data-test "login-banner" :data-test "login-banner"
:role "alert"}]) :role "alert"}])
@ -300,11 +300,13 @@
[:& lk/link {:action go-register [:& lk/link {:action go-register
:class (stl/css :register-link) :class (stl/css :register-link)
:data-test "register-submit"} :data-test "register-submit"}
(tr "auth.register-submit")]])] (tr "auth.register-submit")]])
(when (contains? cf/flags :demo-users) (when (contains? cf/flags :demo-users)
[:div {:class (stl/css :link-entry :demo-account)} [:div {:class (stl/css :demo-account)}
[:span (tr "auth.create-demo-profile") " "] [:span {:class (stl/css :demo-account-text)}
[:& lk/link {:action create-demo-profile (tr "auth.create-demo-profile") " "]
:data-test "demo-account-link"} [:& lk/link {:action create-demo-profile
(tr "auth.create-demo-account")]])])) :class (stl/css :demo-account-link)
:data-test "demo-account-link"}
(tr "auth.create-demo-account")]])]]))

View file

@ -12,5 +12,6 @@
margin-top: $s-8; margin-top: $s-8;
padding: $s-12; padding: $s-12;
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
color: var(--input-foreground-color-active);
overflow: auto; overflow: auto;
} }

View file

@ -17,6 +17,7 @@
(def current-page-id (mf/create-context nil)) (def current-page-id (mf/create-context nil))
(def current-file-id (mf/create-context nil)) (def current-file-id (mf/create-context nil))
(def current-vbox (mf/create-context nil)) (def current-vbox (mf/create-context nil))
(def current-svg-root-id (mf/create-context nil))
(def active-frames (mf/create-context nil)) (def active-frames (mf/create-context nil))
(def render-thumbnails (mf/create-context nil)) (def render-thumbnails (mf/create-context nil))

View file

@ -85,8 +85,6 @@
color: var(--title-foreground-color-hover); color: var(--title-foreground-color-hover);
cursor: pointer; cursor: pointer;
height: $s-16; height: $s-16;
display: inline-flex;
align-items: center;
} }
.info-wrapper { .info-wrapper {

View file

@ -17,6 +17,7 @@
(mf/fnc group-shape (mf/fnc group-shape
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs") childs (unchecked-get props "childs")
render-id (mf/use-ctx muc/render-id) render-id (mf/use-ctx muc/render-id)
@ -36,21 +37,31 @@
mask-props (if ^boolean masked-group? mask-props (if ^boolean masked-group?
#js {:mask (mask-url render-id mask)} #js {:mask (mask-url render-id mask)}
#js {})] #js {})
current-svg-root-id (mf/use-ctx muc/current-svg-root-id)
;; We need to create a "scope" for svg classes. The root of the imported SVG (first group) will
;; be stored in the context. When rendering the styles we add its id as prefix.
[svg-wrapper svg-wrapper-props]
(if (and (contains? shape :svg-attrs) (not current-svg-root-id))
[(mf/provider muc/current-svg-root-id) #js {:value (:id shape)}]
[mf/Fragment #js {}])]
;; We need to separate mask and clip into two because a bug in ;; We need to separate mask and clip into two because a bug in
;; Firefox breaks when the group has clip+mask+foreignObject ;; Firefox breaks when the group has clip+mask+foreignObject
;; Clip and mask separated will work in every platform Firefox ;; Clip and mask separated will work in every platform Firefox
;; bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1734805 ;; bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1734805
[:> wrapper clip-props [:> svg-wrapper svg-wrapper-props
[:> wrapper mask-props [:> wrapper clip-props
(when ^boolean masked-group? [:> wrapper mask-props
[:& render-mask {:mask mask}]) (when ^boolean masked-group?
[:& render-mask {:mask mask}])
(for [item childs] (for [item childs]
[:& shape-wrapper [:& shape-wrapper
{:shape item {:shape item
:key (dm/str (dm/get-prop item :id))}])]])))) :key (dm/str (dm/get-prop item :id))}])]]]))))

View file

@ -104,9 +104,20 @@
svg-root? (and (map? content) (= tag :svg)) svg-root? (and (map? content) (= tag :svg))
svg-tag? (map? content) svg-tag? (map? content)
svg-leaf? (string? content) svg-leaf? (string? content)
valid-tag? (contains? csvg/svg-tags tag)] valid-tag? (contains? csvg/svg-tags tag)
current-svg-root-id (mf/use-ctx muc/current-svg-root-id)
;; We need to create a "scope" for svg classes. The root of the imported SVG (first group) will
;; be stored in the context and with this we scoped the styles:
style-content
(when (= tag :style)
(dm/str "#shape-" current-svg-root-id "{ " (->> shape :content :content (str/join "\n")) " }"))]
(cond (cond
(= tag :style)
[:style style-content]
^boolean svg-root? ^boolean svg-root?
[:& svg-root {:shape shape} [:& svg-root {:shape shape}
(for [item childs] (for [item childs]

View file

@ -54,13 +54,7 @@
get-sorted-colors get-sorted-colors
(mf/use-fn (mf/use-fn
(fn [colors] (fn [colors]
(sort (fn [a b] (sort c/sort-colors (into [] (filter check-valid-color?) colors))))
(let [[ah _ al] (c/hex->hsl (:color a))
[bh _ bl] (c/hex->hsl (:color b))
a (+ (* ah 100) (* al 99))
b (+ (* bh 100) (* bl 99))]
(compare a b)))
(into [] (filter check-valid-color?) colors))))
toggle-palette toggle-palette
(mf/use-fn (mf/use-fn

View file

@ -101,6 +101,7 @@
(fn [] (fn []
(->> (http/send! {:method :get (->> (http/send! {:method :get
:uri plugin-url :uri plugin-url
:omit-default-headers true
:response-type :json}) :response-type :json})
(rx/map :body) (rx/map :body)
(rx/subs! (rx/subs!

View file

@ -32,12 +32,6 @@
;; ;;
;; PLUGINS PUBLIC API - The plugins will able to access this functions ;; PLUGINS PUBLIC API - The plugins will able to access this functions
;; ;;
(def ^:private
xf-map-shape-proxy
(comp
(map val)
(map shape/data->shape-proxy)))
(defn create-shape (defn create-shape
[type] [type]
(let [page-id (:current-page-id @st/state) (let [page-id (:current-page-id @st/state)
@ -50,7 +44,7 @@
(cb/with-objects (:objects page)) (cb/with-objects (:objects page))
(cb/add-object shape))] (cb/add-object shape))]
(st/emit! (ch/commit-changes changes)) (st/emit! (ch/commit-changes changes))
(shape/data->shape-proxy shape))) (shape/shape-proxy (:id shape))))
(deftype PenpotContext [] (deftype PenpotContext []
Object Object
@ -64,13 +58,13 @@
(getFile (getFile
[_] [_]
(file/data->file-proxy (:workspace-file @st/state) (:workspace-data @st/state))) (file/file-proxy (:current-file-id @st/state)))
(getPage (getPage
[_] [_]
(let [page-id (:current-page-id @st/state) (let [file-id (:current-file-id @st/state)
page (dm/get-in @st/state [:workspace-data :pages-index page-id])] page-id (:current-page-id @st/state)]
(page/data->page-proxy page))) (page/page-proxy file-id page-id)))
(getSelected (getSelected
[_] [_]
@ -79,17 +73,12 @@
(getSelectedShapes (getSelectedShapes
[_] [_]
(let [page-id (:current-page-id @st/state) (let [selection (get-in @st/state [:workspace-local :selected])]
selection (get-in @st/state [:workspace-local :selected]) (apply array (sequence (map shape/shape-proxy) selection))))
objects (dm/get-in @st/state [:workspace-data :pages-index page-id :objects])
shapes (select-keys objects selection)]
(apply array (sequence xf-map-shape-proxy shapes))))
(getRoot (getRoot
[_] [_]
(let [page-id (:current-page-id @st/state) (shape/shape-proxy uuid/zero))
root (dm/get-in @st/state [:workspace-data :pages-index page-id :objects uuid/zero])]
(shape/data->shape-proxy root)))
(getTheme (getTheme
[_] [_]
@ -100,7 +89,7 @@
(uploadMediaUrl (uploadMediaUrl
[_ name url] [_ name url]
(let [file-id (get-in @st/state [:workspace-file :id])] (let [file-id (:current-file-id @st/state)]
(p/create (p/create
(fn [resolve reject] (fn [resolve reject]
(->> (dwm/upload-media-url name file-id url) (->> (dwm/upload-media-url name file-id url)
@ -110,17 +99,17 @@
(group (group
[_ shapes] [_ shapes]
(let [page-id (:current-page-id @st/state) (let [file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)
id (uuid/next) id (uuid/next)
ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)] ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwg/group-shapes id ids)) (st/emit! (dwg/group-shapes id ids))
(shape/data->shape-proxy (shape/shape-proxy file-id page-id id)))
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id]))))
(ungroup (ungroup
[_ group & rest] [_ group & rest]
(let [shapes (concat [group] rest) (let [shapes (concat [group] rest)
ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)] ids (into #{} (map #(obj/get % "$id")) shapes)]
(st/emit! (dwg/ungroup-shapes ids)))) (st/emit! (dwg/ungroup-shapes ids))))
(createFrame (createFrame
@ -133,7 +122,8 @@
(createText (createText
[_ text] [_ text]
(let [page-id (:current-page-id @st/state) (let [file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)
page (dm/get-in @st/state [:workspace-data :pages-index page-id]) page (dm/get-in @st/state [:workspace-data :pages-index page-id])
shape (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width}) shape (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
(txt/change-text text) (txt/change-text text)
@ -144,23 +134,24 @@
(cb/with-objects (:objects page)) (cb/with-objects (:objects page))
(cb/add-object shape))] (cb/add-object shape))]
(st/emit! (ch/commit-changes changes)) (st/emit! (ch/commit-changes changes))
(shape/data->shape-proxy shape))) (shape/shape-proxy file-id page-id (:id shape))))
(createShapeFromSvg (createShapeFromSvg
[_ svg-string] [_ svg-string]
(when (some? svg-string) (when (some? svg-string)
(let [id (uuid/next) (let [id (uuid/next)
file-id (:current-file-id @st/state)
page-id (:current-page-id @st/state)] page-id (:current-page-id @st/state)]
(st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0))) (st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0)))
(shape/data->shape-proxy (shape/shape-proxy file-id page-id id)))))
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id]))))))
(defn create-context (defn create-context
[] []
(cr/add-properties! (cr/add-properties!
(PenpotContext.) (PenpotContext.)
{:name "root" :get #(.getRoot ^js %)} {:name "root" :get #(.getRoot ^js %)}
{:name "currentFile" :get #(.getFile ^js %)}
{:name "currentPage" :get #(.getPage ^js %)} {:name "currentPage" :get #(.getPage ^js %)}
{:name "selection" :get #(.getSelectedShapes ^js %)} {:name "selection" :get #(.getSelectedShapes ^js %)}
{:name "viewport" :get #(.getViewport ^js %)} {:name "viewport" :get #(.getViewport ^js %)}
{:name "library" :get (fn [_] (library/create-library-subcontext))})) {:name "library" :get (fn [_] (library/library-subcontext))}))

View file

@ -23,17 +23,18 @@
(if (and (identical? old-file new-file) (if (and (identical? old-file new-file)
(identical? old-data new-data)) (identical? old-data new-data))
::not-changed ::not-changed
(file/data->file-proxy new-file new-data)))) (file/file-proxy (:id new-file)))))
(defmethod handle-state-change "pagechange" (defmethod handle-state-change "pagechange"
[_ old-val new-val] [_ old-val new-val]
(let [old-page-id (:current-page-id old-val) (let [file-id (:current-file-id new-val)
old-page-id (:current-page-id old-val)
new-page-id (:current-page-id new-val) new-page-id (:current-page-id new-val)
old-page (dm/get-in old-val [:workspace-data :pages-index old-page-id]) old-page (dm/get-in old-val [:workspace-data :pages-index old-page-id])
new-page (dm/get-in new-val [:workspace-data :pages-index new-page-id])] new-page (dm/get-in new-val [:workspace-data :pages-index new-page-id])]
(if (identical? old-page new-page) (if (identical? old-page new-page)
::not-changed ::not-changed
(page/data->page-proxy new-page)))) (page/page-proxy file-id new-page-id))))
(defmethod handle-state-change "selectionchange" (defmethod handle-state-change "selectionchange"
[_ old-val new-val] [_ old-val new-val]

View file

@ -7,38 +7,34 @@
(ns app.plugins.file (ns app.plugins.file
"RPC for plugins runtime." "RPC for plugins runtime."
(:require (:require
[app.common.data.macros :as dm]
[app.common.record :as crc] [app.common.record :as crc]
[app.plugins.page :as page] [app.plugins.page :as page]
[app.plugins.utils :refer [get-data-fn]])) [app.plugins.utils :refer [locate-file proxy->file]]
[app.util.object :as obj]))
(def ^:private (deftype FileProxy [$id]
xf-map-page-proxy
(comp
(map val)
(map page/data->page-proxy)))
(deftype FileProxy [#_:clj-kondo/ignore _data]
Object Object
(getPages [_] (getPages [_]
;; Returns a lazy (iterable) of all available pages (let [file (locate-file $id)]
(apply array (sequence xf-map-page-proxy (:pages-index _data))))) (apply array (sequence (map #(page/page-proxy $id %)) (dm/get-in file [:data :pages]))))))
(crc/define-properties! (crc/define-properties!
FileProxy FileProxy
{:name js/Symbol.toStringTag {:name js/Symbol.toStringTag
:get (fn [] (str "FileProxy"))}) :get (fn [] (str "FileProxy"))})
(defn data->file-proxy (defn file-proxy
[file data] [id]
(crc/add-properties! (crc/add-properties!
(FileProxy. (merge file data)) (FileProxy. id)
{:name "_data" :enumerable false} {:name "$id" :enumerable false :get (constantly id)}
{:name "id" {:name "id"
:get (get-data-fn :id str)} :get #(dm/str (obj/get % "$id"))}
{:name "name" {:name "name"
:get (get-data-fn :name)} :get #(-> % proxy->file :name)}
{:name "pages" {:name "pages"
:get #(.getPages ^js %)})) :get #(.getPages ^js %)}))

View file

@ -0,0 +1,282 @@
;; 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.plugins.flex
(:require
[app.common.data :as d]
[app.common.record :as crc]
[app.common.spec :as us]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.plugins.utils :as utils :refer [proxy->shape]]
[app.util.object :as obj]
[potok.v2.core :as ptk]))
(deftype FlexLayout [$file $page $id]
Object
(remove
[_]
(st/emit! (dwsl/remove-layout #{$id})))
(appendChild
[_ child]
(let [child-id (obj/get child "$id")]
(st/emit! (dwt/move-shapes-to-frame #{child-id} $id nil nil)
(ptk/data-event :layout/update {:ids [$id]})))))
(defn flex-layout-proxy
[file-id page-id id]
(-> (FlexLayout. file-id page-id id)
(crc/add-properties!
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "dir"
:get #(-> % proxy->shape :layout-flex-dir d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? ctl/flex-direction-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-flex-dir value})))))}
{:name "alignItems"
:get #(-> % proxy->shape :layout-align-items d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? ctl/align-items-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))}
{:name "alignContent"
:get #(-> % proxy->shape :layout-align-content d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? ctl/align-content-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))}
{:name "justifyItems"
:get #(-> % proxy->shape :layout-justify-items d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? ctl/justify-items-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))}
{:name "justifyContent"
:get #(-> % proxy->shape :layout-justify-content d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? ctl/justify-content-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))}
{:name "rowGap"
:get #(-> % proxy->shape :layout-gap :row-gap)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))}
{:name "columnGap"
:get #(-> % proxy->shape :layout-gap :column-gap)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))}
{:name "verticalPadding"
:get #(-> % proxy->shape :layout-padding :p1)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))}
{:name "horizontalPadding"
:get #(-> % proxy->shape :layout-padding :p2)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))}
{:name "topPadding"
:get #(-> % proxy->shape :layout-padding :p1)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}})))))}
{:name "rightPadding"
:get #(-> % proxy->shape :layout-padding :p2)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}})))))}
{:name "bottomPadding"
:get #(-> % proxy->shape :layout-padding :p3)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}})))))}
{:name "leftPadding"
:get #(-> % proxy->shape :layout-padding :p4)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}})))))})))
(deftype LayoutChildProxy [$file $page $id])
(defn layout-child-proxy
[file-id page-id id]
(-> (LayoutChildProxy. file-id page-id id)
(crc/add-properties!
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "absolute"
:get #(-> % proxy->shape :layout-item-absolute boolean)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (boolean? value)
(st/emit! (dwsl/update-layout #{id} {:layout-item-absolute value})))))}
{:name "zIndex"
:get #(-> % proxy->shape :layout-item-z-index (d/nilv 0))
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-int? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-z-index value})))))}
{:name "horizontalSizing"
:get #(-> % proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? ctl/item-h-sizing-types value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-h-sizing value})))))}
{:name "verticalSizing"
:get #(-> % proxy->shape :layout-item-v-sizing (d/nilv :fix) d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? ctl/item-v-sizing-types value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-v-sizing value})))))}
{:name "alignSelf"
:get #(-> % proxy->shape :layout-item-align-self (d/nilv :auto) d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? ctl/item-align-self-types value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-align-self value})))))}
{:name "verticalMargin"
:get #(-> % proxy->shape :layout-item-margin :m1 (d/nilv 0))
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value :m3 value}})))))}
{:name "horizontalMargin"
:get #(-> % proxy->shape :layout-item-margin :m2 (d/nilv 0))
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value :m4 value}})))))}
{:name "topMargin"
:get #(-> % proxy->shape :layout-item-margin :m1 (d/nilv 0))
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value}})))))}
{:name "rightMargin"
:get #(-> % proxy->shape :layout-item-margin :m2 (d/nilv 0))
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value}})))))}
{:name "bottomMargin"
:get #(-> % proxy->shape :layout-item-margin :m3 (d/nilv 0))
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m3 value}})))))}
{:name "leftMargin"
:get #(-> % proxy->shape :layout-item-margin :m4 (d/nilv 0))
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m4 value}})))))}
{:name "maxWidth"
:get #(-> % proxy->shape :layout-item-max-w)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-w value})))))}
{:name "minWidth"
:get #(-> % proxy->shape :layout-item-min-w)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-w value})))))}
{:name "maxHeight"
:get #(-> % proxy->shape :layout-item-max-h)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-h value})))))}
{:name "minHeight"
:get #(-> % proxy->shape :layout-item-min-h)
:set
(fn [self value]
(let [id (obj/get self "$id")]
(when (us/safe-number? value)
(st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-h value})))))})))

View file

@ -10,11 +10,10 @@
[app.common.record :as crc] [app.common.record :as crc]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]
[app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.transforms :as dwt]
[app.main.store :as st] [app.main.store :as st]
[app.plugins.utils :as utils :refer [get-data get-state]] [app.plugins.utils :as utils :refer [proxy->shape locate-shape]]
[app.util.object :as obj] [app.util.object :as obj]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
@ -24,183 +23,266 @@
js/Object js/Object
(apply array (->> tracks (map utils/to-js))))) (apply array (->> tracks (map utils/to-js)))))
(deftype GridLayout [_data] (deftype GridLayout [$file $page $id]
Object Object
(addRow (addRow
[self type value] [_ type value]
(let [id (get-data self :id) (let [type (keyword type)]
type (keyword type)] (st/emit! (dwsl/add-layout-track #{$id} :row {:type type :value value}))))
(st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value}))))
(addRowAtIndex (addRowAtIndex
[self index type value] [_ index type value]
(let [id (get-data self :id) (let [type (keyword type)]
type (keyword type)] (st/emit! (dwsl/add-layout-track #{$id} :row {:type type :value value} index))))
(st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value} index))))
(addColumn (addColumn
[self type value] [_ type value]
(let [id (get-data self :id) (let [type (keyword type)]
type (keyword type)] (st/emit! (dwsl/add-layout-track #{$id} :column {:type type :value value}))))
(st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value}))))
(addColumnAtIndex (addColumnAtIndex
[self index type value] [_ index type value]
(let [id (get-data self :id) (let [type (keyword type)]
type (keyword type)] (st/emit! (dwsl/add-layout-track #{$id} :column {:type type :value value} index))))
(st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value} index))))
(removeRow (removeRow
[self index] [_ index]
(let [id (get-data self :id)] (st/emit! (dwsl/remove-layout-track #{$id} :row index)))
(st/emit! (dwsl/remove-layout-track #{id} :row index))))
(removeColumn (removeColumn
[self index] [_ index]
(let [id (get-data self :id)] (st/emit! (dwsl/remove-layout-track #{$id} :column index)))
(st/emit! (dwsl/remove-layout-track #{id} :column index))))
(setColumn (setColumn
[self index type value] [_ index type value]
(let [id (get-data self :id) (let [type (keyword type)]
type (keyword type)] (st/emit! (dwsl/change-layout-track #{$id} :column index (d/without-nils {:type type :value value})))))
(st/emit! (dwsl/change-layout-track #{id} :column index (d/without-nils {:type type :value value})))))
(setRow (setRow
[self index type value] [_ index type value]
(let [id (get-data self :id) (let [type (keyword type)]
type (keyword type)] (st/emit! (dwsl/change-layout-track #{$id} :row index (d/without-nils {:type type :value value})))))
(st/emit! (dwsl/change-layout-track #{id} :row index (d/without-nils {:type type :value value})))))
(remove (remove
[self] [_]
(let [id (get-data self :id)] (st/emit! (dwsl/remove-layout #{$id})))
(st/emit! (dwsl/remove-layout #{id}))))
(appendChild (appendChild
[self child row column] [_ child row column]
(let [parent-id (get-data self :id) (let [child-id (obj/get child "$id")]
child-id (uuid/uuid (obj/get child "id"))] (st/emit! (dwt/move-shapes-to-frame #{child-id} $id nil [row column])
(st/emit! (dwt/move-shapes-to-frame #{child-id} parent-id nil [row column]) (ptk/data-event :layout/update {:ids [$id]})))))
(ptk/data-event :layout/update {:ids [parent-id]})))))
(defn grid-layout-proxy (defn grid-layout-proxy
[data] [file-id page-id id]
(-> (GridLayout. data) (-> (GridLayout. file-id page-id id)
(crc/add-properties! (crc/add-properties!
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "dir" {:name "dir"
:get #(get-state % :layout-grid-dir d/name) :get #(-> % proxy->shape :layout-grid-dir d/name)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")
value (keyword value)] value (keyword value)]
(when (contains? ctl/grid-direction-types value) (when (contains? ctl/grid-direction-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))} (st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))}
{:name "rows" {:name "rows"
:get #(get-state % :layout-grid-rows make-tracks)} :get #(-> % proxy->shape :layout-grid-rows make-tracks)}
{:name "columns" {:name "columns"
:get #(get-state % :layout-grid-columns make-tracks)} :get #(-> % proxy->shape :layout-grid-columns make-tracks)}
{:name "alignItems" {:name "alignItems"
:get #(get-state % :layout-align-items d/name) :get #(-> % proxy->shape :layout-align-items d/name)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")
value (keyword value)] value (keyword value)]
(when (contains? ctl/align-items-types value) (when (contains? ctl/align-items-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))} (st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))}
{:name "alignContent" {:name "alignContent"
:get #(get-state % :layout-align-content d/name) :get #(-> % proxy->shape :layout-align-content d/name)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")
value (keyword value)] value (keyword value)]
(when (contains? ctl/align-content-types value) (when (contains? ctl/align-content-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))} (st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))}
{:name "justifyItems" {:name "justifyItems"
:get #(get-state % :layout-justify-items d/name) :get #(-> % proxy->shape :layout-justify-items d/name)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")
value (keyword value)] value (keyword value)]
(when (contains? ctl/justify-items-types value) (when (contains? ctl/justify-items-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))} (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))}
{:name "justifyContent" {:name "justifyContent"
:get #(get-state % :layout-justify-content d/name) :get #(-> % proxy->shape :layout-justify-content d/name)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")
value (keyword value)] value (keyword value)]
(when (contains? ctl/justify-content-types value) (when (contains? ctl/justify-content-types value)
(st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))} (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))}
{:name "rowGap" {:name "rowGap"
:get #(:row-gap (get-state % :layout-gap)) :get #(-> % proxy->shape :layout-gap :row-gap)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))} (st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))}
{:name "columnGap" {:name "columnGap"
:get #(:column-gap (get-state % :layout-gap)) :get #(-> % proxy->shape :layout-gap :column-gap)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))} (st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))}
{:name "verticalPadding" {:name "verticalPadding"
:get #(:p1 (get-state % :layout-padding)) :get #(-> % proxy->shape :layout-padding :p1)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))} (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))}
{:name "horizontalPadding" {:name "horizontalPadding"
:get #(:p2 (get-state % :layout-padding)) :get #(-> % proxy->shape :layout-padding :p2)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))} (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))}
{:name "topPadding" {:name "topPadding"
:get #(:p1 (get-state % :layout-padding)) :get #(-> % proxy->shape :layout-padding :p1)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}})))))} (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}})))))}
{:name "rightPadding" {:name "rightPadding"
:get #(:p2 (get-state % :layout-padding)) :get #(-> % proxy->shape :layout-padding :p2)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}})))))} (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}})))))}
{:name "bottomPadding" {:name "bottomPadding"
:get #(:p3 (get-state % :layout-padding)) :get #(-> % proxy->shape :layout-padding :p3)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}})))))} (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}})))))}
{:name "leftPadding" {:name "leftPadding"
:get #(:p4 (get-state % :layout-padding)) :get #(-> % proxy->shape :layout-padding :p4)
:set :set
(fn [self value] (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}})))))}))) (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}})))))})))
(deftype GridCellProxy [$file $page $id])
(defn layout-cell-proxy
[file-id page-id id]
(letfn [(locate-cell [_]
(let [shape (locate-shape file-id page-id id)
parent (locate-shape file-id page-id (:parent-id shape))]
(ctl/get-cell-by-shape-id parent id)))]
(-> (GridCellProxy. file-id page-id id)
(crc/add-properties!
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "row"
:get #(-> % locate-cell :row)
:set
(fn [self value]
(let [shape (proxy->shape self)
cell (locate-cell self)]
(when (us/safe-int? value)
(st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:row value})))))}
{:name "rowSpan"
:get #(-> % locate-cell :row-span)
:set
(fn [self value]
(let [shape (proxy->shape self)
cell (locate-cell self)]
(when (us/safe-int? value)
(st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:row-span value})))))}
{:name "column"
:get #(-> % locate-cell :column)
:set
(fn [self value]
(let [shape (proxy->shape self)
cell (locate-cell self)]
(when (us/safe-int? value)
(st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:column value})))))}
{:name "columnSpan"
:get #(-> % locate-cell :column-span)
:set
(fn [self value]
(let [shape (proxy->shape self)
cell (locate-cell self)]
(when (us/safe-int? value)
(st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:column-span value})))))}
{:name "areaName"
:get #(-> % locate-cell :area-name)
:set
(fn [self value]
(let [shape (proxy->shape self)
cell (locate-cell self)]
(when (string? value)
(st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:area-name value})))))}
{:name "position"
:get #(-> % locate-cell :position d/name)
:set
(fn [self value]
(let [shape (proxy->shape self)
cell (locate-cell self)
value (keyword value)]
(when (contains? ctl/grid-position-types value)
(st/emit! (dwsl/change-cells-mode (:parent-id shape) #{(:id cell)} value)))))}
{:name "alignSelf"
:get #(-> % locate-cell :align-self d/name)
:set
(fn [self value]
(let [shape (proxy->shape self)
value (keyword value)
cell (locate-cell self)]
(when (contains? ctl/grid-cell-align-self-types value)
(st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:align-self value})))))}
{:name "justifySelf"
:get #(-> % locate-cell :justify-self d/name)
:set
(fn [self value]
(let [shape (proxy->shape self)
value (keyword value)
cell (locate-cell self)]
(when (contains? ctl/grid-cell-justify-self-types value)
(st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:justify-self value})))))}))))

View file

@ -7,71 +7,135 @@
(ns app.plugins.library (ns app.plugins.library
"RPC for plugins runtime." "RPC for plugins runtime."
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.record :as cr] [app.common.record :as cr]
[app.main.store :as st] [app.main.store :as st]
[app.plugins.utils :as utils :refer [get-data]] [app.plugins.utils :as u]))
[app.util.object :as obj]))
(defn get-library-info (deftype LibraryColorProxy [$file $id]
([self attr] Object
(let [lib-id (get-data self :id)
current-file-id (:current-file-id @st/state)]
(if (= lib-id current-file-id)
(dm/get-in @st/state [:workspace-file attr])
(dm/get-in @st/state [:workspace-libraries lib-id attr]))))
([self attr mapfn] (asFill [_]
(-> (get-library-info self attr) (let [color (u/locate-library-color $file $id)]
(mapfn)))) (u/to-js
(d/without-nils
{:fill-color (:color color)
:fill-opacity (:opacity color)
:fill-color-gradient (:gradient color)
:fill-color-ref-file $file
:fill-color-ref-id $id
:fill-image (:image color)}))))
(defn get-library-data (asStroke [_]
([self attr] (let [color (u/locate-library-color $file $id)]
(let [lib-id (get-data self :id) (u/to-js
current-file-id (:current-file-id @st/state)] (d/without-nils
(if (= lib-id current-file-id) {:stroke-color (:color color)
(dm/get-in @st/state [:workspace-data attr]) :stroke-opacity (:opacity color)
(dm/get-in @st/state [:workspace-libraries lib-id :data attr])))) :stroke-color-gradient (:gradient color)
:stroke-color-ref-file $file
:stroke-color-ref-id $id
:stroke-image (:image color)
:stroke-style :solid
:stroke-alignment :inner})))))
([self attr mapfn] (defn lib-color-proxy
(-> (get-library-data self attr) [file-id id]
(mapfn)))) (assert (uuid? file-id))
(assert (uuid? id))
(defn- array-to-js
[value]
(.freeze
js/Object
(apply array (->> value (map utils/to-js)))))
(deftype Library [_data]
Object)
(defn create-library
[data]
(cr/add-properties! (cr/add-properties!
(Library. data) (LibraryColorProxy. file-id id)
{:name "_data" {:name "$id" :enumerable false :get (constantly id)}
:enumerable false} {:name "$file" :enumerable false :get (constantly file-id)}
{:name "id" {:name "id" :get (fn [_] (dm/str id))}
:get (fn [self]
(str (:id (obj/get self "_data"))))}
{:name "name" {:name "name"
:get (fn [self] :get #(-> % u/proxy->library-color :name)}
(get-library-info self :name))}
{:name "color"
:get #(-> % u/proxy->library-color :color)}
{:name "opacity"
:get #(-> % u/proxy->library-color :opacity)}
{:name "gradient"
:get #(-> % u/proxy->library-color :gradient u/to-js)}
{:name "image"
:get #(-> % u/proxy->library-color :image u/to-js)}))
(deftype LibraryTypographyProxy [$file $id]
Object)
(defn lib-typography-proxy
[file-id id]
(assert (uuid? file-id))
(assert (uuid? id))
(cr/add-properties!
(LibraryTypographyProxy. file-id id)
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "id" :get (fn [_] (dm/str id))}
{:name "name"
:get #(-> % u/proxy->library-typography :name)}))
(deftype LibraryComponentProxy [$file $id]
Object)
(defn lib-component-proxy
[file-id id]
(assert (uuid? file-id))
(assert (uuid? id))
(cr/add-properties!
(LibraryComponentProxy. file-id id)
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "id" :get (fn [_] (dm/str id))}
{:name "name"
:get #(-> % u/proxy->library-component :name)}))
(deftype Library [$id]
Object)
(defn library-proxy
[file-id]
(assert (uuid? file-id) "File id not valid")
(cr/add-properties!
(Library. file-id)
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "id"
:get #(-> % u/proxy->file :id str)}
{:name "name"
:get #(-> % u/proxy->file :name)}
{:name "colors" {:name "colors"
:get (fn [self] :get
(array-to-js (get-library-data self :colors vals)))} (fn [_]
(let [file (u/locate-file file-id)
colors (->> file :data :colors keys (map #(lib-color-proxy file-id %)))]
(apply array colors)))}
{:name "typographies" {:name "typographies"
:get (fn [self] :get
(array-to-js (get-library-data self :typographies vals)))} (fn [_]
(let [file (u/locate-file file-id)
typographies (->> file :data :typographies keys (map #(lib-typography-proxy file-id %)))]
(apply array typographies)))}
{:name "components" {:name "components"
:get (fn [self] :get
(array-to-js (get-library-data self :components vals)))})) (fn [_]
(let [file (u/locate-file file-id)
components (->> file :data :componentes keys (map #(lib-component-proxy file-id %)))]
(apply array components)))}))
(deftype PenpotLibrarySubcontext [] (deftype PenpotLibrarySubcontext []
Object Object
@ -80,17 +144,15 @@
(find [_])) (find [_]))
(defn create-library-subcontext (defn library-subcontext
[] []
(cr/add-properties! (cr/add-properties!
(PenpotLibrarySubcontext.) (PenpotLibrarySubcontext.)
{:name "local" :get {:name "local" :get
(fn [_] (fn [_]
(let [file (get @st/state :workspace-file) (library-proxy (:current-file-id @st/state)))}
data (get @st/state :workspace-data)]
(create-library (assoc file :data data))))}
{:name "connected" :get {:name "connected" :get
(fn [_] (fn [_]
(let [libraries (get @st/state :workspace-libraries)] (let [libraries (get @st/state :workspace-libraries)]
(apply array (->> libraries vals (map create-library)))))})) (apply array (->> libraries vals (map library-proxy)))))}))

View file

@ -7,46 +7,48 @@
(ns app.plugins.page (ns app.plugins.page
"RPC for plugins runtime." "RPC for plugins runtime."
(:require (:require
[app.common.data.macros :as dm]
[app.common.record :as crc] [app.common.record :as crc]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.plugins.shape :as shape] [app.plugins.shape :as shape]
[app.plugins.utils :refer [get-data-fn]])) [app.plugins.utils :refer [locate-page proxy->page]]
[app.util.object :as obj]))
(def ^:private (deftype PageProxy [$file $id]
xf-map-shape-proxy
(comp
(map val)
(map shape/data->shape-proxy)))
(deftype PageProxy [#_:clj-kondo/ignore _data]
Object Object
(getShapeById [_ id] (getShapeById
(shape/data->shape-proxy (get (:objects _data) (uuid/uuid id)))) [_ shape-id]
(let [shape-id (uuid/uuid shape-id)]
(shape/shape-proxy $file $id shape-id)))
(getRoot [_] (getRoot
(shape/data->shape-proxy (get (:objects _data) uuid/zero))) [_]
(shape/shape-proxy $file $id uuid/zero))
(findShapes [_] (findShapes
[_]
;; Returns a lazy (iterable) of all available shapes ;; Returns a lazy (iterable) of all available shapes
(apply array (sequence xf-map-shape-proxy (:objects _data))))) (let [page (locate-page $file $id)]
(apply array (sequence (map shape/shape-proxy) (keys (:objects page)))))))
(crc/define-properties! (crc/define-properties!
PageProxy PageProxy
{:name js/Symbol.toStringTag {:name js/Symbol.toStringTag
:get (fn [] (str "PageProxy"))}) :get (fn [] (str "PageProxy"))})
(defn data->page-proxy (defn page-proxy
[data] [file-id id]
(crc/add-properties! (crc/add-properties!
(PageProxy. data) (PageProxy. file-id id)
{:name "_data" :enumerable false} {:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "id" {:name "id"
:get (get-data-fn :id str)} :get #(dm/str (obj/get % "$id"))}
{:name "name" {:name "name"
:get (get-data-fn :name)} :get #(-> % proxy->page :name)}
{:name "root" {:name "root"
:enumerable false
:get #(.getRoot ^js %)})) :get #(.getRoot ^js %)}))

View file

@ -8,364 +8,407 @@
"RPC for plugins runtime." "RPC for plugins runtime."
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.record :as crc] [app.common.record :as crc]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.uuid :as uuid] [app.common.types.shape.layout :as ctl]
[app.main.data.workspace :as udw] [app.main.data.workspace :as udw]
[app.main.data.workspace.changes :as dwc] [app.main.data.workspace.changes :as dwc]
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.store :as st] [app.main.store :as st]
[app.plugins.flex :as flex]
[app.plugins.grid :as grid] [app.plugins.grid :as grid]
[app.plugins.utils :as utils :refer [get-data get-data-fn get-state]] [app.plugins.utils :as utils :refer [locate-objects locate-shape proxy->shape array-to-js]]
[app.util.object :as obj])) [app.util.object :as obj]))
(declare data->shape-proxy) (declare shape-proxy)
(defn- array-to-js (deftype ShapeProxy [$file $page $id]
[value]
(.freeze
js/Object
(apply array (->> value (map utils/to-js)))))
(defn- locate-shape
[shape-id]
(let [page-id (:current-page-id @st/state)]
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects shape-id])))
(deftype ShapeProxy [#_:clj-kondo/ignore _data]
Object Object
(resize (resize
[self width height] [_ width height]
(let [id (get-data self :id)] (st/emit! (udw/update-dimensions [$id] :width width)
(st/emit! (udw/update-dimensions [id] :width width) (udw/update-dimensions [$id] :height height)))
(udw/update-dimensions [id] :height height))))
(clone [self] (clone
(let [id (get-data self :id) [_]
page-id (:current-page-id @st/state) (let [ret-v (atom nil)]
ret-v (atom nil)] (st/emit! (dws/duplicate-shapes #{$id} :change-selection? false :return-ref ret-v))
(st/emit! (dws/duplicate-shapes #{id} :change-selection? false :return-ref ret-v)) (shape-proxy (deref ret-v))))
(let [new-id (deref ret-v)
shape (dm/get-in @st/state [:workspace-data :pages-index page-id :objects new-id])]
(data->shape-proxy shape))))
(remove [self] (remove
(let [id (get-data self :id)] [_]
(st/emit! (dwsh/delete-shapes #{id})))) (st/emit! (dwsh/delete-shapes #{$id})))
;; Only for frames + groups + booleans ;; Only for frames + groups + booleans
(getChildren (getChildren
[self] [_]
(apply array (->> (get-state self :shapes) (apply array (->> (locate-shape $file $page $id)
(map locate-shape) :shapes
(map data->shape-proxy)))) (map #(shape-proxy $file $page %)))))
(appendChild [self child] (appendChild
(let [parent-id (get-data self :id) [_ child]
child-id (uuid/uuid (obj/get child "id"))] (let [child-id (obj/get child "$id")]
(st/emit! (udw/relocate-shapes #{child-id} parent-id 0)))) (st/emit! (udw/relocate-shapes #{child-id} $id 0))))
(insertChild [self index child] (insertChild
(let [parent-id (get-data self :id) [_ index child]
child-id (uuid/uuid (obj/get child "id"))] (let [child-id (obj/get child "$id")]
(st/emit! (udw/relocate-shapes #{child-id} parent-id index)))) (st/emit! (udw/relocate-shapes #{child-id} $id index))))
;; Only for frames ;; Only for frames
(addFlexLayout [self] (addFlexLayout
(let [id (get-data self :id)] [_]
(st/emit! (dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false)))) (st/emit! (dwsl/create-layout-from-id $id :flex :from-frame? true :calculate-params? false))
(grid/grid-layout-proxy $file $page $id))
(addGridLayout [self] (addGridLayout
(let [id (get-data self :id)] [_]
(st/emit! (dwsl/create-layout-from-id id :grid :from-frame? true :calculate-params? false)) (st/emit! (dwsl/create-layout-from-id $id :grid :from-frame? true :calculate-params? false))
(grid/grid-layout-proxy (obj/get self "_data"))))) (grid/grid-layout-proxy $file $page $id)))
(crc/define-properties! (crc/define-properties!
ShapeProxy ShapeProxy
{:name js/Symbol.toStringTag {:name js/Symbol.toStringTag
:get (fn [] (str "ShapeProxy"))}) :get (fn [] (str "ShapeProxy"))})
(defn data->shape-proxy (defn shape-proxy
[data] ([id]
(shape-proxy (:current-file-id @st/state) (:current-page-id @st/state) id))
(-> (ShapeProxy. data) ([page-id id]
(crc/add-properties! (shape-proxy (:current-file-id @st/state) page-id id))
{:name "_data"
:enumerable false}
{:name "id" ([file-id page-id id]
:get (get-data-fn :id str)} (assert (uuid? file-id))
(assert (uuid? page-id))
(assert (uuid? id))
{:name "type" (let [data (locate-shape file-id page-id id)]
:get (get-data-fn :type name)} (-> (ShapeProxy. file-id page-id id)
(crc/add-properties!
{:name "$id" :enumerable false :get (constantly id)}
{:name "$file" :enumerable false :get (constantly file-id)}
{:name "$page" :enumerable false :get (constantly page-id)}
{:name "name" {:name "id"
:get #(get-state % :name) :get #(-> % proxy->shape :id str)}
:set (fn [self value]
(let [id (get-data self :id)]
(st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))}
{:name "blocked" {:name "type"
:get #(get-state % :blocked boolean) :get #(-> % proxy->shape :type name)}
:set (fn [self value]
(let [id (get-data self :id)]
(st/emit! (dwc/update-shapes [id] #(assoc % :blocked value)))))}
{:name "hidden" {:name "name"
:get #(get-state % :hidden boolean) :get #(-> % proxy->shape :name)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(st/emit! (dwc/update-shapes [id] #(assoc % :hidden value)))))} (st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))}
{:name "proportionLock" {:name "blocked"
:get #(get-state % :proportion-lock boolean) :get #(-> % proxy->shape :blocked boolean)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(st/emit! (dwc/update-shapes [id] #(assoc % :proportion-lock value)))))} (st/emit! (dwc/update-shapes [id] #(assoc % :blocked value)))))}
{:name "constraintsHorizontal" {:name "hidden"
:get #(get-state % :constraints-h d/name) :get #(-> % proxy->shape :hidden boolean)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")]
value (keyword value)] (st/emit! (dwc/update-shapes [id] #(assoc % :hidden value)))))}
(when (contains? cts/horizontal-constraint-types value)
(st/emit! (dwc/update-shapes [id] #(assoc % :constraints-h value))))))}
{:name "constraintsVertical" {:name "proportionLock"
:get #(get-state % :constraints-v d/name) :get #(-> % proxy->shape :proportion-lock boolean)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")]
value (keyword value)] (st/emit! (dwc/update-shapes [id] #(assoc % :proportion-lock value)))))}
(when (contains? cts/vertical-constraint-types value)
(st/emit! (dwc/update-shapes [id] #(assoc % :constraints-v value))))))}
{:name "borderRadius" {:name "constraintsHorizontal"
:get #(get-state % :rx) :get #(-> % proxy->shape :constraints-h d/name)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")
(when (us/safe-int? value) value (keyword value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :rx value :ry value))))))} (when (contains? cts/horizontal-constraint-types value)
(st/emit! (dwc/update-shapes [id] #(assoc % :constraints-h value))))))}
{:name "borderRadiusTopLeft" {:name "constraintsVertical"
:get #(get-state % :r1) :get #(-> % proxy->shape :constraints-v d/name)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")
(when (us/safe-int? value) value (keyword value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :r1 value))))))} (when (contains? cts/vertical-constraint-types value)
(st/emit! (dwc/update-shapes [id] #(assoc % :constraints-v value))))))}
{:name "borderRadiusTopRight" {:name "borderRadius"
:get #(get-state % :r2) :get #(-> % proxy->shape :rx)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwc/update-shapes [id] #(assoc % :r2 value))))))} (st/emit! (dwc/update-shapes [id] #(assoc % :rx value :ry value))))))}
{:name "borderRadiusBottomRight" {:name "borderRadiusTopLeft"
:get #(get-state % :r3) :get #(-> % proxy->shape :r1)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwc/update-shapes [id] #(assoc % :r3 value))))))} (st/emit! (dwc/update-shapes [id] #(assoc % :r1 value))))))}
{:name "borderRadiusBottomLeft" {:name "borderRadiusTopRight"
:get #(get-state % :r4) :get #(-> % proxy->shape :r2)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (us/safe-int? value) (when (us/safe-int? value)
(st/emit! (dwc/update-shapes [id] #(assoc % :r4 value))))))} (st/emit! (dwc/update-shapes [id] #(assoc % :r2 value))))))}
{:name "opacity" {:name "borderRadiusBottomRight"
:get #(get-state % :opacity) :get #(-> % proxy->shape :r3)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id)] (let [id (obj/get self "$id")]
(when (and (us/safe-number? value) (>= value 0) (<= value 1)) (when (us/safe-int? value)
(st/emit! (dwc/update-shapes [id] #(assoc % :opacity value))))))} (st/emit! (dwc/update-shapes [id] #(assoc % :r3 value))))))}
{:name "blendMode" {:name "borderRadiusBottomLeft"
:get #(get-state % :blend-mode d/name) :get #(-> % proxy->shape :r4)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")]
value (keyword value)] (when (us/safe-int? value)
(when (contains? cts/blend-modes value) (st/emit! (dwc/update-shapes [id] #(assoc % :r4 value))))))}
(st/emit! (dwc/update-shapes [id] #(assoc % :blend-mode value))))))}
{:name "shadows" {:name "opacity"
:get #(get-state % :shadow array-to-js) :get #(-> % proxy->shape :opacity)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")]
value (mapv #(utils/from-js %) value)] (when (and (us/safe-number? value) (>= value 0) (<= value 1))
(st/emit! (dwc/update-shapes [id] #(assoc % :shadows value)))))} (st/emit! (dwc/update-shapes [id] #(assoc % :opacity value))))))}
{:name "blur" {:name "blendMode"
:get #(get-state % :blur utils/to-js) :get #(-> % proxy->shape :blend-mode (d/nilv :normal) d/name)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")
value (utils/from-js value)] value (keyword value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :blur value)))))} (when (contains? cts/blend-modes value)
(st/emit! (dwc/update-shapes [id] #(assoc % :blend-mode value))))))}
{:name "exports" {:name "shadows"
:get #(get-state % :exports array-to-js) :get #(-> % proxy->shape :shadow array-to-js)
:set (fn [self value] :set (fn [self value]
(let [id (get-data self :id) (let [id (obj/get self "$id")
value (mapv #(utils/from-js %) value)] value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :exports value)))))} (st/emit! (dwc/update-shapes [id] #(assoc % :shadows value)))))}
;; Geometry properties {:name "blur"
{:name "x" :get #(-> % proxy->shape :blur utils/to-js)
:get #(get-state % :x) :set (fn [self value]
:set (let [id (obj/get self "$id")
(fn [self value] value (utils/from-js value)]
(let [id (get-data self :id)] (st/emit! (dwc/update-shapes [id] #(assoc % :blur value)))))}
(st/emit! (udw/update-position id {:x value}))))}
{:name "y" {:name "exports"
:get #(get-state % :y) :get #(-> % proxy->shape :exports array-to-js)
:set :set (fn [self value]
(fn [self value] (let [id (obj/get self "$id")
(let [id (get-data self :id)] value (mapv #(utils/from-js %) value)]
(st/emit! (udw/update-position id {:y value}))))} (st/emit! (dwc/update-shapes [id] #(assoc % :exports value)))))}
{:name "parentX" ;; Geometry properties
:get (fn [self] {:name "x"
(let [page-id (:current-page-id @st/state) :get #(-> % proxy->shape :x)
parent-id (get-state self :parent-id) :set
parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])] (fn [self value]
(- (get-state self :x) parent-x))) (let [id (obj/get self "$id")]
:set (st/emit! (udw/update-position id {:x value}))))}
(fn [self value]
(let [page-id (:current-page-id @st/state)
id (get-data self :id)
parent-id (get-state self :parent-id)
parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])]
(st/emit! (udw/update-position id {:x (+ parent-x value)}))))}
{:name "parentY" {:name "y"
:get (fn [self] :get #(-> % proxy->shape :y)
(let [page-id (:current-page-id @st/state) :set
parent-id (get-state self :parent-id) (fn [self value]
parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])] (let [id (obj/get self "$id")]
(- (get-state self :y) parent-y))) (st/emit! (udw/update-position id {:y value}))))}
:set
(fn [self value]
(let [page-id (:current-page-id @st/state)
id (get-data self :id)
parent-id (get-state self :parent-id)
parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])]
(st/emit! (udw/update-position id {:y (+ parent-y value)}))))}
{:name "frameX" {:name "parentX"
:get (fn [self] :get (fn [self]
(let [page-id (:current-page-id @st/state) (let [shape (proxy->shape self)
frame-id (get-state self :frame-id) parent-id (:parent-id shape)
frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])] parent (locate-shape (obj/get self "$file") (obj/get self "$page") parent-id)]
(- (get-state self :x) frame-x))) (- (:x shape) (:x parent))))
:set :set
(fn [self value] (fn [self value]
(let [page-id (:current-page-id @st/state) (let [id (obj/get self "$id")
id (get-data self :id) parent-id (-> self proxy->shape :parent-id)
frame-id (get-state self :frame-id) parent (locate-shape (obj/get self "$file") (obj/get self "$page") parent-id)
frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])] parent-x (:x parent)]
(st/emit! (udw/update-position id {:x (+ frame-x value)}))))} (st/emit! (udw/update-position id {:x (+ parent-x value)}))))}
{:name "frameY" {:name "parentY"
:get (fn [self] :get (fn [self]
(let [page-id (:current-page-id @st/state) (let [shape (proxy->shape self)
frame-id (get-state self :frame-id) parent-id (:parent-id shape)
frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])] parent (locate-shape (obj/get self "$file") (obj/get self "$page") parent-id)
(- (get-state self :y) frame-y))) parent-y (:y parent)]
:set (- (:y shape) parent-y)))
(fn [self value] :set
(let [page-id (:current-page-id @st/state) (fn [self value]
id (get-data self :id) (let [id (obj/get self "$id")
frame-id (get-state self :frame-id) parent-id (-> self proxy->shape :parent-id)
frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])] parent (locate-shape (obj/get self "$file") (obj/get self "$page") parent-id)
(st/emit! (udw/update-position id {:y (+ frame-y value)}))))} parent-y (:y parent)]
(st/emit! (udw/update-position id {:y (+ parent-y value)}))))}
{:name "width" {:name "frameX"
:get #(get-state % :width)} :get (fn [self]
(let [shape (proxy->shape self)
frame-id (:parent-id shape)
frame (locate-shape (obj/get self "$file") (obj/get self "$page") frame-id)
frame-x (:x frame)]
(- (:x shape) frame-x)))
:set
(fn [self value]
(let [id (obj/get self "$id")
frame-id (-> self proxy->shape :frame-id)
frame (locate-shape (obj/get self "$file") (obj/get self "$page") frame-id)
frame-x (:x frame)]
(st/emit! (udw/update-position id {:x (+ frame-x value)}))))}
{:name "height" {:name "frameY"
:get #(get-state % :height)} :get (fn [self]
(let [shape (proxy->shape self)
frame-id (:parent-id shape)
frame (locate-shape (obj/get self "$file") (obj/get self "$page") frame-id)
frame-y (:y frame)]
(- (:y shape) frame-y)))
:set
(fn [self value]
(let [id (obj/get self "$id")
frame-id (-> self proxy->shape :frame-id)
frame (locate-shape (obj/get self "$file") (obj/get self "$page") frame-id)
frame-y (:y frame)]
(st/emit! (udw/update-position id {:y (+ frame-y value)}))))}
{:name "flipX" {:name "width"
:get #(get-state % :flip-x)} :get #(-> % proxy->shape :width)}
{:name "flipY" {:name "height"
:get #(get-state % :flip-y)} :get #(-> % proxy->shape :height)}
;; Strokes and fills {:name "flipX"
{:name "fills" :get #(-> % proxy->shape :flip-x)}
:get #(get-state % :fills array-to-js)
:set (fn [self value]
(let [id (get-data self :id)
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))}
{:name "strokes" {:name "flipY"
:get #(get-state % :strokes array-to-js) :get #(-> % proxy->shape :flip-y)}
:set (fn [self value]
(let [id (get-data self :id)
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))})
(cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) ;; Strokes and fills
(crc/add-properties! {:name "fills"
{:name "children" :get #(-> % proxy->shape :fills array-to-js)
:get #(.getChildren ^js %)})) :set (fn [self value]
(let [id (obj/get self "$id")
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))}
(cond-> (not (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))) {:name "strokes"
(-> (obj/unset! "appendChild") :get #(-> % proxy->shape :strokes array-to-js)
(obj/unset! "insertChild") :set (fn [self value]
(obj/unset! "getChildren"))) (let [id (obj/get self "$id")
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))}
(cond-> (cfh/frame-shape? data) {:name "layoutChild"
(-> (crc/add-properties! :get
{:name "grid" (fn [self]
:get (let [file-id (obj/get self "$file")
(fn [self] page-id (obj/get self "$page")
(let [layout (get-state self :layout)] id (obj/get self "$id")
(when (= :grid layout) objects (locate-objects file-id page-id)]
(grid/grid-layout-proxy data))))} (when (ctl/any-layout-immediate-child-id? objects id)
(flex/layout-child-proxy file-id page-id id))))}
{:name "guides" {:name "layoutCell"
:get #(get-state % :grids array-to-js) :get
:set (fn [self value] (fn [self]
(let [id (get-data self :id) (let [file-id (obj/get self "$file")
value (mapv #(utils/from-js %) value)] page-id (obj/get self "$page")
(st/emit! (dwc/update-shapes [id] #(assoc % :grids value)))))}) id (obj/get self "$id")
objects (locate-objects file-id page-id)]
(when (ctl/grid-layout-immediate-child-id? objects id)
(grid/layout-cell-proxy file-id page-id id))))})
;; TODO: Flex properties (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))
#_(crc/add-properties! (crc/add-properties!
{:name "flex" {:name "children"
:get :enumerable false
(fn [self] :get #(.getChildren ^js %)}))
(let [layout (get-state self :layout)]
(when (= :flex layout)
(flex-layout-proxy data))))})))
(cond-> (not (cfh/frame-shape? data)) (cond-> (not (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)))
(-> (obj/unset! "addGridLayout") (-> (obj/unset! "appendChild")
(obj/unset! "addFlexLayout"))) (obj/unset! "insertChild")
(obj/unset! "getChildren")))
(cond-> (cfh/text-shape? data) (cond-> (cfh/frame-shape? data)
(-> (crc/add-properties! (-> (crc/add-properties!
{:name "characters" {:name "grid"
:get #(get-state % :content txt/content->text) :get
:set (fn [self value] (fn [self]
(let [id (get-data self :id)] (let [layout (-> self proxy->shape :layout)
(st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))}) file-id (obj/get self "$file")
page-id (obj/get self "$page")
id (obj/get self "$id")]
(when (= :grid layout)
(grid/grid-layout-proxy file-id page-id id))))}
(crc/add-properties! {:name "flex"
{:name "growType" :get
:get #(get-state % :grow-type d/name) (fn [self]
:set (fn [self value] (let [layout (-> self proxy->shape :layout)
(let [id (get-data self :id) file-id (obj/get self "$file")
value (keyword value)] page-id (obj/get self "$page")
(when (contains? #{:auto-width :auto-height :fixed} value) id (obj/get self "$id")]
(st/emit! (dwc/update-shapes [id] #(assoc % :grow-type value))))))}))))) (when (= :flex layout)
(flex/flex-layout-proxy file-id page-id id))))}
{:name "guides"
:get #(-> % proxy->shape :grids array-to-js)
:set (fn [self value]
(let [id (obj/get self "$id")
value (mapv #(utils/from-js %) value)]
(st/emit! (dwc/update-shapes [id] #(assoc % :grids value)))))}
{:name "horizontalSizing"
:get #(-> % proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? #{:fix :auto} value)
(st/emit! (dwsl/update-layout #{id} {:layout-item-h-sizing value})))))}
{:name "verticalSizing"
:get #(-> % proxy->shape :layout-item-v-sizing (d/nilv :fix) d/name)
:set
(fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? #{:fix :auto} value)
(st/emit! (dwsl/update-layout #{id} {:layout-item-v-sizing value})))))})))
(cond-> (not (cfh/frame-shape? data))
(-> (obj/unset! "addGridLayout")
(obj/unset! "addFlexLayout")))
(cond-> (cfh/text-shape? data)
(-> (crc/add-properties!
{:name "characters"
:get #(-> % proxy->shape :content txt/content->text)
:set (fn [self value]
(let [id (obj/get self "$id")]
(st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))})
(crc/add-properties!
{:name "growType"
:get #(-> % proxy->shape :grow-type d/name)
:set (fn [self value]
(let [id (obj/get self "$id")
value (keyword value)]
(when (contains? #{:auto-width :auto-height :fixed} value)
(st/emit! (dwc/update-shapes [id] #(assoc % :grow-type value))))))})))))))

View file

@ -16,6 +16,79 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[promesa.core :as p])) [promesa.core :as p]))
(defn locate-file
[id]
(assert (uuid? id) "File not valid uuid")
(if (= id (:current-file-id @st/state))
(-> (:workspace-file @st/state)
(assoc :data (:workspace-data @st/state)))
(dm/get-in @st/state [:workspace-libraries id])))
(defn locate-page
[file-id id]
(assert (uuid? id) "Page not valid uuid")
(dm/get-in (locate-file file-id) [:data :pages-index id]))
(defn locate-objects
[file-id page-id]
(:objects (locate-page file-id page-id)))
(defn locate-shape
[file-id page-id id]
(assert (uuid? id) "Shape not valid uuid")
(dm/get-in (locate-page file-id page-id) [:objects id]))
(defn locate-library-color
[file-id id]
(assert (uuid? id) "Color not valid uuid")
(dm/get-in (locate-file file-id) [:data :colors id]))
(defn locate-library-typography
[file-id id]
(assert (uuid? id) "Typography not valid uuid")
(dm/get-in (locate-file file-id) [:data :typographies id]))
(defn locate-library-component
[file-id id]
(assert (uuid? id) "Component not valid uuid")
(dm/get-in (locate-file file-id) [:data :components id]))
(defn proxy->file
[proxy]
(let [id (obj/get proxy "$id")]
(locate-file id)))
(defn proxy->page
[proxy]
(let [file-id (obj/get proxy "$file")
id (obj/get proxy "$id")]
(locate-page file-id id)))
(defn proxy->shape
[proxy]
(let [file-id (obj/get proxy "$file")
page-id (obj/get proxy "$page")
id (obj/get proxy "$id")]
(locate-shape file-id page-id id)))
(defn proxy->library-color
[proxy]
(let [file-id (obj/get proxy "$file")
id (obj/get proxy "$id")]
(locate-library-color file-id id)))
(defn proxy->library-typography
[proxy]
(let [file-id (obj/get proxy "$file")
id (obj/get proxy "$id")]
(locate-library-color file-id id)))
(defn proxy->library-component
[proxy]
(let [file-id (obj/get proxy "$file")
id (obj/get proxy "$id")]
(locate-library-color file-id id)))
(defn get-data (defn get-data
([self attr] ([self attr]
(-> (obj/get self "_data") (-> (obj/get self "_data")
@ -45,37 +118,51 @@
(defn from-js (defn from-js
"Converts the object back to js" "Converts the object back to js"
([obj] [obj]
(from-js obj identity)) (when (some? obj)
([obj vfn] (let [process-node
(let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})] (fn process-node [node]
(reduce-kv (reduce-kv
(fn [m k v] (fn [m k v]
(let [k (keyword (str/kebab k)) (let [k (keyword (str/kebab k))
v (cond (map? v) v (cond (map? v)
(from-js v) (process-node v)
(and (string? v) (re-matches us/uuid-rx v)) (vector? v)
(uuid/uuid v) (mapv process-node v)
:else (vfn k v))] (and (string? v) (re-matches us/uuid-rx v))
(assoc m k v))) (uuid/uuid v)
{}
ret)))) (= k :type)
(keyword v)
:else v)]
(assoc m k v)))
{}
node))]
(process-node (js->clj obj)))))
(defn to-js (defn to-js
"Converts to javascript an camelize the keys" "Converts to javascript an camelize the keys"
[obj] [obj]
(let [result (when (some? obj)
(reduce-kv (let [result
(fn [m k v] (reduce-kv
(let [v (cond (object? v) (to-js v) (fn [m k v]
(uuid? v) (dm/str v) (let [v (cond (object? v) (to-js v)
:else v)] (uuid? v) (dm/str v)
(assoc m (str/camel (name k)) v))) :else v)]
{} (assoc m (str/camel (name k)) v)))
obj)] {}
(clj->js result))) obj)]
(clj->js result))))
(defn array-to-js
[value]
(.freeze
js/Object
(apply array (->> value (map to-js)))))
(defn result-p (defn result-p
"Creates a pair of atom+promise. The promise will be resolved when the atom gets a value. "Creates a pair of atom+promise. The promise will be resolved when the atom gets a value.

91
package-lock.json generated
View file

@ -1,91 +0,0 @@
{
"name": "penpot",
"version": "1.20.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "penpot",
"version": "1.20.0",
"license": "MPL-2.0",
"devDependencies": {
"@playwright/test": "^1.43.1",
"@types/node": "^20.12.7"
}
},
"node_modules/@playwright/test": {
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz",
"integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==",
"dev": true,
"dependencies": {
"playwright": "1.43.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@types/node": {
"version": "20.12.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz",
"integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==",
"dev": true,
"dependencies": {
"playwright-core": "1.43.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz",
"integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
}
}
}