mirror of
https://github.com/penpot/penpot.git
synced 2025-06-03 03:41:38 +02:00
Merge remote-tracking branch 'penpot/develop' into token-studio-develop
This commit is contained in:
commit
dc14933f3a
116 changed files with 7413 additions and 6245 deletions
|
@ -4,7 +4,7 @@
|
|||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"packageManager": "yarn@4.2.2",
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
|
@ -29,15 +29,16 @@
|
|||
"translations:validate": "node ./scripts/validate-translations.js",
|
||||
"translations:find-unused": "node ./scripts/find-unused-translations.js",
|
||||
"compile": "node ./scripts/compile.js",
|
||||
"compile:cljs": "clojure -M:dev:shadow-cljs compile main",
|
||||
"watch": "node ./scripts/watch.js",
|
||||
"e2e:server": "node ./scripts/e2e-server.js",
|
||||
"e2e:test": "playwright test",
|
||||
"storybook:compile": "gulp template:storybook && clojure -M:dev:shadow-cljs compile storybook",
|
||||
"storybook:watch": "npm run storybook:compile && concurrently \"clojure -M:dev:shadow-cljs watch storybook\" \"storybook dev -p 6006\"",
|
||||
"storybook:build": "npm run storybook:compile && storybook build"
|
||||
"e2e:test": "playwright test --project default",
|
||||
"storybook:compile": "yarn run compile && clojure -M:dev:shadow-cljs compile storybook",
|
||||
"storybook:watch": "yarn run storybook:compile && concurrently \"clojure -M:dev:shadow-cljs watch storybook\" \"storybook dev -p 6006\" \"yarn run watch\"",
|
||||
"storybook:build": "yarn run storybook:compile && storybook build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@storybook/addon-essentials": "^7.6.17",
|
||||
"@storybook/addon-interactions": "^7.6.17",
|
||||
"@storybook/addon-links": "^7.6.17",
|
||||
|
@ -50,7 +51,7 @@
|
|||
"animate.css": "^4.1.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"concurrently": "^8.2.2",
|
||||
"draft-js": "git+https://github.com/penpot/draft-js.git",
|
||||
"draft-js": "git+https://github.com/penpot/draft-js.git#commit=4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0",
|
||||
"express": "^4.19.2",
|
||||
"fancy-log": "^2.0.0",
|
||||
"gettext-parser": "^8.0.0",
|
||||
|
@ -99,8 +100,8 @@
|
|||
"opentype.js": "^1.3.4",
|
||||
"postcss-modules": "^6.0.0",
|
||||
"randomcolor": "^0.6.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"rxjs": "8.0.0-alpha.14",
|
||||
"sax": "^1.3.0",
|
||||
|
|
|
@ -19,6 +19,10 @@ export default defineConfig({
|
|||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests by default; can be overriden with --workers */
|
||||
workers: 1,
|
||||
/* Timeout for expects (longer in CI) */
|
||||
expect: {
|
||||
timeout: process.env.CI ? 20000 : 5000,
|
||||
},
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
|
@ -35,8 +39,17 @@ export default defineConfig({
|
|||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
name: "default",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
testDir: "./playwright/ui/specs",
|
||||
},
|
||||
{
|
||||
name: "ds",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
testDir: "./playwright/ui/visual-specs",
|
||||
expect: {
|
||||
toHaveScreenshot: { maxDiffPixelRatio: 0.01 },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
this.viewport = page.getByTestId("viewport");
|
||||
this.rootShape = page.locator(`[id="shape-00000000-0000-0000-0000-000000000000"]`);
|
||||
this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" });
|
||||
this.colorpicker = page.getByTestId("colorpicker");
|
||||
}
|
||||
|
||||
async goToWorkspace() {
|
||||
|
|
22
frontend/playwright/ui/specs/colorpicker.spec.js
Normal file
22
frontend/playwright/ui/specs/colorpicker.spec.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../pages/WorkspacePage";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
});
|
||||
|
||||
// Fix for https://tree.taiga.io/project/penpot/issue/7549
|
||||
test("Bug 7549 - User clicks on color swatch to display the color picker next to it", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
|
||||
await workspacePage.goToWorkspace();
|
||||
const swatch = workspacePage.page.getByRole("button", { name: "E8E9EA" });
|
||||
const swatchBox = await swatch.boundingBox();
|
||||
await swatch.click();
|
||||
|
||||
await expect(workspacePage.colorpicker).toBeVisible();
|
||||
const pickerBox = await workspacePage.colorpicker.boundingBox();
|
||||
const distance = swatchBox.x - (pickerBox.x + pickerBox.width);
|
||||
expect(distance).toBeLessThan(60);
|
||||
});
|
10
frontend/playwright/ui/visual-specs/example.spec.js
Normal file
10
frontend/playwright/ui/visual-specs/example.spec.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { LoginPage } from "../pages/LoginPage";
|
||||
|
||||
test("Shows login form correctly", async ({ page }) => {
|
||||
await LoginPage.initWithLoggedOutUser(page);
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto("/#/auth/login");
|
||||
|
||||
await expect(page).toHaveScreenshot();
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 152 KiB |
1
frontend/resources/images/icons/external-link.svg
Normal file
1
frontend/resources/images/icons/external-link.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="42" xmlns="http://www.w3.org/2000/svg" height="42" stroke-width="3.5"><path d="M35 23.333v14A4.666 4.666 0 0 1 30.333 42H4.667A4.666 4.666 0 0 1 0 37.333V11.667A4.666 4.666 0 0 1 4.667 7h14"/><path d="M28 0h14v14"/><path d="M16.333 25.667 42 0"/></svg>
|
After Width: | Height: | Size: 265 B |
1
frontend/resources/images/icons/rocket.svg
Normal file
1
frontend/resources/images/icons/rocket.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="m10.165 3.902.117 8.274c.018 1.266-1.134 2.005-2.182 2.02-1.102.015-2.218-.59-2.238-1.957l-.116-8.275L7.9.031s1.823 2.992 2.265 3.871Z"/><circle cx="8" cy="5.604" r=".753"/><path d="M6.109 8.064c-3.276 2.163-3.18 4.351-3.18 7.936l3.121-3.934"/><path d="M9.891 8.064c3.276 2.163 3.18 4.351 3.18 7.936L9.95 12.066"/></svg>
|
After Width: | Height: | Size: 393 B |
File diff suppressed because it is too large
Load diff
|
@ -593,6 +593,9 @@
|
|||
width: 100%;
|
||||
z-index: $z-index-modal;
|
||||
background-color: var(--overlay-color);
|
||||
&.onboarding-a-b-test {
|
||||
background-color: var(--overlay-color-onboarding-a-b-test);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-container-base {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
// Dark background
|
||||
--db-primary: #18181a;
|
||||
--db-primary-60: #{color.change(#18181a, $alpha: 0.6)};
|
||||
--db-primary-90: #{color.change(#18181a, $alpha: 0.9)};
|
||||
--db-secondary: #000000;
|
||||
--db-secondary-30: #{color.change(#000000, $alpha: 0.3)};
|
||||
--db-secondary-80: #{color.change(#000000, $alpha: 0.8)};
|
||||
|
@ -35,6 +36,7 @@
|
|||
// Light background
|
||||
--lb-primary: #ffffff;
|
||||
--lb-primary-60: #{color.change(#ffffff, $alpha: 0.6)};
|
||||
--lb-primary-90: #{color.change(#ffffff, $alpha: 0.9)};
|
||||
--lb-secondary: #e8eaee;
|
||||
--lb-secondary-30: #{color.change(#e8eaee, $alpha: 0.3)};
|
||||
--lb-secondary-80: #{color.change(#e8eaee, $alpha: 0.8)};
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
--color-info-foreground: var(--status-color-info-500);
|
||||
|
||||
--overlay-color: var(--db-primary-60);
|
||||
--overlay-color-onboarding-a-b-test: var(--db-primary-90);
|
||||
|
||||
--shadow-color: var(--db-secondary-30);
|
||||
--radio-button-box-shadow: 0 0 0 1px var(--db-secondary-30) inset;
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
--color-info-foreground: var(--status-color-info-500);
|
||||
|
||||
--overlay-color: var(--lb-primary-60);
|
||||
--overlay-color-onboarding-a-b-test: var(--lb-primary-90);
|
||||
|
||||
--shadow-color: var(--lf-secondary-40);
|
||||
--radio-button-box-shadow: 0 0 0 1px var(--lb-secondary) inset;
|
||||
|
||||
|
|
|
@ -130,6 +130,10 @@
|
|||
(def worker-uri
|
||||
(obj/get global "penpotWorkerURI" "/js/worker.js"))
|
||||
|
||||
(defn external-feature-flag [flag value]
|
||||
(when-let [fn (obj/get global "externalFeatureFlag")]
|
||||
(fn flag value)))
|
||||
|
||||
;; --- Helper Functions
|
||||
|
||||
(defn ^boolean check-browser? [candidate]
|
||||
|
|
|
@ -249,9 +249,18 @@
|
|||
(deleteObject [_ id]
|
||||
(set! file (fb/delete-object file (uuid/uuid id))))
|
||||
|
||||
(getId [_]
|
||||
(:id file))
|
||||
|
||||
(getCurrentPageId [_]
|
||||
(:current-page-id file))
|
||||
|
||||
(asMap [_]
|
||||
(clj->js file))
|
||||
|
||||
(newId [_]
|
||||
(uuid/next))
|
||||
|
||||
(export [_]
|
||||
(->> (export-file file)
|
||||
(rx/subs!
|
||||
|
@ -261,7 +270,8 @@
|
|||
(dom/trigger-download (:name file) export-blob))))))))
|
||||
|
||||
(defn create-file-export [^string name]
|
||||
(File. (fb/create-file name)))
|
||||
(binding [cfeat/*current* cfeat/default-features]
|
||||
(File. (fb/create-file name))))
|
||||
|
||||
(defn exports []
|
||||
#js {:createFile create-file-export})
|
||||
|
|
|
@ -343,9 +343,9 @@
|
|||
(watch [_ state _]
|
||||
(let [on-success (:on-success opts identity)
|
||||
on-error (:on-error opts rx/throw)
|
||||
profile (:profile state)]
|
||||
|
||||
(->> (rp/cmd! :update-profile (dissoc profile :props))
|
||||
profile (:profile state)
|
||||
params (select-keys profile [:fullname :lang :theme])]
|
||||
(->> (rp/cmd! :update-profile params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
|
|
@ -119,7 +119,14 @@
|
|||
(->> stream
|
||||
(rx/filter (ptk/type? :layout/update))
|
||||
(rx/map deref)
|
||||
(rx/map #(update-layout-positions %))
|
||||
;; We buffer the updates to the layout so if there are many changes at the same time
|
||||
;; they are process together. It will get a better performance.
|
||||
(rx/buffer-time 100)
|
||||
(rx/filter #(d/not-empty? %))
|
||||
(rx/map
|
||||
(fn [data]
|
||||
(let [ids (reduce #(into %1 (:ids %2)) #{} data)]
|
||||
(update-layout-positions {:ids ids}))))
|
||||
(rx/take-until stopper))))))
|
||||
|
||||
(defn finalize
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
:is-transparent (and opacity (> 1 opacity))
|
||||
:grid-area area
|
||||
:read-only read-only?)
|
||||
:role "button"
|
||||
:data-readonly (str read-only?)
|
||||
:on-click on-click
|
||||
:title (color-title color)}
|
||||
|
|
|
@ -144,6 +144,7 @@
|
|||
(def ^:icon img (icon-xref :img))
|
||||
(def ^:icon interaction (icon-xref :interaction))
|
||||
(def ^:icon join-nodes (icon-xref :join-nodes))
|
||||
(def ^:icon external-link (icon-xref :external-link))
|
||||
(def ^:icon justify-content-column-around (icon-xref :justify-content-column-around))
|
||||
(def ^:icon justify-content-column-between (icon-xref :justify-content-column-between))
|
||||
(def ^:icon justify-content-column-center (icon-xref :justify-content-column-center))
|
||||
|
@ -160,13 +161,13 @@
|
|||
(def ^:icon library (icon-xref :library))
|
||||
(def ^:icon locate (icon-xref :locate))
|
||||
(def ^:icon lock (icon-xref :lock))
|
||||
(def ^:icon margin (icon-xref :margin))
|
||||
(def ^:icon margin-bottom (icon-xref :margin-bottom))
|
||||
(def ^:icon margin-left (icon-xref :margin-left))
|
||||
(def ^:icon margin-left-right (icon-xref :margin-left-right))
|
||||
(def ^:icon margin-right (icon-xref :margin-right))
|
||||
(def ^:icon margin-top-bottom (icon-xref :margin-top-bottom))
|
||||
(def ^:icon margin-top (icon-xref :margin-top))
|
||||
(def ^:icon margin (icon-xref :margin))
|
||||
(def ^:icon margin-top-bottom (icon-xref :margin-top-bottom))
|
||||
(def ^:icon mask (icon-xref :mask))
|
||||
(def ^:icon masked (icon-xref :masked))
|
||||
(def ^:icon menu (icon-xref :menu))
|
||||
|
@ -179,11 +180,11 @@
|
|||
(def ^:icon open-link (icon-xref :open-link))
|
||||
(def ^:icon padding-bottom (icon-xref :padding-bottom))
|
||||
(def ^:icon padding-extended (icon-xref :padding-extended))
|
||||
(def ^:icon padding-left-right (icon-xref :padding-left-right))
|
||||
(def ^:icon padding-left (icon-xref :padding-left))
|
||||
(def ^:icon padding-left-right (icon-xref :padding-left-right))
|
||||
(def ^:icon padding-right (icon-xref :padding-right))
|
||||
(def ^:icon padding-top-bottom (icon-xref :padding-top-bottom))
|
||||
(def ^:icon padding-top (icon-xref :padding-top))
|
||||
(def ^:icon padding-top-bottom (icon-xref :padding-top-bottom))
|
||||
(def ^:icon path (icon-xref :path))
|
||||
(def ^:icon pentool (icon-xref :pentool))
|
||||
(def ^:icon picker (icon-xref :picker))
|
||||
|
@ -192,11 +193,12 @@
|
|||
(def ^:icon rectangle (icon-xref :rectangle))
|
||||
(def ^:icon reload (icon-xref :reload))
|
||||
(def ^:icon remove-icon (icon-xref :remove))
|
||||
(def ^:icon rgba-complementary (icon-xref :rgba-complementary))
|
||||
(def ^:icon rgba (icon-xref :rgba))
|
||||
(def ^:icon rgba-complementary (icon-xref :rgba-complementary))
|
||||
(def ^:icon rocket (icon-xref :rocket))
|
||||
(def ^:icon rotation (icon-xref :rotation))
|
||||
(def ^:icon row-reverse (icon-xref :row-reverse))
|
||||
(def ^:icon row (icon-xref :row))
|
||||
(def ^:icon row-reverse (icon-xref :row-reverse))
|
||||
(def ^:icon search (icon-xref :search))
|
||||
(def ^:icon separate-nodes (icon-xref :separate-nodes))
|
||||
(def ^:icon shown (icon-xref :shown))
|
||||
|
@ -218,6 +220,7 @@
|
|||
(def ^:icon svg (icon-xref :svg))
|
||||
(def ^:icon swatches (icon-xref :swatches))
|
||||
(def ^:icon switch (icon-xref :switch))
|
||||
(def ^:icon text (icon-xref :text))
|
||||
(def ^:icon text-align-center (icon-xref :text-align-center))
|
||||
(def ^:icon text-align-left (icon-xref :text-align-left))
|
||||
(def ^:icon text-align-right (icon-xref :text-align-right))
|
||||
|
@ -239,7 +242,6 @@
|
|||
(def ^:icon text-top (icon-xref :text-top))
|
||||
(def ^:icon text-underlined (icon-xref :text-underlined))
|
||||
(def ^:icon text-uppercase (icon-xref :text-uppercase))
|
||||
(def ^:icon text (icon-xref :text))
|
||||
(def ^:icon thumbnail (icon-xref :thumbnail))
|
||||
(def ^:icon tick (icon-xref :tick))
|
||||
(def ^:icon to-corner (icon-xref :to-corner))
|
||||
|
@ -258,7 +260,6 @@
|
|||
(def ^:icon view-as-list (icon-xref :view-as-list))
|
||||
(def ^:icon wrap (icon-xref :wrap))
|
||||
|
||||
|
||||
(def ^:icon loader-pencil
|
||||
(mf/html
|
||||
[:svg
|
||||
|
|
|
@ -142,7 +142,9 @@
|
|||
(modal/show! {:type :onboarding-newsletter})
|
||||
|
||||
(contains? cf/flags :onboarding-team)
|
||||
(modal/show! {:type :onboarding-team}))))]
|
||||
(modal/show! {:type :onboarding-team}))))
|
||||
|
||||
onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
|
||||
(mf/with-effect [@slide]
|
||||
(when (not= :start @slide)
|
||||
|
@ -151,8 +153,8 @@
|
|||
(fn []
|
||||
(reset! klass nil)
|
||||
(tm/dispose! sem))))
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class (dm/str @klass " " (stl/css :animated))}
|
||||
(case @slide
|
||||
:start [:& onboarding-welcome {:next #(navigate :opensource)}]
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.onboarding.newsletter
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.config :as cf]
|
||||
[app.main.data.messages :as msg]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.users :as du]
|
||||
|
@ -35,9 +36,11 @@
|
|||
(st/emit! (when (or @newsletter-updates @newsletter-news)
|
||||
(msg/success message))
|
||||
(modal/show {:type :onboarding-team})
|
||||
(du/update-profile-props {:newsletter-updates @newsletter-updates :newsletter-news @newsletter-news}))))]
|
||||
(du/update-profile-props {:newsletter-updates @newsletter-updates :newsletter-news @newsletter-news}))))
|
||||
onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated.fadeInDown {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-left)}
|
||||
[:img {:src "images/deco-newsletter.png"
|
||||
|
|
|
@ -287,9 +287,11 @@
|
|||
(modal/show! {:type :onboarding-team})
|
||||
|
||||
:else
|
||||
(modal/hide!)))))]
|
||||
(modal/hide!)))))
|
||||
onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div {:class (stl/css :modal-container)
|
||||
:ref container}
|
||||
(case @step
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dmc]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.messages :as msg]
|
||||
|
@ -84,14 +85,16 @@
|
|||
::ev/origin "onboarding"
|
||||
:step 1}))))
|
||||
|
||||
teams (mf/deref refs/teams)]
|
||||
teams (mf/deref refs/teams)
|
||||
onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
|
||||
(mf/with-effect [teams]
|
||||
(when (> (count teams) 1)
|
||||
(st/emit! (modal/hide))))
|
||||
|
||||
(when (< (count teams) 2)
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated.fadeIn {:class (stl/css :modal-container)}
|
||||
[:& team-modal-left]
|
||||
[:div {:class (stl/css :separator)}]
|
||||
|
@ -212,9 +215,11 @@
|
|||
(if (> (count emails) 0)
|
||||
(on-invite-now form)
|
||||
(on-invite-later form))
|
||||
(modal/hide!))))]
|
||||
(modal/hide!))))
|
||||
onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated.fadeIn {:class (stl/css :modal-container)}
|
||||
[:& team-modal-left]
|
||||
|
||||
|
|
|
@ -8,196 +8,203 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.config :as cf]
|
||||
[app.main.ui.releases.common :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; TODO: Review all copies and alt text
|
||||
(defmethod c/render-release-notes "2.0"
|
||||
[{:keys [slide klass next finish navigate version]}]
|
||||
(mf/html
|
||||
(case slide
|
||||
:start
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-intro-image.png"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "A graphic illustration with Penpot style"}]
|
||||
(let [onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
(mf/html
|
||||
(case slide
|
||||
:start
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-intro-image.png"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "A graphic illustration with Penpot style"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Welcome to Penpot 2.0! "]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Welcome to Penpot 2.0! "]
|
||||
|
||||
[:div {:class (stl/css :version-tag)}
|
||||
(dm/str "Version " version)]]
|
||||
[:div {:class (stl/css :version-tag)}
|
||||
(dm/str "Version " version)]]
|
||||
|
||||
[:div {:class (stl/css :features-block)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"CSS Grid Layout: "]
|
||||
"Bring your designs to life, knowing that what you create is what developers code."]
|
||||
[:div {:class (stl/css :features-block)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"CSS Grid Layout: "]
|
||||
"Bring your designs to life, knowing that what you create is what developers code."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"Sleeker UI: "]
|
||||
"We’ve polished Penpot to make your experience smoother and more enjoyable."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"Sleeker UI: "]
|
||||
"We’ve polished Penpot to make your experience smoother and more enjoyable."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"New Components System: "]
|
||||
"Managing and using your design components got a whole lot better."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"New Components System: "]
|
||||
"Managing and using your design components got a whole lot better."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"And that’s not all - we’ve fined tuned performance and "
|
||||
"accessibility to give you a better and more fluid design experience."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"And that’s not all - we’ve fined tuned performance and "
|
||||
"accessibility to give you a better and more fluid design experience."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
" Ready to dive in? Let 's get started!"]]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
" Ready to dive in? Let 's get started!"]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:button {:class (stl/css :next-btn)
|
||||
:on-click next} "Continue"]]]]]]
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:button {:class (stl/css :next-btn)
|
||||
:on-click next} "Continue"]]]]]]
|
||||
|
||||
0
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-css-grid.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's CSS Grid Layout"}]
|
||||
0
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-css-grid.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's CSS Grid Layout"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"CSS Grid Layout - Design Meets Development"]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"CSS Grid Layout - Design Meets Development"]]
|
||||
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The much-awaited Grid Layout introduces 2-dimensional"
|
||||
" layout capabilities to Penpot, allowing for the creation"
|
||||
" of adaptive layouts by leveraging the power of CSS properties."]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The much-awaited Grid Layout introduces 2-dimensional"
|
||||
" layout capabilities to Penpot, allowing for the creation"
|
||||
" of adaptive layouts by leveraging the power of CSS properties."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"It’s a host of new features, including columns and"
|
||||
" rows management, flexible units such as FR (fractions),"
|
||||
" the ability to create and name areas, and tons of new "
|
||||
"and unique possibilities within a design tool."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"It’s a host of new features, including columns and"
|
||||
" rows management, flexible units such as FR (fractions),"
|
||||
" the ability to create and name areas, and tons of new "
|
||||
"and unique possibilities within a design tool."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Designers will learn CSS basics while working, "
|
||||
"and as always with Penpot, developers can pick"
|
||||
" up the design as code to take it from there."]]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Designers will learn CSS basics while working, "
|
||||
"and as always with Penpot, developers can pick"
|
||||
" up the design as code to take it from there."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
1
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-new-ui.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's UI Makeover"}]
|
||||
1
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-new-ui.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's UI Makeover"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"UI Makeover - Smoother, Sharper, and Simply More Fun"]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"UI Makeover - Smoother, Sharper, and Simply More Fun"]]
|
||||
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"We've completely overhauled Penpot's user interface. "
|
||||
"The improvements in consistency, the introduction of "
|
||||
"new microinteractions, and attention to countless details"
|
||||
" will significantly enhance the productivity and enjoyment of using Penpot."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Furthermore, we’ve made several accessibility improvements, "
|
||||
"with better color contrast, keyboard navigation,"
|
||||
" and adherence to other best practices."]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"We've completely overhauled Penpot's user interface. "
|
||||
"The improvements in consistency, the introduction of "
|
||||
"new microinteractions, and attention to countless details"
|
||||
" will significantly enhance the productivity and enjoyment of using Penpot."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Furthermore, we’ve made several accessibility improvements, "
|
||||
"with better color contrast, keyboard navigation,"
|
||||
" and adherence to other best practices."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
2
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-components.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's new components system"}]
|
||||
2
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-components.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's new components system"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"New Components System"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The new Penpot components system improves"
|
||||
" control over instances, including their "
|
||||
"inheritances and properties overrides. "
|
||||
"Main components are now accessible as design"
|
||||
" elements, allowing a better updating "
|
||||
"workflow through instant changes synchronization."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"And that’s not all, there are new capabilities "
|
||||
"such as component swapping and annotations "
|
||||
"that will help you to better manage your design systems."]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"New Components System"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The new Penpot components system improves"
|
||||
" control over instances, including their "
|
||||
"inheritances and properties overrides. "
|
||||
"Main components are now accessible as design"
|
||||
" elements, allowing a better updating "
|
||||
"workflow through instant changes synchronization."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"And that’s not all, there are new capabilities "
|
||||
"such as component swapping and annotations "
|
||||
"that will help you to better manage your design systems."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
3
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-html.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt " Penpot's HTML code generator"}]
|
||||
3
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-html.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt " Penpot's HTML code generator"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"And much more"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"In addition to all of this, we’ve included several other requested improvements:"]
|
||||
[:ul {:class (stl/css :feature-list)}
|
||||
[:li "Access HTML markup code directly in inspect mode"]
|
||||
[:li "Images are now treated as element fills, maintaining their aspect ratio on resize, ideal for flexible designs"]
|
||||
[:li "Enjoy new color themes with options for both dark and light modes"]
|
||||
[:li "Feel the speed boost! Enjoy a smoother experience with a bunch of performance improvements"]]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"And much more"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"In addition to all of this, we’ve included several other requested improvements:"]
|
||||
[:ul {:class (stl/css :feature-list)}
|
||||
[:li "Access HTML markup code directly in inspect mode"]
|
||||
[:li "Images are now treated as element fills, maintaining their aspect ratio on resize, ideal for flexible designs"]
|
||||
[:li "Enjoy new color themes with options for both dark and light modes"]
|
||||
[:li "Feel the speed boost! Enjoy a smoother experience with a bunch of performance improvements"]]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:div {:class (stl/css :navigation)}
|
||||
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
|
||||
[:button {:on-click finish
|
||||
:class (stl/css :next-btn)} "Let's go"]]]]]])))
|
||||
[:button {:on-click finish
|
||||
:class (stl/css :next-btn)} "Let's go"]]]]]]))))
|
||||
|
||||
|
|
|
@ -372,15 +372,14 @@
|
|||
(defn calculate-position
|
||||
"Calculates the style properties for the given coordinates and position"
|
||||
[{vh :height} position x y]
|
||||
(let [;; picker height in pixels
|
||||
h 510
|
||||
|
||||
(let [;; picker size in pixels
|
||||
h 510
|
||||
w 284
|
||||
;; Checks for overflow outside the viewport height
|
||||
max-y (- vh h)
|
||||
rulers? (mf/deref refs/rulers?)
|
||||
left-offset (if rulers? 40 18)
|
||||
|
||||
x-pos 400]
|
||||
right-offset (+ w 40)]
|
||||
|
||||
(cond
|
||||
(or (nil? x) (nil? y))
|
||||
|
@ -388,9 +387,9 @@
|
|||
|
||||
(= position :left)
|
||||
(if (> y max-y)
|
||||
#js {:left (dm/str (- x x-pos) "px")
|
||||
#js {:left (dm/str (- x right-offset) "px")
|
||||
:bottom "1rem"}
|
||||
#js {:left (dm/str (- x x-pos) "px")
|
||||
#js {:left (dm/str (- x right-offset) "px")
|
||||
:top (dm/str (- y 70) "px")})
|
||||
|
||||
(= position :right)
|
||||
|
@ -440,6 +439,7 @@
|
|||
(on-close @last-change)))
|
||||
|
||||
[:div {:class (stl/css :colorpicker-tooltip)
|
||||
:data-testid "colorpicker"
|
||||
:style style}
|
||||
|
||||
[:& colorpicker {:data data
|
||||
|
|
|
@ -629,13 +629,13 @@
|
|||
(when (d/not-empty? plugins)
|
||||
[:div {:class (stl/css :separator)}])
|
||||
|
||||
(for [[idx {:keys [name url]}] (d/enumerate plugins)]
|
||||
(for [[idx {:keys [name] :as manifest}] (d/enumerate plugins)]
|
||||
[:> dropdown-menu-item* {:key (dm/str "plugins-menu-" idx)
|
||||
:on-click #(uwp/open-plugin! url)
|
||||
:on-click #(uwp/open-plugin! manifest)
|
||||
:class (stl/css :submenu-item)
|
||||
:on-key-down (fn [event]
|
||||
(when (kbd/enter? event)
|
||||
#(uwp/open-plugin! url)))}
|
||||
#(uwp/open-plugin! manifest)))}
|
||||
[:span {:class (stl/css :item-name)} name]])])))
|
||||
|
||||
(mf/defc menu
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.main.ui.components.search-bar :refer [search-bar]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.avatars :as avatars]
|
||||
[app.util.http :as http]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
|
@ -24,26 +25,30 @@
|
|||
|
||||
|
||||
(mf/defc plugin-entry
|
||||
[{:keys [index _icon url name description on-open-plugin on-remove-plugin]}]
|
||||
[{:keys [index manifest on-open-plugin on-remove-plugin]}]
|
||||
|
||||
(let [handle-open-click
|
||||
(let [{:keys [host icon name description]} manifest
|
||||
handle-open-click
|
||||
(mf/use-callback
|
||||
(mf/deps index url on-open-plugin)
|
||||
(mf/deps index manifest on-open-plugin)
|
||||
(fn []
|
||||
(when on-open-plugin
|
||||
(on-open-plugin index url))))
|
||||
(on-open-plugin manifest))))
|
||||
|
||||
handle-delete-click
|
||||
(mf/use-callback
|
||||
(mf/deps index url on-remove-plugin)
|
||||
(mf/deps index on-remove-plugin)
|
||||
(fn []
|
||||
(when on-remove-plugin
|
||||
(on-remove-plugin index url))))]
|
||||
(on-remove-plugin index))))]
|
||||
[:div {:class (stl/css :plugins-list-element)}
|
||||
[:div {:class (stl/css :plugin-icon)} ""]
|
||||
[:div {:class (stl/css :plugin-icon)}
|
||||
[:img {:src (if (some? icon)
|
||||
(dm/str host icon)
|
||||
(avatars/generate {:name name}))}]]
|
||||
[:div {:class (stl/css :plugin-description)}
|
||||
[:div {:class (stl/css :plugin-title)} name]
|
||||
[:div {:class (stl/css :plugin-summary)} description]]
|
||||
[:div {:class (stl/css :plugin-summary)} (d/nilv description "")]]
|
||||
[:button {:class (stl/css :open-button)
|
||||
:on-click handle-open-click} (tr "workspace.plugins.button-open")]
|
||||
[:button {:class (stl/css :trash-button)
|
||||
|
@ -65,8 +70,15 @@
|
|||
(.setItem ls "plugins" plugins-val)))
|
||||
|
||||
(defn open-plugin!
|
||||
[url]
|
||||
(.ɵloadPlugin js/window #js {:manifest url}))
|
||||
[{:keys [name description host code icon permissions]}]
|
||||
(.ɵloadPlugin
|
||||
js/window #js
|
||||
{:name name
|
||||
:description description
|
||||
:host host
|
||||
:code code
|
||||
:icon icon
|
||||
:permissions (apply array permissions)}))
|
||||
|
||||
(mf/defc plugin-management-dialog
|
||||
{::mf/register modal/components
|
||||
|
@ -107,7 +119,20 @@
|
|||
(rx/subs!
|
||||
(fn [body]
|
||||
(let [name (obj/get body "name")
|
||||
new-state (conj plugins-state {:name name :url plugin-url})]
|
||||
desc (obj/get body "description")
|
||||
code (obj/get body "code")
|
||||
icon (obj/get body "icon")
|
||||
permissions (obj/get body "permissions")
|
||||
origin (obj/get (js/URL. plugin-url) "origin")
|
||||
|
||||
new-state
|
||||
(conj plugins-state
|
||||
{:name name
|
||||
:description desc
|
||||
:host origin
|
||||
:code code
|
||||
:icon icon
|
||||
:permissions (->> permissions (mapv str))})]
|
||||
(reset! input-status* :success)
|
||||
(reset! plugin-url* "")
|
||||
(reset! plugins-state* new-state)
|
||||
|
@ -117,18 +142,18 @@
|
|||
|
||||
handle-open-plugin
|
||||
(mf/use-callback
|
||||
(fn [_ url]
|
||||
(open-plugin! url)
|
||||
(fn [manifest]
|
||||
(open-plugin! manifest)
|
||||
(modal/hide!)))
|
||||
|
||||
handle-remove-plugin
|
||||
(mf/use-callback
|
||||
(mf/deps plugins-state)
|
||||
(fn [rm-idx _]
|
||||
(fn [plugin-index]
|
||||
(let [new-state
|
||||
(into []
|
||||
(keep-indexed (fn [idx item]
|
||||
(when (not= idx rm-idx) item)))
|
||||
(when (not= idx plugin-index) item)))
|
||||
plugins-state)]
|
||||
|
||||
(reset! plugins-state* new-state)
|
||||
|
@ -160,22 +185,22 @@
|
|||
|
||||
[:hr]
|
||||
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.plugins.installed-plugins")}]
|
||||
|
||||
(if (empty? plugins-state)
|
||||
[:div {:class (stl/css :plugins-empty)}
|
||||
[:div {:class (stl/css :plugins-empty-logo)} i/logo-icon]
|
||||
[:div {:class (stl/css :plugins-empty-text)} (tr "workspace.plugins.empty-plugins")]]
|
||||
[:div {:class (stl/css :plugins-empty-logo)} i/rocket]
|
||||
[:div {:class (stl/css :plugins-empty-text)} (tr "workspace.plugins.empty-plugins")]
|
||||
[:a {:class (stl/css :plugins-link) :href "#"}
|
||||
(tr "workspace.plugins.plugin-list-link") i/external-link]]
|
||||
|
||||
[:div {:class (stl/css :plugins-list)}
|
||||
[:*
|
||||
[:& title-bar {:collapsable false
|
||||
:title (tr "workspace.plugins.installed-plugins")}]
|
||||
|
||||
(for [[idx {:keys [name url]}] (d/enumerate plugins-state)]
|
||||
[:& plugin-entry {:key (dm/str "plugin-" idx)
|
||||
:name name
|
||||
:url url
|
||||
:index idx
|
||||
:icon nil
|
||||
:description "Nullam ullamcorper ligula ac felis commodo pulvinar."
|
||||
:on-open-plugin handle-open-plugin
|
||||
:on-remove-plugin handle-remove-plugin}])])]]]))
|
||||
[:div {:class (stl/css :plugins-list)}
|
||||
|
||||
(for [[idx manifest] (d/enumerate plugins-state)]
|
||||
[:& plugin-entry {:key (dm/str "plugin-" idx)
|
||||
:index idx
|
||||
:manifest manifest
|
||||
:on-open-plugin handle-open-plugin
|
||||
:on-remove-plugin handle-remove-plugin}])]])]]]))
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
max-height: $s-472;
|
||||
width: $s-472;
|
||||
max-width: $s-472;
|
||||
|
||||
hr {
|
||||
border-color: $db-tertiary;
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
|
@ -31,7 +35,7 @@
|
|||
|
||||
.modal-title {
|
||||
@include headlineMediumTypography;
|
||||
margin-block-end: $s-16;
|
||||
margin-block-end: $s-32;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
|
@ -39,6 +43,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: $s-380;
|
||||
padding-bottom: $s-16;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
|
@ -88,7 +93,7 @@
|
|||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: $s-12;
|
||||
}
|
||||
|
||||
.plugins-list-element {
|
||||
|
@ -101,56 +106,61 @@
|
|||
min-height: $s-32;
|
||||
width: $s-32;
|
||||
height: $s-32;
|
||||
background: #b1b2b5;
|
||||
background: var(--button-secondary-background-color-rest);
|
||||
padding: $s-2;
|
||||
border-radius: $s-4;
|
||||
}
|
||||
|
||||
.plugin-description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $s-8;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plugin-title {
|
||||
@include bodyMediumTypography;
|
||||
color: #ffffff;
|
||||
color: $df-primary;
|
||||
}
|
||||
|
||||
.plugin-summary {
|
||||
@include bodySmallTypography;
|
||||
color: #8f9da3;
|
||||
color: $df-secondary;
|
||||
}
|
||||
|
||||
.plugins-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-top: 3rem;
|
||||
gap: $s-20;
|
||||
margin-top: $s-16;
|
||||
}
|
||||
|
||||
.plugins-empty-logo {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
width: $s-44;
|
||||
height: $s-44;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #212426;
|
||||
background: $db-tertiary;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: #8f9da3;
|
||||
width: $s-16;
|
||||
height: $s-16;
|
||||
fill: none;
|
||||
stroke: $df-secondary;
|
||||
stroke-width: 0.8px;
|
||||
}
|
||||
}
|
||||
|
||||
.plugins-empty-text {
|
||||
@include bodySmallTypography;
|
||||
color: white;
|
||||
color: $df-primary;
|
||||
}
|
||||
|
||||
div.input-error {
|
||||
border: 1px solid var(--input-border-color-error);
|
||||
border: $s-1 solid var(--input-border-color-error);
|
||||
}
|
||||
|
||||
.info {
|
||||
|
@ -165,3 +175,19 @@ div.input-error {
|
|||
color: var(--input-border-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
.plugins-link {
|
||||
color: $da-primary;
|
||||
font-size: $fs-12;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $s-4;
|
||||
|
||||
svg {
|
||||
margin-top: calc(-1 * var($s-2));
|
||||
width: $s-12;
|
||||
height: $s-12;
|
||||
stroke: $da-primary;
|
||||
fill: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,20 @@
|
|||
[app.main.store :as st]
|
||||
[app.plugins.api :as api]
|
||||
[app.util.globals :refer [global]]
|
||||
[app.util.object :as obj]))
|
||||
[app.util.object :as obj]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn init!
|
||||
[]
|
||||
(when (features/active-feature? @st/state "plugins/runtime")
|
||||
(when-let [init-runtime (obj/get global "initPluginsRuntime")]
|
||||
(let [context (api/create-context)]
|
||||
(init-runtime context)))))
|
||||
(->> st/stream
|
||||
(rx/filter (ptk/type? ::features/initialize))
|
||||
(rx/take 1)
|
||||
;; We need to wait to the init event to finish
|
||||
(rx/observe-on :async)
|
||||
(rx/subs!
|
||||
(fn []
|
||||
(when (features/active-feature? @st/state "plugins/runtime")
|
||||
(when-let [init-runtime (obj/get global "initPluginsRuntime")]
|
||||
(let [context (api/create-context)]
|
||||
(init-runtime context))))))))
|
||||
|
|
|
@ -52,6 +52,10 @@
|
|||
[_ type callback]
|
||||
(events/add-listener type callback))
|
||||
|
||||
(removeListener
|
||||
[_ listener-id]
|
||||
(events/remove-listener listener-id))
|
||||
|
||||
(getViewport
|
||||
[_]
|
||||
(viewport/create-proxy))
|
||||
|
|
|
@ -14,6 +14,14 @@
|
|||
|
||||
(defmulti handle-state-change (fn [type _] type))
|
||||
|
||||
(defmethod handle-state-change "finish"
|
||||
[_ old-val new-val]
|
||||
(let [old-file-id (:current-file-id old-val)
|
||||
new-file-id (:current-file-id new-val)]
|
||||
(if (and (some? old-file-id) (nil? new-file-id))
|
||||
(str old-file-id)
|
||||
::not-changed)))
|
||||
|
||||
(defmethod handle-state-change "filechange"
|
||||
[_ old-val new-val]
|
||||
(let [old-file (:workspace-file old-val)
|
||||
|
@ -72,3 +80,6 @@
|
|||
;; return the generated key
|
||||
key))
|
||||
|
||||
(defn remove-listener
|
||||
[key]
|
||||
(remove-watch st/state key))
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
(ns app.plugins.library
|
||||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.record :as cr]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.utils :as u]))
|
||||
|
||||
|
@ -53,13 +55,32 @@
|
|||
{:name "id" :get (fn [_] (dm/str id))}
|
||||
|
||||
{:name "name"
|
||||
:get #(-> % u/proxy->library-color :name)}
|
||||
:get #(-> % u/proxy->library-color :name)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(if (and (some? value) (string? value))
|
||||
(st/emit! (dwl/rename-color file-id id value))
|
||||
(u/display-not-valid :library-color-name value)))}
|
||||
|
||||
{:name "color"
|
||||
:get #(-> % u/proxy->library-color :color)}
|
||||
:get #(-> % u/proxy->library-color :color)
|
||||
:set
|
||||
(fn [self value]
|
||||
(if (and (some? value) (string? value) (cc/valid-hex-color? value))
|
||||
(let [color (-> (u/proxy->library-color self)
|
||||
(assoc :color value))]
|
||||
(st/emit! (dwl/update-color color file-id)))
|
||||
(u/display-not-valid :library-color-color value)))}
|
||||
|
||||
{:name "opacity"
|
||||
:get #(-> % u/proxy->library-color :opacity)}
|
||||
:get #(-> % u/proxy->library-color :opacity)
|
||||
:set
|
||||
(fn [self value]
|
||||
(if (and (some? value) (number? value) (>= value 0) (<= value 1))
|
||||
(let [color (-> (u/proxy->library-color self)
|
||||
(assoc :opacity value))]
|
||||
(st/emit! (dwl/update-color color file-id)))
|
||||
(u/display-not-valid :library-color-color value)))}
|
||||
|
||||
{:name "gradient"
|
||||
:get #(-> % u/proxy->library-color :gradient u/to-js)}
|
||||
|
@ -96,8 +117,7 @@
|
|||
{: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)}))
|
||||
{:name "name" :get #(-> % u/proxy->library-component :name)}))
|
||||
|
||||
(deftype Library [$id]
|
||||
Object)
|
||||
|
|
|
@ -28,8 +28,9 @@
|
|||
(findShapes
|
||||
[_]
|
||||
;; Returns a lazy (iterable) of all available shapes
|
||||
(let [page (locate-page $file $id)]
|
||||
(apply array (sequence (map shape/shape-proxy) (keys (:objects page)))))))
|
||||
(when (and (some? $file) (some? $id))
|
||||
(let [page (locate-page $file $id)]
|
||||
(apply array (sequence (map shape/shape-proxy) (keys (:objects page))))))))
|
||||
|
||||
(crc/define-properties!
|
||||
PageProxy
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.plugins.shape
|
||||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.record :as crc]
|
||||
|
@ -14,19 +15,30 @@
|
|||
[app.common.text :as txt]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.changes :as dwc]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.flex :as flex]
|
||||
[app.plugins.grid :as grid]
|
||||
[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]
|
||||
[app.util.text-editor :as ted]))
|
||||
|
||||
(declare shape-proxy)
|
||||
|
||||
(defn text-props
|
||||
[shape]
|
||||
(d/merge
|
||||
(dwt/current-root-values {:shape shape :attrs txt/root-attrs})
|
||||
(dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs})
|
||||
(dwt/current-text-values {:shape shape :attrs txt/text-node-attrs})))
|
||||
|
||||
(deftype ShapeProxy [$file $page $id]
|
||||
Object
|
||||
(resize
|
||||
|
@ -145,37 +157,52 @@
|
|||
{:name "borderRadius"
|
||||
:get #(-> % proxy->shape :rx)
|
||||
:set (fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(let [id (obj/get self "$id")
|
||||
shape (proxy->shape self)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :rx value :ry value))))))}
|
||||
(when (or (not (ctsr/has-radius? shape)) (ctsr/radius-4? shape))
|
||||
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-1)))
|
||||
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-1 % value))))))}
|
||||
|
||||
{:name "borderRadiusTopLeft"
|
||||
:get #(-> % proxy->shape :r1)
|
||||
:set (fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(let [id (obj/get self "$id")
|
||||
shape (proxy->shape self)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :r1 value))))))}
|
||||
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
|
||||
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-4)))
|
||||
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-4 % :r1 value))))))}
|
||||
|
||||
{:name "borderRadiusTopRight"
|
||||
:get #(-> % proxy->shape :r2)
|
||||
:set (fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(let [id (obj/get self "$id")
|
||||
shape (proxy->shape self)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :r2 value))))))}
|
||||
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
|
||||
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-4)))
|
||||
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-4 % :r2 value))))))}
|
||||
|
||||
{:name "borderRadiusBottomRight"
|
||||
:get #(-> % proxy->shape :r3)
|
||||
:set (fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(let [id (obj/get self "$id")
|
||||
shape (proxy->shape self)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :r3 value))))))}
|
||||
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
|
||||
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-4)))
|
||||
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-4 % :r3 value))))))}
|
||||
|
||||
{:name "borderRadiusBottomLeft"
|
||||
:get #(-> % proxy->shape :r4)
|
||||
:set (fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(let [id (obj/get self "$id")
|
||||
shape (proxy->shape self)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :r4 value))))))}
|
||||
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
|
||||
(st/emit! (dwc/update-shapes [id] ctsr/switch-to-radius-4)))
|
||||
(st/emit! (dwc/update-shapes [id] #(ctsr/set-radius-4 % :r4 value))))))}
|
||||
|
||||
{:name "opacity"
|
||||
:get #(-> % proxy->shape :opacity)
|
||||
|
@ -196,15 +223,35 @@
|
|||
:get #(-> % proxy->shape :shadow 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 % :shadows value)))))}
|
||||
value (mapv (fn [val]
|
||||
;; Merge default shadow properties
|
||||
(d/patch-object
|
||||
{:id (uuid/next)
|
||||
:style :drop-shadow
|
||||
:color {:color clr/black :opacity 0.2}
|
||||
:offset-x 4
|
||||
:offset-y 4
|
||||
:blur 4
|
||||
:spread 0
|
||||
:hidden false}
|
||||
(utils/from-js val #{:style :type})))
|
||||
value)]
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :shadow value)))))}
|
||||
|
||||
{:name "blur"
|
||||
:get #(-> % proxy->shape :blur utils/to-js)
|
||||
:set (fn [self value]
|
||||
(let [id (obj/get self "$id")
|
||||
value (utils/from-js value)]
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :blur value)))))}
|
||||
(if (nil? value)
|
||||
(st/emit! (dwc/update-shapes [id] #(dissoc % :blur)))
|
||||
(let [id (obj/get self "$id")
|
||||
value
|
||||
(d/patch-object
|
||||
{:id (uuid/next)
|
||||
:type :layer-blur
|
||||
:value 4
|
||||
:hidden false}
|
||||
(utils/from-js value))]
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :blur value))))))}
|
||||
|
||||
{:name "exports"
|
||||
:get #(-> % proxy->shape :exports array-to-js)
|
||||
|
@ -301,7 +348,9 @@
|
|||
|
||||
;; Strokes and fills
|
||||
{:name "fills"
|
||||
:get #(-> % proxy->shape :fills array-to-js)
|
||||
:get #(if (cfh/text-shape? data)
|
||||
(-> % proxy->shape text-props :fills array-to-js)
|
||||
(-> % proxy->shape :fills array-to-js))
|
||||
:set (fn [self value]
|
||||
(let [id (obj/get self "$id")
|
||||
value (mapv #(utils/from-js %) value)]
|
||||
|
@ -311,7 +360,7 @@
|
|||
:get #(-> % proxy->shape :strokes array-to-js)
|
||||
:set (fn [self value]
|
||||
(let [id (obj/get self "$id")
|
||||
value (mapv #(utils/from-js %) value)]
|
||||
value (mapv #(utils/from-js % #{:stroke-style :stroke-alignment}) value)]
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))}
|
||||
|
||||
{:name "layoutChild"
|
||||
|
@ -397,18 +446,93 @@
|
|||
(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 "characters"
|
||||
:get #(-> % proxy->shape :content txt/content->text)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
;; The user is currently editing the text. We need to update the
|
||||
;; editor as well
|
||||
(when (contains? (:workspace-editor-state @st/state) id)
|
||||
(let [shape (proxy->shape self)
|
||||
editor
|
||||
(-> shape
|
||||
(txt/change-text value)
|
||||
:content
|
||||
ted/import-content
|
||||
ted/create-editor-state)]
|
||||
(st/emit! (dwt/update-editor-state shape editor))))
|
||||
(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))))))})))))))
|
||||
{: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))))))}
|
||||
|
||||
{:name "fontId"
|
||||
:get #(-> % proxy->shape text-props :font-id)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
||||
|
||||
{:name "fontFamily"
|
||||
:get #(-> % proxy->shape text-props :font-family)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
||||
|
||||
{:name "fontVariantId"
|
||||
:get #(-> % proxy->shape text-props :font-variant-id)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
||||
|
||||
{:name "fontSize"
|
||||
:get #(-> % proxy->shape text-props :font-size)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
||||
|
||||
{:name "fontWeight"
|
||||
:get #(-> % proxy->shape text-props :font-weight)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
||||
|
||||
{:name "fontStyle"
|
||||
:get #(-> % proxy->shape text-props :font-style)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:font-style value}))))}
|
||||
|
||||
{:name "lineHeight"
|
||||
:get #(-> % proxy->shape text-props :line-height)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:line-height value}))))}
|
||||
|
||||
{:name "letterSpacing"
|
||||
:get #(-> % proxy->shape text-props :letter-spacing)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:letter-spacing value}))))}
|
||||
|
||||
{:name "textTransform"
|
||||
:get #(-> % proxy->shape text-props :text-transform)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (obj/get self "$id")]
|
||||
(st/emit! (dwt/update-attrs id {:text-transform value}))))}))))))
|
||||
|
|
|
@ -56,38 +56,44 @@
|
|||
(defn proxy->file
|
||||
[proxy]
|
||||
(let [id (obj/get proxy "$id")]
|
||||
(locate-file id)))
|
||||
(when (some? 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)))
|
||||
(when (and (some? file-id) (some? 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)))
|
||||
(when (and (some? file-id) (some? page-id) (some? 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)))
|
||||
(when (and (some? file-id) (some? 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)))
|
||||
(when (and (some? file-id) (some? 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)))
|
||||
(when (and (some? file-id) (some? id))
|
||||
(locate-library-color file-id id))))
|
||||
|
||||
(defn get-data
|
||||
([self attr]
|
||||
|
@ -118,30 +124,32 @@
|
|||
|
||||
(defn from-js
|
||||
"Converts the object back to js"
|
||||
[obj]
|
||||
(when (some? obj)
|
||||
(let [process-node
|
||||
(fn process-node [node]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [k (keyword (str/kebab k))
|
||||
v (cond (map? v)
|
||||
(process-node v)
|
||||
([obj]
|
||||
(from-js obj #{:type}))
|
||||
([obj keyword-keys]
|
||||
(when (some? obj)
|
||||
(let [process-node
|
||||
(fn process-node [node]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [k (keyword (str/kebab k))
|
||||
v (cond (map? v)
|
||||
(process-node v)
|
||||
|
||||
(vector? v)
|
||||
(mapv process-node v)
|
||||
(vector? v)
|
||||
(mapv process-node v)
|
||||
|
||||
(and (string? v) (re-matches us/uuid-rx v))
|
||||
(uuid/uuid v)
|
||||
(and (string? v) (re-matches us/uuid-rx v))
|
||||
(uuid/uuid v)
|
||||
|
||||
(= k :type)
|
||||
(keyword v)
|
||||
(contains? keyword-keys k)
|
||||
(keyword v)
|
||||
|
||||
:else v)]
|
||||
(assoc m k v)))
|
||||
{}
|
||||
node))]
|
||||
(process-node (js->clj obj)))))
|
||||
:else v)]
|
||||
(assoc m k v)))
|
||||
{}
|
||||
node))]
|
||||
(process-node (js->clj obj))))))
|
||||
|
||||
(defn to-js
|
||||
"Converts to javascript an camelize the keys"
|
||||
|
@ -180,3 +188,7 @@
|
|||
(remove-watch ret-v ::watcher)
|
||||
(resolve value)))))]
|
||||
[ret-v ret-p]))
|
||||
|
||||
(defn display-not-valid
|
||||
[code value]
|
||||
(.error js/console (dm/str "[PENPOT PLUGIN] Value not valid: " value ". Code: " code)))
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
[frontend-tests.helpers.pages :as thp]
|
||||
[frontend-tests.helpers.state :as ths]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before thp/reset-idmap!})
|
||||
|
||||
;; Related .penpot file: common/test/cases/remove-swap-slots.penpot
|
||||
(defn- setup-file
|
||||
[]
|
||||
|
@ -813,3 +816,57 @@
|
|||
;; copied-blue1 has swap-id
|
||||
(t/is (some? copied-blue2'))
|
||||
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
|
||||
|
||||
|
||||
|
||||
(t/deftest test-remove-swap-slot-copy-paste-swapped-main
|
||||
(t/async
|
||||
done
|
||||
(let [;; ==== Setup
|
||||
;; {:frame-red} [:name frame-blue] # [Component :red]
|
||||
;; {:frame-blue} [:name frame-blue] #[Component :blue]
|
||||
;; {:frame-green} [:name frame-green] #[Component :green]
|
||||
;; :blue1 [:name frame-blue, :swap-slot-label :red-copy-green] @--> frame-blue
|
||||
|
||||
file (-> (cthf/sample-file :file1)
|
||||
(ctho/add-frame :frame-red :name "frame-blue")
|
||||
(cthc/make-component :red :frame-red)
|
||||
(ctho/add-frame :frame-blue :name "frame-blue")
|
||||
(cthc/make-component :blue :frame-blue)
|
||||
(ctho/add-frame :frame-green :name "frame-green")
|
||||
(cthc/make-component :green :frame-green)
|
||||
(cthc/instantiate-component :red :red-copy-green :parent-label :frame-green)
|
||||
(cthc/component-swap :red-copy-green :blue :blue1))
|
||||
store (ths/setup-store file)
|
||||
|
||||
;; ==== Action
|
||||
page (cthf/current-page file)
|
||||
green (cths/get-shape file :frame-green)
|
||||
features #{"components/v2"}
|
||||
version 47
|
||||
|
||||
pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version)
|
||||
|
||||
events
|
||||
[(dws/select-shape uuid/zero)
|
||||
(dw/paste-shapes pdata)]]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [;; ==== Get
|
||||
file' (ths/get-file-from-store new-state)
|
||||
page' (cthf/current-page file')
|
||||
green' (cths/get-shape file' :frame-green)
|
||||
blue1' (cths/get-shape file' :blue1)
|
||||
copied-green' (find-copied-shape green' page' uuid/zero)
|
||||
copied-blue1' (find-copied-shape blue1' page' (:id copied-green'))]
|
||||
|
||||
;; ==== Check
|
||||
;; blue1 has swap-id
|
||||
(t/is (some? (ctk/get-swap-slot blue1')))
|
||||
|
||||
;; copied-blue1 has not swap-id
|
||||
(t/is (some? copied-blue1'))
|
||||
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
|
||||
|
||||
|
|
|
@ -0,0 +1,396 @@
|
|||
;; 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 frontend-tests.logic.copying-and-duplicating-test
|
||||
(:require
|
||||
[app.common.test-helpers.components :as cthc]
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
[frontend-tests.helpers.state :as ths]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before thp/reset-idmap!})
|
||||
|
||||
;; Related .penpot file: common/test/cases/copying-and-duplicating.penpot
|
||||
(defn- setup-file []
|
||||
(-> (cthf/sample-file :file1 :page-label :page-1)
|
||||
(ctho/add-simple-component :simple-1 :frame-simple-1 :rect-simple-1 :child-params {:type :rect :fills (cths/sample-fills-color :fill-color "#2152e5") :name "rect-simple-1"})
|
||||
|
||||
(ctho/add-frame :frame-composed-1 :name "frame-composed-1")
|
||||
(cthc/instantiate-component :simple-1 :copy-simple-1 :parent-label :frame-composed-1 :children-labels [:composed-1-simple-1])
|
||||
(cths/add-sample-shape :rect-composed-1 :parent-label :frame-composed-1 :fills (cths/sample-fills-color :fill-color "#B1B2B5"))
|
||||
(cthc/make-component :composed-1 :frame-composed-1)
|
||||
|
||||
(ctho/add-frame :frame-composed-2 :name "frame-composed-2")
|
||||
(cthc/instantiate-component :composed-1 :copy-composed-1-composed-2 :parent-label :frame-composed-2 :children-labels [:composed-1-composed-2])
|
||||
(cthc/make-component :composed-2 :frame-composed-2)
|
||||
|
||||
(cthc/instantiate-component :composed-2 :copy-composed-2)
|
||||
|
||||
(ctho/add-frame :frame-composed-3 :name "frame-composed-3")
|
||||
(ctho/add-group :group-3 :parent-label :frame-composed-3)
|
||||
(cthc/instantiate-component :composed-2 :copy-composed-1-composed-3 :parent-label :group-3 :children-labels [:composed-1-composed-2])
|
||||
(cths/add-sample-shape :circle-composed-3 :parent-label :group-3 :fills (cths/sample-fills-color :fill-color "#B1B2B5"))
|
||||
(cthc/make-component :composed-3 :frame-composed-3)
|
||||
|
||||
(cthc/instantiate-component :composed-3 :copy-composed-3 :children-labels [:composed-2-composed-3])
|
||||
(cthf/add-sample-page :page-2)
|
||||
(cthf/switch-to-page :page-1)))
|
||||
|
||||
|
||||
(defn- copy-paste-shape
|
||||
[id file & {:keys [target-page-label target-container-id]}]
|
||||
(let [features #{"components/v2"}
|
||||
version 46
|
||||
page (cthf/current-page file)
|
||||
target-page-id (cthi/id target-page-label)
|
||||
shape (if (keyword? id)
|
||||
(cths/get-shape file id)
|
||||
(cths/get-shape-by-id file id))
|
||||
pdata (thp/simulate-copy-shape #{(:id shape)} (:objects page) {(:id file) file} page file features version)
|
||||
target-container-id (or target-container-id (:parent-id shape))]
|
||||
|
||||
(filter some?
|
||||
[(when target-page-id (dw/initialize-page target-page-id))
|
||||
(dws/select-shape target-container-id)
|
||||
(dw/paste-shapes pdata)
|
||||
(when target-page-id (dw/initialize-page (:id page)))])))
|
||||
|
||||
(defn- sync-file [file]
|
||||
(map (fn [component-tag]
|
||||
(->> component-tag
|
||||
(cthc/get-component file)
|
||||
:component-id
|
||||
(dwl/sync-file (:id file) (:id file) :components)))
|
||||
[:simple-1 :composed-1 :composed-2 :composed-3]))
|
||||
|
||||
(defn- set-color-bottom-shape [label file color]
|
||||
(let [shape (ctho/bottom-shape file label)]
|
||||
(concat
|
||||
[(dws/select-shape (:id shape))
|
||||
(dc/apply-color-from-palette color false)]
|
||||
(sync-file file))))
|
||||
|
||||
(defn- count-shapes [file name color]
|
||||
(let [page (cthf/current-page file)]
|
||||
(->> (vals (:objects page))
|
||||
(filter #(and
|
||||
(= (:name %) name)
|
||||
(-> (cths/get-shape-by-id file (:id %))
|
||||
:fills
|
||||
first
|
||||
:fill-color
|
||||
(= color))))
|
||||
(count))))
|
||||
|
||||
(defn- duplicate-each-main-and-first-level-copy [file]
|
||||
(concat (copy-paste-shape :frame-simple-1 file)
|
||||
(copy-paste-shape :frame-simple-1 file)
|
||||
(copy-paste-shape :frame-composed-1 file)
|
||||
(copy-paste-shape :frame-composed-1 file)
|
||||
(copy-paste-shape :frame-composed-2 file)
|
||||
(copy-paste-shape :frame-composed-2 file)
|
||||
(copy-paste-shape :frame-composed-3 file)
|
||||
(copy-paste-shape :frame-composed-3 file)
|
||||
(copy-paste-shape :copy-composed-2 file)
|
||||
(copy-paste-shape :copy-composed-2 file)
|
||||
(copy-paste-shape :copy-composed-3 file)
|
||||
(copy-paste-shape :copy-composed-3 file)))
|
||||
|
||||
(defn- duplicate-simple-nested-in-main-and-group [file]
|
||||
(concat (copy-paste-shape :copy-simple-1 file)
|
||||
(copy-paste-shape :copy-simple-1 file)
|
||||
(copy-paste-shape :group-3 file)
|
||||
(copy-paste-shape :group-3 file)))
|
||||
|
||||
(defn- duplicate-copy-nested-and-group-out-of-the-main
|
||||
[file & {:keys [target-page-label]}]
|
||||
(let [page (cthf/current-page file)
|
||||
frame-1-instance-ids (->> (vals (:objects page))
|
||||
(filter #(and
|
||||
(or
|
||||
(= (:name %) "Frame1")
|
||||
(= (:name %) "Group1"))
|
||||
(not (:component-root %))))
|
||||
(map :id))]
|
||||
(concat
|
||||
(apply concat (mapv #(copy-paste-shape % file :target-page-label target-page-label :target-container-id uuid/zero) frame-1-instance-ids))
|
||||
(apply concat (mapv #(copy-paste-shape % file :target-page-label target-page-label :target-container-id uuid/zero) frame-1-instance-ids)))))
|
||||
|
||||
(t/deftest main-and-first-level-copy-1
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
;; ==== Action
|
||||
|
||||
|
||||
;; For each main and first level copy:
|
||||
;; - Duplicate it two times with copy-paste.
|
||||
events
|
||||
(concat
|
||||
(duplicate-each-main-and-first-level-copy file)
|
||||
;; - Change color of Simple1
|
||||
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 18)))))))))
|
||||
|
||||
(t/deftest main-and-first-level-copy-2
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
;; ==== Action
|
||||
|
||||
|
||||
;; For each main and first level copy:
|
||||
;; - Duplicate it two times with copy-paste.
|
||||
events
|
||||
(concat
|
||||
(duplicate-each-main-and-first-level-copy file)
|
||||
;; - Change color of Simple1
|
||||
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
|
||||
;; - Change color of the nearest main and check propagation to duplicated.
|
||||
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 15)))))))))
|
||||
|
||||
(t/deftest main-and-first-level-copy-3
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
;; ==== Action
|
||||
|
||||
|
||||
;; For each main and first level copy:
|
||||
;; - Duplicate it two times with copy-paste.
|
||||
events
|
||||
(concat
|
||||
(duplicate-each-main-and-first-level-copy file)
|
||||
;; - Change color of Simple1
|
||||
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
|
||||
;; - Change color of the nearest main and check propagation to duplicated.
|
||||
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
|
||||
(set-color-bottom-shape :frame-composed-2 file {:color "#333333"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 12)))))))))
|
||||
|
||||
|
||||
(t/deftest main-and-first-level-copy-4
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
;; ==== Action
|
||||
|
||||
|
||||
;; For each main and first level copy:
|
||||
;; - Duplicate it two times with copy-paste.
|
||||
events
|
||||
(concat
|
||||
(duplicate-each-main-and-first-level-copy file)
|
||||
;; - Change color of Simple1
|
||||
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
|
||||
;; - Change color of the nearest main and check propagation to duplicated.
|
||||
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
|
||||
(set-color-bottom-shape :frame-composed-2 file {:color "#333333"})
|
||||
(set-color-bottom-shape :frame-composed-3 file {:color "#444444"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#444444") 6)))))))))
|
||||
|
||||
(t/deftest copy-nested-in-main-1
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
|
||||
;; ==== Action
|
||||
;; For each copy of Simple1 nested in a main, and the group inside Composed3 main:
|
||||
;; - Duplicate it two times, keeping the duplicated inside the same main.
|
||||
events
|
||||
(concat
|
||||
(duplicate-simple-nested-in-main-and-group file)
|
||||
;; - Change color of Simple1
|
||||
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
;; Check propagation to all copies.
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 28)))))))))
|
||||
|
||||
(t/deftest copy-nested-in-main-2
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
|
||||
;; ==== Action
|
||||
;; For each copy of Simple1 nested in a main, and the group inside Composed3 main:
|
||||
;; - Duplicate it two times, keeping the duplicated inside the same main.
|
||||
events
|
||||
(concat
|
||||
(duplicate-simple-nested-in-main-and-group file)
|
||||
;; - Change color of the nearest main
|
||||
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
;; Check propagation to duplicated.
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 9)))))))))
|
||||
|
||||
(t/deftest copy-nested-in-main-3
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
|
||||
;; ==== Action
|
||||
;; For each copy of Simple1 nested in a main, and the group inside Composed3 main:
|
||||
;; - Duplicate it two times, keeping the duplicated inside the same main.
|
||||
events
|
||||
(concat
|
||||
(duplicate-simple-nested-in-main-and-group file)
|
||||
;; - Change color of the copy you duplicated from.
|
||||
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
;; Check that it's NOT PROPAGATED.
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))))
|
||||
|
||||
(t/deftest copy-nested-1
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
|
||||
;; ==== Action
|
||||
;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3
|
||||
;; main and copy:
|
||||
;; - Duplicate it two times, moving the duplicates out of the main.
|
||||
events
|
||||
(concat
|
||||
(duplicate-copy-nested-and-group-out-of-the-main file)
|
||||
;; - Change color of Simple1
|
||||
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
;; Check propagation to all copies.
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 20)))))))))
|
||||
|
||||
|
||||
(t/deftest copy-nested-2
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
|
||||
;; ==== Action
|
||||
;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3
|
||||
;; main and copy:
|
||||
;; - Duplicate it two times, moving the duplicates out of the main.
|
||||
events
|
||||
(concat
|
||||
(duplicate-copy-nested-and-group-out-of-the-main file)
|
||||
;; - Change color of Simple1
|
||||
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
|
||||
;; - Change color of the previous main
|
||||
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
|
||||
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)]
|
||||
;; Check that it's NOT PROPAGATED.
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 11))
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 7))
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))))
|
||||
|
||||
|
||||
(t/deftest copy-nested-3
|
||||
(t/async
|
||||
done
|
||||
(with-redefs [uuid/next cthi/next-uuid]
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
|
||||
;; ==== Action
|
||||
;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3
|
||||
;; main and copy:
|
||||
;; - Duplicate it two times, moving the duplicates to another page
|
||||
events
|
||||
(concat
|
||||
(duplicate-copy-nested-and-group-out-of-the-main file :target-page-label :page-2)
|
||||
;; - Change color of Simple1
|
||||
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
|
||||
;; - Change color of the previous main
|
||||
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
|
||||
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (-> (ths/get-file-from-store new-state)
|
||||
(cthf/switch-to-page :page-2))]
|
||||
;; Check that it's NOT PROPAGATED.
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 10))
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 4))
|
||||
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 0)))))))))
|
|
@ -392,7 +392,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Lêers wat by biblioteke gevoeg is, sal hier verskyn. Probeer om jou lêers "
|
||||
"te deel of voeg by vanaf ons [Biblioteke en "
|
||||
"sjablone](https://penpot.app/libraries-templates.html)."
|
||||
"sjablone](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Laai %s Penpot lêers (.penpot) af"
|
||||
|
|
|
@ -301,7 +301,7 @@ msgstr "تكرير %s الملفات"
|
|||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr ""
|
||||
"أوه لا! ليس لديك ملفات بعد! إذا كنت تريد تجربة بعض القوالب ، فانتقل إلى "
|
||||
"[المكتبات والقوالب] (https://penpot.app/libraries-templates.html)"
|
||||
"[المكتبات والقوالب] (https://penpot.app/libraries-templates)"
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "تنزيل ملفات ٪s Penpot (.penpot)"
|
||||
|
|
|
@ -307,7 +307,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Encara no hi ha fitxers. Si voleu provar algunes plantilles, podeu anar a "
|
||||
"la secció [Biblioteques i "
|
||||
"plantilles](https://penpot.app/libraries-templates.html)"
|
||||
"plantilles](https://penpot.app/libraries-templates)"
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Baixa %s fitxers Penpot (.penpot)"
|
||||
|
|
|
@ -406,7 +406,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Zde se zobrazí soubory přidané do knihoven. Zkuste své soubory sdílet nebo "
|
||||
"je přidat z našich [Libraries & "
|
||||
"templates](https://penpot.app/libraries-templates.html)."
|
||||
"templates](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Stáhnout soubory %s Penpot (.penpot)"
|
||||
|
|
|
@ -408,7 +408,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Files added to Libraries will appear here. Try sharing your files or add "
|
||||
"from our [Libraries & "
|
||||
"templates](https://penpot.app/libraries-templates.html)."
|
||||
"templates](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Download %s Penpot files (.penpot)"
|
||||
|
@ -5205,3 +5205,6 @@ msgstr "Plugins"
|
|||
|
||||
msgid "workspace.plugins.menu.plugins-manager"
|
||||
msgstr "Plugins manager"
|
||||
|
||||
msgid "workspace.plugins.plugin-list-link"
|
||||
msgstr "Plugins List"
|
||||
|
|
|
@ -416,7 +416,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Los archivos agregados a las bibliotecas aparecerán aquí. Si quieres probar "
|
||||
"con alguna plantilla ve a [Bibliotecas y "
|
||||
"plantillas](https://penpot.app/libraries-templates.html)."
|
||||
"plantillas](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Descargar %s archivos Penpot (.penpot)"
|
||||
|
@ -5332,3 +5332,5 @@ msgstr "Extensiones"
|
|||
msgid "workspace.plugins.menu.plugins-manager"
|
||||
msgstr "Gestor de extensiones"
|
||||
|
||||
msgid "workspace.plugins.plugin-list-link"
|
||||
msgstr "Lista de extensiones"
|
||||
|
|
|
@ -404,7 +404,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Los archivos agregados a las Bibliotecas aparecerán aquí. Intente compartir "
|
||||
"sus archivos o agréguelos desde nuestras [Libraries & "
|
||||
"templates](https://penpot.app/libraries-templates.html)."
|
||||
"templates](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Descargar %s archivos Penpot (.penpot)"
|
||||
|
|
|
@ -305,7 +305,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Oh ez! Oraindik ez duzu fitxategirik! Txantiloi batekin proba egin nahi "
|
||||
"baduzu joan [Liburutegi eta "
|
||||
"txantiloiak](https://penpot.app/libraries-templates.html) atalera."
|
||||
"txantiloiak](https://penpot.app/libraries-templates) atalera."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Deskargatu %s Penpot fitxategi (.penpot)"
|
||||
|
|
|
@ -304,7 +304,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"وای نه! شما هنوز هیچ فایلی ندارید! اگر میخواهید چند الگو را امتحان کنید، "
|
||||
"به [کتابخانهها و الگوها] بروید "
|
||||
"(https://penpot.app/libraries-templates.html)"
|
||||
"(https://penpot.app/libraries-templates)"
|
||||
|
||||
#, fuzzy
|
||||
msgid "dashboard.export-binary-multi"
|
||||
|
|
|
@ -294,7 +294,7 @@ msgstr "Tvítak %s fílur"
|
|||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr ""
|
||||
"Áh nei! Tú hevur ongar fílur enn! Um tú vilt royna við nøkrum skapilónum, "
|
||||
"vitja [Libraries & templates](https://penpot.app/libraries-templates.html)"
|
||||
"vitja [Libraries & templates](https://penpot.app/libraries-templates)"
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Heinta %s Penpot fílur (.penpot)"
|
||||
|
|
|
@ -397,7 +397,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Oh non ! Vous n'avez pas encore de fichiers ! Si vous voulez essayer avec "
|
||||
"des modèles, allez sur [Bibliothèques et modèles] "
|
||||
"(https://penpot.app/libraries-templates.html)."
|
||||
"(https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Télécharger %s fichiers Penpot (.penpot)"
|
||||
|
|
|
@ -302,7 +302,7 @@ msgstr "Duplicar % ficheiros"
|
|||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr ""
|
||||
"Ai non! Ainda non tes ficheiros! Se queres facer a proba con algún modelo "
|
||||
"vai a [Bibliotecas e modelos] (https://penpot.app/libraries-templates.html)"
|
||||
"vai a [Bibliotecas e modelos] (https://penpot.app/libraries-templates)"
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Descargar %s ficheiros Penpot (.penpot)"
|
||||
|
|
|
@ -402,7 +402,7 @@ msgstr "שכפול %s קבצים"
|
|||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr ""
|
||||
"קבצים שנוספו לספריות יתווספו לכאן. כדאי לנסות לשתף את הקבצים שלך או להוסיף "
|
||||
"אותם מ[הספריות והתבניות](https://penpot.app/libraries-templates.html)."
|
||||
"אותם מ[הספריות והתבניות](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "הורדת %s קובצי Penpot (.penpot)"
|
||||
|
|
|
@ -304,7 +304,7 @@ msgstr "Kopiraj %s datoteka"
|
|||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr ""
|
||||
"O ne! Još nemaš datoteka! Ako želiš isprobati neke predloške, idi na "
|
||||
"[Biblioteke i predlošci](https://penpot.app/libraries-templates.html)"
|
||||
"[Biblioteke i predlošci](https://penpot.app/libraries-templates)"
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Preuzmi %s Penpot datoteke (.penpot)"
|
||||
|
|
|
@ -404,7 +404,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Berkas yang ditambahkan ke Pustaka akan muncul di sini. Coba membagikan "
|
||||
"berkas Anda atau menambahkan dari [Pustaka & "
|
||||
"templat](https://penpot.app/libraries-templates.html) kami."
|
||||
"templat](https://penpot.app/libraries-templates) kami."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Unduh %s berkas Penpot (.penpot)"
|
||||
|
|
|
@ -300,7 +300,7 @@ msgstr "Duplicare %s file"
|
|||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr ""
|
||||
"Oh no! Non hai ancora nessun file! Se desideri provare alcuni template vai "
|
||||
"su [Librerie e template](https://penpot.app/libraries-templates.html)"
|
||||
"su [Librerie e template](https://penpot.app/libraries-templates)"
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Scarica %s file Penpot (.penpot)"
|
||||
|
|
|
@ -263,7 +263,7 @@ msgstr "%s ファイルを複製"
|
|||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr ""
|
||||
"まだファイルがありません。もしいくつかのテンプレートを試してみたいなら、[Libraries & "
|
||||
"templates](https://penpot.app/libraries-templates.html) をチェックしてみてください。"
|
||||
"templates](https://penpot.app/libraries-templates) をチェックしてみてください。"
|
||||
|
||||
msgid "dashboard.export-frames"
|
||||
msgstr "PDFでエクスポート"
|
||||
|
|
|
@ -293,7 +293,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Čia bus rodomi prie bibliotekų pridėti failai. Pabandykite bendrinti failus "
|
||||
"arba pridėti iš mūsų [Bibliotekos ir šablonai] "
|
||||
"(https://penpot.app/libraries-templates.html)"
|
||||
"(https://penpot.app/libraries-templates)"
|
||||
|
||||
msgid "dashboard.export-frames"
|
||||
msgstr "Eksportuokite darbalaukius į PDF"
|
||||
|
|
|
@ -413,7 +413,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Šeit tiks parādītas bibliotēkām pievienotās datnes. Mēģini koplietot datnes "
|
||||
"vai pievienot tās no mūsu [bibliotēkām un "
|
||||
"veidnēm](https://penpot.app/libraries-templates.html)."
|
||||
"veidnēm](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Lejupielādēt %s Penpot datnes (.penpot)"
|
||||
|
|
|
@ -221,7 +221,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"ഇതുവരെയും ഇവിടെ ഫയലുകളില്ല. നിങ്ങൾക്ക് ചില ടെമ്പ്ലേറ്റുകൾ "
|
||||
"പരീക്ഷിക്കണമെന്നുണ്ടെങ്കിൽ [ലൈബ്രറികളുടെയും ടെമ്പ്ലേറ്റുകളുടെയും "
|
||||
"വിഭാഗത്തിലേക്ക്] (https://penpot.app/libraries-templates.html) പോകാവുന്നതാണ്"
|
||||
"വിഭാഗത്തിലേക്ക്] (https://penpot.app/libraries-templates) പോകാവുന്നതാണ്"
|
||||
|
||||
msgid "dashboard.export-frames"
|
||||
msgstr "ആർട്ട്ബോർഡുകൾ പിഡിഎഫായി എക്സ്പോർട്ട് ചെയ്യുക"
|
||||
|
|
|
@ -387,7 +387,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Fail yang ditambahkan pada Perpustakaan akan dipaparkan di sini. Cuba "
|
||||
"kongsi fail anda atau tambahkan daripada [Perpustakaan & "
|
||||
"templat](https://penpot.app/libraries-templates.html) kami."
|
||||
"templat](https://penpot.app/libraries-templates) kami."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Muat turun %s fail Penpot (.penpot)"
|
||||
|
|
|
@ -427,7 +427,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Bestanden die aan bibliotheken zijn toegevoegd, worden hier weergegeven. "
|
||||
"Probeer je bestanden te delen of toe te voegen vanuit onze [Bibliotheken & "
|
||||
"sjablonen] (https://penpot.app/libraries-templates.html)."
|
||||
"sjablonen] (https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "%s Penpot-bestanden downloaden (.penpot)"
|
||||
|
|
|
@ -305,7 +305,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Tutaj pojawią się pliki dodane do Bibliotek. Spróbuj udostępnić swoje pliki "
|
||||
"lub dodać z naszych [Bibliotek i "
|
||||
"szablonów](https://penpot.app/libraries-templates.html)."
|
||||
"szablonów](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Pobierz %s plików Penpot (.penpot)"
|
||||
|
|
|
@ -303,7 +303,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Arquivos adicionados na biblioteca de ativos vão aparecer aqui. Tente "
|
||||
"compartilhar seus arquivos ou adicione das nossas [Bibliotecas & "
|
||||
"modelos](https://penpot.app/libraries-templates.html)."
|
||||
"modelos](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Baixar %s arquivos Penpot (.penpot)"
|
||||
|
|
|
@ -403,7 +403,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Os ficheiros adicionados às Bibliotecas irão aparecer aqui. Experimenta "
|
||||
"adicionar os teus ficheiros ou experimenta algumas das nossas [Bibliotecas "
|
||||
"e templates](https://penpot.app/libraries-templates.html)."
|
||||
"e templates](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Descarrega %s ficheiros Penpot (.penpot)"
|
||||
|
|
|
@ -402,7 +402,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Fișierele adăugate la Biblioteci vor apărea aici. Încercați să partajați "
|
||||
"fișierele dvs. sau adăugați-le din [Biblioteci și "
|
||||
"șabloane](https://penpot.app/libraries-templates.html)."
|
||||
"șabloane](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Descărcați %s fișiere Penpot (.penpot)"
|
||||
|
|
|
@ -323,7 +323,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Файлы, добавленные в Библиотеки, появятся здесь. Попробуйте поделиться "
|
||||
"своими файлами или добавить их из наших [Библиотек и "
|
||||
"шаблонов](https://penpot.app/libraries-templates.html)."
|
||||
"шаблонов](https://penpot.app/libraries-templates)."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Скачать файлы Penpot (.penpot) (%s)"
|
||||
|
|
|
@ -415,7 +415,7 @@ msgid "dashboard.empty-placeholder-drafts"
|
|||
msgstr ""
|
||||
"Kütüphanelere eklenen dosyalar burada görünecektir. Dosyalarınızı "
|
||||
"paylaşmayı deneyin veya [Kütüphaneler ve "
|
||||
"şablonlarımızdan](https://penpot.app/libraries-templates.html) ekleyin."
|
||||
"şablonlarımızdan](https://penpot.app/libraries-templates) ekleyin."
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "%s Penpot dosyasını indir (.penpot)"
|
||||
|
|
|
@ -291,7 +291,7 @@ msgstr "複製 %s 個檔案"
|
|||
msgid "dashboard.empty-placeholder-drafts"
|
||||
msgstr ""
|
||||
"添加在資料庫的檔案會在此處列出。請分享你的檔案或由我們的 [資料庫 & "
|
||||
"模板區段](https://penpot.app/libraries-templates.html) 添加。"
|
||||
"模板區段](https://penpot.app/libraries-templates) 添加。"
|
||||
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "下載 %s 個Penpot 檔案 (.penpot)"
|
||||
|
|
6941
frontend/yarn.lock
6941
frontend/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue