diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index eb7312744c..f2fdf75aa4 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -554,7 +554,7 @@ (when (features/active-feature? state "text-editor/v2") (let [instance (:workspace-editor state) styles (some-> (editor.v2/getCurrentStyle instance) - (styles/get-styles-from-style-declaration) + (styles/get-styles-from-style-declaration :removed-mixed true) ((comp update-node-fn migrate-node)) (styles/attrs->styles))] (editor.v2/applyStylesToSelection instance styles))))))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 7583f3c4da..dd427e6bd7 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -238,12 +238,12 @@ :always (ctm/resize scalev resize-origin shape-transform shape-transform-inverse) - (and (ctl/any-layout-immediate-child? objects shape) + (and (or (ctl/any-layout-immediate-child? objects shape) (ctl/any-layout? shape)) (not= (:layout-item-h-sizing shape) :fix) ^boolean change-width?) (ctm/change-property :layout-item-h-sizing :fix) - (and (ctl/any-layout-immediate-child? objects shape) + (and (or (ctl/any-layout-immediate-child? objects shape) (ctl/any-layout? shape)) (not= (:layout-item-v-sizing shape) :fix) ^boolean change-height?) (ctm/change-property :layout-item-v-sizing :fix) diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 45419d56ca..0e6c55882a 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -50,7 +50,8 @@ touched? (and (contains? (:data @form) input-name) (get-in @form [:touched input-name])) - error (get-in @form [:errors input-name]) + error (or (get-in @form [:errors input-name]) + (get-in @form [:extra-errors input-name])) value (get-in @form [:data input-name] "") diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs index 5de1e2796b..a5a2dd2b93 100644 --- a/frontend/src/app/main/ui/settings/password.cljs +++ b/frontend/src/app/main/ui/settings/password.cljs @@ -18,16 +18,18 @@ (defn- on-error [form error] - (case (:code (ex-data error)) - :old-password-not-match - (swap! form assoc-in [:errors :password-old] - {:message (tr "errors.wrong-old-password")}) - :email-as-password - (swap! form assoc-in [:errors :password-1] - {:message (tr "errors.email-as-password")}) + (let [data (ex-data error)] + (case (:code data) + :old-password-not-match + (swap! form assoc-in [:extra-errors :password-old] + {:message (tr "errors.wrong-old-password")}) - (let [msg (tr "generic.error")] - (st/emit! (ntf/error msg))))) + :email-as-password + (swap! form assoc-in [:extra-errors :password-1] + {:message (tr "errors.email-as-password")}) + + (let [msg (tr "generic.error")] + (st/emit! (ntf/error msg)))))) (defn- on-success [form] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index b6f919c8c3..00a7ca455e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -308,7 +308,7 @@ :title (tr "inspect.attributes.typography.font-family") :on-click #(reset! open-selector? true)} (cond - (= :multiple font-id) + (or (= :multiple font-id) (= "mixed" font-id)) "--" (some? font) diff --git a/frontend/src/app/util/forms.cljs b/frontend/src/app/util/forms.cljs index 1fa7feb469..a48fae2310 100644 --- a/frontend/src/app/util/forms.cljs +++ b/frontend/src/app/util/forms.cljs @@ -135,8 +135,18 @@ (not extra-errors) valid?))))) + +(defn- make-initial-state + [initial-data] + (let [initial (if (fn? initial-data) (initial-data) initial-data) + initial (d/nilv initial {})] + {:initial initial + :data initial + :errors {} + :touched {}})) + (defn- create-form-mutator - [internal-state rerender-fn wrap-update-fn initial opts] + [internal-state rerender-fn wrap-update-fn opts] (reify IDeref (-deref [_] @@ -145,7 +155,10 @@ IReset (-reset! [_ new-value] (if (nil? new-value) - (mf/set-ref-val! internal-state (if (fn? initial) (initial) initial)) + (let [initial (-> (mf/ref-val internal-state) + (get :initial) + (make-initial-state))] + (mf/set-ref-val! internal-state initial)) (mf/set-ref-val! internal-state new-value)) (rerender-fn)) @@ -176,26 +189,20 @@ initial (mf/with-memo [initial] - {:data (if (fn? initial) (initial) initial) - :errors {} - :touched {}}) + (make-initial-state initial)) internal-state - (mf/use-ref nil) + (mf/use-ref initial) form-mutator - (mf/with-memo [initial schema validators] + (mf/with-memo [schema validators] (let [mutator (create-form-mutator internal-state rerender-fn wrap-update-schema-fn - initial (select-keys opts [:schema :validators]))] (swap! mutator identity) mutator))] - (mf/with-effect [initial] - (mf/set-ref-val! internal-state initial)) - ;; Initialize internal state once - (mf/with-layout-effect [] + (mf/with-effect [] (mf/set-ref-val! internal-state initial)) (mf/with-effect [initial] diff --git a/frontend/src/app/util/text/content/styles.cljs b/frontend/src/app/util/text/content/styles.cljs index f37938525c..4b801ee864 100644 --- a/frontend/src/app/util/text/content/styles.cljs +++ b/frontend/src/app/util/text/content/styles.cljs @@ -187,19 +187,23 @@ style-value (normalize-style-value style-name v)] (assoc acc style-name style-value)))) {} style-defaults))) +(def mixed-values #{:mixed :multiple "mixed" "multiple"}) + (defn get-styles-from-style-declaration "Returns a ClojureScript object compatible with text nodes" - [style-declaration] + [style-declaration & {:keys [removed-mixed] :or {removed-mixed false}}] (reduce (fn [acc k] (if (contains? mapping k) (let [style-name (get-style-name-as-css-variable k) [_ style-decode] (get mapping k) style-value (.getPropertyValue style-declaration style-name)] - (assoc acc k (style-decode style-value))) + (when (or (not removed-mixed) (not (contains? mixed-values style-value))) + (assoc acc k (style-decode style-value)))) (let [style-name (get-style-name k) style-value (normalize-attr-value k (.getPropertyValue style-declaration style-name))] - (assoc acc k style-value)))) {} txt/text-style-attrs)) + (when (or (not removed-mixed) (not (contains? mixed-values style-value))) + (assoc acc k style-value))))) {} txt/text-style-attrs)) (defn get-styles-from-event "Returns a ClojureScript object compatible with text nodes" diff --git a/library/CHANGES.md b/library/CHANGES.md index e136c7759c..ac3e4ca0bd 100644 --- a/library/CHANGES.md +++ b/library/CHANGES.md @@ -1,5 +1,11 @@ # CHANGELOG + +## 1.2.0-RC1 + +- Add the ability to add relations (with `addRelation` method) + + ## 1.1.0 - Same as 1.1.0-RC2 diff --git a/library/package.json b/library/package.json index 3eb2bac236..ab32814487 100644 --- a/library/package.json +++ b/library/package.json @@ -1,9 +1,9 @@ { "name": "@penpot/library", - "version": "1.1.0", + "version": "1.2.0-RC1", "license": "MPL-2.0", "author": "Kaleidos INC", - "packageManager": "yarn@4.11.0+sha512.4e54aeace9141df2f0177c266b05ec50dc044638157dae128c471ba65994ac802122d7ab35bcd9e81641228b7dcf24867d28e750e0bcae8a05277d600008ad54", + "packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8", "type": "module", "repository": { "type": "git", diff --git a/library/playground/sample-relations.js b/library/playground/sample-relations.js new file mode 100644 index 0000000000..ab2350c812 --- /dev/null +++ b/library/playground/sample-relations.js @@ -0,0 +1,30 @@ +import * as penpot from "#self"; +import { writeFile, readFile } from "fs/promises"; + +(async function () { + const context = penpot.createBuildContext(); + + { + const file1 = context.addFile({ name: "Test File 1" }); + const file2 = context.addFile({ name: "Test File 1" }); + + context.addRelation(file1, file2); + } + + { + let result = await penpot.exportAsBytes(context); + await writeFile("sample-relations.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); + }); diff --git a/library/src/lib/builder.cljs b/library/src/lib/builder.cljs index 316631d8d4..4cc8cc97e0 100644 --- a/library/src/lib/builder.cljs +++ b/library/src/lib/builder.cljs @@ -87,7 +87,8 @@ (try (let [params (-> params decode-params fb/decode-file)] (-> (swap! state fb/add-file params) - (get ::fb/current-file-id))) + (get ::fb/current-file-id) + (dm/str))) (catch :default cause (handle-exception cause)))) @@ -273,6 +274,16 @@ (catch :default cause (handle-exception cause)))) + :addRelation + (fn [file-id library-id] + (let [file-id (uuid/parse file-id) + library-id (uuid/parse library-id)] + (if (and file-id library-id) + (do + (swap! state update :relations assoc file-id library-id) + true) + false))) + :genId (fn [] (dm/str (uuid/next))) diff --git a/library/src/lib/export.cljs b/library/src/lib/export.cljs index 72c779c24e..1c0e883d0e 100644 --- a/library/src/lib/export.cljs +++ b/library/src/lib/export.cljs @@ -194,7 +194,8 @@ :generated-by "penpot-library/%version%" :referer (get opts :referer) :files files - :relations []} + :relations (->> (:relations state) + (mapv vec))} params (d/without-nils params)] ["manifest.json" diff --git a/library/test/builder.test.js b/library/test/builder.test.js index 207f863e35..e4c7bf8a37 100644 --- a/library/test/builder.test.js +++ b/library/test/builder.test.js @@ -54,6 +54,33 @@ test("create context with two file", () => { assert.equal(file.data.pages.length, 0) }); +test("create context with two file and relation between", () => { + const context = penpot.createBuildContext(); + + const fileId_1 = context.addFile({name: "sample 1"}); + const fileId_2 = context.addFile({name: "sample 2"}); + + context.addRelation(fileId_1, fileId_2); + + const internalState = context.getInternalState(); + + assert.ok(internalState.files[fileId_1]); + assert.ok(internalState.files[fileId_2]); + assert.equal(internalState.files[fileId_1].name, "sample 1"); + assert.equal(internalState.files[fileId_2].name, "sample 2"); + + assert.ok(internalState.relations[fileId_1]); + assert.equal(internalState.relations[fileId_1], fileId_2); + + const file = internalState.files[fileId_2]; + + assert.ok(file.data); + assert.ok(file.data.pages); + assert.ok(file.data.pagesIndex); + assert.equal(file.data.pages.length, 0) +}); + + test("create context with file and page", () => { const context = penpot.createBuildContext();