mirror of
https://github.com/penpot/penpot.git
synced 2025-07-31 00:08:31 +02:00
Merge pull request #6606 from penpot/niwinz-develop-fixes-2
✨ Fix several issues on penpot library
This commit is contained in:
commit
08aeb93710
18 changed files with 200 additions and 184 deletions
|
@ -10,15 +10,21 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
;; [app.common.features :as cfeat]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.files.changes :as ch]
|
||||
;; [app.common.features :as cfeat]
|
||||
[app.common.files.helpers :as cph]
|
||||
[app.common.files.migrations :as fmig]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.svg :as csvg]
|
||||
[app.common.time :as dt]
|
||||
[app.common.types.color :as types.color]
|
||||
[app.common.types.component :as types.comp]
|
||||
[app.common.types.container :as types.cont]
|
||||
[app.common.types.file :as types.file]
|
||||
[app.common.types.page :as types.page]
|
||||
[app.common.types.path :as types.path]
|
||||
[app.common.types.shape :as types.shape]
|
||||
[app.common.types.typography :as types.typography]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -293,7 +299,7 @@
|
|||
|
||||
(defn close-group
|
||||
[state]
|
||||
(let [group-id (-> state :parent-stack peek)
|
||||
(let [group-id (-> state ::parent-stack peek)
|
||||
group (get-shape state group-id)
|
||||
children (->> (get group :shapes)
|
||||
(into [] (keep (partial get-shape state)))
|
||||
|
@ -329,6 +335,35 @@
|
|||
(commit-change state change :add-container true)))]
|
||||
(update state ::parent-stack pop))))
|
||||
|
||||
(defn- update-bool-style-properties
|
||||
[bool-shape objects]
|
||||
(let [xform
|
||||
(comp
|
||||
(map (d/getf objects))
|
||||
(remove cph/frame-shape?)
|
||||
(remove types.comp/is-variant?)
|
||||
(remove (partial types.cont/has-any-copy-parent? objects)))
|
||||
|
||||
children
|
||||
(->> (get bool-shape :shapes)
|
||||
(into [] xform)
|
||||
(not-empty))]
|
||||
|
||||
(when-not children
|
||||
(ex/raise :type :validation
|
||||
:code :empty-children
|
||||
:hint "expected a group with at least one shape for creating a bool"))
|
||||
|
||||
(let [head (if (= type :difference)
|
||||
(first children)
|
||||
(last children))
|
||||
fills (if (and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
types.path/default-bool-fills
|
||||
(get head :fills))]
|
||||
(-> bool-shape
|
||||
(assoc :fills fills)
|
||||
(assoc :stroks (get head :strokes))))))
|
||||
|
||||
(defn add-bool
|
||||
[state params]
|
||||
(let [{:keys [group-id type]}
|
||||
|
@ -337,32 +372,40 @@
|
|||
group
|
||||
(get-shape state group-id)
|
||||
|
||||
children
|
||||
(->> (get group :shapes)
|
||||
(not-empty))]
|
||||
objects
|
||||
(get-current-objects state)
|
||||
|
||||
(assert (some? children) "expect group to have at least 1 element")
|
||||
bool
|
||||
(-> group
|
||||
(assoc :type :bool)
|
||||
(assoc :bool-type type)
|
||||
(update-bool-style-properties objects)
|
||||
(types.path/update-bool-shape objects))
|
||||
|
||||
(let [objects (get-current-objects state)
|
||||
bool (-> group
|
||||
(assoc :type :bool)
|
||||
(gsh/update-bool objects))
|
||||
change {:type :mod-obj
|
||||
:id (:id bool)
|
||||
:operations
|
||||
[{:type :set :attr :content :val (:content bool) :ignore-touched true}
|
||||
{:type :set :attr :type :val :bool :ignore-touched true}
|
||||
{:type :set :attr :bool-type :val type :ignore-touched true}
|
||||
{:type :set :attr :selrect :val (:selrect bool) :ignore-touched true}
|
||||
{:type :set :attr :points :val (:points bool) :ignore-touched true}
|
||||
{:type :set :attr :x :val (-> bool :selrect :x) :ignore-touched true}
|
||||
{:type :set :attr :y :val (-> bool :selrect :y) :ignore-touched true}
|
||||
{:type :set :attr :width :val (-> bool :selrect :width) :ignore-touched true}
|
||||
{:type :set :attr :height :val (-> bool :selrect :height) :ignore-touched true}]}]
|
||||
selrect
|
||||
(get bool :selrect)
|
||||
|
||||
(-> state
|
||||
(commit-change change :add-container true)
|
||||
(assoc ::last-id group-id)))))
|
||||
operations
|
||||
[{:type :set :attr :content :val (:content bool) :ignore-touched true}
|
||||
{:type :set :attr :type :val :bool :ignore-touched true}
|
||||
{:type :set :attr :bool-type :val type :ignore-touched true}
|
||||
{:type :set :attr :selrect :val selrect :ignore-touched true}
|
||||
{:type :set :attr :points :val (:points bool) :ignore-touched true}
|
||||
{:type :set :attr :x :val (get selrect :x) :ignore-touched true}
|
||||
{:type :set :attr :y :val (get selrect :y) :ignore-touched true}
|
||||
{:type :set :attr :width :val (get selrect :width) :ignore-touched true}
|
||||
{:type :set :attr :height :val (get selrect :height) :ignore-touched true}
|
||||
{:type :set :attr :fills :val (:fills bool) :ignore-touched true}
|
||||
{:type :set :attr :strokes :val (:strokes bool) :ignore-touched true}]
|
||||
|
||||
change
|
||||
{:type :mod-obj
|
||||
:id (:id bool)
|
||||
:operations operations}]
|
||||
|
||||
(-> state
|
||||
(commit-change change :add-container true)
|
||||
(assoc ::last-id group-id))))
|
||||
|
||||
(defn add-shape
|
||||
[state params]
|
||||
|
@ -533,6 +576,7 @@
|
|||
:size (get blob :size)})
|
||||
(update ::file-media assoc id
|
||||
{:id id
|
||||
:created-at (dt/now)
|
||||
:name name
|
||||
:width width
|
||||
:height height
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
[app.common.types.grid :as ctg]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
|
@ -744,7 +745,7 @@
|
|||
group
|
||||
|
||||
(= :bool (:type group))
|
||||
(gsh/update-bool group objects)
|
||||
(path/update-bool-shape group objects)
|
||||
|
||||
(:masked-group group)
|
||||
(->> (map lookup children)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
@ -685,10 +686,10 @@
|
|||
(empty? children) ;; a parent with no children will be deleted,
|
||||
nil ;; so it does not need resize
|
||||
|
||||
(= (:type parent) :bool)
|
||||
(gsh/update-bool parent objects)
|
||||
(cfh/bool-shape? parent)
|
||||
(path/update-bool-shape parent objects)
|
||||
|
||||
(= (:type parent) :group)
|
||||
(cfh/group-shape? parent)
|
||||
;; FIXME: this functions should be
|
||||
;; normalized in the same way as
|
||||
;; update-bool in order to make all
|
||||
|
|
|
@ -164,7 +164,6 @@
|
|||
(dm/export gtr/calculate-geometry)
|
||||
(dm/export gtr/update-group-selrect)
|
||||
(dm/export gtr/update-mask-selrect)
|
||||
(dm/export gtr/update-bool)
|
||||
(dm/export gtr/apply-transform)
|
||||
(dm/export gtr/transform-shape)
|
||||
(dm/export gtr/transform-selrect)
|
||||
|
|
|
@ -456,13 +456,6 @@
|
|||
(assoc :flip-x (-> mask :flip-x))
|
||||
(assoc :flip-y (-> mask :flip-y)))))
|
||||
|
||||
(defn update-bool
|
||||
"Calculates the selrect+points for the boolean shape"
|
||||
[shape objects]
|
||||
(let [content (path/calc-bool-content shape objects)
|
||||
shape (assoc shape :content content)]
|
||||
(path/update-geometry shape)))
|
||||
|
||||
;; FIXME: revisit
|
||||
(defn update-shapes-geometry
|
||||
[objects ids]
|
||||
|
@ -477,7 +470,7 @@
|
|||
(update-mask-selrect shape children)
|
||||
|
||||
(cfh/bool-shape? shape)
|
||||
(update-bool shape objects)
|
||||
(path/update-bool-shape shape objects)
|
||||
|
||||
(cfh/group-shape? shape)
|
||||
(update-group-selrect shape children)
|
||||
|
|
|
@ -22,6 +22,14 @@
|
|||
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
(def ^:cosnt bool-group-style-properties bool/group-style-properties)
|
||||
(def ^:const bool-style-properties bool/style-properties)
|
||||
(def ^:const default-bool-fills bool/default-fills)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TRANSFORMATIONS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn content?
|
||||
[o]
|
||||
(impl/path-data? o))
|
||||
|
@ -197,6 +205,13 @@
|
|||
(-> (calc-bool-content* shape objects)
|
||||
(impl/path-data)))
|
||||
|
||||
(defn update-bool-shape
|
||||
"Calculates the selrect+points for the boolean shape"
|
||||
[shape objects]
|
||||
(let [content (calc-bool-content shape objects)
|
||||
shape (assoc shape :content content)]
|
||||
(update-geometry shape)))
|
||||
|
||||
(defn shape-with-open-path?
|
||||
[shape]
|
||||
(let [svg? (contains? shape :svg-attrs)
|
||||
|
|
|
@ -18,28 +18,13 @@
|
|||
(def default-fills
|
||||
[{:fill-color clr/black}])
|
||||
|
||||
(def style-group-properties
|
||||
[:shadow :blur])
|
||||
(def group-style-properties
|
||||
#{:shadow :blur})
|
||||
|
||||
;; FIXME: revisit
|
||||
(def style-properties
|
||||
(into style-group-properties
|
||||
[:fill-color
|
||||
:fill-opacity
|
||||
:fill-color-gradient
|
||||
:fill-color-ref-file
|
||||
:fill-color-ref-id
|
||||
:fill-image
|
||||
:fills
|
||||
:stroke-color
|
||||
:stroke-color-ref-file
|
||||
:stroke-color-ref-id
|
||||
:stroke-opacity
|
||||
:stroke-style
|
||||
:stroke-width
|
||||
:stroke-alignment
|
||||
:stroke-cap-start
|
||||
:stroke-cap-end
|
||||
:strokes]))
|
||||
(into group-style-properties
|
||||
[:fills :strokes]))
|
||||
|
||||
(defn add-previous
|
||||
([content]
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.bool :as bool]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
|
@ -35,7 +36,7 @@
|
|||
head
|
||||
(cond-> head
|
||||
(and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
(assoc :fills bool/default-fills))
|
||||
(assoc :fills path/default-bool-fills))
|
||||
|
||||
shape
|
||||
{:id shape-id
|
||||
|
@ -48,12 +49,26 @@
|
|||
|
||||
shape
|
||||
(-> shape
|
||||
(merge (select-keys head bool/style-properties))
|
||||
(merge (select-keys head path/bool-style-properties))
|
||||
(cts/setup-shape)
|
||||
(gsh/update-bool objects))]
|
||||
(path/update-bool-shape objects))]
|
||||
|
||||
[shape (cph/get-position-on-parent objects (:id head))]))
|
||||
|
||||
(defn- group->bool
|
||||
[type group objects]
|
||||
(let [shapes (->> (:shapes group)
|
||||
(map (d/getf objects)))
|
||||
head (if (= type :difference) (first shapes) (last shapes))
|
||||
head (cond-> head
|
||||
(and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
(assoc :fills path/default-bool-fills))]
|
||||
(-> group
|
||||
(assoc :type :bool)
|
||||
(assoc :bool-type type)
|
||||
(merge (select-keys head bool/style-properties))
|
||||
(path/update-bool-shape objects))))
|
||||
|
||||
(defn create-bool
|
||||
[type & {:keys [ids force-shape-id]}]
|
||||
|
||||
|
@ -101,20 +116,6 @@
|
|||
(rx/of (dch/commit-changes changes)
|
||||
(dws/select-shapes (d/ordered-set shape-id)))))))))
|
||||
|
||||
(defn group->bool
|
||||
[type group objects]
|
||||
(let [shapes (->> (:shapes group)
|
||||
(map (d/getf objects)))
|
||||
head (if (= type :difference) (first shapes) (last shapes))
|
||||
head (cond-> head
|
||||
(and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
(assoc :fills bool/default-fills))]
|
||||
(-> group
|
||||
(assoc :type :bool)
|
||||
(assoc :bool-type type)
|
||||
(merge (select-keys head bool/style-properties))
|
||||
(gsh/update-bool objects))))
|
||||
|
||||
(defn group-to-bool
|
||||
[shape-id type]
|
||||
(ptk/reify ::group-to-bool
|
||||
|
@ -130,7 +131,7 @@
|
|||
(-> shape
|
||||
(assoc :type :group)
|
||||
(dissoc :bool-type)
|
||||
(d/without-keys bool/style-group-properties)
|
||||
(d/without-keys path/bool-group-style-properties)
|
||||
(gsh/update-group-selrect
|
||||
(mapv (d/getf objects)
|
||||
(:shapes shape)))))
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
# CHANGELOG
|
||||
|
||||
## 1.0.2
|
||||
|
||||
- Fix incorrect boolean type assignation
|
||||
- Fix fill and stroke handling on boolean shape creation
|
||||
- Add sample-bool.js to the playground directory
|
||||
- Fix compatibility issue on file media with penpot 2.7.x
|
||||
|
||||
## 1.0.1
|
||||
|
||||
- Make the library generate a .penpot file compatible with penpot 2.7.x
|
||||
|
|
|
@ -52,7 +52,7 @@ await penpot.exportStream(context, writable);
|
|||
Build the library:
|
||||
|
||||
```bash
|
||||
yarn run build
|
||||
./scripts/build
|
||||
```
|
||||
|
||||
Login on npm:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@penpot/library",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
import * as penpot from "#self";
|
||||
import { createWriteStream } from 'fs';
|
||||
import { Writable } from "stream";
|
||||
|
||||
// Example of creating component and instance out of order
|
||||
|
||||
(async function() {
|
||||
const context = penpot.createBuildContext();
|
||||
|
||||
{
|
||||
context.addFile({name: "Test File 1"});
|
||||
context.addPage({name: "Foo Page"})
|
||||
|
||||
const mainBoardId = context.genId();
|
||||
const mainRectId = context.genId();
|
||||
|
||||
// First create instance (just for with the purpose of teaching
|
||||
// that it can be done, without putting that under obligation to
|
||||
// do it in this order or the opposite)
|
||||
|
||||
context.addBoard({
|
||||
name: "Board Instance 1",
|
||||
x: 700,
|
||||
y: 0,
|
||||
width: 500,
|
||||
height: 300,
|
||||
shapeRef: mainBoardId,
|
||||
touched: ["name-group"]
|
||||
})
|
||||
|
||||
context.addRect({
|
||||
name: "Rect Instance 1",
|
||||
x: 800,
|
||||
y: 20,
|
||||
width:100,
|
||||
height:200,
|
||||
shapeRef: mainRectId,
|
||||
touched: ["name-group"]
|
||||
});
|
||||
|
||||
// this function call takes the current board from context, but it
|
||||
// also can be passed as parameter on an explicit way if you
|
||||
// prefer
|
||||
context.addComponentInstance({
|
||||
componentId: "00000000-0000-0000-0000-000000000001"
|
||||
});
|
||||
|
||||
context.closeBoard();
|
||||
|
||||
// Then, create the main instance
|
||||
context.addBoard({
|
||||
id: mainBoardId,
|
||||
name: "Board",
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 500,
|
||||
height: 300,
|
||||
})
|
||||
|
||||
context.addRect({
|
||||
id: mainRectId,
|
||||
name: "Rect 1",
|
||||
x: 20,
|
||||
y: 20,
|
||||
width:100,
|
||||
height:200,
|
||||
});
|
||||
|
||||
context.addComponent({
|
||||
componentId: "00000000-0000-0000-0000-000000000001",
|
||||
name: "Component 1",
|
||||
});
|
||||
|
||||
context.closeBoard();
|
||||
context.closeFile();
|
||||
}
|
||||
|
||||
{
|
||||
// Create a file stream to write the zip to
|
||||
const output = createWriteStream('sample-with-components.zip');
|
||||
// Wrap Node's stream in a WHATWG WritableStream
|
||||
const writable = Writable.toWeb(output);
|
||||
await penpot.exportStream(context, writable);
|
||||
}
|
||||
|
||||
})().catch((cause) => {
|
||||
console.error(cause);
|
||||
|
||||
const causeExplain = cause.explain;
|
||||
if (causeExplain) {
|
||||
console.log("EXPLAIN:")
|
||||
console.error(cause.explain);
|
||||
}
|
||||
|
||||
// const innerCause = cause.cause;
|
||||
// if (innerCause) {
|
||||
// console.log("INNER:");
|
||||
// console.error(innerCause);
|
||||
// }
|
||||
process.exit(-1);
|
||||
}).finally(() => {
|
||||
process.exit(0);
|
||||
})
|
58
library/playground/sample-bool.js
Normal file
58
library/playground/sample-bool.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import * as penpot from "#self";
|
||||
import { writeFile, readFile } from "fs/promises";
|
||||
|
||||
(async function () {
|
||||
const context = penpot.createBuildContext();
|
||||
|
||||
{
|
||||
context.addFile({ name: "Test File 1" });
|
||||
context.addPage({ name: "Foo Page" });
|
||||
|
||||
const groupId = context.addGroup({
|
||||
name: "Bool Group"
|
||||
})
|
||||
|
||||
context.addRect({
|
||||
name: "Rect 1",
|
||||
x: 20,
|
||||
y: 20,
|
||||
width:100,
|
||||
height:100,
|
||||
});
|
||||
|
||||
context.addRect({
|
||||
name: "Rect 2",
|
||||
x: 90,
|
||||
y: 90,
|
||||
width:100,
|
||||
height:100,
|
||||
fills: [{fillColor: "#fabada", fillOpacity:1}]
|
||||
});
|
||||
|
||||
context.closeGroup();
|
||||
context.addBool({
|
||||
groupId: groupId,
|
||||
type: "union"
|
||||
});
|
||||
|
||||
context.closeBoard();
|
||||
context.closeFile();
|
||||
}
|
||||
|
||||
{
|
||||
let result = await penpot.exportAsBytes(context);
|
||||
await writeFile("sample-bool.zip", result);
|
||||
}
|
||||
})()
|
||||
.catch((cause) => {
|
||||
console.error(cause);
|
||||
|
||||
const innerCause = cause.cause;
|
||||
if (innerCause) {
|
||||
console.error("Inner cause:", innerCause);
|
||||
}
|
||||
process.exit(-1);
|
||||
})
|
||||
.finally(() => {
|
||||
process.exit(0);
|
||||
});
|
12
library/scripts/build
Executable file
12
library/scripts/build
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
# NOTE: this script should be called from the parent directory to
|
||||
# properly work.
|
||||
|
||||
export CURRENT_VERSION=$(node -p "require('./package.json').version");
|
||||
export NODE_ENV=production;
|
||||
|
||||
set -ex
|
||||
|
||||
yarn run build
|
||||
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" target/library/penpot.js
|
|
@ -41,6 +41,9 @@
|
|||
(def ^:private encode-component
|
||||
(sm/encoder types.component/schema:component sm/json-transformer))
|
||||
|
||||
(def encode-file-media
|
||||
(sm/encoder types.file/schema:media sm/json-transformer))
|
||||
|
||||
(def encode-color
|
||||
(sm/encoder types.color/schema:color sm/json-transformer))
|
||||
|
||||
|
@ -175,6 +178,7 @@
|
|||
[(str "files/" (:file-id file-media) "/media/" file-media-id ".json")
|
||||
(delay (-> file-media
|
||||
(dissoc :file-id)
|
||||
(encode-file-media)
|
||||
(json/encode)))]))))))
|
||||
|
||||
(defn- generate-manifest-procs
|
||||
|
@ -186,8 +190,7 @@
|
|||
:features (:features file)})))
|
||||
params {:type "penpot/export-files"
|
||||
:version 1
|
||||
;; FIXME: set proper placeholder for replacement on build
|
||||
:generated-by "penpot-lib/develop"
|
||||
:generated-by "penpot-library/%version%"
|
||||
:files files
|
||||
:relations []}]
|
||||
["manifest.json" (delay (json/encode params))]))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue