From 106fe05657b3cd8fa8c15843113ad6c6d6212c0b Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 23 Apr 2024 11:22:40 +0200 Subject: [PATCH 01/34] :sparkles: Add more login integration tests --- frontend/playwright.config.js | 2 ++ .../fixtures/login-with-password-error.json | 4 +++ frontend/playwright/helpers/index.js | 9 +++++-- frontend/playwright/login.spec.js | 26 ++++++++++++++++++- 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 frontend/playwright/fixtures/login-with-password-error.json diff --git a/frontend/playwright.config.js b/frontend/playwright.config.js index 73245e3ce7..08a372946d 100644 --- a/frontend/playwright.config.js +++ b/frontend/playwright.config.js @@ -28,6 +28,8 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", + + locale: "en-US" }, /* Configure projects for major browsers */ diff --git a/frontend/playwright/fixtures/login-with-password-error.json b/frontend/playwright/fixtures/login-with-password-error.json new file mode 100644 index 0000000000..c54589e42c --- /dev/null +++ b/frontend/playwright/fixtures/login-with-password-error.json @@ -0,0 +1,4 @@ +{ + "~:type": "~:validation", + "~:code": "~:wrong-credentials" +} diff --git a/frontend/playwright/helpers/index.js b/frontend/playwright/helpers/index.js index 50f7e5487a..419dfa9184 100644 --- a/frontend/playwright/helpers/index.js +++ b/frontend/playwright/helpers/index.js @@ -1,7 +1,12 @@ -export const interceptRPC = async (page, path, jsonFilename) => { +export const interceptRPC = async (page, path, jsonFilename, options = {}) => { + const interceptConfig = { + status: 200, + ...options + }; + await page.route(`**/api/rpc/command/${path}`, (route) => { route.fulfill({ - status: 200, + ...interceptConfig, contentType: "application/transit+json", path: `playwright/fixtures/${jsonFilename}`, }); diff --git a/frontend/playwright/login.spec.js b/frontend/playwright/login.spec.js index 5463a74f2a..5b10ca2ceb 100644 --- a/frontend/playwright/login.spec.js +++ b/frontend/playwright/login.spec.js @@ -37,7 +37,16 @@ test("Shows login page when going to index and user is logged out", async ({ pag await page.goto("/"); await expect(page).toHaveURL(/auth\/login$/); - await expect(page.getByText("Log into my account")).toBeVisible(); + await expect(page.getByRole("heading", { name: "Log into my account" } )).toBeVisible(); +}); + +test("User submit a wrong formated email ", async ({ page }) => { + await interceptRPC(page, "get-profile", "get-profile-anonymous.json"); + await page.goto("/"); + await page.getByLabel("Email").fill("foo"); + + await expect(page).toHaveURL(/auth\/login$/); + await expect(page.getByText("Enter a valid email please")).toBeVisible(); }); test("User logs in by filling the login form", async ({ page }) => { @@ -54,3 +63,18 @@ test("User logs in by filling the login form", async ({ page }) => { await expect(page).toHaveURL(/dashboard/); }); + +test("User submits wrong credentials", async ({ page }) => { + await interceptRPC(page, "get-profile", "get-profile-anonymous.json"); + await interceptRPC(page, "login-with-password", "login-with-password-error.json", { status: 400 }); + + await page.goto("/"); + + await page.getByLabel("Email").fill("foo123@example.com"); + await page.getByLabel("Password").fill("aaaa"); + + await page.getByRole("button", { name: "Login" }).click(); + + await expect(page.getByText("Email or password is incorrect")).toBeVisible(); + await expect(page).toHaveURL(/auth\/login$/); +}); From 306a8edbec08738f9cf4790a3e8ddad6f127b983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 23 Apr 2024 15:49:57 +0200 Subject: [PATCH 02/34] :sparkles: Swap http-server for a custom server with express (front-end integration tests) --- frontend/package.json | 4 +- frontend/scripts/e2e-server.js | 13 ++ frontend/yarn.lock | 333 +++++++++------------------------ 3 files changed, 99 insertions(+), 251 deletions(-) create mode 100644 frontend/scripts/e2e-server.js diff --git a/frontend/package.json b/frontend/package.json index ba2d09f72d..e8aaa62c71 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "translations:find-unused": "node ./scripts/find-unused-translations.js", "compile": "node ./scripts/compile.js", "watch": "node ./scripts/watch.js", - "e2e:server": "NODE_NO_WARNINGS=1 http-server ./resources/public -p 3500 -a 0.0.0.0", + "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\"", @@ -51,6 +51,7 @@ "autoprefixer": "^10.4.17", "concurrently": "^8.2.2", "draft-js": "git+https://github.com/penpot/draft-js.git", + "express": "^4.19.2", "fancy-log": "^2.0.0", "gettext-parser": "^8.0.0", "gulp": "4.0.2", @@ -62,7 +63,6 @@ "gulp-sass": "^5.1.0", "gulp-sourcemaps": "^3.0.0", "gulp-svg-sprite": "^2.0.3", - "http-server": "^14.1.1", "jsdom": "^24.0.0", "map-stream": "0.0.7", "marked": "^12.0.0", diff --git a/frontend/scripts/e2e-server.js b/frontend/scripts/e2e-server.js new file mode 100644 index 0000000000..cebddcdd31 --- /dev/null +++ b/frontend/scripts/e2e-server.js @@ -0,0 +1,13 @@ +import express from "express"; +import { fileURLToPath } from "url"; +import path from "path"; + +const app = express(); +const port = 3500; + +const staticPath = path.join(fileURLToPath(import.meta.url), "../../resources/public"); +app.use(express.static(staticPath)); + +app.listen(port, () => { + console.log(`Listening at 0.0.0.0:${port}`); +}); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5ba630a738..63b549a055 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4913,15 +4913,6 @@ __metadata: languageName: node linkType: hard -"async@npm:^2.6.4": - version: 2.6.4 - resolution: "async@npm:2.6.4" - dependencies: - lodash: "npm:^4.17.14" - checksum: 0ebb3273ef96513389520adc88e0d3c45e523d03653cc9b66f5c46f4239444294899bfd13d2b569e7dbfde7da2235c35cf5fd3ece9524f935d41bbe4efccdad0 - languageName: node - linkType: hard - "async@npm:^3.2.3, async@npm:^3.2.4": version: 3.2.5 resolution: "async@npm:3.2.5" @@ -5074,15 +5065,6 @@ __metadata: languageName: node linkType: hard -"basic-auth@npm:^2.0.1": - version: 2.0.1 - resolution: "basic-auth@npm:2.0.1" - dependencies: - safe-buffer: "npm:5.1.2" - checksum: 05f56db3a0fc31c89c86b605231e32ee143fb6ae38dc60616bc0970ae6a0f034172def99e69d3aed0e2c9e7cac84e2d63bc51a0b5ff6ab5fc8808cc8b29923c1 - languageName: node - linkType: hard - "better-opn@npm:^3.0.2": version: 3.0.2 resolution: "better-opn@npm:3.0.2" @@ -5174,6 +5156,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:1.20.2": + version: 1.20.2 + resolution: "body-parser@npm:1.20.2" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.11.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 06f1438fff388a2e2354c96aa3ea8147b79bfcb1262dfcc2aae68ec13723d01d5781680657b74e9f83c808266d5baf52804032fbde2b7382b89bd8cdb273ace9 + languageName: node + linkType: hard + "boolbase@npm:^1.0.0": version: 1.0.0 resolution: "boolbase@npm:1.0.0" @@ -5511,19 +5513,6 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.7": - version: 1.0.7 - resolution: "call-bind@npm:1.0.7" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.1" - checksum: a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d - languageName: node - linkType: hard - "camelcase@npm:^3.0.0": version: 3.0.0 resolution: "camelcase@npm:3.0.0" @@ -6061,7 +6050,7 @@ __metadata: languageName: node linkType: hard -"content-type@npm:^1.0.5, content-type@npm:~1.0.4": +"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af @@ -6096,6 +6085,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686 + languageName: node + linkType: hard + "copy-descriptor@npm:^0.1.0": version: 0.1.1 resolution: "copy-descriptor@npm:0.1.1" @@ -6136,13 +6132,6 @@ __metadata: languageName: node linkType: hard -"corser@npm:^2.0.1": - version: 2.0.1 - resolution: "corser@npm:2.0.1" - checksum: 1f319a752a560342dd22d936e5a4c158bfcbc332524ef5b05a7277236dad8b0b2868fd5cf818559f29954ec4d777d82e797fccd76601fcfe431610e4143c8acc - languageName: node - linkType: hard - "create-ecdh@npm:^4.0.0": version: 4.0.4 resolution: "create-ecdh@npm:4.0.4" @@ -6384,7 +6373,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:3.X, debug@npm:^3.2.7": +"debug@npm:3.X": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -6507,17 +6496,6 @@ __metadata: languageName: node linkType: hard -"define-data-property@npm:^1.1.4": - version: 1.1.4 - resolution: "define-data-property@npm:1.1.4" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.0.1" - checksum: dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 - languageName: node - linkType: hard - "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -7037,22 +7015,6 @@ __metadata: languageName: node linkType: hard -"es-define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "es-define-property@npm:1.0.0" - dependencies: - get-intrinsic: "npm:^1.2.4" - checksum: 6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4 - languageName: node - linkType: hard - -"es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 - languageName: node - linkType: hard - "es-get-iterator@npm:^1.1.3": version: 1.1.3 resolution: "es-get-iterator@npm:1.1.3" @@ -7425,13 +7387,6 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.0": - version: 4.0.7 - resolution: "eventemitter3@npm:4.0.7" - checksum: 5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b - languageName: node - linkType: hard - "events@npm:^3.0.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" @@ -7561,6 +7516,45 @@ __metadata: languageName: node linkType: hard +"express@npm:^4.19.2": + version: 4.19.2 + resolution: "express@npm:4.19.2" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.2" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.6.0" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.2.0" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.1" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.7" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.11.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.18.0" + serve-static: "npm:1.15.0" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: e82e2662ea9971c1407aea9fc3c16d6b963e55e3830cd0ef5e00b533feda8b770af4e3be630488ef8a752d7c75c4fcefb15892868eeaafe7353cb9e3e269fdcb + languageName: node + linkType: hard + "ext@npm:^1.1.2": version: 1.7.0 resolution: "ext@npm:1.7.0" @@ -7928,16 +7922,6 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0": - version: 1.15.6 - resolution: "follow-redirects@npm:1.15.6" - peerDependenciesMeta: - debug: - optional: true - checksum: 9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 - languageName: node - linkType: hard - "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -8034,6 +8018,7 @@ __metadata: date-fns: "npm:^3.3.1" draft-js: "git+https://github.com/penpot/draft-js.git" eventsource-parser: "npm:^1.1.2" + express: "npm:^4.19.2" fancy-log: "npm:^2.0.0" gettext-parser: "npm:^8.0.0" gulp: "npm:4.0.2" @@ -8046,7 +8031,6 @@ __metadata: gulp-sourcemaps: "npm:^3.0.0" gulp-svg-sprite: "npm:^2.0.3" highlight.js: "npm:^11.9.0" - http-server: "npm:^14.1.1" js-beautify: "npm:^1.15.1" jsdom: "npm:^24.0.0" jszip: "npm:^3.10.1" @@ -8288,19 +8272,6 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.2.4": - version: 1.2.4 - resolution: "get-intrinsic@npm:1.2.4" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - has-proto: "npm:^1.0.1" - has-symbols: "npm:^1.0.3" - hasown: "npm:^2.0.0" - checksum: 0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7 - languageName: node - linkType: hard - "get-nonce@npm:^1.0.0": version: 1.0.1 resolution: "get-nonce@npm:1.0.1" @@ -8791,15 +8762,6 @@ __metadata: languageName: node linkType: hard -"has-property-descriptors@npm:^1.0.2": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" - dependencies: - es-define-property: "npm:^1.0.0" - checksum: 253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 - languageName: node - linkType: hard - "has-proto@npm:^1.0.1": version: 1.0.1 resolution: "has-proto@npm:1.0.1" @@ -8892,15 +8854,6 @@ __metadata: languageName: node linkType: hard -"he@npm:^1.2.0": - version: 1.2.0 - resolution: "he@npm:1.2.0" - bin: - he: bin/he - checksum: a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 - languageName: node - linkType: hard - "highlight.js@npm:^11.9.0": version: 11.9.0 resolution: "highlight.js@npm:11.9.0" @@ -8935,15 +8888,6 @@ __metadata: languageName: node linkType: hard -"html-encoding-sniffer@npm:^3.0.0": - version: 3.0.0 - resolution: "html-encoding-sniffer@npm:3.0.0" - dependencies: - whatwg-encoding: "npm:^2.0.0" - checksum: b17b3b0fb5d061d8eb15121c3b0b536376c3e295ecaf09ba48dd69c6b6c957839db124fe1e2b3f11329753a4ee01aa7dedf63b7677999e86da17fbbdd82c5386 - languageName: node - linkType: hard - "html-encoding-sniffer@npm:^4.0.0": version: 4.0.0 resolution: "html-encoding-sniffer@npm:4.0.0" @@ -8990,40 +8934,6 @@ __metadata: languageName: node linkType: hard -"http-proxy@npm:^1.18.1": - version: 1.18.1 - resolution: "http-proxy@npm:1.18.1" - dependencies: - eventemitter3: "npm:^4.0.0" - follow-redirects: "npm:^1.0.0" - requires-port: "npm:^1.0.0" - checksum: 148dfa700a03fb421e383aaaf88ac1d94521dfc34072f6c59770528c65250983c2e4ec996f2f03aa9f3fe46cd1270a593126068319311e3e8d9e610a37533e94 - languageName: node - linkType: hard - -"http-server@npm:^14.1.1": - version: 14.1.1 - resolution: "http-server@npm:14.1.1" - dependencies: - basic-auth: "npm:^2.0.1" - chalk: "npm:^4.1.2" - corser: "npm:^2.0.1" - he: "npm:^1.2.0" - html-encoding-sniffer: "npm:^3.0.0" - http-proxy: "npm:^1.18.1" - mime: "npm:^1.6.0" - minimist: "npm:^1.2.6" - opener: "npm:^1.5.1" - portfinder: "npm:^1.0.28" - secure-compare: "npm:3.0.1" - union: "npm:~0.5.0" - url-join: "npm:^4.0.1" - bin: - http-server: bin/http-server - checksum: c5770ddd722dd520ce0af25efee6bfb7c6300ff4e934636d4eec83fa995739e64de2e699e89e7a795b3a1894bcc37bec226617c1023600aacd7871fd8d6ffe6d - languageName: node - linkType: hard - "https-browserify@npm:^1.0.0": version: 1.0.0 resolution: "https-browserify@npm:1.0.0" @@ -10384,7 +10294,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.14, lodash@npm:^4.17.21": +"lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -10779,7 +10689,7 @@ __metadata: languageName: node linkType: hard -"mime@npm:1.6.0, mime@npm:^1.6.0": +"mime@npm:1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" bin: @@ -10976,7 +10886,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.6": +"mkdirp@npm:^0.5.4": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -11550,15 +11460,6 @@ __metadata: languageName: node linkType: hard -"opener@npm:^1.5.1": - version: 1.5.2 - resolution: "opener@npm:1.5.2" - bin: - opener: bin/opener-bin.js - checksum: dd56256ab0cf796585617bc28e06e058adf09211781e70b264c76a1dbe16e90f868c974e5bf5309c93469157c7d14b89c35dc53fe7293b0e40b4d2f92073bc79 - languageName: node - linkType: hard - "opentype.js@npm:^1.3.4": version: 1.3.4 resolution: "opentype.js@npm:1.3.4" @@ -12132,17 +12033,6 @@ __metadata: languageName: node linkType: hard -"portfinder@npm:^1.0.28": - version: 1.0.32 - resolution: "portfinder@npm:1.0.32" - dependencies: - async: "npm:^2.6.4" - debug: "npm:^3.2.7" - mkdirp: "npm:^0.5.6" - checksum: cef8b567b78aabccc59fe8e103bac8b394bb45a6a69be626608f099f454124c775aaf47b274c006332c07ab3f501cde55e49aaeb9d49d78d90362d776a565cbf - languageName: node - linkType: hard - "posix-character-classes@npm:^0.1.0": version: 0.1.1 resolution: "posix-character-classes@npm:0.1.1" @@ -12569,15 +12459,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.4.0": - version: 6.12.0 - resolution: "qs@npm:6.12.0" - dependencies: - side-channel: "npm:^1.0.6" - checksum: e165a77ac5f3ca60c15c5f3d51b321ddec7aa438804436b29d160117bc6fb7bf7dab94abd0c7d7c0785890d3a75ae41e1d6346e158aaf1540c6fe53a31f11675 - languageName: node - linkType: hard - "querystring-es3@npm:^0.2.0": version: 0.2.1 resolution: "querystring-es3@npm:0.2.1" @@ -12651,6 +12532,18 @@ __metadata: languageName: node linkType: hard +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4 + languageName: node + linkType: hard + "react-colorful@npm:^5.1.2": version: 5.6.1 resolution: "react-colorful@npm:5.6.1" @@ -13715,13 +13608,6 @@ __metadata: languageName: node linkType: hard -"secure-compare@npm:3.0.1": - version: 3.0.1 - resolution: "secure-compare@npm:3.0.1" - checksum: af3102f3f555d917c8ffff7a5f6f00f70195708f4faf82d48794485c9f3cb365cee0dd4da6b4e53e8964f172970bce6069b6101ba3ce8c309bff54f460d1f650 - languageName: node - linkType: hard - "semver-greatest-satisfied-range@npm:^1.1.0": version: 1.1.0 resolution: "semver-greatest-satisfied-range@npm:1.1.0" @@ -13812,20 +13698,6 @@ __metadata: languageName: node linkType: hard -"set-function-length@npm:^1.2.1": - version: 1.2.2 - resolution: "set-function-length@npm:1.2.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.2" - checksum: 82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c - languageName: node - linkType: hard - "set-function-name@npm:^2.0.0": version: 2.0.1 resolution: "set-function-name@npm:2.0.1" @@ -13957,18 +13829,6 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.6": - version: 1.0.6 - resolution: "side-channel@npm:1.0.6" - dependencies: - call-bind: "npm:^1.0.7" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.4" - object-inspect: "npm:^1.13.1" - checksum: d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f - languageName: node - linkType: hard - "siginfo@npm:^2.0.0": version: 2.0.0 resolution: "siginfo@npm:2.0.0" @@ -15309,15 +15169,6 @@ __metadata: languageName: node linkType: hard -"union@npm:~0.5.0": - version: 0.5.0 - resolution: "union@npm:0.5.0" - dependencies: - qs: "npm:^6.4.0" - checksum: 9ac158d99991063180e56f408f5991e808fa07594713439c098116da09215c154672ee8c832e16a6b39b037609c08bcaff8ff07c1e3e46c3cc622897972af2aa - languageName: node - linkType: hard - "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" @@ -15461,13 +15312,6 @@ __metadata: languageName: node linkType: hard -"url-join@npm:^4.0.1": - version: 4.0.1 - resolution: "url-join@npm:4.0.1" - checksum: ac65e2c7c562d7b49b68edddcf55385d3e922bc1dd5d90419ea40b53b6de1607d1e45ceb71efb9d60da02c681d13c6cb3a1aa8b13fc0c989dfc219df97ee992d - languageName: node - linkType: hard - "url-parse@npm:^1.5.3": version: 1.5.10 resolution: "url-parse@npm:1.5.10" @@ -15923,15 +15767,6 @@ __metadata: languageName: node linkType: hard -"whatwg-encoding@npm:^2.0.0": - version: 2.0.0 - resolution: "whatwg-encoding@npm:2.0.0" - dependencies: - iconv-lite: "npm:0.6.3" - checksum: 91b90a49f312dc751496fd23a7e68981e62f33afe938b97281ad766235c4872fc4e66319f925c5e9001502b3040dd25a33b02a9c693b73a4cbbfdc4ad10c3e3e - languageName: node - linkType: hard - "whatwg-encoding@npm:^3.1.1": version: 3.1.1 resolution: "whatwg-encoding@npm:3.1.1" From a65282c01b7ede81e9f6dce200a0adfcaf96f7e7 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 26 Apr 2024 06:30:00 +0200 Subject: [PATCH 03/34] :bug: Fix update-temp-file audit_log stored data --- backend/src/app/rpc/commands/files_temp.clj | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/app/rpc/commands/files_temp.clj b/backend/src/app/rpc/commands/files_temp.clj index bc183cfd95..4eef262145 100644 --- a/backend/src/app/rpc/commands/files_temp.clj +++ b/backend/src/app/rpc/commands/files_temp.clj @@ -16,6 +16,7 @@ [app.db.sql :as sql] [app.features.components-v2 :as feat.compv2] [app.features.fdata :as fdata] + [app.loggers.audit :as audit] [app.rpc :as-alias rpc] [app.rpc.commands.files :as files] [app.rpc.commands.files-create :as files.create] @@ -23,6 +24,7 @@ [app.rpc.commands.projects :as projects] [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.util.blob :as blob] [app.util.pointer-map :as pmap] [app.util.services :as sv] @@ -100,7 +102,9 @@ :revn revn :data nil :changes (blob/encode changes)}) - nil))) + (rph/with-meta (rph/wrap nil) + {::audit/replace-props {:file-id id + :revn revn}})))) ;; --- MUTATION COMMAND: persist-temp-file From 6901acb37e79974f132146b60b85cdb031e9025f Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Thu, 25 Apr 2024 19:50:22 +0200 Subject: [PATCH 04/34] :bug: Fix ungrouping detach components --- frontend/src/app/main/data/workspace/groups.cljs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index c3a4032694..bbda31ecc7 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -143,18 +143,12 @@ (map-indexed vector) (filter #(#{(:id group)} (second %))) (ffirst) - inc) - - ;; Shapes that are in a component (including root) must be detached, - ;; because cannot be easyly synchronized back to the main component. - shapes-to-detach (filter ctk/in-component-copy? - (cfh/get-children-with-self objects (:id group)))] + inc)] (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) (pcb/change-parent parent-id children index-in-parent) - (pcb/remove-objects [(:id group)]) - (pcb/update-shapes (map :id shapes-to-detach) ctk/detach-shape)))) + (pcb/remove-objects [(:id group)])))) (defn remove-frame-changes [it page-id frame objects] From 1026f5b972f25e0593478abb1c2aa1a5fc521910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 24 Apr 2024 15:41:12 +0200 Subject: [PATCH 05/34] :wrench: Change common tests runner to kaocha --- .circleci/config.yml | 2 +- common/deps.edn | 8 ++------ common/test/common_tests/geom_point_test.cljc | 2 +- common/tests.edn | 4 ++++ 4 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 common/tests.edn diff --git a/.circleci/config.yml b/.circleci/config.yml index e1a6e629fe..18d7cf5d7d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,7 +93,7 @@ jobs: working_directory: "./common" command: | yarn test - clojure -X:dev:test :patterns '["common-tests.*-test"]' + clojure -M:dev:test - run: name: "frontend tests" diff --git a/common/deps.edn b/common/deps.edn index b014882f92..7040212887 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -76,12 +76,8 @@ :ns-default build} :test - {:extra-paths ["test"] - :extra-deps - {io.github.cognitect-labs/test-runner - {:git/tag "v0.5.1" :git/sha "dfb30dd"}} - :main-opts ["-m" "cognitect.test-runner"] - :exec-fn cognitect.test-runner.api/test} + {:main-opts ["-m" "kaocha.runner"] + :extra-deps {lambdaisland/kaocha {:mvn/version "1.88.1376"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"]} diff --git a/common/test/common_tests/geom_point_test.cljc b/common/test/common_tests/geom_point_test.cljc index 0490e1d02e..6ba7239f07 100644 --- a/common/test/common_tests/geom_point_test.cljc +++ b/common/test/common_tests/geom_point_test.cljc @@ -203,7 +203,7 @@ (t/is (mth/close? 1.5 (:x rs))) (t/is (mth/close? 3.5 (:y rs))))) -(t/deftest transform-point +(t/deftest ^:kaocha/skip transform-point ;;todo ) diff --git a/common/tests.edn b/common/tests.edn new file mode 100644 index 0000000000..9f487a7eaf --- /dev/null +++ b/common/tests.edn @@ -0,0 +1,4 @@ +#kaocha/v1 + {:tests [{:id :unit + :test-paths ["test"]}] + :kaocha/reporter [kaocha.report/dots]} From dde89e60ddf30005287e2f56868729e1b4bc1fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 18 Apr 2024 18:10:00 +0200 Subject: [PATCH 06/34] :white_check_mark: Add new helper functions for common tests --- backend/src/app/rpc/commands/files_create.clj | 41 +- common/src/app/common/files/builder.cljc | 2 +- common/src/app/common/files/changes.cljc | 2 +- common/src/app/common/types/color.cljc | 17 + common/src/app/common/types/file.cljc | 33 +- common/src/app/common/types/page.cljc | 6 +- common/src/app/common/types/typography.cljc | 21 +- .../test/common_tests/helpers/components.cljc | 146 ------- common/test/common_tests/helpers/files.cljc | 358 ++++++++++++------ common/test/common_tests/helpers/ids_map.cljc | 36 ++ common/test/common_tests/record_test.cljc | 1 - common/test/common_tests/types_file_test.cljc | 345 +++++++++-------- 12 files changed, 556 insertions(+), 452 deletions(-) delete mode 100644 common/test/common_tests/helpers/components.cljc create mode 100644 common/test/common_tests/helpers/ids_map.cljc diff --git a/backend/src/app/rpc/commands/files_create.clj b/backend/src/app/rpc/commands/files_create.clj index cc15830d47..ab386eca0b 100644 --- a/backend/src/app/rpc/commands/files_create.clj +++ b/backend/src/app/rpc/commands/files_create.clj @@ -6,13 +6,10 @@ (ns app.rpc.commands.files-create (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.features :as cfeat] - [app.common.files.defaults :refer [version]] [app.common.schema :as sm] [app.common.types.file :as ctf] - [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] [app.features.fdata :as feat.fdata] @@ -40,7 +37,7 @@ (defn create-file [{:keys [::db/conn] :as cfg} {:keys [id name project-id is-shared revn - modified-at deleted-at create-page + modified-at deleted-at create-page page-id ignore-sync-until features] :or {is-shared false revn 0 create-page true} :as params}] @@ -51,23 +48,17 @@ (binding [pmap/*tracked* (pmap/create-tracked) cfeat/*current* features] - (let [id (or id (uuid/next)) - - data (if create-page - (ctf/make-file-data id) - (ctf/make-file-data id nil)) - - file {:id id - :project-id project-id - :name name - :revn revn - :is-shared is-shared - :version version - :data data - :features features - :ignore-sync-until ignore-sync-until - :modified-at modified-at - :deleted-at deleted-at} + (let [file (ctf/make-file {:id id + :project-id project-id + :name name + :revn revn + :is-shared is-shared + :features features + :ignore-sync-until ignore-sync-until + :modified-at modified-at + :deleted-at deleted-at + :create-page create-page + :page-id page-id}) file (if (contains? features "fdata/objects-map") (feat.fdata/enable-objects-map file) @@ -75,9 +66,7 @@ file (if (contains? features "fdata/pointer-map") (feat.fdata/enable-pointer-map file) - file) - - file (d/without-nils file)] + file)] (db/insert! conn :file (-> file @@ -86,9 +75,9 @@ {::db/return-keys false}) (when (contains? features "fdata/pointer-map") - (feat.fdata/persist-pointers! cfg id)) + (feat.fdata/persist-pointers! cfg (:id file))) - (->> (assoc params :file-id id :role :owner) + (->> (assoc params :file-id (:id file) :role :owner) (create-file-role! conn)) (db/update! conn :project diff --git a/common/src/app/common/files/builder.cljc b/common/src/app/common/files/builder.cljc index c30a2e8c81..f5519f736c 100644 --- a/common/src/app/common/files/builder.cljc +++ b/common/src/app/common/files/builder.cljc @@ -147,7 +147,7 @@ [file data] (dm/assert! (nil? (:current-component-id file))) (let [page-id (or (:id data) (uuid/next)) - page (-> (ctp/make-empty-page page-id "Page 1") + page (-> (ctp/make-empty-page {:id page-id :name "Page 1"}) (d/deep-merge data))] (-> file (commit-change diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 978cc8edf3..a82ab947c0 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -578,7 +578,7 @@ (ex/raise :type :conflict :hint "id+name or page should be provided, never both")) (let [page (if (and (string? name) (uuid? id)) - (ctp/make-empty-page id name) + (ctp/make-empty-page {:id id :name name}) page)] (ctpl/add-page data page))) diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index 7bded14921..111343d58d 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -13,6 +13,7 @@ [app.common.types.color.generic :as-alias color-generic] [app.common.types.color.gradient :as-alias color-gradient] [app.common.types.color.gradient.stop :as-alias color-gradient-stop] + [app.common.uuid :as uuid] [clojure.test.check.generators :as tgen])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -105,6 +106,22 @@ ;; HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; --- factory + +(defn make-color + [{:keys [id name path value color opacity ref-id ref-file gradient image]}] + (-> {:id (or id (uuid/next)) + :name (or name color "Black") + :path path + :value value + :color (or color "#000000") + :opacity (or opacity 1) + :ref-id ref-id + :ref-file ref-file + :gradient gradient + :image image} + (d/without-nils))) + ;; --- fill (defn fill->shape-color diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 0c5fbf5726..d12b759dfd 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.features :as cfeat] + [app.common.files.defaults :refer [version]] [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] @@ -78,7 +79,7 @@ ([file-id page-id] (let [page (when (some? page-id) - (ctp/make-empty-page page-id "Page 1"))] + (ctp/make-empty-page {:id page-id :name "Page 1"}))] (cond-> (assoc empty-file-data :id file-id) (some? page-id) @@ -87,6 +88,34 @@ (contains? cfeat/*current* "components/v2") (assoc-in [:options :components-v2] true))))) +(defn make-file + [{:keys [id project-id name revn is-shared features + ignore-sync-until modified-at deleted-at + create-page page-id] + :or {is-shared false revn 0 create-page true}}] + + (let [id (or id (uuid/next)) + + data (if create-page + (if page-id + (make-file-data id page-id) + (make-file-data id)) + (make-file-data id nil)) + + file {:id id + :project-id project-id + :name name + :revn revn + :is-shared is-shared + :version version + :data data + :features features + :ignore-sync-until ignore-sync-until + :modified-at modified-at + :deleted-at deleted-at}] + + (d/without-nils file))) + ;; Helpers (defn file-data @@ -457,7 +486,7 @@ (gpt/point 0 0) (ctn/shapes-seq library-page))] [file-data (:id library-page) position]) - (let [library-page (ctp/make-empty-page (uuid/next) "Main components")] + (let [library-page (ctp/make-empty-page {:id (uuid/next) :name "Main components"})] [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) (defn- absorb-components diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 6c1d427dff..0b20389288 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -69,10 +69,10 @@ :name "Root Frame"})}}) (defn make-empty-page - [id name] + [{:keys [id name]}] (-> empty-page-data - (assoc :id id) - (assoc :name name))) + (assoc :id (or id (uuid/next))) + (assoc :name (or name "Page 1")))) ;; --- Helpers for flow diff --git a/common/src/app/common/types/typography.cljc b/common/src/app/common/types/typography.cljc index 4fe5c9565a..6e216020a8 100644 --- a/common/src/app/common/types/typography.cljc +++ b/common/src/app/common/types/typography.cljc @@ -6,8 +6,10 @@ (ns app.common.types.typography (:require + [app.common.data :as d] [app.common.schema :as sm] - [app.common.text :as txt])) + [app.common.text :as txt] + [app.common.uuid :as uuid])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SCHEMA @@ -36,6 +38,23 @@ ;; HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn make-typography + [{:keys [id name path font-id font-family font-variant-id font-size + font-weight font-style line-height letter-spacing text-transform]}] + (-> {:id (or id (uuid/next)) + :name (or name "Typography 1") + :path path + :font-id (or font-id "sourcesanspro") + :font-family (or font-family "sourcesanspro") + :font-variant-id (or font-variant-id "regular") + :font-size (or font-size "14") + :font-weight (or font-weight "480") + :font-style (or font-style "normal") + :line-height (or line-height "1.2") + :letter-spacing (or letter-spacing "0") + :text-transform (or text-transform "none")} + (d/without-nils))) + (defn uses-library-typographies? "Check if the shape uses any typography in the given library." [shape library-id] diff --git a/common/test/common_tests/helpers/components.cljc b/common/test/common_tests/helpers/components.cljc deleted file mode 100644 index 523438c449..0000000000 --- a/common/test/common_tests/helpers/components.cljc +++ /dev/null @@ -1,146 +0,0 @@ -;; 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 common-tests.helpers.components - (:require - [app.common.files.helpers :as cfh] - [app.common.types.component :as ctk] - [app.common.types.container :as ctn] - [app.common.types.file :as ctf] - [clojure.test :as t])) - -;; ---- Helpers to manage libraries and synchronization - -(defn check-instance-root - [shape] - (t/is (some? (:shape-ref shape))) - (t/is (some? (:component-id shape))) - (t/is (= (:component-root shape) true))) - -(defn check-instance-subroot - [shape] - (t/is (some? (:shape-ref shape))) - (t/is (some? (:component-id shape))) - (t/is (nil? (:component-root shape)))) - -(defn check-instance-child - [shape] - (t/is (some? (:shape-ref shape))) - (t/is (nil? (:component-id shape))) - (t/is (nil? (:component-file shape))) - (t/is (nil? (:component-root shape)))) - -(defn check-instance-inner - [shape] - (if (some? (:component-id shape)) - (check-instance-subroot shape) - (check-instance-child shape))) - -(defn check-noninstance - [shape] - (t/is (nil? (:shape-ref shape))) - (t/is (nil? (:component-id shape))) - (t/is (nil? (:component-file shape))) - (t/is (nil? (:component-root shape))) - (t/is (nil? (:remote-synced? shape))) - (t/is (nil? (:touched shape)))) - -(defn check-from-file - [shape file] - (t/is (= (:component-file shape) - (:id file)))) - -(defn resolve-instance - "Get the shape with the given id and all its children, and - verify that they are a well constructed instance tree." - [page root-inst-id] - (let [root-inst (ctn/get-shape page root-inst-id) - shapes-inst (cfh/get-children-with-self (:objects page) - root-inst-id)] - (check-instance-root (first shapes-inst)) - (run! check-instance-inner (rest shapes-inst)) - - shapes-inst)) - -(defn resolve-noninstance - "Get the shape with the given id and all its children, and - verify that they are not a component instance." - [page root-inst-id] - (let [root-inst (ctn/get-shape page root-inst-id) - shapes-inst (cfh/get-children-with-self (:objects page) - root-inst-id)] - (run! check-noninstance shapes-inst) - - shapes-inst)) - -(defn resolve-instance-and-main - "Get the shape with the given id and all its children, and also - the main component and all its shapes." - [page root-inst-id libraries] - (let [root-inst (ctn/get-shape page root-inst-id) - - component (ctf/get-component libraries (:component-file root-inst) (:component-id root-inst)) - - shapes-inst (cfh/get-children-with-self (:objects page) root-inst-id) - shapes-main (cfh/get-children-with-self (:objects component) (:shape-ref root-inst)) - - unique-refs (into #{} (map :shape-ref) shapes-inst) - - main-exists? (fn [shape] - (let [component-shape - (ctn/get-component-shape (:objects page) shape) - - component - (ctf/get-component libraries (:component-file component-shape) (:component-id component-shape)) - - main-shape - (ctn/get-shape component (:shape-ref shape))] - - (t/is (some? main-shape))))] - - ;; Validate that the instance tree is well constructed - (check-instance-root (first shapes-inst)) - (run! check-instance-inner (rest shapes-inst)) - (t/is (= (count shapes-inst) - (count shapes-main) - (count unique-refs))) - (run! main-exists? shapes-inst) - - [shapes-inst shapes-main component])) - -(defn resolve-instance-and-main-allow-dangling - "Get the shape with the given id and all its children, and also - the main component and all its shapes. Allows shapes with the - corresponding component shape missing." - [page root-inst-id libraries] - (let [root-inst (ctn/get-shape page root-inst-id) - - component (ctf/get-component libraries (:component-file root-inst) (:component-id root-inst)) - - shapes-inst (cfh/get-children-with-self (:objects page) root-inst-id) - shapes-main (cfh/get-children-with-self (:objects component) (:shape-ref root-inst)) - - unique-refs (into #{} (map :shape-ref) shapes-inst) - - main-exists? (fn [shape] - (let [component-shape - (ctn/get-component-shape (:objects page) shape) - - component - (ctf/get-component libraries (:component-file component-shape) (:component-id component-shape)) - - main-shape - (ctn/get-shape component (:shape-ref shape))] - - (t/is (some? main-shape))))] - - ;; Validate that the instance tree is well constructed - (check-instance-root (first shapes-inst)) - - [shapes-inst shapes-main component])) - - - diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index 3f27d7021b..306d2ef81d 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -6,150 +6,278 @@ (ns common-tests.helpers.files (:require + [app.common.data.macros :as dm] [app.common.features :as ffeat] + [app.common.files.changes :as cfc] + [app.common.files.helpers :as cfh] + [app.common.files.validate :as cfv] [app.common.geom.point :as gpt] + [app.common.pprint :refer [pprint]] + [app.common.types.color :as ctc] [app.common.types.colors-list :as ctcl] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] - [app.common.types.typographies-list :as ctyl] - [app.common.uuid :as uuid])) + [app.common.types.typographies-list :as cttl] + [app.common.types.typography :as ctt] + [common-tests.helpers.ids-map :as thi])) -(defn- make-file-data - [file-id page-id] - (binding [ffeat/*current* #{"components/v2"}] - (ctf/make-file-data file-id page-id))) - -(def ^:private idmap (atom {})) - -(defn reset-idmap! - [next] - (reset! idmap {}) - (next)) - -(defn id - [label] - (get @idmap label)) +;; ----- Files (defn sample-file - ([file-id page-id] (sample-file file-id page-id nil)) - ([file-id page-id props] - (merge {:id file-id - :name (get props :name "File1") - :data (make-file-data file-id page-id)} - props))) + [label & {:keys [page-label name] :as params}] + (binding [ffeat/*current* #{"components/v2"}] + (let [params (cond-> params + label + (assoc :id (thi/new-id! label)) + + page-label + (assoc :page-id (thi/new-id! page-label)) + + (nil? name) + (assoc :name "Test file")) + + file (-> (ctf/make-file (dissoc params :page-label)) + (assoc :features #{"components/v2"})) + + page (-> file + :data + (ctpl/pages-seq) + (first))] + + (with-meta file + {:current-page-id (:id page)})))) + +(defn validate-file! + ([file] (validate-file! file {})) + ([file libraries] + (cfv/validate-file-schema! file) + (cfv/validate-file! file libraries))) + +(defn apply-changes + [file changes] + (let [file' (ctf/update-file-data file #(cfc/process-changes % (:redo-changes changes) true))] + (validate-file! file') + file')) + +(declare current-page-id) +(declare get-page) + +(defn dump-file + [file & {:keys [page-label libraries] :as params}] + (let [params (-> params + (or {:show-ids true :show-touched true}) + (dissoc page-label libraries)) + page (if (some? page-label) + (:id (get-page file page-label)) + (current-page-id file)) + libraries (or libraries {})] + + (ctf/dump-tree file page libraries params))) + +(defn pprint-file + [file & {:keys [level length] :or {level 10 length 1000}}] + (pprint file {:level level :length length})) + +;; ----- Pages + +(defn sample-page + [label & {:keys [] :as params}] + (ctp/make-empty-page (assoc params :id (thi/new-id! label)))) + +(defn add-sample-page + [file label & {:keys [] :as params}] + (let [page (sample-page label params)] + (-> file + (ctf/update-file-data #(ctpl/add-page % page)) + (vary-meta assoc :current-page-id (:id page))))) + +(defn get-page + [file label] + (ctpl/get-page (:data file) (thi/id label))) + +(defn current-page-id + [file] + (:current-page-id (meta file))) + +(defn current-page + [file] + (ctpl/get-page (:data file) (current-page-id file))) + +(defn switch-to-page + [file label] + (vary-meta file assoc :current-page-id (thi/id label))) + +;; ----- Shapes (defn sample-shape - [file label type page-id props] - (ctf/update-file-data - file - (fn [file-data] - (let [frame-id (get props :frame-id uuid/zero) - parent-id (get props :parent-id uuid/zero) - shape (cts/setup-shape - (-> {:type type - :width 1 - :height 1} - (merge props)))] + [label & {:keys [type] :as params}] + (let [params (cond-> params + label + (assoc :id (thi/new-id! label)) - (swap! idmap assoc label (:id shape)) + (nil? type) + (assoc :type :rect))] + + (cts/setup-shape params))) + +(defn add-sample-shape + [file label & {:keys [parent-label] :as params}] + (let [page (current-page file) + shape (sample-shape label (dissoc params :parent-label)) + parent-id (when parent-label + (thi/id parent-label)) + parent (when parent-id + (ctst/get-shape page parent-id)) + frame-id (if (cfh/frame-shape? parent) + (:id parent) + (:frame-id parent))] + (ctf/update-file-data + file + (fn [file-data] (ctpl/update-page file-data - page-id + (:id page) #(ctst/add-shape (:id shape) shape % frame-id parent-id - 0 + nil true)))))) -(defn sample-component - [file label page-id shape-id] - (ctf/update-file-data - file - (fn [file-data] - (let [page (ctpl/get-page file-data page-id) +(defn get-shape + [file label & {:keys [page-label]}] + (let [page (if page-label + (get-page file page-label) + (current-page file))] + (ctst/get-shape page (thi/id label)))) - [component-shape component-shapes updated-shapes] - (ctn/make-component-shape (ctn/get-shape page shape-id) - (:objects page) - (:id file) - true)] +(defn get-shape-by-id + [file id & {:keys [page-label]}] + (let [page (if page-label + (get-page file page-label) + (current-page file))] + (ctst/get-shape page id))) - (swap! idmap assoc label (:id component-shape)) - (-> file-data - (ctpl/update-page page-id - #(reduce (fn [page shape] (ctst/set-shape page shape)) - % - updated-shapes)) - (ctkl/add-component {:id (:id component-shape) - :name (:name component-shape) - :path "" - :main-instance-id shape-id - :main-instance-page page-id - :shapes component-shapes})))))) +;; ----- Components -(defn sample-instance - [file label page-id library component-id] - (ctf/update-file-data - file - (fn [file-data] - (let [[instance-shape instance-shapes] - (ctn/make-component-instance (ctpl/get-page file-data page-id) - (ctkl/get-component (:data library) component-id) - (:data library) - (gpt/point 0 0) - true)] +(defn make-component + [file label root-label] + (let [page (current-page file) + root (get-shape file root-label)] - (swap! idmap assoc label (:id instance-shape)) - (-> file-data - (ctpl/update-page page-id - #(reduce (fn [page shape] - (ctst/add-shape (:id shape) - shape - page - uuid/zero - (:parent-id shape) - 0 - true)) - % - instance-shapes))))))) + (dm/assert! + "Need that root is already a frame" + (cfh/frame-shape? root)) + + (let [[_new-root _new-shapes updated-shapes] + (ctn/convert-shape-in-component root (:objects page) (:id file)) + + updated-root (first updated-shapes)] ; Can't use new-root because it has a new id + + (thi/set-id! label (:component-id updated-root)) + + (ctf/update-file-data + file + (fn [file-data] + (as-> file-data $ + (reduce (fn [file-data shape] + (ctpl/update-page file-data + (:id page) + #(update % :objects assoc (:id shape) shape))) + $ + updated-shapes) + (ctkl/add-component $ + {:id (:component-id updated-root) + :name (:name updated-root) + :main-instance-id (:id updated-root) + :main-instance-page (:id page) + :shapes updated-shapes}))))))) + +(defn get-component + [file label] + (ctkl/get-component (:data file) (thi/id label))) + +(defn get-component-by-id + [file id] + (ctkl/get-component (:data file) id)) + +(defn instantiate-component + [file component-label copy-root-label & {:keys [parent-label library] :as params}] + (let [page (current-page file) + library (or library file) + component (get-component library component-label) + parent-id (when parent-label + (thi/id parent-label)) + parent (when parent-id + (ctst/get-shape page parent-id)) + frame-id (if (cfh/frame-shape? parent) + (:id parent) + (:frame-id parent)) + + [copy-root copy-shapes] + (ctn/make-component-instance page + component + (:data library) + (gpt/point 100 100) + true + {:force-id (thi/new-id! copy-root-label) + :force-frame-id frame-id}) + + copy-root' (cond-> copy-root + (some? parent) + (assoc :parent-id parent-id) + + (some? frame-id) + (assoc :frame-id frame-id) + + (and (some? parent) (ctn/in-any-component? (:objects page) parent)) + (dissoc :component-root))] + + (ctf/update-file-data + file + (fn [file-data] + (as-> file-data $ + (ctpl/update-page $ + (:id page) + #(ctst/add-shape (:id copy-root') + copy-root' + % + frame-id + parent-id + nil + true)) + (reduce (fn [file-data shape] + (ctpl/update-page file-data + (:id page) + #(ctst/add-shape (:id shape) + shape + % + (:parent-id shape) + (:frame-id shape) + nil + true))) + $ + (remove #(= (:id %) (:did copy-root')) copy-shapes))))))) (defn sample-color - [file label props] - (ctf/update-file-data - file - (fn [file-data] - (let [id (uuid/next) - props (merge {:id id - :name "Color 1" - :color "#000000" - :opacity 1} - props)] - (swap! idmap assoc label id) - (ctcl/add-color file-data props))))) + [label & {:keys [] :as params}] + (ctc/make-color (assoc params :id (thi/new-id! label)))) + +(defn add-sample-color + [file label & {:keys [] :as params}] + (let [color (sample-color label params)] + (ctf/update-file-data file #(ctcl/add-color % color)))) (defn sample-typography - [file label props] - (ctf/update-file-data - file - (fn [file-data] - (let [id (uuid/next) - props (merge {:id id - :name "Typography 1" - :font-id "sourcesanspro" - :font-family "sourcesanspro" - :font-size "14" - :font-style "normal" - :font-variant-id "regular" - :font-weight "400" - :line-height "1.2" - :letter-spacing "0" - :text-transform "none"} - props)] - (swap! idmap assoc label id) - (ctyl/add-typography file-data props))))) + [label & {:keys [] :as params}] + (ctt/make-typography (assoc params :id (thi/new-id! label)))) +(defn add-sample-typography + [file label & {:keys [] :as params}] + (let [typography (sample-typography label params)] + (ctf/update-file-data file #(cttl/add-typography % typography)))) diff --git a/common/test/common_tests/helpers/ids_map.cljc b/common/test/common_tests/helpers/ids_map.cljc new file mode 100644 index 0000000000..dc196598d9 --- /dev/null +++ b/common/test/common_tests/helpers/ids_map.cljc @@ -0,0 +1,36 @@ +;; 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 common-tests.helpers.ids-map + (:require + [app.common.uuid :as uuid])) + +;; ---- Helpers to manage ids as known identifiers + +(def ^:private idmap (atom {})) + +(defn reset-idmap! [] + (reset! idmap {})) + +(defn set-id! + [label id] + (swap! idmap assoc label id)) + +(defn new-id! + [label] + (let [id (uuid/next)] + (set-id! label id) + id)) + +(defn id + [label] + (get @idmap label)) + +(defn test-fixture + ;; Ensure that each test starts with a clean ids map + [f] + (reset-idmap!) + (f)) diff --git a/common/test/common_tests/record_test.cljc b/common/test/common_tests/record_test.cljc index 64532078b5..cbf62d021f 100644 --- a/common/test/common_tests/record_test.cljc +++ b/common/test/common_tests/record_test.cljc @@ -27,7 +27,6 @@ (t/testing "unknown assoc" (let [o (assoc o :c 176)] - (prn o) (t/is (= 1 (:a o))) (t/is (= 2 (:b o))) (t/is (= 176 (:c o))))) diff --git a/common/test/common_tests/types_file_test.cljc b/common/test/common_tests/types_file_test.cljc index c95ea38934..1e316bc788 100644 --- a/common/test/common_tests/types_file_test.cljc +++ b/common/test/common_tests/types_file_test.cljc @@ -7,201 +7,234 @@ (ns common-tests.types-file-test (:require [app.common.data :as d] - [app.common.geom.point :as gpt] + [app.common.files.changes-builder :as pcb] + [app.common.files.libraries-helpers :as cflh] [app.common.text :as txt] [app.common.types.colors-list :as ctcl] [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] - [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.pages-list :as ctpl] - [app.common.types.shape :as cts] - [app.common.types.shape-tree :as ctst] [app.common.types.typographies-list :as ctyl] - [app.common.uuid :as uuid] - [clojure.pprint :refer [pprint]] [clojure.test :as t] - [common-tests.helpers.components :as thk] [common-tests.helpers.files :as thf] - [cuerdas.core :as str])) + [common-tests.helpers.ids-map :as thi])) -(t/use-fixtures :each thf/reset-idmap!) +(t/use-fixtures :each thi/test-fixture) -#_(t/deftest test-absorb-components - (let [library-id (uuid/custom 1 1) - library-page-id (uuid/custom 2 2) - file-id (uuid/custom 3 3) - file-page-id (uuid/custom 4 4) +(t/deftest test-create-file + (let [f1 (thf/sample-file :file1) + f2 (thf/sample-file :file2 :page-label :page1) + f3 (thf/sample-file :file3 :name "testing file") + f4 (-> (thf/sample-file :file4 :page-label :page2) + (thf/add-sample-page :page3 :name "testing page") + (thf/add-sample-shape :shape1)) + f5 (-> f4 + (thf/add-sample-shape :shape2) + (thf/switch-to-page :page2) + (thf/add-sample-shape :shape3 :name "testing shape" :width 100)) + s1 (thf/get-shape f4 :shape1) + s2 (thf/get-shape f5 :shape2 :page-label :page3) + s3 (thf/get-shape f5 :shape3)] - library (-> (thf/sample-file library-id library-page-id {:is-shared true}) - (thf/sample-shape :group1 - :group - library-page-id - {:name "Group1"}) - (thf/sample-shape :shape1 - :rect - library-page-id - {:name "Rect1" - :parent-id (thf/id :group1)}) - (thf/sample-component :component1 - library-page-id - (thf/id :group1))) + ;; (thf/pprint-file f4) - file (-> (thf/sample-file file-id file-page-id) - (thf/sample-instance :instance1 - file-page-id - library - (thf/id :component1))) + (t/is (= (:name f1) "Test file")) + (t/is (= (:name f3) "testing file")) + (t/is (= (:id f2) (thi/id :file2))) + (t/is (= (:id f4) (thi/id :file4))) + (t/is (= (-> f4 :data :pages-index vals first :id) (thi/id :page2))) + (t/is (= (-> f4 :data :pages-index vals first :name) "Page 1")) + (t/is (= (-> f4 :data :pages-index vals second :id) (thi/id :page3))) + (t/is (= (-> f4 :data :pages-index vals second :name) "testing page")) - absorbed-file (ctf/update-file-data - file - #(ctf/absorb-assets % (:data library))) + (t/is (= (:id (thf/current-page f2)) (thi/id :page1))) + (t/is (= (:id (thf/current-page f4)) (thi/id :page3))) + (t/is (= (:id (thf/current-page f5)) (thi/id :page2))) - pages (ctpl/pages-seq (ctf/file-data absorbed-file)) - components (ctkl/components-seq (ctf/file-data absorbed-file)) - shapes-1 (ctn/shapes-seq (first pages)) - shapes-2 (ctn/shapes-seq (second pages)) + (t/is (= (:id s1) (thi/id :shape1))) + (t/is (= (:name s1) "Rectangle")) + (t/is (= (:id s2) (thi/id :shape2))) + (t/is (= (:name s2) "Rectangle")) + (t/is (= (:id s3) (thi/id :shape3))) + (t/is (= (:name s3) "testing shape")) + (t/is (= (:width s3) 100)) + (t/is (= (:width (:selrect s3)) 100)))) - [[p-group p-shape] [c-group1 c-shape1] component1] - (thk/resolve-instance-and-main - (first pages) - (:id (second shapes-1)) - {file-id absorbed-file}) +(t/deftest test-create-components + (let [f1 (-> (thf/sample-file :file1) + (thf/add-sample-shape :main-root :type :frame) + (thf/add-sample-shape :main-child :parent-label :main-root) + (thf/make-component :component1 :main-root) + (thf/instantiate-component :component1 :copy-root))] - [[lp-group lp-shape] [c-group2 c-shape2] component2] - (thk/resolve-instance-and-main - (second pages) - (:id (second shapes-2)) - {file-id absorbed-file})] + #_(thf/dump-file f1) + #_(thf/pprint-file f4) - ;; Uncomment to debug + (t/is (= (:name f1) "Test file")))) - ;; (println "\n===== library") - ;; (ctf/dump-tree (:data library) - ;; library-page-id - ;; {} - ;; true) +(t/deftest test-add-component-from-single-shape + (let [; Setup + file (-> (thf/sample-file :file1) + (thf/add-sample-shape :shape1 :type :frame)) - ;; (println "\n===== file") - ;; (ctf/dump-tree (:data file) - ;; file-page-id - ;; {library-id library} - ;; true) + page (thf/current-page file) + shape1 (thf/get-shape file :shape1) - ;; (println "\n===== absorbed file") - ;; (println (str "\n<" (:name (first pages)) ">")) - ;; (ctf/dump-tree (:data absorbed-file) - ;; (:id (first pages)) - ;; {file-id absorbed-file} - ;; false) - ;; (println (str "\n<" (:name (second pages)) ">")) - ;; (ctf/dump-tree (:data absorbed-file) - ;; (:id (second pages)) - ;; {file-id absorbed-file} - ;; false) + ; Action + [_ component-id changes] + (cflh/generate-add-component (pcb/empty-changes) + [shape1] + (:objects page) + (:id page) + (:id file) + true + nil + nil) - (t/is (= (count pages) 2)) - (t/is (= (:name (first pages)) "Page 1")) - (t/is (= (:name (second pages)) "Main components")) + file' (thf/apply-changes file changes) - (t/is (= (count components) 1)) + ; Get + component (thf/get-component-by-id file' component-id) + root (thf/get-shape-by-id file' (:main-instance-id component))] - (t/is (= (:name p-group) "Group1")) - (t/is (ctk/instance-of? p-group file-id (:id component1))) - (t/is (not (:main-instance? p-group))) - (t/is (not (ctk/main-instance-of? (:id p-group) file-page-id component1))) - (t/is (ctk/is-main-of? c-group1 p-group)) + ; Check + (t/is (some? component)) + (t/is (some? root)) + (t/is (= (:component-id root) (:id component))))) - (t/is (= (:name p-shape) "Rect1")) - (t/is (ctk/is-main-of? c-shape1 p-shape)))) +(t/deftest test-absorb-components + (let [; Setup + library (-> (thf/sample-file :library + :is-shared true) + (thf/add-sample-shape :main-root + :type :frame + :name "Frame1") + (thf/add-sample-shape :rect1 + :type :rect + :name "Rect1" + :parent-label :main-root) + (thf/make-component :component1 :main-root)) + file (-> (thf/sample-file :file) + (thf/instantiate-component :component1 + :copy-root + :library library)) + + ; Action + file' (ctf/update-file-data + file + #(ctf/absorb-assets % (:data library))) + + _ (thf/validate-file! file') + + ; Get + pages' (ctpl/pages-seq (ctf/file-data file')) + components' (ctkl/components-seq (ctf/file-data file')) + component' (first components') + + copy-root' (thf/get-shape file' :copy-root) + main-root' (ctf/get-ref-shape (ctf/file-data file') component' copy-root')] + + ; Check + (t/is (= (count pages') 2)) + (t/is (= (:name (first pages')) "Page 1")) + (t/is (= (:name (second pages')) "Main components")) + + (t/is (= (count components') 1)) + + (t/is (ctk/instance-of? copy-root' (:id file') (:id component'))) + (t/is (ctk/is-main-of? main-root' copy-root' true)) + (t/is (ctk/main-instance-of? (:id main-root') (:id (second pages')) component')))) (t/deftest test-absorb-colors - (let [library-id (uuid/custom 1 1) - library-page-id (uuid/custom 2 2) - file-id (uuid/custom 3 3) - file-page-id (uuid/custom 4 4) + (let [; Setup + library (-> (thf/sample-file :library + :name "Test library" + :is-shared true) + (thf/add-sample-color :color1 {:name "Test color" + :color "#abcdef"})) - library (-> (thf/sample-file library-id library-page-id {:is-shared true}) - (thf/sample-color :color1 {:name "Test color" - :color "#abcdef"})) + file (-> (thf/sample-file :file + :name "Test file") + (thf/add-sample-shape :shape1 + :type :rect + :name "Rect1" + :fills [{:fill-color "#abcdef" + :fill-opacity 1 + :fill-color-ref-id (thi/id :color1) + :fill-color-ref-file (thi/id :library)}])) - file (-> (thf/sample-file file-id file-page-id) - (thf/sample-shape :shape1 - :rect - file-page-id - {:name "Rect1" - :fills [{:fill-color "#abcdef" - :fill-opacity 1 - :fill-color-ref-id (thf/id :color1) - :fill-color-ref-file library-id}]})) + ; Action + file' (ctf/update-file-data + file + #(ctf/absorb-assets % (:data library))) - absorbed-file (ctf/update-file-data - file - #(ctf/absorb-assets % (:data library))) + _ (thf/validate-file! file') - colors (ctcl/colors-seq (ctf/file-data absorbed-file)) - page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id) - shape1 (ctn/get-shape page (thf/id :shape1)) - fill (first (:fills shape1))] + ; Get + colors' (ctcl/colors-seq (ctf/file-data file')) + shape1' (thf/get-shape file' :shape1) + fill' (first (:fills shape1'))] - (t/is (= (count colors) 1)) - (t/is (= (:id (first colors)) (thf/id :color1))) - (t/is (= (:name (first colors)) "Test color")) - (t/is (= (:color (first colors)) "#abcdef")) + ; Check + (t/is (= (count colors') 1)) + (t/is (= (:id (first colors')) (thi/id :color1))) + (t/is (= (:name (first colors')) "Test color")) + (t/is (= (:color (first colors')) "#abcdef")) - (t/is (= (:fill-color fill) "#abcdef")) - (t/is (= (:fill-color-ref-id fill) (thf/id :color1))) - (t/is (= (:fill-color-ref-file fill) file-id)))) + (t/is (= (:fill-color fill') "#abcdef")) + (t/is (= (:fill-color-ref-id fill') (thi/id :color1))) + (t/is (= (:fill-color-ref-file fill') (:id file'))))) (t/deftest test-absorb-typographies - (let [library-id (uuid/custom 1 1) - library-page-id (uuid/custom 2 2) - file-id (uuid/custom 3 3) - file-page-id (uuid/custom 4 4) + (let [; Setup + library (-> (thf/sample-file :library + :name "Test library" + :is-shared true) + (thf/add-sample-typography :typography1 {:name "Test typography"})) - library (-> (thf/sample-file library-id library-page-id {:is-shared true}) - (thf/sample-typography :typography1 {:name "Test typography"})) + file (-> (thf/sample-file :file + :name "Test file") + (thf/add-sample-shape :shape1 + :type :text + :name "Text1" + :content {:type "root" + :children [{:type "paragraph-set" + :children [{:type "paragraph" + :key "67uep" + :children [{:text "Example text" + :typography-ref-id (thi/id :typography1) + :typography-ref-file (thi/id :library) + :line-height "1.2" + :font-style "normal" + :text-transform "none" + :text-align "left" + :font-id "sourcesanspro" + :font-family "sourcesanspro" + :font-size "14" + :font-weight "400" + :font-variant-id "regular" + :text-decoration "none" + :letter-spacing "0" + :fills [{:fill-color "#000000" + :fill-opacity 1}]}]}]}]})) + ; Action + file' (ctf/update-file-data + file + #(ctf/absorb-assets % (:data library))) - file (-> (thf/sample-file file-id file-page-id) - (thf/sample-shape :shape1 - :text - file-page-id - {:name "Text1" - :content {:type "root" - :children [{:type "paragraph-set" - :children [{:type "paragraph" - :key "67uep" - :children [{:text "Example text" - :typography-ref-id (thf/id :typography1) - :typography-ref-file library-id - :line-height "1.2" - :font-style "normal" - :text-transform "none" - :text-align "left" - :font-id "sourcesanspro" - :font-family "sourcesanspro" - :font-size "14" - :font-weight "400" - :font-variant-id "regular" - :text-decoration "none" - :letter-spacing "0" - :fills [{:fill-color "#000000" - :fill-opacity 1}]}]}]}]}})) - absorbed-file (ctf/update-file-data - file - #(ctf/absorb-assets % (:data library))) + _ (thf/validate-file! file') - typographies (ctyl/typographies-seq (ctf/file-data absorbed-file)) - page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id) + ; Get + typographies' (ctyl/typographies-seq (ctf/file-data file')) + shape1' (thf/get-shape file' :shape1) + text-node' (d/seek #(some? (:text %)) (txt/node-seq (:content shape1')))] - shape1 (ctn/get-shape page (thf/id :shape1)) - text-node (d/seek #(some? (:text %)) (txt/node-seq (:content shape1)))] + (t/is (= (count typographies') 1)) + (t/is (= (:id (first typographies')) (thi/id :typography1))) + (t/is (= (:name (first typographies')) "Test typography")) - (t/is (= (count typographies) 1)) - (t/is (= (:id (first typographies)) (thf/id :typography1))) - (t/is (= (:name (first typographies)) "Test typography")) - - (t/is (= (:typography-ref-id text-node) (thf/id :typography1))) - (t/is (= (:typography-ref-file text-node) file-id)))) + (t/is (= (:typography-ref-id text-node') (thi/id :typography1))) + (t/is (= (:typography-ref-file text-node') (:id file'))))) From 22939aa689847b2a55d919608feb592fa1dbb120 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Fri, 26 Apr 2024 11:55:56 +0200 Subject: [PATCH 07/34] :bug: Fix inspect permission on shared prototype for owners --- frontend/src/app/main/ui/viewer/header.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 77460f0b6b..abf81479f4 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -332,7 +332,7 @@ :title (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))} i/comments]) - (when (or (= (:type permissions) :membership) + (when (or (:in-team permissions) (and (= (:type permissions) :share-link) (= (:who-inspect permissions) "all"))) [:button {:on-click go-to-inspect From bebdc78ce6d34b38058f2badfd6f0523115185e3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 25 Apr 2024 15:35:53 +0200 Subject: [PATCH 08/34] :bug: Fix problem with exporter texts --- frontend/src/app/main/ui/shapes/export.cljs | 6 +++++- frontend/src/app/main/ui/shapes/shape.cljs | 7 +++++++ frontend/src/app/worker/import/parser.cljs | 20 ++++++++++++++++---- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index d10378e190..8ac2387e88 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -122,7 +122,11 @@ (add! :stroke-cap-end))) (cond-> text? - (-> (add! :grow-type) + (-> (add! :x) + (add! :y) + (add! :width) + (add! :height) + (add! :grow-type) (add! :content (comp json/encode uuid->string)) (add! :position-data (comp json/encode uuid->string)))) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index cbda63671d..e0ab37c45b 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -94,6 +94,13 @@ (obj/unset! "disable-shadows?") (obj/set! "ref" ref) (obj/set! "id" (dm/fmt "shape-%" shape-id)) + + ;; TODO: This is added for backward compatibility. + (cond-> (and (cfh/text-shape? shape) (empty? (:position-data shape))) + (-> (obj/set! "x" (:x shape)) + (obj/set! "y" (:y shape)) + (obj/set! "width" (:width shape)) + (obj/set! "height" (:height shape)))) (obj/set! "style" styles)) wrapper-props diff --git a/frontend/src/app/worker/import/parser.cljs b/frontend/src/app/worker/import/parser.cljs index a0da1e60d0..fab4075cab 100644 --- a/frontend/src/app/worker/import/parser.cljs +++ b/frontend/src/app/worker/import/parser.cljs @@ -272,9 +272,21 @@ (def has-position? #{:frame :rect :image :text}) (defn parse-position - [props svg-data] - (let [values (->> (select-keys svg-data [:x :y :width :height]) - (d/mapm (fn [_ val] (d/parse-double val))))] + [props node svg-data] + (let [x (get-meta node :x d/parse-double) + y (get-meta node :y d/parse-double) + width (get-meta node :width d/parse-double) + height (get-meta node :height d/parse-double) + + values (->> (select-keys svg-data [:x :y :width :height]) + (d/mapm (fn [_ val] (d/parse-double val)))) + + values + (cond-> values + (some? x) (assoc :x x) + (some? y) (assoc :y y) + (some? width) (assoc :width width) + (some? height) (assoc :height height))] (d/merge props values))) (defn parse-circle @@ -392,7 +404,7 @@ center (gpt/point center-x center-y)] (cond-> props (has-position? type) - (parse-position svg-data) + (parse-position node svg-data) (= type :svg-raw) (add-svg-position node) From 3a71068a4868d4a76132fb7501dab1a7b93c0e56 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 26 Apr 2024 12:16:05 +0200 Subject: [PATCH 09/34] :bug: Add warning when font cannot be found --- frontend/src/app/main/fonts.cljs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index a172011245..31949e7fe1 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -133,7 +133,10 @@ (defn- fetch-gfont-css [url] (->> (http/send! {:method :get :uri url :mode :cors :response-type :text}) - (rx/map :body))) + (rx/map :body) + (rx/catch (fn [err] + (.warn js/console "Cannot find the font" (obj/get err "message")) + (rx/empty))))) (defmethod load-font :google [{:keys [id ::on-loaded] :as font}] From ff4e27a1d5154ad7f671ba3a19f253f6fe7da7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 25 Apr 2024 16:57:37 +0200 Subject: [PATCH 10/34] :white_check_mark: Add composition helpers --- .../common_tests/helpers/compositions.cljc | 42 +++++++++++++++++++ common/test/common_tests/types_file_test.cljc | 37 ++++------------ 2 files changed, 51 insertions(+), 28 deletions(-) create mode 100644 common/test/common_tests/helpers/compositions.cljc diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc new file mode 100644 index 0000000000..6306fc51a6 --- /dev/null +++ b/common/test/common_tests/helpers/compositions.cljc @@ -0,0 +1,42 @@ +;; 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 common-tests.helpers.compositions + (:require + [common-tests.helpers.files :as thf])) + +(defn add-rect + [file rect-label] + (thf/add-sample-shape file rect-label + :type :rect + :name "Rect1")) + +(defn add-frame + [file frame-label] + (thf/add-sample-shape file frame-label + :type :frame + :name "Frame1")) + +(defn add-frame-with-child + [file frame-label child-label] + (-> file + (add-frame frame-label) + (thf/add-sample-shape child-label + :type :rect + :name "Rect1" + :parent-label frame-label))) + +(defn add-simple-component + [file component-label root-label child-label] + (-> file + (add-frame-with-child root-label child-label) + (thf/make-component component-label root-label))) + +(defn add-simple-component-with-copy + [file component-label main-root-label main-child-label copy-root-label] + (-> file + (add-simple-component component-label main-root-label main-child-label) + (thf/instantiate-component component-label copy-root-label))) diff --git a/common/test/common_tests/types_file_test.cljc b/common/test/common_tests/types_file_test.cljc index 1e316bc788..04b76b5656 100644 --- a/common/test/common_tests/types_file_test.cljc +++ b/common/test/common_tests/types_file_test.cljc @@ -17,6 +17,7 @@ [app.common.types.pages-list :as ctpl] [app.common.types.typographies-list :as ctyl] [clojure.test :as t] + [common-tests.helpers.compositions :as tho] [common-tests.helpers.files :as thf] [common-tests.helpers.ids-map :as thi])) @@ -63,10 +64,7 @@ (t/deftest test-create-components (let [f1 (-> (thf/sample-file :file1) - (thf/add-sample-shape :main-root :type :frame) - (thf/add-sample-shape :main-child :parent-label :main-root) - (thf/make-component :component1 :main-root) - (thf/instantiate-component :component1 :copy-root))] + (tho/add-simple-component-with-copy :component1 :main-root :main-child :copy-root))] #_(thf/dump-file f1) #_(thf/pprint-file f4) @@ -105,21 +103,11 @@ (t/deftest test-absorb-components (let [; Setup - library (-> (thf/sample-file :library - :is-shared true) - (thf/add-sample-shape :main-root - :type :frame - :name "Frame1") - (thf/add-sample-shape :rect1 - :type :rect - :name "Rect1" - :parent-label :main-root) - (thf/make-component :component1 :main-root)) + library (-> (thf/sample-file :library :is-shared true) + (tho/add-simple-component :component1 :main-root :rect1)) file (-> (thf/sample-file :file) - (thf/instantiate-component :component1 - :copy-root - :library library)) + (thf/instantiate-component :component1 :copy-root :library library)) ; Action file' (ctf/update-file-data @@ -149,14 +137,11 @@ (t/deftest test-absorb-colors (let [; Setup - library (-> (thf/sample-file :library - :name "Test library" - :is-shared true) + library (-> (thf/sample-file :library :is-shared true) (thf/add-sample-color :color1 {:name "Test color" :color "#abcdef"})) - file (-> (thf/sample-file :file - :name "Test file") + file (-> (thf/sample-file :file) (thf/add-sample-shape :shape1 :type :rect :name "Rect1" @@ -189,13 +174,10 @@ (t/deftest test-absorb-typographies (let [; Setup - library (-> (thf/sample-file :library - :name "Test library" - :is-shared true) + library (-> (thf/sample-file :library :is-shared true) (thf/add-sample-typography :typography1 {:name "Test typography"})) - file (-> (thf/sample-file :file - :name "Test file") + file (-> (thf/sample-file :file) (thf/add-sample-shape :shape1 :type :text :name "Text1" @@ -237,4 +219,3 @@ (t/is (= (:typography-ref-id text-node') (thi/id :typography1))) (t/is (= (:typography-ref-file text-node') (:id file'))))) - From 40e43fd5016993089986b4fd0c5566f99d1e885d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 26 Apr 2024 12:28:52 +0200 Subject: [PATCH 11/34] :recycle: Reorganize tests by level --- .../logic/logic_comp_creation_test.cljc | 45 +++++++++++++++++++ .../types_libraries_test.cljc} | 34 +------------- 2 files changed, 46 insertions(+), 33 deletions(-) create mode 100644 common/test/common_tests/logic/logic_comp_creation_test.cljc rename common/test/common_tests/{types_file_test.cljc => types/types_libraries_test.cljc} (88%) diff --git a/common/test/common_tests/logic/logic_comp_creation_test.cljc b/common/test/common_tests/logic/logic_comp_creation_test.cljc new file mode 100644 index 0000000000..577bef06f3 --- /dev/null +++ b/common/test/common_tests/logic/logic_comp_creation_test.cljc @@ -0,0 +1,45 @@ +;; 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 common-tests.logic.logic-comp-creation-test + (:require + [app.common.files.changes-builder :as pcb] + [app.common.files.libraries-helpers :as cflh] + [clojure.test :as t] + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) + +(t/use-fixtures :each thi/test-fixture) + +(t/deftest test-add-component-from-single-shape + (let [; Setup + file (-> (thf/sample-file :file1) + (thf/add-sample-shape :shape1 :type :frame)) + + page (thf/current-page file) + shape1 (thf/get-shape file :shape1) + + ; Action + [_ component-id changes] + (cflh/generate-add-component (pcb/empty-changes) + [shape1] + (:objects page) + (:id page) + (:id file) + true + nil + nil) + + file' (thf/apply-changes file changes) + + ; Get + component (thf/get-component-by-id file' component-id) + root (thf/get-shape-by-id file' (:main-instance-id component))] + + ; Check + (t/is (some? component)) + (t/is (some? root)) + (t/is (= (:component-id root) (:id component))))) diff --git a/common/test/common_tests/types_file_test.cljc b/common/test/common_tests/types/types_libraries_test.cljc similarity index 88% rename from common/test/common_tests/types_file_test.cljc rename to common/test/common_tests/types/types_libraries_test.cljc index 04b76b5656..0eab0db078 100644 --- a/common/test/common_tests/types_file_test.cljc +++ b/common/test/common_tests/types/types_libraries_test.cljc @@ -4,11 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns common-tests.types-file-test +(ns common-tests.types.types-libraries-test (:require [app.common.data :as d] - [app.common.files.changes-builder :as pcb] - [app.common.files.libraries-helpers :as cflh] [app.common.text :as txt] [app.common.types.colors-list :as ctcl] [app.common.types.component :as ctk] @@ -71,36 +69,6 @@ (t/is (= (:name f1) "Test file")))) -(t/deftest test-add-component-from-single-shape - (let [; Setup - file (-> (thf/sample-file :file1) - (thf/add-sample-shape :shape1 :type :frame)) - - page (thf/current-page file) - shape1 (thf/get-shape file :shape1) - - ; Action - [_ component-id changes] - (cflh/generate-add-component (pcb/empty-changes) - [shape1] - (:objects page) - (:id page) - (:id file) - true - nil - nil) - - file' (thf/apply-changes file changes) - - ; Get - component (thf/get-component-by-id file' component-id) - root (thf/get-shape-by-id file' (:main-instance-id component))] - - ; Check - (t/is (some? component)) - (t/is (some? root)) - (t/is (= (:component-id root) (:id component))))) - (t/deftest test-absorb-components (let [; Setup library (-> (thf/sample-file :library :is-shared true) From d956f7c72c0a61fa7159d7965bd0fc79a6237795 Mon Sep 17 00:00:00 2001 From: Yamila Moreno Date: Tue, 30 Apr 2024 12:21:28 +0200 Subject: [PATCH 12/34] Update README.md update penpot fest information --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 64b9649bc1..52171b8c94 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,7 @@ Penpot is available on browser and [self host](https://penpot.app/self-host). It Penpot’s latest [huge release 2.0](https://penpot.app/dev-diaries), takes the platform to a whole new level. This update introduces the ground-breaking [CSS Grid Layout feature](https://penpot.app/penpot-2.0), a complete UI redesign, a new Components system, and much more. Plus, it's faster and more accessible. -🎇 **Penpot Fest is back!** Our design, code & Open Source event is happening in Barcelona | June 5-7th. [Get your tickets](https://www.eventbrite.es/e/penpot-fest-2024-tickets-859331883797) to join other designers and developers from open-source communities and beyond. -Check out the highlights from [Penpot Fest 2023 edition](https://www.youtube.com/watch?v=sOpLZaK5mDc)! +🎇 **Penpot Fest** is our design, code & Open Source event. Check out the highlights from [Penpot Fest 2023 edition](https://www.youtube.com/watch?v=sOpLZaK5mDc)! ## Table of contents ## From f84cd933a8fb7147e4c4136beced8fe9249db6a1 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 30 Apr 2024 09:45:27 +0200 Subject: [PATCH 13/34] :sparkles: Swap and reset generate changes tests --- common/test/cases/swap-and-reset.penpot | Bin 0 -> 17080 bytes .../logic/swap_and_reset_test.cljc | 176 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 common/test/cases/swap-and-reset.penpot create mode 100644 common/test/common_tests/logic/swap_and_reset_test.cljc diff --git a/common/test/cases/swap-and-reset.penpot b/common/test/cases/swap-and-reset.penpot new file mode 100644 index 0000000000000000000000000000000000000000..d8ea3188a46b8883eb8f57ffaf42a293dcec5433 GIT binary patch literal 17080 zcmX9_1zc0#+aDo}k}d%qA)*osVt}-i0*+FU919rD7$wrFG}0x~B@!y#O8giKA|j2_ z0wN$S@LvA!K0CYj+!ObS@AI7JKKG1*;YuV5^UCHU=>I&yG7flR}AfL);TB>4d|NqNH;m;kEX-CS|5Bu@MVk5ZlvXm^~%#i<0z&mG4Kup{Au!xDfh zk>CQH?Xh1l3Jj1SIS_GZtb`}d!xiL;cEK(1nBY7~5DY*$#M1}u4nezOApiw95)tR^ zDnd_k@N%(pMH8Gi7-vXm`vp2WH#vj z$^GLcS_bJOyCOTr6z$sVTe1=*uK$wW=nqpc{D9hqzO*(><0z?_dp`Zy)gxeKtrK$s94RVXZd6L>>>5YhMDodIU$1~ zgX0kv_h!ik$B`U_@OK;q(B#-3$~XjE291-;epYEI4MoVx!DKMfHR`|B7Z?&G&7N{z z;C7mTC3s2z*dXGt`$MlJ-HC1hKZpDc%!V<+-oaFVey*wc5xGBK;0D06Jxj=8^=0<{O0dktXK--ir$ZyVvfF(H` ztAs!KpuNJCGK@L^l!|nB|8j&Y1{cW@~}P9_QvFb#YFyNtObUVIVSsEHmv3 zoA&dC`^~$%5nM^0g{u_HAW-RY=`x6p1Q?Si-i_$8wZPmT+^G!1=nn5lQ>Z0P`0o080|iZWt#|64A{G zV0e+j*|Tto9^>ZhM*JbH#pvda#t=w;6rP0}^nkF8KSS;CF~QBj)U?|QsZxLb910Nf z3QK?%qKXagc37~+h5fFdRoCnu>IWVwfFQ?J)+0q!_M9KMQn5^@LuJJm5~DK$I7YkL zJLAaW7!p~^XoUb5YQ{sSWRynXYU#4kig8Zq zva!I>5E-ROMrrGol2P~d56Gwo)5HZjd!n0<1i=@FUARnemGCCud~ie#2?7b{BHt`M3Gg#}j0EgaRREwffG!MfZ}Gwnl2JB=OWXTqQTh3iGYXKKB%CiGjAPZvlnd8R zMTQ-#CN3lR$ui=KN-D>yQ6!Ywv1(Ep0EMKq$dtT1G75+GBDqNbWQ)%Nx5PLQoUwrJ z#TS?W4500aXm@~X9EpqyVQ?n6;v{BZl5l_?iFv>|yPyH)!!E9nQ4tq+mY8*QbEQW+ z6YO1QopJadfJ0;Qbn_x&aGn6>0K5>M;HD9>KGIMqvp1SZK>r}(?7f`PL@W;DM!f8d zBav`K3C|0_xY|!ap?I_l!P$?D3bC;HvHI#GvAki|tkJ4~*q-uK<|Y19SmO2Bz|+ z2=Tw;yQf7)JvoCvUbz7MMTuNMdEI?raKi+mp`&J?tqC#F(KUu>>FQ|;3JTKG>!Y1; z5Htkquth~hk#p*90Nm`+?m#@_DJG-N?SO9L*?(T4eG>FBA2teBh zz`Zlhm(3F}^*LaPB(i$~qP#FSz{&YY0K+f*o0p60DVL*{Cy9XfyHIQs9vCA5z~#b0 z0R3=4JhNC_T#T|SD#C!sC^t|^2|zdOnrV7TNzuiK5;Cgv!ef*Jt(8D|4U{(^0Jyam zt@UKo+l%A-3y(lXHIPxw7u8lWs_kM>2hh2TjOzK=M@IEuH2hb#kADrmD3L!jodM_V zIgcR_F#y?8#$ob`#$m0%_ZJZA0INO`KrYEs!W#et&f#M47XX2)7saR(0Cb@ua8NQT zB&!%50#Y@a=rk$>AbdD6aRF31A`U-13cxfKCJ8adIRoYaAvxe60MGzy!$CCM-2Ld` zK+{F^10WJuZGa#MLXVJy+;s!MDP(jJ4ME(1)dq;ea6%9_S67^mvmZoS5~53j0AA0_ z84Ce$iHZkkApp3v1aNt-2vn7Ts)`}tC)JD{&>iO&A0tBPvf(el$Qn0OEj9 z>VW;i0071^7lZ+UPGrqGBQSu!nmU-^okPoEy`^Q4((X7JL7cY((c$zor*Q39HC)lF z!zUQhag>>_EFB@3nc@6{6{4z$K$0P+n@Zjb>N{4C?*0yV&ZiLc3x(q&&?;JgwH z=pHz}0q+mZV*qE5BOa>?sm=o7^ab9CIGe&{z5xbk{^=0l(Zh2trl167ms3K**COlcme3K$FQ8p;OjV?$edS zkzmj@l$rpr${0g2^`K)_g$vmntLiO-dTm{lgYTP`TrA-ig)B)bJX933S-b$~hYyjn z7br1dQ8ZyOz4`xzyLjrFuP!(y?;}oGaW;0WTwigu>uBI1-MP!C+;vc6b~TAq#~9 zpux%Ev3QJ(9TY1kBZ~w=Odv+b*ui9_?Vtz@QU+~@#Le4zVVrQJ1BTOCyWiGK~KPw1Rn^pntCS#m)2ft6g&opGCa)KSICJQ3a9BRKv;m&n}f+<|~ zDF#*VVqHu#Dn<$SOD`u0{VR;O2^+hdTDz&&;cPhh{ED(s{RCE{mP*sko)l_I8)5(v z-1t2e@baK7ui+**xn^s#eF%RrlRH6|<@s4@#V4rgf}N9A$#y&q(br<2-T#!FliW<%YhDVxl^TuH$;c*~nh;A5Pvt zU$fBg(pwXbkXw;Z+ROa5+SWg)JL|fqVEhYF494hNqg!pt#2()r`75Sxc;8mp z2vN*(yB0Rz@!WyLBDz|O5=$ADCfGPRIn{j_n|!Ep9ZdDYOxhEF8kd}MN+auDNVp=0 zb_Tz1D^pi9i!PUrqxm5v_TzxoJFDay?F($}bah%T^x55?^txZJ2IpaU6LFUr6@L)J z;Hkf&p4Cy#CHTf~9UPU})KzT>K;o?>`3ALkoDN}(q?Z-t2kXrWd=KyBU3roti)8!^ zuj){tqkGjcl;bxM>9@OFZs64@Xv5V_^70zO@$7$mn~}?G5RNvtZjV;_AV%|0~W(IYC6>I2nNgoaDSUd!^&CJmL0SDV!enTCsMYu;Fr znwlgjdj0xpnYO1QJYdAms1hrD``MpH@S-fGylWz+De5NuR!CEvrDNGW10J40M?zMU zRdVWHjY(_!*UNr}Dn0ox0`r2b$D3RR7PncycXSnZzru#srv2yTX(`n`>&b zUr<0dFFaGMD>#Jy6>l)PGCYQra<<@SqNe&RBPVlby~L)Pt>n#{ATRiJZ`-QR9-}%1 zl~8Jyo>(x&Im3vLa|5{zA%$L+VBEo)t?KtHak9>0TrUdv8X6jcOmF1| zGCoOL2b({4iT{#H`-PFAYq)-R^p3Tj#7CCMUu(M3IQUP5LNCK}SZGQvKebup(HjUj zTR7?C6g`ucR5UbO)}=r=saQ@Y`LHNH4)Yp_sE_r_dDhiGov)!?#&I4rFXk#BNn z($;f}SzND@T#uEY7lHGpRywk-GxAYD3!ol*5;#$h7&p~>jrr7iLket0hivXAlej6` zp0L>RN_TpYHzgz_-v8E^5Y@Xgdtm#Re23{o;_O#Qj4vhaw6dAwxd*{>bjEwQMl)lk z#pm-kuX<+58XH&bn@Q#3)GP%{?qYA>*Vg`MI1+Er%rJXkV}C34*ijj`c#s+cVkn&| za1eKJvk5#lvR=H-7=HWd?OeTf>U%mYCQCblZg1XIC24Ln(F+$~U=J zmmf7W%qh4E1=G<{4X!%P)31B$kGDHl$_klqf>F|^9%4L}D&BQ(xDiSCdAbw z?2$Ih0{r!@M2Nw6b&%1UHSxaIpNKyigXuPRD6XbMYrnV4^WJ>N=}pRZ9#apmpr?rb zqR7Tb{Y_3z+xzLaS`WI>vAu81oTewd&R_K3+!BdKyk`lv)(ZQcnNHnxuA`yRvw9RE zDWrGjYKN@wz=O3b?P1da?<3qy=_$}Olp*s9;Prne*Xee@St$A3tT>j=(Xug2)nO@h z76h$na(t%<{JMg8pA!_b6MB^<9#pd4{Dz9{YxH&Z2p3Q5r*qTc+l}^k7DLv(e5ADIw7Vpg?QCad_54J&QXEs?> zLWwf*-Xh&|w!Fvf|GrK0j>>m${NBvPVT*^dD&}mSAas~gLsdIJcdQ?H&c)vqqT*&L zX=&NQ>pyDfX&T|a^^w=kevLI)T1Y{E#3txu^Gvc)0-mNnH95f)RT{wboBQ-#){7hI zS7(1!Q6zIJ#e1wiQeuEI)D3Hq6xFy z6%|1#5`)vWmK6srvSsR{EHDc-h{Eyn z>~E1fd-wA`3zKRhK<}JsawG^si+nFr#k#yihmx%YEcLn4+%Ao0^+dl+P!eDxL+R zO?A<<_$SG|QflVi{pCznzEmFJGnqBl66Uyb?^V|w6aXUzS?r@zT|>`9>aSO(ua30S#(X= z8NEz+<+-LT&Y`0&_`p-}%`W$K4xyWaJ9f*2kQk%1q@w7&Wew{Use8{IQXzy#yFZt8 zLp(;SAL}H3_>{M(VO@GdZ^JoNq~|n$z%?muUP%zA~g_eAm`a2!HI@ zkN7Yv8oOX0J)p5NdtFbBIX7VsUh5>HJ9T;OrS6#`^LA9hT7pK`eDm^hujkgCysfiF z=3EQPZ`or?N^JsmqPxaDP~Ce7=ETSXF(ol-YegmU8&|gu(<`5s=;$zX8i%5aL?329BuvE4_nCs z3U`p_r+@9@?0o2=eBitX#kA?og=IP~6Z#u<^Nt3i950>bQPO}z|MV3P>HAEhK9FqpE~r}O#v~)gq4-m?vJLQHfRBX|_C65Q;4( zou8e`tiAmUD$1TK!{my;+Z0mzxM-GkM0Lk{c4Tt$$=7Fuc)Azs1O?2V^5eN!uhW(q z;w_yhcHw7LEnp~k@A!1%I4LQqVe_)4y@Xgx&nNI(_T>yx35z& z4z@~BhYFOGcU;y;`u(;#6uf(WZ@%j0NXkI;=T#ml_s6iLmrto7V!t|S!e{xPiO!;MKPj@R}c30-`p!6P3Sr!d;NK-xt{%$r_E4Uj~EqUyE;))ay@~2q4qWtKM-<+ z6ozRtQQ2td80&eal!zxTk)ROgI4wR|4vVnc;kB{RAK4D@bw7oNM=dR?42xR*!K#0! zGZP`s5qZTuOs}h1d*MCEfIxWS9#0b*vXYg3CC|(H$xpUwcD(r2M#&1X{}g(pr}~xSvWIXFxSjuF`eW(N zpugU68@u+j{ITT?^7p5ry=4{hbJB59`K3)SHo#L(t9HeZ;He_knI0i;hCw6$5OHFy zf{H~u5CPt*eUjDO+-%NcJZEgOq-Hg4-{^8@S3w^NMf zjp5xnGF%3YPQz1{HuN`ItJ%|rZV0Y@-fjNma$htd0Xg&AC)8$u4#SJN^x~z0m!st1 z@yjG^Ii0H@)F4)*Zb8^~^Ba+0uD^D=ha=)3#SWbHR*+Dj72kIvm!q4v2C_eW7|SH6 z5dub~JmkB))sG)_LLcgKmVNZ7t&`?qH1GtiR~>sV{M+Nu&pGBn zg(A2PiXuYzX?8YLmik7#Nwzqn(k=Z$HL&-+nzK%W$*;#b;YMln2{+X;>OYzD1%h8i zJTK7X&`(h3D!R_1xKaL!{vT}tik8W_cmC4b@Dkk&Jv=-t`27Lzf%;SVfULT3d4_vI zLFbI-2SbL78_tt|->F4E3@cenf7HXvnR6sUc&-fU*~-52xou?^7A7IV^!_((YK*jT z9@IXhR`y&Ac$x$gyq*`>B$&M=+oSaBglsMi5=|sQxnHxV92S~bBel+hEx21)b_A9i z|L7Vn8dO;}_5aeRr_UzYY1GznYGmvRyz8e5jzaMACl`%CuQ5tQQh$)3>5Z}dWXx>y z-pqDdjCq%p%G>Ti*M|+LY@C^vk-yWW-{BQR^LS%p=(Bg9{7dfWe(Ea_qT8M5eJeV+ zx3?#jsaG&EV#sxt{3g8jv8tg%dqTkqPl?C7kT~9N$pWA1eGi|_+kf&|&E@x?BCRZX z-sq8b>D2UQDww=`I48`Zc`Z!Pb69#i7|(OQLztxfR>z|gMoZh{JCv2Zryll3fY)68 z$2moIxPD++ufzi&M*-5+G`L%>ZW~Gk!{YHW(lA*ddyj+Su(D8`oiql4m4nLJA!RXm zJPrqwk(b6nu`*~`J6SndG*(^)kA-9LSUEfb36rOcp_KVjOV7>D%{kO>lq6s$vWuy%9^3WJj*cwUN+w$mK3f67P2|+_RL841U zn#ho#_jR!>MgJX-Aki|?XgMH(g8|aLNI3))E)T=Y<8V;8og7kH4j~U@c5oPZq%7PH z0k^ZWLr5dh^73*r@-SIhyfhq6P3yP*asYU%LiH)9>z;)nhvwFOyBui&UPXyBwHS)? zYlL^Fm)5G{K3$ng`q(-P{`~kxGrHsc_bOG?ANOke+>iTAt!u~a=cf1kjOPLZeD;PI zC_mFNy8ipkLZfJ{20~j?JWCFuW})E|p#%&6DBwZ6s|p1}*(9H{($F4te@*xxWi_5U zGyd((kLj85#7FH=!I;T(*iM9B7F5C*pMFb(-0my+@9Na%dp*$?M6bQq|E8UJ^Z@_xFuOO9;YWnVsPml`EJ`t(^PbxY;j-pNGSQ=d@&tDgoZCMsGL zl>>dffAZo@7H5~ITx^VsZ>Oi*3?3hs&?EM){;8TW$T)gxZSX)PWod-9JNBM`_;MJO zo7*OfzW#B*-3-=!`6u_oYlS`qMXK?HCxIcc;p@eAY|Gcxe|?UMvd%-B5dDoFbenOj zb(?v7!;8dbJ1hi>#Bw5m2Q=O8MvwPf+TCWEFQ5-|L}K~uPk^RW`IQ@Fcd2IkJUaDjv6h0&mV1#8KsBZuv`=odehe=P66|JT$~c_ZPUE`3XO{GP(^ zpPL0_=F5%!-`vOf-vv?coQs^EuQAEk^fG7kArAjWo+9$*$|OSmg%bu5XVT%%Y-)W} z!?$;0w<4k2xA!kcL#^|+XIMl7Kh9FH_6{Q2`MN*6`K}1*_Vw%a(a1RD^ryWGL>z~% zVD=8XtzhuN67L)n`sbq!0Ty)YorM-by?@W!sDxtE?VrjebpN~b&Xq}q;rgn$3{*~D z9s@(-VK^vK4ljqsBXM#_7!odxmqo~8(NGK$jsb9sL1NL;NNEH@P8x%kk%t1mFqoX3 zJk8M}V0VVyDEj*}g}D|wCKmj?DsF@_!6a|k07QfN>b0lz_vmkNq~0r_-R7bupqPJb zYKC6jRV&D6&`EEgL}*?z)Row!3DtR0gO>g(ZOnN6tD$GSQ;&Skf&;=oOW$e#cz?ZW z&*psZwBx0xId{N@_ld$GisBX|jM64RHu&veBAsVGr3q-1I$1KGaz{)RvQ&Nv@rv?k zR1s}iupa#%0izN}DEAU4B=J9sV9Nd?P)JJo6|$z^C2-LWS3U@SQx$@%pz$=crl2fu zy(9(%BEx9R*to%PFB4|cxrw_A3|5O(k8E> zl2B42nxhaxqkf;kXR7Ad9y25(dUjzw7f7t7a_L( zrAknI<)b&Q&f_uao}RnClCOGt8L`!608LjGR+ExgbCmqKfKI&&eF;hF>GovR<>&W+ zTYAKE%?aDI`i8{DGMYSk#3z^NC&UiSn3K(S;`$q;1v1)nRD9t|IN zcHo!!k-=_r?LWH0XdAsZvszx6tZ75&7Qa~oK`7t~{(-MuyY`{C*KXxguB)1$a`m^b zU+2HqVpcaczW&^vct`;-BrsIl*ci(&ad95{J1aqPfV4>@okx$-_h*1NKL-9DF@uT%`uNUpif# zoO+(I-yrp&%BTQE#d~k9t*JRW{yGnSdVZ$#b78@&9)?6l7Zr&_pT3^Hw_>7vL5jXz z{X}AJZd^sht<8_OA63tf6l(uDIPm#ZHpFyhp1?04Fy_6%!OguM{|ZG53}|?Na(t{X z+v-=S=DV|~+Yl6+nUSIT`0*7Q8k)wqPe(RfK&RZ6#Yc~$jjsVR&~S5;sS2>U;4qc>90(+kVWbbJ^i6j%T) zR?VEZz~(3F?fVA?VBV@*@dkjB_Aj6P`9byX-#->^?v?jL78Vxod~|BdTieuEj&2Z?gA(( z96LEVdABZyx3_=C{gDDwTXT(_y)c`h_GW%{wSi*bb=lXuN97l+DO@QkEYz5?628n8 zb3}5b?f2Q0IQYSS0_DyXjpZFnYfODLF9LyKZ^tGn@H|!rGPJ?T>C2GDpOWf z_0_qWkjc=^Cl=2TDL;?n>+0as?E%?0?pU%X0(LQ;Q&5`PNKmzm zeHU~d$jHP*7!-YKX%q}JrN{mx7QTBYvw2c*7-1^zW!e68VyWF33}`a0%huPI2YBWJ z)6v23AWy9eT;zP(|67EU`}Udu3+a!? za|jpaYGCibRs~*M?l%9QYx5^4H9Ra zy#lVMLE58qf(PN?qUk-f_jACna^6Ok-fp0zHu`-GRs6^SNqo+kGn#5aL3z_BAS2ff z5Y9*XPiz0Z23&HBGT8tIxMaIaE2O1|!K(jRUBj1V07Gj-J)4QQ0k2-oTyLxg_-L~( zd8;PCn-j;QCT^C1LQJaWbE>E59aO6abA{^*DgP7uv*(s%!bjDAhl2tm4VS~>W#Dp9 z83Y;*gjYCuI93`5lLq#&$suLrWpHw`(lS^C9x5k;mH}F@P>j5+w7fi29*7R180yEe z!1@MDQhki0rgqSn$phbe`=ek^EW~+^Q)#-5LNkay<@p7eF@9K=wu=2SoHQ61rj0>&LY-N*S}XiUawd` zHE~b&gKmGXw;iJW$#UDlW~<-sESZXl^WX8wi6N3dZFEig;n;)sI>~Qe$Zzodm-D&Y zMGmG_yU;l5s0wc4!3d%6TxNN?0e^%T+6b(N)~wv}H?ZyL>FIJ# ztD+3nwBqI$;xgZCmdvR%QdwXPjSMeFrk}@%bgA4B743cE@1OayX_m*v))L9XuNNU$ zq?Dp?a3(s<;Hrq!*jf?q$0$`-oA8=_7k@m{K&Sx+Mb!nJ>fF-b|7Ki zG2usW0##hxBfZha>Cw?$Z`q^zP(nHF2{H0a>D^l7MEKCb`JM0o7+9OHCXO`ei0u5; zI93Li5X!fRe!F5($Hexy{o5~FL>BnOBo}(GHMa<#lcn9XYR+Hj?NVc^hNduqoKnL` zTA>WHBI@`*ss6~^PXs0d(Ww1jksgso&0pMt%^~>(R|0zte`mpBQ+j4*=Gt$Ksaz6I z(sBO6M4a{beym_wuDkEqAQDG$RO(`tgei_X5~)%2F&Y#b%3Ye!m{WhF+H~yumftw^ zzctd6jsH$oELp40M}+;h*SD3EKfhd&G<%U~t<2y4hI$F}w1YEDmsM}{t&CsHnKP>s zritEBCZRFQ98W=Bh}R%&uwA) zCzO*LTFMXFtMB?stf+$p%)9$69%o5y7s=DMei7zyoX`+om&-d3NU1bE%nz}hcVndT zfA2bT$nWdwkoCw7iTh2YEA^|72UGD$NoXcUMkbko=A$olRSCD)KK&ImF>P{S z-4%)MTbPRFZ?cG!ddLj-y>y0MOJ06ApO@B}Z@(wJXKNzkJYT)4Ixih|7cP@LfOWD? zHleD3XrzVOgtTm*EHvdz80mMtOWT_$z5hPU%z5~|GvD}RhT|_c1}R0b>+JLGg~9TM zCzV2GN}n z$*AV*X7~Mpox;*sAWlYO?PO#yFdP;GY=V`?z>%^@oSnQZ7K^~*xMqlg}BFk_+yf6YsUt*6o%bN70Jk; zrX1H{d~lskRCNB$N9*5~|1Cv07iQuy(m?o*L;`f70nY~qw(r8wb{J`C7!(JPhml3Y zWMOjhNMO%^EL;YG!vFz342^~3aR?b;#{pso33xy`^WkTMz_DqvmbDwoL2U>;728Hm z_SYEF@=3n*dfK&?x6yyH{j_a--ad%gmq~d}f%c!C%Diva<-@^8hf>OLtC8w5-;h}~ z*yV~eNR}@DJydL1#r0@BTNE3|LcSwiLFR{7)E|*$k3$_qJ;YA8V}O?U`PGi!Zalv$ zxY;7andH!br1Ofxa<75$b02i`Qn6#`6R2F$bu`~t^!{LCf5z=sZV23i!4l4(T z+Q|YNneAlpNIdwh{Cl>adms?lf5{@i+2Rpr$3ei<=l$=&l7BTwVciv~4UVU@BGLuV zL`4W+ij#{2-dd?W7yWR^y>HH4arsO_T__c0KKah*tBCi{5~o7eLx!`mwCci3afTUp z$E%EAm7i`L{Py2!&)way*K<5*a+;i6)=evHJ@E)UcslDDf786TZ}-lV`A!PLScR#L zH*)jUSN}xMI`yJ%`rq4}HJpKLDalUIec^lIgE@%_m%3UQjiq!#50*d1aL--4devJ@ z&sy@!Dp4GGk;FCP)i&yVSf4WdI#jIsXYgh4b&x12#x_Un`3H|&W?Q3&Y2kBMB0VHC z^|&;+!MyJGA@+DYo8}-NkTvLgUPWrIz7MiQR%TvnnCAXoTRZaM1+u2lKXGh7q)_(tpL}RxRF{OG+BoOAQm14@7h)B%;6Umf1tn7-f^vSuJ_Y z_&%7ap?_5G-tR;EM@B|ghkLNj$x-io|NB=V?!S&??2%##VY1VNLbJTJ>orj__S1jI zWo)YOaiOdYsQ)*w*Vh;?Uv9tlUezkGj14ItAby&`VR&%+`M%evctLcTsoj@h!3fro z`4h+7cS1?gy?X1L>a~YGSmJ8Jb1s_hk z{vPYGqM*;uC~>UlnPJW(++iqC(9SLs=w+4L9S#(dNrzTx1^%a z&>Hg;*0Bf{tJH}Nap9-77B+LVuX6YWl*(_gIp0nAm=_sMMM`v>&kkttx@|HO-&7XH z;-B9o(^X9P80vxKHhnoYGUH;h^++EX@kcMQX!VoXonyWpF|z_Ov3ANd_4*R3XGNwF z@p_{|6^2mnJf+bfWa>`b@LqUA!sn{^yGwJPzpFj0Xf4CHM1Q9x5mJtn16Hje3Y5J& z40Sp!5O_0(IO>OiqpQJ_L&rcL@vHv4A5yyLWU{Z$g5Q3IsjjX*F_GPAYTJ4$T6<$` zEMnw)kR}WNnMQNL=>XiE18eDMw9U96NjpR|HN9yzeYX5&V`7m{5t^gTW#q+Nq_neo$@CWdSJ zBgXfKM40nK5YtEmM9`=(exZHuQ|9Pgv2Tly{3#f8Yn8cDvYkoddw5tVgPhj9fys+U z!_1gzX+U9!gOGd4BFOg>8l%P&ZIqnW&OghRN9kj(F{!!NB$OZ~=)j#X zRpnjLm5&E=5BD#9bx$rCo!B+WU$+>Riu7n)l)LGdjxVW8@0=y2xQ)GDzbB<_B%+Gt#WBWPbFQDftq`J-5mD-;gI$r*572dqI-FW9x zmuj;`^3)yg+}zx@QrpRN!Rt5PElAwu8dxFRN=?4%haIIeWV;q6K*V#?MBcK+pzb@aFWL^MvLT30ai^1c+jI1_#U$a;m-j`A4j^NkQDp*y z#0*Np04+uLYx>D=p_ z+dBGJtrBXWjzVPF+Co!~N>X9O!9kxJari@(MNIG7=4fjw&>i8Y4U`OItkLo zOgq1O9L*rR^s++8+hw-(sHd~+-vd*IX}Xyj4~$VlZvq#?r7H20IIw=s`Sva{V}tg7 zPjHN5d!ql&#=n0&w&hl1>Whg5CK7`a%%;m|sQM5}{f+-Fvj{8bar0HfT`iBr%Jr*< zhW7S1yh}{-vc7pP|IzD?f1y#^sGK;sL?*sb9p?Ya$kov7)A_t_;qffAgcyT8L43BH zYcfrr*AuNbN)ax!Ex#(o!N}n6@ZSxe1_YQh`1^forGQ(WISrN%rztiiIh7cfL>N15 zgA?N8l-ytD`3n4^pM%HViT-Ux`}m{UC%f7V`r=-d41D0qdzY!2N1dhqG?aT z!pmXhAb;|%z|jIE_EqYXis|3#Cf8iWo)Yb#45UD)o=k91@~;*zkdZeVjQCn_*WNXN zuRl;PT{*CQ5isoVs#BEFJH0}vaEZpk{LKfYUt-TIp!lVHfuEX&i*A1p4$e#eGx%EA zQ5zqhA41oG$YftHU)+45>BnNB`lCtMD!HzJWeUxk?kB~_w0B+{D^_#pMPbdoJ?)Mi zyD!$!C#^sClhy5w_-*Fd-+_;&@Z6BO^E$$n(cys{x0FxWtmDm2b}sBsL>pA4MjvpP zpH1%gNY;B+-w$FnBrF?vNseFQ`Z+Y@jbRjV%05F z)!-gwK-1q(l&P8QZg&aIF>lR}Qtjowk9Jnvj}~Yts96q+sjHQawjuD2l;ZQN6x3(T6iFUjU8%$%f^yv*m_kP1PrDs z38Y%t^%J-pwFhjX`DdxJzU!t!M=A7T7%M_)MJY7ubuhOy?glR%B?u(+xpa)&3GzJp zx$iCD>KX{V)%B6WcJzPMROwq<-r?TriAq?P3Ob#7a};t@GBryjj;H{pIa#kATy?t?|>PZ1Nsre5Nh&RLGcH z0`3(ywZ)y;Y?oBdHysF2#~GtHGrxbLSyF9={YBF-Ck6NVC}b?xcp8{gew!<^xoh(v zM$f9Y|0|PghYj}EbIlT0_Y+{MN8&%7u(KWKHD`ZkviJV}^`4oX{igEG{v@Gq;mOmd zmUDq8eS7Xf9n~-WDw&(MqF6JVr$*P_y1Kf4>q|I)9CY5NG7hAICC}R*VGT+bnp-|B z$=RGfnF%;rY7Sg(ZsD#2?pE;T{87+Z%)#1d!a7O$bbB zJHeTmB8g#LWo60CtN+YxbN=S+LR0*gY3Hzy3p7iX1|L%ssqc!Gl5MFUQ{N4YCkKHz zma`epy)6C0D$5EAQtsxegIQTwd0AO?vx=zT^eoS5U@XgGy@zbU!@lDEXhsb+mOe2Y zi(oj5I#^H$_^7K_O4Gn6=N^NWJ3^^Kw44q2mghZJL-x^BRS`m-pOq;{cCgq--8IVm z%W}D*KbJt6JeM=Xp?cd_H(&g688rQ#Gz(4{2&1c{z8`7uo88~EbG-b!sOq*Hbh=z! zE5G=$426At;#JVVT&J@-y}aCVZpqjc$8ay1KTp%XP~{&nI9FQrPonh2btyD@DCT!d zaH;Z5`3EPhJ)pl%{EO>$t9uDJHiP5;9N+NnACbv5 z_a9u>|4`;op0HF@cmKXT?D4MxdAbk!?0c)ibQ92@N{`~ne!Nqfx^c2~i{c;A+?|X9 z=V!w4c~}Z<%zp^3tqP$VJSW31?O<)QyZ`!kKxa3}BV7 zo~i2!|K^AJs>-h3eRVpoimNNi5srwZ+qj=0lF}iuLdcGJ9W48j=2lEEPq0yF`DWQV zZa`JNue2F$Sp6PLyb%Zg+Ds+ll=g^GnFCwMpf3s;D9~XK*ps99H+k=EU@Rt<{dLEt ze7}Z;$AarZJU>I8xL~xQss)ufs`3F)j>Q(}^3K#p2z8{{F zUn?reFPpp-|E_I6PW?&`{b1})(OQ?%QLT3ux zSiH0AWSY4SY{Y+JzP%;8S75ZR8K~AapJe`gmg0%NjIw256{Y_1s}z=yAV`^)YV=pC zsTNrh>$MagHL;ACitMU%uKoGQO9EYC_a0mkotOF3a`snZEc32q?{3JHnr2p$e-|WZ zF@?YF)m-h)Ld6o!D{t-6?XN*Zj}v3w%*Ad=>_k{HRRISHQU8vvOY_mF_7R;s53;Sm z`c5ddN#J1$3p7W6Ik@_(JnV`rRS_qOit7(4skLC3V)LkG`}Z~=`f)9ZF+ zzXfCrPMNeqr4xAqx^4=q=#L4|!YTy~+NlLRDUCRWu~i;d%&IgBK3uW=oc0o_GU|L26}f^VO`GJm!V>~?Nb?fFT`+UE`(Y% zxy=6k5;4tQHIl`m=tMS>LAtj#@O<~!C&}O4h5j&$nh1V$2Z~xsC4D9y}W6Nov3G5&W=w`3%2)p8~(N}nNuhOZt z){V5}Y4q`xee(EHFx&7?t5YO@;pT3*${U{ovCBnYP z$Ffs>6>kvt9lvf^@y@4eQSMjjJBxi$R|f@ELP0Qr%C)Z?LtG!9>wRZlzL~RN-1?v~ z_SXS;4ej;|;#9R#ml2`Ou%PuT{E?EtCiL;W+ItYmEm?T8u$7R>X$SKC+R4)A;&I(V z_L;QQpLN`m>ERMPE%{xfSC>D~KXc(JxQhI!t=G&EEHIeXhdj)Jmpr^}^})1p`$nwI zn&k(PdsY%*S!Vj>ye;il25;T%tV%#3DMlLpUgxG7tVikvJV!e6QAYf&tqtCZFg;Y* zUoEZu^}Y)Bi0!?kU;cI1low;~w5d6I5fggc>3vZJ21z5~=AxBf28&3`#HnSY2Fzyc8hfIYy9#x zqW{XzYGo5juIIU=9CMesoKJwht)S?Wm?`l8yNpSZ98Q@R6)la&m*({O*b4gGB=$F0 zyp4~#RLyYj{r}#mwsiE%2 (thf/sample-file :file1) + (thc/add-simple-component-with-copy :component-1 :component-1-main-root :component-1-main-child :component-1-copy-root) + (thc/add-simple-component :component-2 :component-2-root :component-2-child)) + + component-1-copy-root (thf/get-shape file :component-1-copy-root) + component-1 (thf/get-component file :component-1) + component-2 (thf/get-component file :component-2) + page (thf/current-page file) + + ;; Action + [new-shape all-parents changes] + (cflh/generate-component-swap (pcb/empty-changes) + (:objects page) + component-1-copy-root + (:data file) + page + {(:id file) file} + (:id component-2) + 0 + nil + {}) + + file' (thf/apply-changes file changes) + + ;; Get + swapped (thf/get-shape-by-id file' (:id new-shape))] + + ;; Check + (t/is (not= (:component-id component-1-copy-root) (:component-id swapped))) + (t/is (= (:id component-2) (:component-id swapped))) + (t/is (= (:id file) (:component-file swapped))))) + +(t/deftest test-swap-nested + (let [;; Setup + file + (-> (thf/sample-file :file1) + (thc/add-simple-component :component-1 :component-1-main-root :component-1-main-child) + (thc/add-frame :component-container) + (thf/instantiate-component :component-1 :component-1-copy-root :parent-label :component-container) + (thf/make-component :component-container-main :component-container) + (thf/instantiate-component :component-container-main :component-container-instance) + (thc/add-simple-component :component-2 :component-2-main-root :component-2-main-child)) + + page (thf/current-page file) + component-1 (thf/get-component file :component-1) + component-2 (thf/get-component file :component-2) + component-3 (thf/get-component file :component-3) + + copy + (->> + (thf/get-shape file :component-container-instance) + :shapes + first + (thf/get-shape-by-id file)) + + libraries {(:id file) file} + + ;; Action + [new-shape all-parents changes] + (cflh/generate-component-swap (pcb/empty-changes) + (:objects page) + copy + (:data file) + page + libraries + (:id component-2) + 0 + nil + {}) + + file' (thf/apply-changes file changes) + libraries' {(:id file') file'} + page' (thf/current-page file') + + ;; Get + swapped (thf/get-shape-by-id file' (:id new-shape)) + component-1-copy-root (thf/get-shape file' :component-1-copy-root) + slot (-> (ctf/find-swap-slot swapped + page' + file' + libraries') + (ctk/build-swap-slot-group))] + + ;; Check + (t/is (not= (:component-id copy) (:component-id swapped))) + (t/is (= (:id component-2) (:component-id swapped))) + (t/is (= (:id file) (:component-file swapped))) + (t/is (contains? (:touched swapped) slot)) + (t/is (= (ctk/get-swap-slot swapped) (:id component-1-copy-root))))) + +(t/deftest test-swap-and-reset-override + (let [;; Setup + file + (-> (thf/sample-file :file1) + (thc/add-simple-component :component-1 :component-1-main-root :component-1-main-child) + (thc/add-frame :component-container) + (thf/instantiate-component :component-1 :component-1-copy-root :parent-label :component-container) + (thf/make-component :component-container-main :component-container) + (thf/instantiate-component :component-container-main :component-container-instance) + (thc/add-simple-component :component-2 :component-2-main-root :component-2-main-child)) + + page (thf/current-page file) + component-1 (thf/get-component file :component-1) + component-2 (thf/get-component file :component-2) + + copy + (->> + (thf/get-shape file :component-container-instance) + :shapes + first + (thf/get-shape-by-id file)) + + ;; Action + [new-shape all-parents changes-swap] + (cflh/generate-component-swap (pcb/empty-changes) + (:objects page) + copy + (:data file) + page + {(:id file) file} + (:id component-2) + 0 + nil + {}) + + file-swap (thf/apply-changes file changes-swap) + page-swap (thf/current-page file-swap) + + changes + (cflh/generate-reset-component (pcb/empty-changes) + file-swap + {(:id file-swap) file-swap} + page-swap + (:id new-shape) + true) + + file' (thf/apply-changes file changes) + page' (thf/current-page file') + ;; Get + reset + (->> + (thf/get-shape file' :component-container-instance) + :shapes + first + (thf/get-shape-by-id file')) + + component-1-copy-root (thf/get-shape file' :component-1-copy-root)] + + ;; Check + (t/is (= (:id component-1) (:component-id reset))) + (t/is (nil? (ctk/get-swap-slot reset))))) From f3549424873326c7daaa38afdaeefe992ee9fa75 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Mon, 29 Apr 2024 16:37:21 +0200 Subject: [PATCH 14/34] :recycle: Components refactor: generator for relocate shapes (and tests) --- .../app/common/files/libraries_helpers.cljc | 186 ++++++++++++++++ .../common_tests/helpers/compositions.cljc | 12 +- common/test/common_tests/helpers/debug.cljc | 33 +++ common/test/common_tests/helpers/files.cljc | 92 +++++--- common/test/common_tests/helpers/ids_map.cljc | 6 + .../logic/comp_remove_swap_slots_test.cljc | 194 +++++++++++++++++ frontend/src/app/main/data/workspace.cljs | 205 +----------------- 7 files changed, 499 insertions(+), 229 deletions(-) create mode 100644 common/test/common_tests/helpers/debug.cljc create mode 100644 common/test/common_tests/logic/comp_remove_swap_slots_test.cljc diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index d700237d26..5aa612a52c 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -2098,3 +2098,189 @@ (cond-> changes (some? swap-slot) (generate-sync-head file-full libraries container id components-v2 true)))) + +(defn generate-relocate-shapes [changes objects parents parent-id page-id to-index ids] + (let [groups-to-delete + (loop [current-id (first parents) + to-check (rest parents) + removed-id? (set ids) + result #{}] + + (if-not current-id + ;; Base case, no next element + result + + (let [group (get objects current-id)] + (if (and (not= :frame (:type group)) + (not= current-id parent-id) + (empty? (remove removed-id? (:shapes group)))) + + ;; Adds group to the remove and check its parent + (let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])] + (recur (first to-check) + (rest to-check) + (conj removed-id? current-id) + (conj result current-id))) + + ;; otherwise recur + (recur (first to-check) + (rest to-check) + removed-id? + result))))) + + groups-to-unmask + (reduce (fn [group-ids id] + ;; When a masked group loses its mask shape, because it's + ;; moved outside the group, the mask condition must be + ;; removed, and it must be converted to a normal group. + (let [obj (get objects id) + parent (get objects (:parent-id obj))] + (if (and (:masked-group parent) + (= id (first (:shapes parent))) + (not= (:id parent) parent-id)) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids) + + + ;; TODO: Probably implementing this using loop/recur will + ;; be more efficient than using reduce and continuous data + ;; desturcturing. + + ;; Sets the correct components metadata for the moved shapes + ;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside + ;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component + ;; `shapes-to-reroot` Adds a root flag when a nested component instance is moved outside + [shapes-to-detach shapes-to-deroot shapes-to-reroot] + (reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id] + (let [shape (get objects id) + parent (get objects parent-id) + component-shape (ctn/get-component-shape objects shape) + component-shape-parent (ctn/get-component-shape objects parent {:allow-main? true}) + root-parent (ctn/get-instance-root objects parent) + + detach? (and (ctk/in-component-copy-not-head? shape) + (not= (:id component-shape) + (:id component-shape-parent))) + deroot? (and (ctk/instance-root? shape) + root-parent) + reroot? (and (ctk/subinstance-head? shape) + (not component-shape-parent)) + + ids-to-detach (when detach? + (cons id (cfh/get-children-ids objects id)))] + + [(cond-> shapes-to-detach detach? (into ids-to-detach)) + (cond-> shapes-to-deroot deroot? (conj id)) + (cond-> shapes-to-reroot reroot? (conj id))])) + [[] [] []] + (->> ids + (mapcat #(ctn/get-child-heads objects %)) + (map :id))) + + shapes-to-unconstraint ids + + ordered-indexes (cfh/order-by-indexed-shapes objects ids) + shapes (map (d/getf objects) ordered-indexes) + parent (get objects parent-id) + component-main-parent (ctn/find-component-main objects parent false) + child-heads + (->> ordered-indexes + (mapcat #(ctn/get-child-heads objects %)) + (map :id))] + + (-> changes + (pcb/with-page-id page-id) + (pcb/with-objects objects) + + ;; Remove layout-item properties when moving a shape outside a layout + (cond-> (not (ctl/any-layout? parent)) + (pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) + + ;; Remove the hide in viewer flag + (cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent)) + (pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))) + + ;; Remove the swap slots if it is moving to a different component + (pcb/update-shapes child-heads + (fn [shape] + (cond-> shape + (not= component-main-parent (ctn/find-component-main objects shape false)) + (ctk/remove-swap-slot)))) + + ;; Add component-root property when moving a component outside a component + (cond-> (not (ctn/get-instance-root objects parent)) + (pcb/update-shapes child-heads #(assoc % :component-root true))) + + ;; Move the shapes + (pcb/change-parent parent-id + shapes + to-index) + + ;; Remove empty groups + (pcb/remove-objects groups-to-delete) + + ;; Unmask groups whose mask have moved outside + (pcb/update-shapes groups-to-unmask + (fn [shape] + (assoc shape :masked-group false))) + + ;; Detach shapes moved out of their component + (pcb/update-shapes shapes-to-detach ctk/detach-shape) + + ;; Make non root a component moved inside another one + (pcb/update-shapes shapes-to-deroot + (fn [shape] + (assoc shape :component-root nil))) + + ;; Make root a subcomponent moved outside its parent component + (pcb/update-shapes shapes-to-reroot + (fn [shape] + (assoc shape :component-root true))) + + ;; Reset constraints depending on the new parent + (pcb/update-shapes shapes-to-unconstraint + (fn [shape] + (let [frame-id (if (= (:type parent) :frame) + (:id parent) + (:frame-id parent)) + moved-shape (assoc shape + :parent-id parent-id + :frame-id frame-id)] + (assoc shape + :constraints-h (gsh/default-constraints-h moved-shape) + :constraints-v (gsh/default-constraints-v moved-shape)))) + {:ignore-touched true}) + + ;; Fix the sizing when moving a shape + (pcb/update-shapes parents + (fn [parent] + (if (ctl/flex-layout? parent) + (cond-> parent + (ctl/change-h-sizing? (:id parent) objects (:shapes parent)) + (assoc :layout-item-h-sizing :fix) + + (ctl/change-v-sizing? (:id parent) objects (:shapes parent)) + (assoc :layout-item-v-sizing :fix)) + parent))) + + ;; Update grid layout + (cond-> (ctl/grid-layout? objects parent-id) + (pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index))) + + (pcb/update-shapes parents + (fn [parent objects] + (cond-> parent + (ctl/grid-layout? parent) + (ctl/assign-cells objects))) + {:with-objects? true}) + + (pcb/reorder-grid-children parents) + + ;; If parent locked, lock the added shapes + (cond-> (:blocked parent) + (pcb/update-shapes ordered-indexes #(assoc % :blocked true))) + + ;; Resize parent containers that need to + (pcb/resize-parents parents)))) diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc index 6306fc51a6..e28526ee67 100644 --- a/common/test/common_tests/helpers/compositions.cljc +++ b/common/test/common_tests/helpers/compositions.cljc @@ -6,7 +6,8 @@ (ns common-tests.helpers.compositions (:require - [common-tests.helpers.files :as thf])) + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) (defn add-rect [file rect-label] @@ -15,10 +16,11 @@ :name "Rect1")) (defn add-frame - [file frame-label] - (thf/add-sample-shape file frame-label - :type :frame - :name "Frame1")) + ([file frame-label & {:keys [parent-label]}] + (thf/add-sample-shape file frame-label + :type :frame + :name "Frame1" + :parent-label parent-label))) (defn add-frame-with-child [file frame-label child-label] diff --git a/common/test/common_tests/helpers/debug.cljc b/common/test/common_tests/helpers/debug.cljc new file mode 100644 index 0000000000..984e47159c --- /dev/null +++ b/common/test/common_tests/helpers/debug.cljc @@ -0,0 +1,33 @@ +(ns common-tests.helpers.debug + (:require + [app.common.uuid :as uuid] + [common-tests.helpers.ids-map :as thi])) + +(defn dump-shape + "Dumps a shape, with each attribute in a line" + [shape] + (println "{") + (doseq [[k v] (sort shape)] + (when (some? v) + (println (str " " k " : " v)))) + (println "}")) + +(defn- stringify-keys [m keys] + (apply str (interpose ", " (map #(str % ": " (get m %)) keys)))) + +(defn dump-page + "Dumps the layer tree of the page. Prints the label of each shape, and the specified keys. + Example: (thd/dump-page (thf/current-page file) [:id :touched])" + ([page keys] + (dump-page page uuid/zero "" keys)) + ([page id padding keys] + (let [objects (vals (:objects page)) + root-objects (filter #(and + (= (:parent-id %) id) + (not= (:id %) id)) + objects)] + (doseq [val root-objects] + (println padding (thi/label (:id val)) + (when keys + (str "[" (stringify-keys val keys) "]"))) + (dump-page page (:id val) (str padding " ") keys))))) diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index 306d2ef81d..bf605f5dc3 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -9,12 +9,15 @@ [app.common.data.macros :as dm] [app.common.features :as ffeat] [app.common.files.changes :as cfc] + [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] + [app.common.files.libraries-helpers :as cflh] [app.common.files.validate :as cfv] [app.common.geom.point :as gpt] [app.common.pprint :refer [pprint]] [app.common.types.color :as ctc] [app.common.types.colors-list :as ctcl] + [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] @@ -206,8 +209,16 @@ [file id] (ctkl/get-component (:data file) id)) +(defn set-child-label + [file shape-label child-idx label] + (let [id (-> (get-shape file shape-label) + :shapes + (nth child-idx))] + (when id + (thi/set-id! label id)))) + (defn instantiate-component - [file component-label copy-root-label & {:keys [parent-label library] :as params}] + [file component-label copy-root-label & {:keys [parent-label library children-labels] :as params}] (let [page (current-page file) library (or library file) component (get-component library component-label) @@ -236,33 +247,60 @@ (assoc :frame-id frame-id) (and (some? parent) (ctn/in-any-component? (:objects page) parent)) - (dissoc :component-root))] + (dissoc :component-root)) + file' (ctf/update-file-data + file + (fn [file-data] + (as-> file-data $ + (ctpl/update-page $ + (:id page) + #(ctst/add-shape (:id copy-root') + copy-root' + % + frame-id + parent-id + nil + true)) + (reduce (fn [file-data shape] + (ctpl/update-page file-data + (:id page) + #(ctst/add-shape (:id shape) + shape + % + (:parent-id shape) + (:frame-id shape) + nil + true))) + $ + (remove #(= (:id %) (:did copy-root')) copy-shapes)))))] + (when children-labels + (dotimes [idx (count children-labels)] + (set-child-label file' copy-root-label idx (nth children-labels idx)))) + file')) + + + +(defn component-swap + [file shape-label new-component-label new-shape-label & {:keys [library] :as params}] + (let [shape (get-shape file shape-label) + library (or library file) + libraries {(:id library) library} + page (current-page file) + objects (:objects page) + id-new-component (-> (get-component library new-component-label) + :id) + + ;; Store the properties that need to be maintained when the component is swapped + keep-props-values (select-keys shape ctk/swap-keep-attrs) + + + [new_shape _ changes] + (-> (pcb/empty-changes nil (:id page)) + (cflh/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))] + + (thi/set-id! new-shape-label (:id new_shape)) + (apply-changes file changes))) - (ctf/update-file-data - file - (fn [file-data] - (as-> file-data $ - (ctpl/update-page $ - (:id page) - #(ctst/add-shape (:id copy-root') - copy-root' - % - frame-id - parent-id - nil - true)) - (reduce (fn [file-data shape] - (ctpl/update-page file-data - (:id page) - #(ctst/add-shape (:id shape) - shape - % - (:parent-id shape) - (:frame-id shape) - nil - true))) - $ - (remove #(= (:id %) (:did copy-root')) copy-shapes))))))) (defn sample-color [label & {:keys [] :as params}] diff --git a/common/test/common_tests/helpers/ids_map.cljc b/common/test/common_tests/helpers/ids_map.cljc index dc196598d9..cec7242cfb 100644 --- a/common/test/common_tests/helpers/ids_map.cljc +++ b/common/test/common_tests/helpers/ids_map.cljc @@ -34,3 +34,9 @@ [f] (reset-idmap!) (f)) + +(defn label [id] + (->> @idmap + (filter #(= id (val %))) + (map key) + (first))) diff --git a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc new file mode 100644 index 0000000000..5773b1df05 --- /dev/null +++ b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc @@ -0,0 +1,194 @@ +;; 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 common-tests.logic.comp-remove-swap-slots-test + (:require + [app.common.files.changes-builder :as pcb] + [app.common.files.libraries-helpers :as cflh] + [app.common.types.component :as ctk] + [app.common.uuid :as uuid] + [clojure.test :as t] + [common-tests.helpers.compositions :as tho] + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) + +(t/use-fixtures :each thi/test-fixture) + + +;; Related .penpot file: common/test/cases/remove-swap-slots.penpot +(defn- setup-file + [] + ;; :frame-b1 [:id: 3aee2370-44e4-81c8-8004-46e56a459d70, :touched: ] + ;; :blue1 [:id: 3aee2370-44e4-81c8-8004-46e56a45fc55, :touched: #{:swap-slot-3aee2370-44e4-81c8-8004-46e56a459d75}] + ;; :green-copy [:id: 3aee2370-44e4-81c8-8004-46e56a45fc56, :touched: ] + ;; :blue-copy-in-green-copy [:id: 3aee2370-44e4-81c8-8004-46e56a4631a4, :touched: #{:swap-slot-3aee2370-44e4-81c8-8004-46e56a459d6f}] + ;; :frame-yellow [:id: 3aee2370-44e4-81c8-8004-46e56a459d73, :touched: ] + ;; :frame-green [:id: 3aee2370-44e4-81c8-8004-46e56a459d6c, :touched: ] + ;; :red-copy-green [:id: 3aee2370-44e4-81c8-8004-46e56a459d6f, :touched: ] + ;; :frame-blue [:id: 3aee2370-44e4-81c8-8004-46e56a459d69, :touched: ] + ;; :frame-b2 [:id: 3aee2370-44e4-81c8-8004-46e56a4631a5, :touched: ] + ;; :frame-red [:id: 3aee2370-44e4-81c8-8004-46e56a459d66, :touched: ] + + (-> (thf/sample-file :file1) + (tho/add-frame :frame-red) + (thf/make-component :red :frame-red) + (tho/add-frame :frame-blue) + (thf/make-component :blue :frame-blue) + (tho/add-frame :frame-green) + (thf/make-component :green :frame-green) + (thf/instantiate-component :red :red-copy-green :parent-label :frame-green) + (tho/add-frame :frame-b1) + (thf/make-component :b1 :frame-b1) + (tho/add-frame :frame-yellow :parent-label :frame-b1) + (thf/instantiate-component :red :red-copy :parent-label :frame-b1) + (thf/component-swap :red-copy :blue :blue1) + (thf/instantiate-component :green :green-copy :parent-label :frame-b1 :children-labels [:red-copy-in-green-copy]) + (thf/component-swap :red-copy-in-green-copy :blue :blue-copy-in-green-copy) + (tho/add-frame :frame-b2) + (thf/make-component :b2 :frame-b2))) + +(t/deftest test-keep-swap-slot-relocating-blue1-to-root + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + + ;; ============================== Action ============================== + changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + uuid/zero ;; paremt-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids + file' (thf/apply-changes file changes) + + ;; ============================== Get ================================= + blue1' (thf/get-shape file' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1')) + (t/is (nil? (ctk/get-swap-slot blue1'))))) + + +(t/deftest test-keep-swap-slot-relocating-blue1-to-b2 + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + b2 (thf/get-shape file :frame-b2) + + + ;; ============================== Action ============================== + changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id b2) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids + file' (thf/apply-changes file changes) + + ;; ============================== Get ================================= + blue1' (thf/get-shape file' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1')) + (t/is (nil? (ctk/get-swap-slot blue1'))))) + +(t/deftest test-keep-swap-slot-relocating-yellow-to-root + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + yellow (thf/get-shape file :frame-yellow) + + ;; ============================== Action ============================== + ;; Move blue1 into yellow + changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id yellow) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids + + file' (thf/apply-changes file changes) + page' (thf/current-page file') + yellow' (thf/get-shape file' :frame-yellow) + + ;; Move yellow into root + changes' (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page') + #{(:parent-id yellow')} ;; parents + uuid/zero ;; parent-id + (:id page') ;; page-id + 0 ;; to-index + #{(:id yellow')}) ;; ids + file'' (thf/apply-changes file' changes') + + ;; ============================== Get ================================= + blue1'' (thf/get-shape file'' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1'')) + (t/is (nil? (ctk/get-swap-slot blue1''))))) + + +(t/deftest test-keep-swap-slot-relocating-yellow-to-b2 + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + yellow (thf/get-shape file :frame-yellow) + + ;; ============================== Action ============================== + ;; Move blue1 into yellow + changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id yellow) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids + + file' (thf/apply-changes file changes) + page' (thf/current-page file') + yellow' (thf/get-shape file' :frame-yellow) + b2' (thf/get-shape file' :frame-b2) + + ;; Move yellow into b2 + changes' (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page') + #{(:parent-id yellow')} ;; parents + (:id b2') ;; parent-id + (:id page') ;; page-id + 0 ;; to-index + #{(:id yellow')}) ;; ids + file'' (thf/apply-changes file' changes') + + ;; ============================== Get ================================= + blue1'' (thf/get-shape file'' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1'')) + (t/is (nil? (ctk/get-swap-slot blue1''))))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 1f48ae4ed0..52a09c8c98 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -13,6 +13,7 @@ [app.common.features :as cfeat] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] + [app.common.files.libraries-helpers :as cflh] [app.common.geom.align :as gal] [app.common.geom.point :as gpt] [app.common.geom.proportions :as gpp] @@ -786,112 +787,6 @@ ;; --- Change Shape Order (D&D Ordering) -(defn relocate-shapes-changes [it objects parents parent-id page-id to-index ids - groups-to-delete groups-to-unmask shapes-to-detach - shapes-to-reroot shapes-to-deroot shapes-to-unconstraint] - (let [ordered-indexes (cfh/order-by-indexed-shapes objects ids) - shapes (map (d/getf objects) ordered-indexes) - parent (get objects parent-id) - component-main-parent (ctn/find-component-main objects parent false) - child-heads - (->> ordered-indexes - (mapcat #(ctn/get-child-heads objects %)) - (map :id))] - - (-> (pcb/empty-changes it page-id) - (pcb/with-objects objects) - - ;; Remove layout-item properties when moving a shape outside a layout - (cond-> (not (ctl/any-layout? parent)) - (pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) - - ;; Remove the hide in viewer flag - (cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent)) - (pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))) - - ;; Remove the swap slots if it is moving to a different component - (pcb/update-shapes child-heads - (fn [shape] - (cond-> shape - (not= component-main-parent (ctn/find-component-main objects shape false)) - (ctk/remove-swap-slot)))) - - ;; Add component-root property when moving a component outside a component - (cond-> (not (ctn/get-instance-root objects parent)) - (pcb/update-shapes child-heads #(assoc % :component-root true))) - - ;; Move the shapes - (pcb/change-parent parent-id - shapes - to-index) - - ;; Remove empty groups - (pcb/remove-objects groups-to-delete) - - ;; Unmask groups whose mask have moved outside - (pcb/update-shapes groups-to-unmask - (fn [shape] - (assoc shape :masked-group false))) - - ;; Detach shapes moved out of their component - (pcb/update-shapes shapes-to-detach ctk/detach-shape) - - ;; Make non root a component moved inside another one - (pcb/update-shapes shapes-to-deroot - (fn [shape] - (assoc shape :component-root nil))) - - ;; Make root a subcomponent moved outside its parent component - (pcb/update-shapes shapes-to-reroot - (fn [shape] - (assoc shape :component-root true))) - - ;; Reset constraints depending on the new parent - (pcb/update-shapes shapes-to-unconstraint - (fn [shape] - (let [frame-id (if (= (:type parent) :frame) - (:id parent) - (:frame-id parent)) - moved-shape (assoc shape - :parent-id parent-id - :frame-id frame-id)] - (assoc shape - :constraints-h (gsh/default-constraints-h moved-shape) - :constraints-v (gsh/default-constraints-v moved-shape)))) - {:ignore-touched true}) - - ;; Fix the sizing when moving a shape - (pcb/update-shapes parents - (fn [parent] - (if (ctl/flex-layout? parent) - (cond-> parent - (ctl/change-h-sizing? (:id parent) objects (:shapes parent)) - (assoc :layout-item-h-sizing :fix) - - (ctl/change-v-sizing? (:id parent) objects (:shapes parent)) - (assoc :layout-item-v-sizing :fix)) - parent))) - - ;; Update grid layout - (cond-> (ctl/grid-layout? objects parent-id) - (pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index))) - - (pcb/update-shapes parents - (fn [parent objects] - (cond-> parent - (ctl/grid-layout? parent) - (ctl/assign-cells objects))) - {:with-objects? true}) - - (pcb/reorder-grid-children parents) - - ;; If parent locked, lock the added shapes - (cond-> (:blocked parent) - (pcb/update-shapes ordered-indexes #(assoc % :blocked true))) - - ;; Resize parent containers that need to - (pcb/resize-parents parents)))) - (defn relocate-shapes [ids parent-id to-index & [ignore-parents?]] (dm/assert! (every? uuid? ids)) @@ -913,97 +808,13 @@ all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids) parents (if ignore-parents? #{parent-id} all-parents) - groups-to-delete - (loop [current-id (first parents) - to-check (rest parents) - removed-id? (set ids) - result #{}] - - (if-not current-id - ;; Base case, no next element - result - - (let [group (get objects current-id)] - (if (and (not= :frame (:type group)) - (not= current-id parent-id) - (empty? (remove removed-id? (:shapes group)))) - - ;; Adds group to the remove and check its parent - (let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])] - (recur (first to-check) - (rest to-check) - (conj removed-id? current-id) - (conj result current-id))) - - ;; otherwise recur - (recur (first to-check) - (rest to-check) - removed-id? - result))))) - - groups-to-unmask - (reduce (fn [group-ids id] - ;; When a masked group loses its mask shape, because it's - ;; moved outside the group, the mask condition must be - ;; removed, and it must be converted to a normal group. - (let [obj (get objects id) - parent (get objects (:parent-id obj))] - (if (and (:masked-group parent) - (= id (first (:shapes parent))) - (not= (:id parent) parent-id)) - (conj group-ids (:id parent)) - group-ids))) - #{} - ids) - - ;; TODO: Probably implementing this using loop/recur will - ;; be more efficient than using reduce and continuous data - ;; desturcturing. - - ;; Sets the correct components metadata for the moved shapes - ;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside - ;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component - ;; `shapes-to-reroot` Adds a root flag when a nested component instance is moved outside - [shapes-to-detach shapes-to-deroot shapes-to-reroot] - (reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id] - (let [shape (get objects id) - parent (get objects parent-id) - component-shape (ctn/get-component-shape objects shape) - component-shape-parent (ctn/get-component-shape objects parent {:allow-main? true}) - root-parent (ctn/get-instance-root objects parent) - - detach? (and (ctk/in-component-copy-not-head? shape) - (not= (:id component-shape) - (:id component-shape-parent))) - deroot? (and (ctk/instance-root? shape) - root-parent) - reroot? (and (ctk/subinstance-head? shape) - (not component-shape-parent)) - - ids-to-detach (when detach? - (cons id (cfh/get-children-ids objects id)))] - - [(cond-> shapes-to-detach detach? (into ids-to-detach)) - (cond-> shapes-to-deroot deroot? (conj id)) - (cond-> shapes-to-reroot reroot? (conj id))])) - [[] [] []] - (->> ids - (mapcat #(ctn/get-child-heads objects %)) - (map :id))) - - changes (relocate-shapes-changes it - objects - parents - parent-id - page-id - to-index - ids - groups-to-delete - groups-to-unmask - shapes-to-detach - shapes-to-reroot - shapes-to-deroot - ids) + changes (cflh/generate-relocate-shapes (pcb/empty-changes it) + objects + parents + parent-id + page-id + to-index + ids) undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) From 5611fcfc2c36bfac1c7d8d081c159950329462c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 29 Apr 2024 15:12:43 +0200 Subject: [PATCH 15/34] :wrench: Add generator function for update-shapes --- .../app/common/files/libraries_helpers.cljc | 17 ++++++++++ .../src/app/main/data/workspace/changes.cljs | 31 +++++++++---------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index 5aa612a52c..dc4c152ece 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -34,6 +34,23 @@ ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default (log/set-level! :warn) +(defn generate-update-shapes +[changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}] + (let [changes (reduce + (fn [changes id] + (let [opts {:attrs attrs + :ignore-geometry? (get ignore-tree id) + :ignore-touched ignore-touched + :with-objects? with-objects?}] + (pcb/update-shapes changes [id] update-fn (d/without-nils opts)))) + (-> changes + (pcb/with-objects objects)) + ids) + grid-ids (->> ids (filter (partial ctl/grid-layout? objects))) + changes (pcb/update-shapes changes grid-ids ctl/assign-cell-positions {:with-objects? true}) + changes (pcb/reorder-grid-children changes ids)] + changes)) + (declare generate-sync-container) (declare generate-sync-shape) (declare generate-sync-text-shape) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index b2d5950865..c1e0bb04ff 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -12,6 +12,7 @@ [app.common.files.changes :as cpc] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cph] + [app.common.files.libraries-helpers :as cflh] [app.common.logging :as log] [app.common.schema :as sm] [app.common.types.shape-tree :as ctst] @@ -74,23 +75,19 @@ (filter #(some update-layout-attr? (pcb/changed-attrs % objects update-fn {:attrs attrs :with-objects? with-objects?}))) (map :id)) - changes (reduce - (fn [changes id] - (let [opts {:attrs attrs - :ignore-geometry? (get ignore-tree id) - :ignore-touched ignore-touched - :with-objects? with-objects?}] - (pcb/update-shapes changes [id] update-fn (d/without-nils opts)))) - (-> (pcb/empty-changes it page-id) - (pcb/set-save-undo? save-undo?) - (pcb/set-stack-undo? stack-undo?) - (pcb/with-objects objects) - (cond-> undo-group - (pcb/set-undo-group undo-group))) - ids) - grid-ids (->> ids (filter (partial ctl/grid-layout? objects))) - changes (pcb/update-shapes changes grid-ids ctl/assign-cell-positions {:with-objects? true}) - changes (pcb/reorder-grid-children changes ids) + changes (-> (pcb/empty-changes it page-id) + (pcb/set-save-undo? save-undo?) + (pcb/set-stack-undo? stack-undo?) + (cflh/generate-update-shapes ids + update-fn + objects + {:attrs attrs + :ignore-tree ignore-tree + :ignore-touched ignore-touched + :with-objects? with-objects?}) + (cond-> undo-group + (pcb/set-undo-group undo-group))) + changes (add-undo-group changes state)] (rx/concat (if (seq (:redo-changes changes)) From a40afd5b638c505cb9c22127fc0c9addcac89f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 29 Apr 2024 15:15:10 +0200 Subject: [PATCH 16/34] :white_check_mark: Add test for touched shapes --- .../app/common/files/libraries_helpers.cljc | 2 +- .../common_tests/helpers/compositions.cljc | 47 ++++++++------ common/test/common_tests/helpers/files.cljc | 31 ++++++--- ...test.cljc => component_creation_test.cljc} | 10 +-- .../logic/components_touched_test.cljc | 51 +++++++++++++++ .../types/types_libraries_test.cljc | 27 ++++---- .../src/app/main/data/workspace/changes.cljs | 1 - .../state_components_sync_test.cljs | 64 ------------------- 8 files changed, 122 insertions(+), 111 deletions(-) rename common/test/common_tests/logic/{logic_comp_creation_test.cljc => component_creation_test.cljc} (92%) create mode 100644 common/test/common_tests/logic/components_touched_test.cljc diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index dc4c152ece..617643b61f 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -35,7 +35,7 @@ (log/set-level! :warn) (defn generate-update-shapes -[changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}] + [changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}] (let [changes (reduce (fn [changes id] (let [opts {:attrs attrs diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc index e28526ee67..48e4fff7c3 100644 --- a/common/test/common_tests/helpers/compositions.cljc +++ b/common/test/common_tests/helpers/compositions.cljc @@ -10,35 +10,44 @@ [common-tests.helpers.ids-map :as thi])) (defn add-rect - [file rect-label] + [file rect-label & {:keys [] :as params}] (thf/add-sample-shape file rect-label - :type :rect - :name "Rect1")) + (merge {:type :rect + :name "Rect1"} + params))) (defn add-frame - ([file frame-label & {:keys [parent-label]}] - (thf/add-sample-shape file frame-label - :type :frame - :name "Frame1" - :parent-label parent-label))) + [file frame-label & {:keys [] :as params}] + (thf/add-sample-shape file frame-label + (merge {:type :frame + :name "Frame1"} + params))) (defn add-frame-with-child - [file frame-label child-label] + [file frame-label child-label & {:keys [frame-params child-params]}] (-> file - (add-frame frame-label) + (add-frame frame-label frame-params) (thf/add-sample-shape child-label - :type :rect - :name "Rect1" - :parent-label frame-label))) + (merge {:type :rect + :name "Rect1" + :parent-label frame-label} + child-params)))) (defn add-simple-component - [file component-label root-label child-label] + [file component-label root-label child-label + & {:keys [component-params root-params child-params]}] (-> file - (add-frame-with-child root-label child-label) - (thf/make-component component-label root-label))) + (add-frame-with-child root-label child-label :frame-params root-params :child-params child-params) + (thf/make-component component-label root-label component-params))) (defn add-simple-component-with-copy - [file component-label main-root-label main-child-label copy-root-label] + [file component-label main-root-label main-child-label copy-root-label + & {:keys [component-params main-root-params main-child-params copy-root-params]}] (-> file - (add-simple-component component-label main-root-label main-child-label) - (thf/instantiate-component component-label copy-root-label))) + (add-simple-component component-label + main-root-label + main-child-label + :component-params component-params + :root-params main-root-params + :child-params main-child-params) + (thf/instantiate-component component-label copy-root-label copy-root-params))) diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index bf605f5dc3..b6349636b6 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -6,6 +6,7 @@ (ns common-tests.helpers.files (:require + [app.common.colors :as clr] [app.common.data.macros :as dm] [app.common.features :as ffeat] [app.common.files.changes :as cfc] @@ -169,7 +170,7 @@ ;; ----- Components (defn make-component - [file label root-label] + [file label root-label & {:keys [] :as params}] (let [page (current-page file) root (get-shape file root-label)] @@ -194,12 +195,12 @@ #(update % :objects assoc (:id shape) shape))) $ updated-shapes) - (ctkl/add-component $ - {:id (:component-id updated-root) - :name (:name updated-root) - :main-instance-id (:id updated-root) - :main-instance-page (:id page) - :shapes updated-shapes}))))))) + (ctkl/add-component $ (assoc params + :id (:component-id updated-root) + :name (:name updated-root) + :main-instance-id (:id updated-root) + :main-instance-page (:id page) + :shapes updated-shapes)))))))) (defn get-component [file label] @@ -306,7 +307,21 @@ [label & {:keys [] :as params}] (ctc/make-color (assoc params :id (thi/new-id! label)))) -(defn add-sample-color +(defn sample-fill-color + [& {:keys [fill-color fill-opacity] :as params}] + (let [params (cond-> params + (nil? fill-color) + (assoc :fill-color clr/black) + + (nil? fill-opacity) + (assoc :fill-opacity 1))] + params)) + +(defn sample-fills-color + [& {:keys [] :as params}] + [(sample-fill-color params)]) + +(defn add-sample-library-color [file label & {:keys [] :as params}] (let [color (sample-color label params)] (ctf/update-file-data file #(ctcl/add-color % color)))) diff --git a/common/test/common_tests/logic/logic_comp_creation_test.cljc b/common/test/common_tests/logic/component_creation_test.cljc similarity index 92% rename from common/test/common_tests/logic/logic_comp_creation_test.cljc rename to common/test/common_tests/logic/component_creation_test.cljc index 577bef06f3..3e6499fb2b 100644 --- a/common/test/common_tests/logic/logic_comp_creation_test.cljc +++ b/common/test/common_tests/logic/component_creation_test.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns common-tests.logic.logic-comp-creation-test +(ns common-tests.logic.component-creation-test (:require [app.common.files.changes-builder :as pcb] [app.common.files.libraries-helpers :as cflh] @@ -15,14 +15,14 @@ (t/use-fixtures :each thi/test-fixture) (t/deftest test-add-component-from-single-shape - (let [; Setup + (let [;; Setup file (-> (thf/sample-file :file1) (thf/add-sample-shape :shape1 :type :frame)) page (thf/current-page file) shape1 (thf/get-shape file :shape1) - ; Action + ;; Action [_ component-id changes] (cflh/generate-add-component (pcb/empty-changes) [shape1] @@ -35,11 +35,11 @@ file' (thf/apply-changes file changes) - ; Get + ;; Get component (thf/get-component-by-id file' component-id) root (thf/get-shape-by-id file' (:main-instance-id component))] - ; Check + ;; Check (t/is (some? component)) (t/is (some? root)) (t/is (= (:component-id root) (:id component))))) diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc new file mode 100644 index 0000000000..eedd84fd9e --- /dev/null +++ b/common/test/common_tests/logic/components_touched_test.cljc @@ -0,0 +1,51 @@ +;; 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 common-tests.logic.components-touched-test + (:require + [app.common.files.changes-builder :as pcb] + [app.common.files.libraries-helpers :as cflh] + [clojure.test :as t] + [common-tests.helpers.compositions :as tho] + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) + +(t/use-fixtures :each thi/test-fixture) + +(t/deftest test-touched-when-changing-attribute + (let [;; Setup + file (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy :component1 + :main-root + :main-child + :copy-root + :main-child-params {:fills (thf/sample-fills-color + :fill-color "#abcdef")})) + page (thf/current-page file) + copy-root (thf/get-shape file :copy-root) + + ;; Action + changes (cflh/generate-update-shapes (pcb/empty-changes nil (:id page)) + (:shapes copy-root) + #(assoc % :fills (thf/sample-fills-color + :fill-color "#fabada")) + (:objects page) + {}) + + file' (thf/apply-changes file changes) + + ;; Get + copy-root' (thf/get-shape file' :copy-root) + copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root'))) + fills' (:fills copy-child') + fill' (first fills')] + + ;; Check + (t/is (= (count fills') 1)) + (t/is (= (:fill-color fill') "#fabada")) + (t/is (= (:fill-opacity fill') 1)) + (t/is (= (:touched copy-root') nil)) + (t/is (= (:touched copy-child') #{:fill-group})))) diff --git a/common/test/common_tests/types/types_libraries_test.cljc b/common/test/common_tests/types/types_libraries_test.cljc index 0eab0db078..d95232824d 100644 --- a/common/test/common_tests/types/types_libraries_test.cljc +++ b/common/test/common_tests/types/types_libraries_test.cljc @@ -70,21 +70,21 @@ (t/is (= (:name f1) "Test file")))) (t/deftest test-absorb-components - (let [; Setup + (let [;; Setup library (-> (thf/sample-file :library :is-shared true) (tho/add-simple-component :component1 :main-root :rect1)) file (-> (thf/sample-file :file) (thf/instantiate-component :component1 :copy-root :library library)) - ; Action + ;; Action file' (ctf/update-file-data file #(ctf/absorb-assets % (:data library))) _ (thf/validate-file! file') - ; Get + ;; Get pages' (ctpl/pages-seq (ctf/file-data file')) components' (ctkl/components-seq (ctf/file-data file')) component' (first components') @@ -92,7 +92,7 @@ copy-root' (thf/get-shape file' :copy-root) main-root' (ctf/get-ref-shape (ctf/file-data file') component' copy-root')] - ; Check + ;; Check (t/is (= (count pages') 2)) (t/is (= (:name (first pages')) "Page 1")) (t/is (= (:name (second pages')) "Main components")) @@ -104,10 +104,10 @@ (t/is (ctk/main-instance-of? (:id main-root') (:id (second pages')) component')))) (t/deftest test-absorb-colors - (let [; Setup + (let [;; Setup library (-> (thf/sample-file :library :is-shared true) - (thf/add-sample-color :color1 {:name "Test color" - :color "#abcdef"})) + (thf/add-sample-library-color :color1 {:name "Test color" + :color "#abcdef"})) file (-> (thf/sample-file :file) (thf/add-sample-shape :shape1 @@ -118,19 +118,19 @@ :fill-color-ref-id (thi/id :color1) :fill-color-ref-file (thi/id :library)}])) - ; Action + ;; Action file' (ctf/update-file-data file #(ctf/absorb-assets % (:data library))) _ (thf/validate-file! file') - ; Get + ;; Get colors' (ctcl/colors-seq (ctf/file-data file')) shape1' (thf/get-shape file' :shape1) fill' (first (:fills shape1'))] - ; Check + ;; Check (t/is (= (count colors') 1)) (t/is (= (:id (first colors')) (thi/id :color1))) (t/is (= (:name (first colors')) "Test color")) @@ -141,7 +141,7 @@ (t/is (= (:fill-color-ref-file fill') (:id file'))))) (t/deftest test-absorb-typographies - (let [; Setup + (let [;; Setup library (-> (thf/sample-file :library :is-shared true) (thf/add-sample-typography :typography1 {:name "Test typography"})) @@ -169,18 +169,19 @@ :letter-spacing "0" :fills [{:fill-color "#000000" :fill-opacity 1}]}]}]}]})) - ; Action + ;; Action file' (ctf/update-file-data file #(ctf/absorb-assets % (:data library))) _ (thf/validate-file! file') - ; Get + ;; Get typographies' (ctyl/typographies-seq (ctf/file-data file')) shape1' (thf/get-shape file' :shape1) text-node' (d/seek #(some? (:text %)) (txt/node-seq (:content shape1')))] + ;; Check (t/is (= (count typographies') 1)) (t/is (= (:id (first typographies')) (thi/id :typography1))) (t/is (= (:name (first typographies')) "Test typography")) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index c1e0bb04ff..87dec4048f 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -16,7 +16,6 @@ [app.common.logging :as log] [app.common.schema :as sm] [app.common.types.shape-tree :as ctst] - [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] diff --git a/frontend/test/frontend_tests/state_components_sync_test.cljs b/frontend/test/frontend_tests/state_components_sync_test.cljs index 4928ad74c0..56581b29e7 100644 --- a/frontend/test/frontend_tests/state_components_sync_test.cljs +++ b/frontend/test/frontend_tests/state_components_sync_test.cljs @@ -7,7 +7,6 @@ (ns frontend-tests.state-components-sync-test (:require [app.common.colors :as clr] - [app.common.types.file :as ctf] [app.main.data.workspace :as dw] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.libraries :as dwl] @@ -24,69 +23,6 @@ ;; === Test touched ====================== -(t/deftest test-touched - (t/async done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 - (thp/id :component1))) - - [_group1 shape1'] - (thl/resolve-instance state (thp/id :instance1)) - - store (the/prepare-store state done - (fn [new-state] - ;; Uncomment to debug - ;; (ctf/dump-tree (get new-state :workspace-data) - ;; (get new-state :current-page-id) - ;; (get new-state :workspace-libraries) - ;; false true) - ;; Expected shape tree: - ;;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Rect 1 #--> Rect 1 - ;; Rect 1* ---> Rect 1 - ;; #{:fill-group} - ;;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;;; - (let [[[group shape1] [c-group c-shape1] _component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] - - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) #{:fill-group})) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/white)) - (t/is (= (:fill-opacity c-shape1) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape1')] - (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5}))) - :the/end)))) - (t/deftest test-touched-children-add (t/async done (let [state (-> thp/initial-state From 77d4901db10522d3d5fa2224838be15984709652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 29 Apr 2024 18:40:09 +0200 Subject: [PATCH 17/34] :white_check_mark: Add more tests for touched --- .../common_tests/helpers/compositions.cljc | 32 ++- .../logic/components_touched_test.cljc | 136 ++++++++++-- .../state_components_sync_test.cljs | 195 ------------------ 3 files changed, 148 insertions(+), 215 deletions(-) diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc index 48e4fff7c3..d8b9cbe8b2 100644 --- a/common/test/common_tests/helpers/compositions.cljc +++ b/common/test/common_tests/helpers/compositions.cljc @@ -6,8 +6,8 @@ (ns common-tests.helpers.compositions (:require - [common-tests.helpers.files :as thf] - [common-tests.helpers.ids-map :as thi])) + [app.common.data :as d] + [common-tests.helpers.files :as thf])) (defn add-rect [file rect-label & {:keys [] :as params}] @@ -51,3 +51,31 @@ :root-params main-root-params :child-params main-child-params) (thf/instantiate-component component-label copy-root-label copy-root-params))) + +(defn add-component-with-many-children + [file component-label root-label child-labels + & {:keys [component-params root-params child-params-list]}] + (as-> file $ + (add-frame $ root-label root-params) + (reduce (fn [file [label params]] + (thf/add-sample-shape file + label + (merge {:type :rect + :name "Rect1" + :parent-label root-label} + params))) + $ + (d/zip-all child-labels child-params-list)) + (thf/make-component $ component-label root-label component-params))) + +(defn add-component-with-many-children-and-copy + [file component-label root-label child-labels copy-root-label + & {:keys [component-params root-params child-params-list copy-root-params]}] + (-> file + (add-component-with-many-children component-label + root-label + child-labels + :component-params component-params + :root-params root-params + :child-params-list child-params-list) + (thf/instantiate-component component-label copy-root-label copy-root-params))) diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc index eedd84fd9e..45b11b59f9 100644 --- a/common/test/common_tests/logic/components_touched_test.cljc +++ b/common/test/common_tests/logic/components_touched_test.cljc @@ -17,31 +17,33 @@ (t/deftest test-touched-when-changing-attribute (let [;; Setup - file (-> (thf/sample-file :file1) - (tho/add-simple-component-with-copy :component1 - :main-root - :main-child - :copy-root - :main-child-params {:fills (thf/sample-fills-color - :fill-color "#abcdef")})) - page (thf/current-page file) + file (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy :component1 + :main-root + :main-child + :copy-root + :main-child-params {:fills (thf/sample-fills-color + :fill-color "#abcdef")})) + page (thf/current-page file) copy-root (thf/get-shape file :copy-root) ;; Action - changes (cflh/generate-update-shapes (pcb/empty-changes nil (:id page)) - (:shapes copy-root) - #(assoc % :fills (thf/sample-fills-color - :fill-color "#fabada")) - (:objects page) - {}) + update-fn (fn [shape] + (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) - file' (thf/apply-changes file changes) + changes (cflh/generate-update-shapes (pcb/empty-changes nil (:id page)) + (:shapes copy-root) + update-fn + (:objects page) + {}) + + file' (thf/apply-changes file changes) ;; Get - copy-root' (thf/get-shape file' :copy-root) + copy-root' (thf/get-shape file' :copy-root) copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root'))) - fills' (:fills copy-child') - fill' (first fills')] + fills' (:fills copy-child') + fill' (first fills')] ;; Check (t/is (= (count fills') 1)) @@ -49,3 +51,101 @@ (t/is (= (:fill-opacity fill') 1)) (t/is (= (:touched copy-root') nil)) (t/is (= (:touched copy-child') #{:fill-group})))) + +(t/deftest test-not-touched-when-adding-shape + (let [;; Setup + file (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy :component1 + :main-root + :main-child + :copy-root) + (thf/add-sample-shape :free-shape)) + + page (thf/current-page file) + + ;; Action + ;; IMPORTANT: as modifying copies structure is now forbidden, this action + ;; will not have any effect, and so the parent shape won't also be touched. + changes (cflh/generate-relocate-shapes (pcb/empty-changes) + (:objects page) + #{(thi/id :copy-root)} ; parents + (thi/id :copy-root) ; parent-id + (:id page) ; page-id + 0 ; to-index + #{(thi/id :free-shape)}) ; ids + + file' (thf/apply-changes file changes) + + ;; Get + copy-root' (thf/get-shape file' :copy-root) + copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] + + ;; Check + (t/is (= (:touched copy-root') nil)) + (t/is (= (:touched copy-child') nil)))) + +(t/deftest test-touched-when-deleting-shape + (let [;; Setup + file (-> (thf/sample-file :file1) + (tho/add-simple-component-with-copy :component1 + :main-root + :main-child + :copy-root)) + + page (thf/current-page file) + copy-root (thf/get-shape file :copy-root) + + ;; Action + ;; IMPORTANT: as modifying copies structure is now forbidden, this action will not + ;; delete the child shape, but hide it (thus setting the visibility group). + [_all-parents changes] + (cflh/generate-delete-shapes (pcb/empty-changes) + file + page + (:objects page) + (set (:shapes copy-root)) + {:components-v2 true}) + + file' (thf/apply-changes file changes) + + ;; Get + copy-root' (thf/get-shape file' :copy-root) + copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] + + ;; Check + (t/is (= (:touched copy-root') nil)) + (t/is (= (:touched copy-child') #{:visibility-group})))) + +(t/deftest test-not-touched-when-moving-shape + (let [;; Setup + file (-> (thf/sample-file :file1) + (tho/add-component-with-many-children-and-copy :component1 + :main-root + [:main-child1 :main-child2 :main-child3] + :copy-root) + (thf/add-sample-shape :free-shape)) + + page (thf/current-page file) + copy-root (thf/get-shape file :copy-root) + copy-child1 (thf/get-shape-by-id file (first (:shapes copy-root))) + + ;; Action + ;; IMPORTANT: as modifying copies structure is now forbidden, this action + ;; will not have any effect, and so the parent shape won't also be touched. + changes (cflh/generate-relocate-shapes (pcb/empty-changes) + (:objects page) + #{(thi/id :copy-root)} ; parents + (thi/id :copy-root) ; parent-id + (:id page) ; page-id + 2 ; to-index + #{(:id copy-child1)}) ; ids + + file' (thf/apply-changes file changes) + + ;; Get + copy-root' (thf/get-shape file' :copy-root) + copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] + + ;; Check + (t/is (= (:touched copy-root') nil)) + (t/is (= (:touched copy-child') nil)))) diff --git a/frontend/test/frontend_tests/state_components_sync_test.cljs b/frontend/test/frontend_tests/state_components_sync_test.cljs index 56581b29e7..8ae88562a1 100644 --- a/frontend/test/frontend_tests/state_components_sync_test.cljs +++ b/frontend/test/frontend_tests/state_components_sync_test.cljs @@ -23,201 +23,6 @@ ;; === Test touched ====================== -(t/deftest test-touched-children-add - (t/async done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 - (thp/id :component1)) - (thp/sample-shape :shape2 :circle - {:name "Circle 1"})) - - instance1 (thp/get-shape state :instance1) - shape2 (thp/get-shape state :shape2) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; [Page: Page 1] - ;; Root Frame - ;; {Rect 1} - ;; Rect1 - ;; Rect 1 #--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; Circle 1 - ;; - ;; [Component: Rect 1] core.cljs:200:23 - ;; --> [Page 1] Rect 1 - - (let [[[group shape1] [c-group c-shape1] _component] - (thl/resolve-instance-and-main-allow-dangling - new-state - (thp/id :instance1))] - - (t/is (= (:name group) "Rect 1")) - (t/is (nil? (:touched group))) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) nil)) - (t/is (not= (:shape-ref shape1) nil)) - - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)))))] - - (ptk/emit! - store - (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) ;; We cant't change the structure of component copies, so this operation will do nothing - :the/end)))) - -(t/deftest test-touched-children-delete - (t/async done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1) - (thp/id :shape2)]) - (thp/instantiate-component :instance1 - (thp/id :component1))) - - [_group1 shape1'] - (thl/resolve-instance state (thp/id :instance1)) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;;; - ;; [Page] - ;; Root Frame - ;; Component 1 - ;; Rect 1 - ;; Rect 2 - ;; Component 1 #--> Component 1 - ;; Rect 1* ---> Rect 1 - ;; #{:visibility-group} - ;; Rect 2 ---> Rect 2 - ;;; - ;; [Component 1] - ;; page1 / Component 1 - ;; - (let [[[group shape1 shape2] [c-group c-shape1 c-shape2] _component] - (thl/resolve-instance-and-main-allow-dangling - new-state - (thp/id :instance1))] - - (t/is (= (:name group) "Component 1")) - (t/is (= (:touched group) nil)) - (t/is (not= (:shape-ref group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:hidden shape1) true)) ; Instance shapes are not deleted but hidden - (t/is (= (:touched shape1) #{:visibility-group})) - (t/is (not= (:shape-ref shape1) nil)) - (t/is (= (:name shape2) "Rect 2")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) - - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)) - (t/is (= (:name c-shape2) "Rect 2")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)))))] - - (ptk/emit! - store - (dwsh/delete-shapes #{(:id shape1')}) - :the/end)))) - -(t/deftest test-touched-children-move - (t/async done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) - (thp/sample-shape :shape3 :rect - {:name "Rect 3"}) - (thp/make-component :main1 :component1 - [(thp/id :shape1) - (thp/id :shape2) - (thp/id :shape3)]) - (thp/instantiate-component :instance1 - (thp/id :component1))) - - [group1' shape1'] - (thl/resolve-instance state (thp/id :instance1)) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; [Page: Page 1] - ;; Root Frame - ;; {Component 1} # - ;; Rect 1 - ;; Rect 2 - ;; Rect 3 - ;; Component 1 #--> Component 1 - ;; Rect 1 ---> Rect 1 - ;; Rect 2 ---> Rect 2 - ;; Rect 3 ---> Rect 3 - ;; - ;; ========= Local library - ;; - ;; [Component: Component 1] - ;; --> [Page 1] Component 1 - - (let [[[group shape1 shape2 shape3] - [c-group c-shape1 c-shape2 c-shape3] _component] - (thl/resolve-instance-and-main-allow-dangling - new-state - (thp/id :instance1))] - - (t/is (= (:name group) "Component 1")) - (t/is (nil? (:touched group))) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) nil)) - (t/is (not= (:shape-ref shape1) nil)) - (t/is (= (:name shape2) "Rect 2")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) - (t/is (= (:name shape3) "Rect 3")) - (t/is (= (:touched shape3) nil)) - (t/is (not= (:shape-ref shape3) nil)) - - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)) - (t/is (= (:name c-shape2) "Rect 2")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)) - (t/is (= (:name c-shape3) "Rect 3")) - (t/is (= (:touched c-shape3) nil)) - (t/is (= (:shape-ref c-shape3) nil)))))] - - (ptk/emit! - store - (dw/relocate-shapes #{(:id shape1')} (:id group1') 2) ;; We cant't change the structure of component copies, so this operation will do nothing - :the/end)))) - (t/deftest test-touched-from-lib (t/async done From bca8180aebb00f4a0e477455d4f975963f2ca403 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 30 Apr 2024 10:01:29 +0200 Subject: [PATCH 18/34] :bug: Fix duplicate component --- common/src/app/common/files/libraries_helpers.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index 5aa612a52c..2aceb35fdd 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -166,7 +166,7 @@ [new-component-shape new-component-shapes ; <- null in components-v2 new-main-instance-shape new-main-instance-shapes] - (duplicate-component (:data library) component new-component-id)] + (duplicate-component component new-component-id (:data library))] (-> changes (pcb/with-page main-instance-page) From 0fc7d8529e58d76df111098faffe8c82e27a0d35 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 30 Apr 2024 10:38:58 +0200 Subject: [PATCH 19/34] :sparkles: Delete Bottle tutorial and walkthrough from onboarding dashboard --- .../src/app/main/ui/dashboard/projects.cljs | 117 +----------------- .../src/app/main/ui/dashboard/projects.scss | 86 ------------- 2 files changed, 1 insertion(+), 202 deletions(-) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index cdd9304e97..11ab89f5d6 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -7,15 +7,11 @@ (ns app.main.ui.dashboard.projects (:require-macros [app.main.style :as stl]) (:require - [app.common.data :as d] [app.common.geom.point :as gpt] - [app.config :as cf] [app.main.data.dashboard :as dd] [app.main.data.events :as ev] - [app.main.data.messages :as msg] [app.main.data.modal :as modal] [app.main.data.users :as du] - [app.main.errors :as errors] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.dashboard.grid :refer [line-grid]] @@ -100,80 +96,6 @@ (def builtin-templates (l/derived :builtin-templates st/state)) -(mf/defc tutorial-project - [{:keys [close-tutorial default-project-id] :as props}] - (let [state (mf/use-state {:status :waiting - :file nil}) - - templates (mf/deref builtin-templates) - template (d/seek #(= (:id %) "tutorial-for-beginners") templates) - - on-template-cloned-success - (mf/use-fn - (mf/deps default-project-id) - (fn [response] - (swap! state #(assoc % :status :success :file (:first response))) - (st/emit! (dd/go-to-workspace {:id (first response) :project-id default-project-id :name "tutorial"}) - (du/update-profile-props {:viewed-tutorial? true})))) - - on-template-cloned-error - (mf/use-fn - (fn [cause] - (swap! state assoc :status :error) - (errors/print-error! cause) - (st/emit! (msg/error (tr "dashboard.libraries-and-templates.import-error"))))) - - download-tutorial - (mf/use-fn - (mf/deps template default-project-id) - (fn [] - (let [mdata {:on-success on-template-cloned-success - :on-error on-template-cloned-error} - params {:project-id default-project-id - :template-id (:id template)}] - (swap! state #(assoc % :status :importing)) - (st/emit! (with-meta (dd/clone-template (with-meta params mdata)) - {::ev/origin "get-started-hero-block"})))))] - [:article {:class (stl/css :tutorial)} - [:div {:class (stl/css :thumbnail)}] - [:div {:class (stl/css :text)} - [:h2 {:class (stl/css :title)} (tr "dasboard.tutorial-hero.title")] - [:p {:class (stl/css :info)} (tr "dasboard.tutorial-hero.info")] - [:button {:class (stl/css :btn-primary :action) - :on-click download-tutorial} - (case (:status @state) - :waiting (tr "dasboard.tutorial-hero.start") - :importing [:span.loader i/loader-pencil] - :success "")]] - - [:button {:class (stl/css :close) - :on-click close-tutorial - :aria-label (tr "labels.close")} - close-icon]])) - -(mf/defc interface-walkthrough - {::mf/wrap [mf/memo]} - [{:keys [close-walkthrough] :as props}] - (let [handle-walkthrough-link - (fn [] - (st/emit! (ptk/event ::ev/event {::ev/name "show-walkthrough" - ::ev/origin "get-started-hero-block" - :section "dashboard"})))] - [:article {:class (stl/css :walkthrough)} - [:div {:class (stl/css :thumbnail)}] - [:div {:class (stl/css :text)} - [:h2 {:class (stl/css :title)} (tr "dasboard.walkthrough-hero.title")] - [:p {:class (stl/css :info)} (tr "dasboard.walkthrough-hero.info")] - [:a {:class (stl/css :btn-primary :action) - :href " https://design.penpot.app/walkthrough" - :target "_blank" - :on-click handle-walkthrough-link} - (tr "dasboard.walkthrough-hero.start")]] - [:button {:class (stl/css :close) - :on-click close-walkthrough - :aria-label (tr "labels.close")} - close-icon]])) - (mf/defc project-item [{:keys [project first? team files] :as props}] (let [locale (mf/deref i18n/locale) @@ -365,7 +287,7 @@ (l/derived :dashboard-recent-files st/state)) (mf/defc projects-section - [{:keys [team projects profile default-project-id] :as props}] + [{:keys [team projects profile] :as props}] (let [projects (->> (vals projects) (sort-by :modified-at) (reverse)) @@ -378,8 +300,6 @@ (:team-hero? props true) (not (:is-default team))) - tutorial-viewed? (:viewed-tutorial? props true) - walkthrough-viewed? (:viewed-walkthrough? props true) is-my-penpot (= (:default-team-id profile) (:id team)) team-id (:id team) @@ -391,28 +311,6 @@ (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero" ::ev/origin "dashboard"})))) - close-tutorial - (mf/use-fn - (fn [] - (st/emit! (du/update-profile-props {:viewed-tutorial? true}) - (ptk/data-event ::ev/event {::ev/name "dont-show-tutorial" - ::ev/origin "get-started-hero" - :type "tutorial" - :section "dashboard"})))) - - close-walkthrough - (mf/use-fn - (fn [] - (st/emit! (du/update-profile-props {:viewed-walkthrough? true}) - (ptk/data-event ::ev/event {::ev/name "dont-show-walkthrough" - ::ev/origin "get-started-hero" - :type "walkthrough" - :section "dashboard"})))) - - show-hero? (and is-my-penpot - (or (not tutorial-viewed?) - (not walkthrough-viewed?))) - show-team-hero? (and (not is-my-penpot) team-hero?)] (mf/with-effect [team] @@ -433,22 +331,9 @@ (when team-hero? [:& team-hero {:team team :close-fn close-banner}]) - (when (and (contains? cf/flags :dashboard-templates-section) - show-hero?) - [:div {:class (stl/css :hero-projects)} - (when (and (not tutorial-viewed?) (:is-default team)) - [:& tutorial-project - {:close-tutorial close-tutorial - :default-project-id default-project-id}]) - - (when (and (not walkthrough-viewed?) (:is-default team)) - [:& interface-walkthrough - {:close-walkthrough close-walkthrough}])]) - [:div {:class (stl/css-case :dashboard-container true :no-bg true :dashboard-projects true - :with-hero show-hero? :with-team-hero show-team-hero?)} (for [{:keys [id] :as project} projects] (let [files (when recent-map diff --git a/frontend/src/app/main/ui/dashboard/projects.scss b/frontend/src/app/main/ui/dashboard/projects.scss index e3b57fba55..680b9ef322 100644 --- a/frontend/src/app/main/ui/dashboard/projects.scss +++ b/frontend/src/app/main/ui/dashboard/projects.scss @@ -20,7 +20,6 @@ height: calc(100vh - $s-64); } -.with-hero, .with-team-hero { height: calc(100vh - $s-280); } @@ -242,88 +241,3 @@ width: 0; } } - -.hero-projects { - display: grid; - grid-template-columns: 1fr 1fr; - grid-gap: $s-32; - margin: 0 $s-16 $s-16 $s-20; - - @media (max-width: 1366px) { - grid-template-columns: 1fr; - } - - .tutorial, - .walkthrough { - display: grid; - grid-template-columns: auto 1fr; - position: relative; - border-radius: $br-8; - min-height: $s-216; - background-color: $db-tertiary; - padding: $s-8; - - .thumbnail { - width: $s-200; - height: $s-200; - border-radius: $br-6; - padding: $s-32; - display: block; - background-color: var(--color-canvas); - } - - img { - border-radius: $br-4; - margin-bottom: 0; - width: $s-232; - } - - .text { - padding: $s-32; - display: flex; - flex-direction: column; - } - - .title { - color: $df-primary; - font-size: $fs-24; - font-weight: $fw400; - margin-bottom: $s-8; - } - .info { - flex: 1; - color: $df-secondary; - margin-bottom: $s-20; - font-size: $fs-16; - } - .invite { - height: $s-32; - } - .action { - width: $s-180; - height: $s-40; - } - } - .walkthrough { - .thumbnail { - background-image: url("/images/walkthrough-cover.png"); - background-position: center; - background-repeat: no-repeat; - background-size: cover; - } - } - .tutorial { - .thumbnail { - background-image: url("/images/hands-on-tutorial.png"); - background-position: center; - background-repeat: no-repeat; - background-size: cover; - } - .loader { - display: flex; - svg#loader-pencil { - width: $s-32; - } - } - } -} From de6d8ccbf981539fd61d2f110fafb368ccf0673c Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 30 Apr 2024 19:23:58 +0200 Subject: [PATCH 20/34] :white_check_mark: Small fix on components touched test --- .../test/common_tests/logic/components_touched_test.cljc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc index 45b11b59f9..4d912a6411 100644 --- a/common/test/common_tests/logic/components_touched_test.cljc +++ b/common/test/common_tests/logic/components_touched_test.cljc @@ -61,14 +61,15 @@ :copy-root) (thf/add-sample-shape :free-shape)) - page (thf/current-page file) + page (thf/current-page file) + copy-root (thf/get-shape file :copy-root) ;; Action ;; IMPORTANT: as modifying copies structure is now forbidden, this action ;; will not have any effect, and so the parent shape won't also be touched. changes (cflh/generate-relocate-shapes (pcb/empty-changes) (:objects page) - #{(thi/id :copy-root)} ; parents + #{(:parent-id copy-root)} ; parents (thi/id :copy-root) ; parent-id (:id page) ; page-id 0 ; to-index @@ -134,7 +135,7 @@ ;; will not have any effect, and so the parent shape won't also be touched. changes (cflh/generate-relocate-shapes (pcb/empty-changes) (:objects page) - #{(thi/id :copy-root)} ; parents + #{(:parent-id copy-child1)} ; parents (thi/id :copy-root) ; parent-id (:id page) ; page-id 2 ; to-index From 4a74862bf5cfb18564cfd0f3c11b48c5d774b9f3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 23 Apr 2024 14:52:57 +0200 Subject: [PATCH 21/34] :sparkles: Add viewport information to the plugin --- .../src/app/main/data/workspace/zoom.cljs | 50 ++++++++++-- frontend/src/app/plugins/api.cljs | 40 +++++++--- frontend/src/app/plugins/shape.cljs | 3 +- frontend/src/app/plugins/viewport.cljs | 79 +++++++++++++++++++ 4 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 frontend/src/app/plugins/viewport.cljs diff --git a/frontend/src/app/main/data/workspace/zoom.cljs b/frontend/src/app/main/data/workspace/zoom.cljs index 379776ede7..8672544fd7 100644 --- a/frontend/src/app/main/data/workspace/zoom.cljs +++ b/frontend/src/app/main/data/workspace/zoom.cljs @@ -6,6 +6,8 @@ (ns app.main.data.workspace.zoom (:require + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.geom.align :as gal] [app.common.geom.matrix :as gmt] @@ -54,14 +56,20 @@ #(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01))))))))) (defn set-zoom - [center scale] - (ptk/reify ::set-zoom - ptk/UpdateEvent - (update [_ state] - (update state :workspace-local - #(impl-update-zoom % center (fn [z] (-> (* z scale) - (max 0.01) - (min 200)))))))) + ([scale] + (set-zoom nil scale)) + ([center scale] + (ptk/reify ::set-zoom + ptk/UpdateEvent + (update [_ state] + (let [vp (dm/get-in state [:workspace-local :vbox]) + x (+ (:x vp) (/ (:width vp) 2)) + y (+ (:y vp) (/ (:height vp) 2)) + center (d/nilv center (gpt/point x y))] + (update state :workspace-local + #(impl-update-zoom % center (fn [z] (-> (* z scale) + (max 0.01) + (min 200)))))))))) (def reset-zoom (ptk/reify ::reset-zoom @@ -110,6 +118,32 @@ (assoc :zoom-inverse (/ 1 zoom)) (update :vbox merge srect))))))))))) +(defn fit-to-shapes + [ids] + (ptk/reify ::fit-to-shapes + ptk/UpdateEvent + (update [_ state] + (if (empty? ids) + state + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + _ (prn "??" (->> ids (map #(get objects %)) (map :name))) + srect (->> ids + (map #(get objects %)) + (gsh/shapes->rect))] + + (update state :workspace-local + (fn [{:keys [vport] :as local}] + (let [srect (gal/adjust-to-viewport + vport srect + {:padding 40}) + zoom (/ (:width vport) + (:width srect))] + (-> local + (assoc :zoom zoom) + (assoc :zoom-inverse (/ 1 zoom)) + (update :vbox merge srect)))))))))) + (defn start-zooming [pt] (ptk/reify ::start-zooming ptk/WatchEvent diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 51ed5155c2..683d5677dc 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -17,7 +17,8 @@ [app.plugins.events :as events] [app.plugins.file :as file] [app.plugins.page :as page] - [app.plugins.shape :as shape])) + [app.plugins.shape :as shape] + [app.plugins.viewport :as viewport])) ;; ;; PLUGINS PUBLIC API - The plugins will able to access this functions @@ -28,12 +29,30 @@ (map val) (map shape/data->shape-proxy))) +(defn create-shape + [type] + (let [page-id (:current-page-id @st/state) + page (dm/get-in @st/state [:workspace-data :pages-index page-id]) + shape (cts/setup-shape {:type :type + :x 0 :y 0 :width 100 :height 100}) + changes + (-> (cb/empty-changes) + (cb/with-page page) + (cb/with-objects (:objects page)) + (cb/add-object shape))] + (st/emit! (ch/commit-changes changes)) + (shape/data->shape-proxy shape))) + (deftype PenpotContext [] Object (addListener [_ type callback] (events/add-listener type callback)) + (getViewport + [_] + (viewport/create-proxy)) + (getFile [_] (file/data->file-proxy (:workspace-file @st/state) (:workspace-data @st/state))) @@ -70,19 +89,13 @@ "dark" (get-in @st/state [:profile :theme])))) + (createFrame + [_] + (create-shape :frame)) + (createRectangle [_] - (let [page-id (:current-page-id @st/state) - page (dm/get-in @st/state [:workspace-data :pages-index page-id]) - shape (cts/setup-shape {:type :rect - :x 0 :y 0 :width 100 :height 100}) - changes - (-> (cb/empty-changes) - (cb/with-page page) - (cb/with-objects (:objects page)) - (cb/add-object shape))] - (st/emit! (ch/commit-changes changes)) - (shape/data->shape-proxy shape)))) + (create-shape :rect))) (defn create-context [] @@ -90,4 +103,5 @@ (PenpotContext.) {:name "root" :get #(.getRoot ^js %)} {:name "currentPage" :get #(.getPage ^js %)} - {:name "selection" :get #(.getSelectedShapes ^js %)})) + {:name "selection" :get #(.getSelectedShapes ^js %)} + {:name "viewport" :get #(.getViewport ^js %)})) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 8a7aea8363..1bcbea5dc8 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -69,7 +69,8 @@ (clone [_] (.log js/console (clj->js _data))) (delete [_] (.log js/console (clj->js _data))) - (appendChild [_] (.log js/console (clj->js _data)))) + (appendChild [_ child] (.log js/console (clj->js _data))) + (insertChild [_ index child] (.log js/console (clj->js _data)))) (crc/define-properties! ShapeProxy diff --git a/frontend/src/app/plugins/viewport.cljs b/frontend/src/app/plugins/viewport.cljs new file mode 100644 index 0000000000..c243d327a7 --- /dev/null +++ b/frontend/src/app/plugins/viewport.cljs @@ -0,0 +1,79 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.plugins.viewport + "RPC for plugins runtime." + (:require + [app.common.data.macros :as dm] + [app.common.geom.point :as gpt] + [app.common.record :as crc] + [app.common.record :as crc] + [app.common.uuid :as uuid] + [app.main.data.workspace.viewport :as dwv] + [app.main.data.workspace.zoom :as dwz] + [app.main.store :as st] + [app.plugins.page :as page] + [app.plugins.utils :refer [get-data-fn]] + [app.util.object :as obj])) + +(deftype ViewportProxy [] + Object + (zoomIntoView [_ shapes] + (let [ids + (->> shapes + (map (fn [v] + (if (string? v) + (uuid/uuid v) + (uuid/uuid (obj/get v "x"))))))] + (st/emit! (dwz/fit-to-shapes ids))))) + +(crc/define-properties! + ViewportProxy + {:name js/Symbol.toStringTag + :get (fn [] (str "ViewportProxy"))}) + +(defn create-proxy + [] + (crc/add-properties! + (ViewportProxy.) + {:name "center" + :get + (fn [_] + (let [vp (dm/get-in @st/state [:workspace-local :vbox]) + x (+ (:x vp) (/ (:width vp) 2)) + y (+ (:y vp) (/ (:height vp) 2))] + (.freeze js/Object #js {:x x :y y}))) + + :set + (fn [_ value] + (let [new-x (obj/get value "x") + new-y (obj/get value "y") + vb (dm/get-in @st/state [:workspace-local :vbox]) + old-x (+ (:x vb) (/ (:width vb) 2)) + old-y (+ (:y vb) (/ (:height vb) 2)) + delta-x (- new-x old-x) + delta-y (- new-y old-y) + to-position + {:x #(+ % delta-x) + :y #(+ % delta-y)}] + (st/emit! (dwv/update-viewport-position to-position))))} + + {:name "zoom" + :get + (fn [_] + (dm/get-in @st/state [:workspace-local :zoom])) + :set + (fn [_ value] + (let [z (dm/get-in @st/state [:workspace-local :zoom])] + (st/emit! (dwz/set-zoom (/ value z)))))} + + {:name "bounds" + :get + (fn [_] + (let [vport (dm/get-in @st/state [:workspace-local :vport])] + (.freeze js/Object (clj->js vport))))})) + + From 75d89653653868a046b0f1f9c5a5f88a8183a53a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 24 Apr 2024 10:18:19 +0200 Subject: [PATCH 22/34] :sparkles: Add method to append children --- frontend/src/app/main/data/workspace.cljs | 1 + frontend/src/app/plugins/shape.cljs | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 52a09c8c98..078f6c1a32 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -790,6 +790,7 @@ (defn relocate-shapes [ids parent-id to-index & [ignore-parents?]] (dm/assert! (every? uuid? ids)) + (dm/assert! (set? ids)) (dm/assert! (uuid? parent-id)) (dm/assert! (number? to-index)) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 1bcbea5dc8..33148f7525 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -12,10 +12,12 @@ [app.common.files.helpers :as cfh] [app.common.record :as crc] [app.common.text :as txt] + [app.common.uuid :as uuid] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dwc] [app.main.store :as st] [app.plugins.utils :refer [get-data get-data-fn]] + [app.util.object :as obj] [cuerdas.core :as str])) (declare data->shape-proxy) @@ -69,8 +71,16 @@ (clone [_] (.log js/console (clj->js _data))) (delete [_] (.log js/console (clj->js _data))) - (appendChild [_ child] (.log js/console (clj->js _data))) - (insertChild [_ index child] (.log js/console (clj->js _data)))) + + (appendChild [self child] + (let [parent-id (get-data self :id) + child-id (uuid/uuid (obj/get child "id"))] + (st/emit! (udw/relocate-shapes #{ child-id } parent-id 0)))) + + (insertChild [self index child] + (let [parent-id (get-data self :id) + child-id (uuid/uuid (obj/get child "id"))] + (st/emit! (udw/relocate-shapes #{ child-id } parent-id index))))) (crc/define-properties! ShapeProxy @@ -117,9 +127,6 @@ (let [id (get-data self :id)] (st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))} - {:name "children" - :get #(.getChildren ^js %)} - {:name "fills" :get #(get-state % :fills make-fills) ;;:set (fn [self value] (.log js/console self value)) @@ -130,6 +137,11 @@ ;;:set (fn [self value] (.log js/console self value)) }) + (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) + (crc/add-properties! + {:name "children" + :get #(.getChildren ^js %)})) + (cond-> (cfh/text-shape? data) (crc/add-properties! {:name "characters" From 21d38a058bb6f6e3f23b8a8af4cd32863d90aa7f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 29 Apr 2024 12:20:15 +0200 Subject: [PATCH 23/34] :sparkles: Add to plugin api: upload media, group and ungroup --- .../src/app/main/data/workspace/groups.cljs | 76 ++++++++++++------- .../src/app/main/data/workspace/media.cljs | 12 ++- frontend/src/app/plugins/api.cljs | 38 +++++++++- frontend/src/app/plugins/shape.cljs | 28 ++++--- frontend/src/app/plugins/utils.cljs | 41 +++++++++- frontend/src/app/plugins/viewport.cljs | 4 - 6 files changed, 148 insertions(+), 51 deletions(-) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index 30046ecefc..be758443e8 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -15,6 +15,7 @@ [app.common.types.container :as ctn] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] @@ -68,7 +69,7 @@ result))))))) (defn prepare-create-group - [changes objects page-id shapes base-name keep-name?] + [changes id objects page-id shapes base-name keep-name?] (let [frame-id (:frame-id (first shapes)) parent-id (:parent-id (first shapes)) gname (if (and keep-name? @@ -84,7 +85,8 @@ (cfh/get-position-on-parent objects) inc) - group (cts/setup-shape {:type :group + group (cts/setup-shape {:id id + :type :group :name gname :shapes (mapv :id shapes) :selrect selrect @@ -173,30 +175,43 @@ ;; GROUPS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def group-selected - (ptk/reify ::group-selected +(defn group-shapes + [id ids & {:keys [change-selection?] :or {change-selection? false}}] + (ptk/reify ::group-shapes ptk/WatchEvent (watch [it state _] - (let [page-id (:current-page-id state) + (let [id (d/nilv id (uuid/next)) + page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - selected (->> (wsh/lookup-selected state) - (cfh/clean-loops objects) - (remove #(ctn/has-any-copy-parent? objects (get objects %)))) - shapes (shapes-for-grouping objects selected) + + shapes + (->> ids + (cfh/clean-loops objects) + (remove #(ctn/has-any-copy-parent? objects (get objects %))) + (shapes-for-grouping objects)) parents (into #{} (map :parent-id) shapes)] (when-not (empty? shapes) (let [[group changes] - (prepare-create-group (pcb/empty-changes it) objects page-id shapes "Group" false)] + (prepare-create-group (pcb/empty-changes it) id objects page-id shapes "Group" false)] (rx/of (dch/commit-changes changes) - (dws/select-shapes (d/ordered-set (:id group))) + (when change-selection? + (dws/select-shapes (d/ordered-set (:id group)))) (ptk/data-event :layout/update {:ids parents})))))))) -(def ungroup-selected - (ptk/reify ::ungroup-selected +(def group-selected + (ptk/reify ::group-selected + ptk/WatchEvent + (watch [_ state _] + (let [selected (wsh/lookup-selected state)] + (rx/of group-shapes nil selected))))) + +(defn ungroup-shapes + [ids & {:keys [change-selection?] :or {change-selection? false}}] + (ptk/reify ::ungroup-shapes ptk/WatchEvent (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) prepare (fn [shape-id] @@ -213,35 +228,42 @@ (ctl/grid-layout? objects (:parent-id shape)) (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells {:with-objects? true})))) - selected (->> (wsh/lookup-selected state) - (remove #(ctn/has-any-copy-parent? objects (get objects %))) - ;; components can't be ungrouped - (remove #(ctk/instance-head? (get objects %)))) - changes-list (sequence - (keep prepare) - selected) + ids (->> ids + (remove #(ctn/has-any-copy-parent? objects (get objects %))) + ;; components can't be ungrouped + (remove #(ctk/instance-head? (get objects %)))) + + changes-list (sequence (keep prepare) ids) parents (into #{} (comp (map #(cfh/get-parent objects %)) (keep :id)) - selected) + ids) child-ids (into (d/ordered-set) (mapcat #(dm/get-in objects [% :shapes])) - selected) + ids) changes {:redo-changes (vec (mapcat :redo-changes changes-list)) :undo-changes (vec (mapcat :undo-changes changes-list)) :origin it} undo-id (js/Symbol)] - (when-not (empty? selected) + (when-not (empty? ids) (rx/of (dwu/start-undo-transaction undo-id) (dch/commit-changes changes) (ptk/data-event :layout/update {:ids parents}) (dwu/commit-undo-transaction undo-id) - (dws/select-shapes child-ids))))))) + (when change-selection? + (dws/select-shapes child-ids)))))))) + +(def ungroup-selected + (ptk/reify ::ungroup-selected + ptk/WatchEvent + (watch [_ state _] + (let [selected (wsh/lookup-selected state)] + (rx/of (ungroup-shapes selected :change-selection? true)))))) (def mask-group (ptk/reify ::mask-group @@ -262,7 +284,7 @@ (= (:type (first shapes)) :group)) [first-shape (-> (pcb/empty-changes it page-id) (pcb/with-objects objects))] - (prepare-create-group (pcb/empty-changes it) objects page-id shapes "Mask" true)) + (prepare-create-group (pcb/empty-changes it) (uuid/next) objects page-id shapes "Mask" true)) changes (-> changes (pcb/update-shapes (:shapes group) diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 693207d87e..8156190594 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -87,7 +87,17 @@ (->> (svg/upload-images svg-data file-id) (rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position)))))) -(defn- process-uris + +(defn upload-media-url + [name file-id url] + (rp/cmd! + :create-file-media-object-from-url + {:name name + :file-id file-id + :url url + :is-local true})) + +(defn process-uris [{:keys [file-id local? name uris mtype on-image on-svg]}] (letfn [(svg-url? [url] (or (and mtype (= mtype "image/svg+xml")) diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 683d5677dc..5efaddfc95 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -13,12 +13,18 @@ [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as ch] + [app.main.data.workspace.groups :as dwg] + [app.main.data.workspace.media :as dwm] [app.main.store :as st] [app.plugins.events :as events] [app.plugins.file :as file] [app.plugins.page :as page] [app.plugins.shape :as shape] - [app.plugins.viewport :as viewport])) + [app.plugins.utils :as utils] + [app.plugins.viewport :as viewport] + [app.util.object :as obj] + [beicon.v2.core :as rx] + [promesa.core :as p])) ;; ;; PLUGINS PUBLIC API - The plugins will able to access this functions @@ -33,7 +39,7 @@ [type] (let [page-id (:current-page-id @st/state) page (dm/get-in @st/state [:workspace-data :pages-index page-id]) - shape (cts/setup-shape {:type :type + shape (cts/setup-shape {:type type :x 0 :y 0 :width 100 :height 100}) changes (-> (cb/empty-changes) @@ -89,13 +95,39 @@ "dark" (get-in @st/state [:profile :theme])))) + (uploadMediaUrl + [_ name url] + (let [file-id (get-in @st/state [:workspace-file :id])] + (p/create + (fn [resolve reject] + (->> (dwm/upload-media-url name file-id url) + (rx/map utils/to-js) + (rx/take 1) + (rx/subs! resolve reject)))))) + + (group + [_ shapes] + (let [page-id (:current-page-id @st/state) + id (uuid/next) + ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)] + (st/emit! (dwg/group-shapes id ids)) + (shape/data->shape-proxy + (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id])))) + + (ungroup + [_ group & rest] + (let [shapes (concat [group] rest) + ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)] + (st/emit! (dwg/ungroup-shapes ids)))) + (createFrame [_] (create-shape :frame)) (createRectangle [_] - (create-shape :rect))) + (create-shape :rect)) + ) (defn create-context [] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 33148f7525..56383e38ad 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -16,9 +16,8 @@ [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dwc] [app.main.store :as st] - [app.plugins.utils :refer [get-data get-data-fn]] - [app.util.object :as obj] - [cuerdas.core :as str])) + [app.plugins.utils :as utils :refer [get-data get-data-fn]] + [app.util.object :as obj])) (declare data->shape-proxy) @@ -26,19 +25,13 @@ [fills] (.freeze js/Object - (apply array - (->> fills - ;; TODO: Transform explicitly instead of cljs->js? - (map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))})))))) + (apply array (->> fills (map utils/to-js))))) (defn- make-strokes [strokes] (.freeze js/Object - (apply array - (->> strokes - ;; TODO: Transform explicitly instead of cljs->js? - (map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))})))))) + (apply array (->> strokes (map utils/to-js))))) (defn- locate-shape [shape-id] @@ -64,7 +57,6 @@ (resize [self width height] - (let [id (get-data self :id)] (st/emit! (udw/update-dimensions [id] :width width) (udw/update-dimensions [id] :height height)))) @@ -99,7 +91,7 @@ :get (get-data-fn :id str)} {:name "type" - :get (get-data-fn :type)} + :get (get-data-fn :type name)} {:name "x" :get #(get-state % :x) @@ -129,12 +121,18 @@ {:name "fills" :get #(get-state % :fills make-fills) - ;;:set (fn [self value] (.log js/console self value)) + :set (fn [self value] + (let [id (get-data self :id) + value (mapv #(utils/from-js %) value)] + (st/emit! (dwc/update-shapes [id] #(assoc % :fills value))))) } {:name "strokes" :get #(get-state % :strokes make-strokes) - ;;:set (fn [self value] (.log js/console self value)) + :set (fn [self value] + (let [id (get-data self :id) + value (mapv #(utils/from-js %) value)] + (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value))))) }) (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 1b392d84cb..32add7bfd3 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -7,7 +7,13 @@ (ns app.plugins.utils "RPC for plugins runtime." (:require - [app.util.object :as obj])) + [app.common.data.macros :as dm] + [app.common.uuid :as uuid] + [app.util.object :as obj] + [cuerdas.core :as str])) + +(def uuid-regex + #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}") (defn get-data ([self attr] @@ -27,4 +33,37 @@ (fn [self] (get-data self attr transform-fn)))) +(defn from-js + "Converts the object back to js" + [obj] + (let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})] + (reduce-kv + (fn [m k v] + (let [v (cond (map? v) + (from-js v) + + (and (string? v) (re-matches uuid-regex v)) + (uuid/uuid v) + + :else v)] + (assoc m (keyword (str/kebab k)) v))) + {} + ret))) + + +(defn to-js + "Converts to javascript an camelize the keys" + [obj] + (let [result + (reduce-kv + (fn [m k v] + (let [v (cond (object? v) (to-js v) + (uuid? v) (dm/str v) + :else v)] + (assoc m (str/camel (name k)) v))) + {} + obj)] + (clj->js result))) + + diff --git a/frontend/src/app/plugins/viewport.cljs b/frontend/src/app/plugins/viewport.cljs index c243d327a7..8d647042af 100644 --- a/frontend/src/app/plugins/viewport.cljs +++ b/frontend/src/app/plugins/viewport.cljs @@ -8,15 +8,11 @@ "RPC for plugins runtime." (:require [app.common.data.macros :as dm] - [app.common.geom.point :as gpt] - [app.common.record :as crc] [app.common.record :as crc] [app.common.uuid :as uuid] [app.main.data.workspace.viewport :as dwv] [app.main.data.workspace.zoom :as dwz] [app.main.store :as st] - [app.plugins.page :as page] - [app.plugins.utils :refer [get-data-fn]] [app.util.object :as obj])) (deftype ViewportProxy [] From 67d48435e7f92fc87a2782fe618e54f85b8ef0d4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 29 Apr 2024 15:43:48 +0200 Subject: [PATCH 24/34] :sparkles: Plugins create svg shapes --- common/src/app/common/svg/shapes_builder.cljc | 111 +++++++++--------- .../src/app/main/data/workspace/groups.cljs | 2 +- .../src/app/main/data/workspace/media.cljs | 9 ++ .../app/main/data/workspace/svg_upload.cljs | 89 +++++++------- frontend/src/app/plugins/api.cljs | 10 +- frontend/src/app/plugins/shape.cljs | 10 +- 6 files changed, 128 insertions(+), 103 deletions(-) diff --git a/common/src/app/common/svg/shapes_builder.cljc b/common/src/app/common/svg/shapes_builder.cljc index 619a81b94f..41f25e1e2f 100644 --- a/common/src/app/common/svg/shapes_builder.cljc +++ b/common/src/app/common/svg/shapes_builder.cljc @@ -22,6 +22,7 @@ [app.common.svg :as csvg] [app.common.svg.path :as path] [app.common.types.shape :as cts] + [app.common.uuid :as uuid] [cuerdas.core :as str])) (def default-rect @@ -78,67 +79,68 @@ (declare parse-svg-element) (defn create-svg-shapes - [svg-data {:keys [x y]} objects frame-id parent-id selected center?] - (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + ([svg-data pos objects frame-id parent-id selected center?] + (create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?)) + ([id svg-data {:keys [x y]} objects frame-id parent-id selected center?] + (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + unames (cfh/get-used-names objects) + svg-name (str/replace (:name svg-data) ".svg" "") - unames (cfh/get-used-names objects) - svg-name (str/replace (:name svg-data) ".svg" "") + svg-data (-> svg-data + (assoc :x (mth/round + (if center? + (- x vb-x (/ vb-width 2)) + x))) + (assoc :y (mth/round + (if center? + (- y vb-y (/ vb-height 2)) + y))) + (assoc :offset-x vb-x) + (assoc :offset-y vb-y) + (assoc :width vb-width) + (assoc :height vb-height) + (assoc :name svg-name)) - svg-data (-> svg-data - (assoc :x (mth/round - (if center? - (- x vb-x (/ vb-width 2)) - x))) - (assoc :y (mth/round - (if center? - (- y vb-y (/ vb-height 2)) - y))) - (assoc :offset-x vb-x) - (assoc :offset-y vb-y) - (assoc :width vb-width) - (assoc :height vb-height) - (assoc :name svg-name)) + [def-nodes svg-data] + (-> svg-data + (csvg/fix-default-values) + (csvg/fix-percents) + (csvg/extract-defs)) - [def-nodes svg-data] - (-> svg-data - (csvg/fix-default-values) - (csvg/fix-percents) - (csvg/extract-defs)) + ;; In penpot groups have the size of their children. To + ;; respect the imported svg size and empty space let's create + ;; a transparent shape as background to respect the imported + ;; size + background + {:tag :rect + :attrs {:x (dm/str vb-x) + :y (dm/str vb-y) + :width (dm/str vb-width) + :height (dm/str vb-height) + :fill "none" + :id "base-background"} + :hidden true + :content []} - ;; In penpot groups have the size of their children. To - ;; respect the imported svg size and empty space let's create - ;; a transparent shape as background to respect the imported - ;; size - background - {:tag :rect - :attrs {:x (dm/str vb-x) - :y (dm/str vb-y) - :width (dm/str vb-width) - :height (dm/str vb-height) - :fill "none" - :id "base-background"} - :hidden true - :content []} + svg-data (-> svg-data + (assoc :defs def-nodes) + (assoc :content (into [background] (:content svg-data)))) - svg-data (-> svg-data - (assoc :defs def-nodes) - (assoc :content (into [background] (:content svg-data)))) + root-shape (create-svg-root id frame-id parent-id svg-data) + root-id (:id root-shape) - root-shape (create-svg-root frame-id parent-id svg-data) - root-id (:id root-shape) + ;; Create the root shape + root-attrs (-> (:attrs svg-data) + (csvg/format-styles)) - ;; Create the root shape - root-attrs (-> (:attrs svg-data) - (csvg/format-styles)) + [_ children] + (reduce (partial create-svg-children objects selected frame-id root-id svg-data) + [unames []] + (d/enumerate (->> (:content svg-data) + (mapv #(csvg/inherit-attributes root-attrs %)))))] - [_ children] - (reduce (partial create-svg-children objects selected frame-id root-id svg-data) - [unames []] - (d/enumerate (->> (:content svg-data) - (mapv #(csvg/inherit-attributes root-attrs %)))))] - - [root-shape children])) + [root-shape children]))) (defn create-raw-svg [name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}] @@ -157,12 +159,13 @@ :svg-viewbox vbox}))) (defn create-svg-root - [frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] + [id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] (let [props (-> (dissoc attrs :viewBox :view-box :xmlns) (d/without-keys csvg/inheritable-props) (csvg/attrs->props))] (cts/setup-shape - {:type :group + {:id id + :type :group :name name :frame-id frame-id :parent-id parent-id diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index be758443e8..43bed0489a 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -203,7 +203,7 @@ ptk/WatchEvent (watch [_ state _] (let [selected (wsh/lookup-selected state)] - (rx/of group-shapes nil selected))))) + (rx/of (group-shapes nil selected)))))) (defn ungroup-shapes [ids & {:keys [change-selection?] :or {change-selection? false}}] diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 8156190594..50105e9b00 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -459,3 +459,12 @@ (rx/tap on-success) (rx/catch on-error) (rx/finalize #(st/emit! (msg/hide-tag :media-loading))))))))) + +(defn create-svg-shape + [id name svg-string position] + (ptk/reify ::create-svg-shape + ptk/WatchEvent + (watch [_ _ _] + (->> (svg->clj [name svg-string]) + (rx/take 1) + (rx/map #(svg/add-svg-shapes id % position {:change-selection? false})))))) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index fa159cf04c..2517a73aa0 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -13,6 +13,7 @@ [app.common.svg :as csvg] [app.common.svg.shapes-builder :as csvg.shapes-builder] [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] @@ -60,52 +61,58 @@ (rx/reduce conj {}))) (defn add-svg-shapes - [svg-data position] - (ptk/reify ::add-svg-shapes - ptk/WatchEvent - (watch [it state _] - (try - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - frame-id (ctst/top-nested-frame objects position) - selected (wsh/lookup-selected state) - base (cfh/get-base-shape objects selected) + ([svg-data position] + (add-svg-shapes nil svg-data position nil)) - selected-id (first selected) - selected-frame? (and (= 1 (count selected)) - (= :frame (dm/get-in objects [selected-id :type]))) + ([id svg-data position {:keys [change-selection?] :or {change-selection? false}}] + (ptk/reify ::add-svg-shapes + ptk/WatchEvent + (watch [it state _] + (try + (let [id (d/nilv id (uuid/next)) + page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + frame-id (ctst/top-nested-frame objects position) + selected (wsh/lookup-selected state) + base (cfh/get-base-shape objects selected) - parent-id (if (or selected-frame? (empty? selected)) - frame-id - (:parent-id base)) + selected-id (first selected) + selected-frame? (and (= 1 (count selected)) + (= :frame (dm/get-in objects [selected-id :type]))) - [new-shape new-children] - (csvg.shapes-builder/create-svg-shapes svg-data position objects frame-id parent-id selected true) + parent-id (if (or selected-frame? (empty? selected)) + frame-id + (:parent-id base)) - changes (-> (pcb/empty-changes it page-id) - (pcb/with-objects objects) - (pcb/add-object new-shape)) + [new-shape new-children] + (csvg.shapes-builder/create-svg-shapes id svg-data position objects frame-id parent-id selected true) - changes (reduce (fn [changes new-child] - (pcb/add-object changes new-child)) - changes - new-children) + changes (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects) + (pcb/add-object new-shape)) - changes (pcb/resize-parents changes - (->> (:redo-changes changes) - (filter #(= :add-obj (:type %))) - (map :id) - (reverse) - (vec))) - undo-id (js/Symbol)] + changes (reduce (fn [changes new-child] + (pcb/add-object changes new-child)) + changes + new-children) - (rx/of (dwu/start-undo-transaction undo-id) - (dch/commit-changes changes) - (dws/select-shapes (d/ordered-set (:id new-shape))) - (ptk/data-event :layout/update {:ids [(:id new-shape)]}) - (dwu/commit-undo-transaction undo-id))) + changes (pcb/resize-parents changes + (->> (:redo-changes changes) + (filter #(= :add-obj (:type %))) + (map :id) + (reverse) + (vec))) + undo-id (js/Symbol)] + + (rx/of (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (when change-selection? + (dws/select-shapes (d/ordered-set (:id new-shape)))) + (ptk/data-event :layout/update {:ids [(:id new-shape)]}) + (dwu/commit-undo-transaction undo-id))) + + (catch :default cause + (js/console.log (.-stack cause)) + (rx/throw {:type :svg-parser + :data cause}))))))) - (catch :default cause - (js/console.log (.-stack cause)) - (rx/throw {:type :svg-parser - :data cause})))))) diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 5efaddfc95..5c06da8595 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -9,6 +9,7 @@ (:require [app.common.data.macros :as dm] [app.common.files.changes-builder :as cb] + [app.common.geom.point :as gpt] [app.common.record :as cr] [app.common.types.shape :as cts] [app.common.uuid :as uuid] @@ -127,7 +128,14 @@ (createRectangle [_] (create-shape :rect)) - ) + + (createShapeFromSvg + [_ svg-string] + (let [id (uuid/next) + page-id (:current-page-id @st/state)] + (st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0))) + (shape/data->shape-proxy + (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id]))))) (defn create-context [] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 56383e38ad..27dc930c5f 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -67,12 +67,12 @@ (appendChild [self child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] - (st/emit! (udw/relocate-shapes #{ child-id } parent-id 0)))) + (st/emit! (udw/relocate-shapes #{child-id} parent-id 0)))) (insertChild [self index child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] - (st/emit! (udw/relocate-shapes #{ child-id } parent-id index))))) + (st/emit! (udw/relocate-shapes #{child-id} parent-id index))))) (crc/define-properties! ShapeProxy @@ -124,16 +124,14 @@ :set (fn [self value] (let [id (get-data self :id) value (mapv #(utils/from-js %) value)] - (st/emit! (dwc/update-shapes [id] #(assoc % :fills value))))) - } + (st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))} {:name "strokes" :get #(get-state % :strokes make-strokes) :set (fn [self value] (let [id (get-data self :id) value (mapv #(utils/from-js %) value)] - (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value))))) - }) + (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))}) (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) (crc/add-properties! From e30c21a71f4cb58ba6865e3997aac7ca878cfec5 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 29 Apr 2024 17:07:01 +0200 Subject: [PATCH 25/34] :sparkles: Add relative positioning --- frontend/src/app/plugins/shape.cljs | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 27dc930c5f..e285efc9ae 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -107,6 +107,62 @@ (let [id (get-data self :id)] (st/emit! (udw/update-position id {:y value}))))} + {:name "parentX" + :get (fn [self] + (let [page-id (:current-page-id @st/state) + parent-id (get-state self :parent-id) + parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])] + (- (get-state self :x) parent-x))) + :set + (fn [self value] + (let [page-id (:current-page-id @st/state) + id (get-data self :id) + parent-id (get-state self :parent-id) + parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])] + (st/emit! (udw/update-position id {:x (+ parent-x value)}))))} + + {:name "parentY" + :get (fn [self] + (let [page-id (:current-page-id @st/state) + parent-id (get-state self :parent-id) + parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])] + (- (get-state self :y) parent-y))) + :set + (fn [self value] + (let [page-id (:current-page-id @st/state) + id (get-data self :id) + parent-id (get-state self :parent-id) + parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])] + (st/emit! (udw/update-position id {:y (+ parent-y value)}))))} + + {:name "frameX" + :get (fn [self] + (let [page-id (:current-page-id @st/state) + frame-id (get-state self :frame-id) + frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])] + (- (get-state self :x) frame-x))) + :set + (fn [self value] + (let [page-id (:current-page-id @st/state) + id (get-data self :id) + frame-id (get-state self :frame-id) + frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])] + (st/emit! (udw/update-position id {:x (+ frame-x value)}))))} + + {:name "frameY" + :get (fn [self] + (let [page-id (:current-page-id @st/state) + frame-id (get-state self :frame-id) + frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])] + (- (get-state self :y) frame-y))) + :set + (fn [self value] + (let [page-id (:current-page-id @st/state) + id (get-data self :id) + frame-id (get-state self :frame-id) + frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])] + (st/emit! (udw/update-position id {:y (+ frame-y value)}))))} + {:name "width" :get #(get-state % :width)} From 9243ba937d99be869a8a7a57d45c2d02a5657292 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 30 Apr 2024 11:34:50 +0200 Subject: [PATCH 26/34] :sparkles: Add to plugins clone and remove --- .../app/main/data/workspace/selection.cljs | 112 ++++++++++-------- frontend/src/app/plugins/shape.cljs | 16 ++- frontend/src/app/plugins/utils.cljs | 28 +++-- frontend/src/app/plugins/viewport.cljs | 27 +++-- 4 files changed, 112 insertions(+), 71 deletions(-) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index d139216879..b271f1ae6e 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -723,62 +723,76 @@ (gpt/subtract new-pos pt-obj))))) +(defn duplicate-shapes + [ids & {:keys [move-delta? alt-duplication? change-selection? return-ref] + :or {move-delta? false alt-duplication? false change-selection? true return-ref nil}}] + (ptk/reify ::duplicate-shapes + ptk/WatchEvent + (watch [it state _] + (let [page (wsh/lookup-page state) + objects (:objects page) + ids (into #{} + (comp (map (d/getf objects)) + (filter #(ctk/allow-duplicate? objects %)) + (map :id)) + ids)] + (when (seq ids) + (let [obj (get objects (first ids)) + delta (if move-delta? + (calc-duplicate-delta obj state objects) + (gpt/point 0 0)) + + file-id (:current-file-id state) + libraries (wsh/get-libraries state) + library-data (wsh/get-file state file-id) + + changes (->> (prepare-duplicate-changes objects page ids delta it libraries library-data file-id) + (duplicate-changes-update-indices objects ids)) + + tags (or (:tags changes) #{}) + + changes (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication))) + + id-original (first ids) + + new-ids (->> changes + :redo-changes + (filter #(= (:type %) :add-obj)) + (filter #(ids (:old-id %))) + (map #(get-in % [:obj :id])) + (into (d/ordered-set))) + + id-duplicated (first new-ids) + + frames (into #{} + (map #(get-in objects [% :frame-id])) + ids) + undo-id (js/Symbol)] + + ;; Warning: This order is important for the focus mode. + (->> (rx/of + (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (when change-selection? + (select-shapes new-ids)) + (ptk/data-event :layout/update {:ids frames}) + (memorize-duplicated id-original id-duplicated) + (dwu/commit-undo-transaction undo-id)) + (rx/tap #(when (some? return-ref) + (reset! return-ref id-duplicated)))))))))) + (defn duplicate-selected ([move-delta?] (duplicate-selected move-delta? false)) ([move-delta? alt-duplication?] (ptk/reify ::duplicate-selected ptk/WatchEvent - (watch [it state _] + (watch [_ state _] (when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform]))) - (let [page (wsh/lookup-page state) - objects (:objects page) - selected (->> (wsh/lookup-selected state) - (map (d/getf objects)) - (filter #(ctk/allow-duplicate? objects %)) - (map :id) - set)] - (when (seq selected) - (let [obj (get objects (first selected)) - delta (if move-delta? - (calc-duplicate-delta obj state objects) - (gpt/point 0 0)) - - file-id (:current-file-id state) - libraries (wsh/get-libraries state) - library-data (wsh/get-file state file-id) - - changes (->> (prepare-duplicate-changes objects page selected delta it libraries library-data file-id) - (duplicate-changes-update-indices objects selected)) - - tags (or (:tags changes) #{}) - - changes (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication))) - - id-original (first selected) - - new-selected (->> changes - :redo-changes - (filter #(= (:type %) :add-obj)) - (filter #(selected (:old-id %))) - (map #(get-in % [:obj :id])) - (into (d/ordered-set))) - - id-duplicated (first new-selected) - - frames (into #{} - (map #(get-in objects [% :frame-id])) - selected) - undo-id (js/Symbol)] - - ;; Warning: This order is important for the focus mode. - (rx/of - (dwu/start-undo-transaction undo-id) - (dch/commit-changes changes) - (select-shapes new-selected) - (ptk/data-event :layout/update {:ids frames}) - (memorize-duplicated id-original id-duplicated) - (dwu/commit-undo-transaction undo-id)))))))))) + (let [selected (wsh/lookup-selected state)] + (rx/of (duplicate-shapes selected + :move-delta? move-delta? + :alt-duplication? alt-duplication?)))))))) (defn change-hover-state [id value] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index e285efc9ae..b537109d7b 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -15,6 +15,8 @@ [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.shapes :as dwsh] [app.main.store :as st] [app.plugins.utils :as utils :refer [get-data get-data-fn]] [app.util.object :as obj])) @@ -61,8 +63,18 @@ (st/emit! (udw/update-dimensions [id] :width width) (udw/update-dimensions [id] :height height)))) - (clone [_] (.log js/console (clj->js _data))) - (delete [_] (.log js/console (clj->js _data))) + (clone [self] + (let [id (get-data self :id) + page-id (:current-page-id @st/state) + ret-v (atom nil)] + (st/emit! (dws/duplicate-shapes #{id} :change-selection? false :return-ref ret-v)) + (let [new-id (deref ret-v) + shape (dm/get-in @st/state [:workspace-data :pages-index page-id :objects new-id])] + (data->shape-proxy shape)))) + + (remove [self] + (let [id (get-data self :id)] + (st/emit! (dwsh/delete-shapes #{id})))) (appendChild [self child] (let [parent-id (get-data self :id) diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 32add7bfd3..23d8f276a9 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -8,12 +8,11 @@ "RPC for plugins runtime." (:require [app.common.data.macros :as dm] + [app.common.spec :as us] [app.common.uuid :as uuid] [app.util.object :as obj] - [cuerdas.core :as str])) - -(def uuid-regex - #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}") + [cuerdas.core :as str] + [promesa.core :as p])) (defn get-data ([self attr] @@ -42,7 +41,7 @@ (let [v (cond (map? v) (from-js v) - (and (string? v) (re-matches uuid-regex v)) + (and (string? v) (re-matches us/uuid-rx v)) (uuid/uuid v) :else v)] @@ -50,7 +49,6 @@ {} ret))) - (defn to-js "Converts to javascript an camelize the keys" [obj] @@ -65,5 +63,19 @@ obj)] (clj->js result))) - - +(defn result-p + "Creates a pair of atom+promise. The promise will be resolved when the atom gets a value. + We use this to return the promise to the library clients and resolve its value when a value is passed + to the atom" + [] + (let [ret-v (atom nil) + ret-p + (p/create + (fn [resolve _] + (add-watch + ret-v + ::watcher + (fn [_ _ _ value] + (remove-watch ret-v ::watcher) + (resolve value)))))] + [ret-v ret-p])) diff --git a/frontend/src/app/plugins/viewport.cljs b/frontend/src/app/plugins/viewport.cljs index 8d647042af..656a65674c 100644 --- a/frontend/src/app/plugins/viewport.cljs +++ b/frontend/src/app/plugins/viewport.cljs @@ -9,6 +9,7 @@ (:require [app.common.data.macros :as dm] [app.common.record :as crc] + [app.common.spec :as us] [app.common.uuid :as uuid] [app.main.data.workspace.viewport :as dwv] [app.main.data.workspace.zoom :as dwz] @@ -46,16 +47,17 @@ :set (fn [_ value] (let [new-x (obj/get value "x") - new-y (obj/get value "y") - vb (dm/get-in @st/state [:workspace-local :vbox]) - old-x (+ (:x vb) (/ (:width vb) 2)) - old-y (+ (:y vb) (/ (:height vb) 2)) - delta-x (- new-x old-x) - delta-y (- new-y old-y) - to-position - {:x #(+ % delta-x) - :y #(+ % delta-y)}] - (st/emit! (dwv/update-viewport-position to-position))))} + new-y (obj/get value "y")] + (when (and (us/safe-number? new-x) (us/safe-number? new-y)) + (let [vb (dm/get-in @st/state [:workspace-local :vbox]) + old-x (+ (:x vb) (/ (:width vb) 2)) + old-y (+ (:y vb) (/ (:height vb) 2)) + delta-x (- new-x old-x) + delta-y (- new-y old-y) + to-position + {:x #(+ % delta-x) + :y #(+ % delta-y)}] + (st/emit! (dwv/update-viewport-position to-position))))))} {:name "zoom" :get @@ -63,8 +65,9 @@ (dm/get-in @st/state [:workspace-local :zoom])) :set (fn [_ value] - (let [z (dm/get-in @st/state [:workspace-local :zoom])] - (st/emit! (dwz/set-zoom (/ value z)))))} + (when (us/safe-number? value) + (let [z (dm/get-in @st/state [:workspace-local :zoom])] + (st/emit! (dwz/set-zoom (/ value z))))))} {:name "bounds" :get From fde0bcfd3e9583f74208f7755149a3962cabf8df Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 3 May 2024 13:44:31 +0200 Subject: [PATCH 27/34] :sparkles: Add grid layout options to context --- .../app/main/data/workspace/shape_layout.cljs | 18 +- frontend/src/app/plugins/grid.cljs | 162 ++++++++++++++++++ frontend/src/app/plugins/shape.cljs | 67 +++++--- frontend/src/app/plugins/utils.cljs | 38 ++-- 4 files changed, 245 insertions(+), 40 deletions(-) create mode 100644 frontend/src/app/plugins/grid.cljs diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 37e40cf91a..b3c0d513f0 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -72,7 +72,7 @@ :layout-grid-columns []}) (defn get-layout-initializer - [type from-frame?] + [type from-frame? calculate-params?] (let [[initial-layout-data calculate-params] (case type :flex [initial-flex-layout flex/calculate-params] @@ -87,9 +87,11 @@ (cond-> (not from-frame?) (assoc :show-content true :hide-in-viewer true))) - params (calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape)] + params (when calculate-params? + (calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape))] (cond-> (merge shape params) - (= type :grid) (-> (ctl/assign-cells objects) ctl/reorder-grid-children)))))) + (= type :grid) + (-> (ctl/assign-cells objects) ctl/reorder-grid-children)))))) ;; Never call this directly but through the data-event `:layout/update` ;; Otherwise a lot of cycle dependencies could be generated @@ -124,7 +126,7 @@ (ptk/reify ::finalize)) (defn create-layout-from-id - [id type from-frame?] + [id type & {:keys [from-frame? calculate-params?] :or {from-frame? false calculate-params? true}}] (dm/assert! "expected uuid for `id`" (uuid? id)) @@ -135,7 +137,7 @@ (let [objects (wsh/lookup-page-objects state) parent (get objects id) undo-id (js/Symbol) - layout-initializer (get-layout-initializer type from-frame?)] + layout-initializer (get-layout-initializer type from-frame? calculate-params?)] (rx/of (dwu/start-undo-transaction undo-id) (dch/update-shapes [id] layout-initializer {:with-objects? true}) @@ -177,7 +179,7 @@ (dwse/select-shapes ordered-ids) (dwsh/create-artboard-from-selection new-shape-id parent-id group-index (:name (first selected-shapes))) (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1}) - (create-layout-from-id new-shape-id type false) + (create-layout-from-id new-shape-id type) (dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto)) (dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix)) (dwsh/delete-shapes page-id selected) @@ -188,7 +190,7 @@ (rx/of (dwsh/create-artboard-from-selection new-shape-id) (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1}) - (create-layout-from-id new-shape-id type false) + (create-layout-from-id new-shape-id type) (dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto)) (dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix)))) @@ -227,7 +229,7 @@ (rx/of (dwu/start-undo-transaction undo-id) (if (and single? is-frame?) - (create-layout-from-id (first selected) type true) + (create-layout-from-id (first selected) type :from-frame? true) (create-layout-from-selection type)) (dwu/commit-undo-transaction undo-id)))))) diff --git a/frontend/src/app/plugins/grid.cljs b/frontend/src/app/plugins/grid.cljs new file mode 100644 index 0000000000..7dc46bc9eb --- /dev/null +++ b/frontend/src/app/plugins/grid.cljs @@ -0,0 +1,162 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.plugins.grid + (:require + [app.common.data :as d] + [app.common.record :as crc] + [app.common.spec :as us] + [app.common.types.shape.layout :as ctl] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.store :as st] + [app.plugins.utils :as utils :refer [get-data get-state]])) + +(defn- make-tracks + [tracks] + (.freeze + js/Object + (apply array (->> tracks (map utils/to-js))))) + +(deftype GridLayout [_data] + Object + + (addRow + [self type value] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value})))) + + (addRowAtIndex + [self type value index] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value} index)))) + + (addColumn + [self type value] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value})))) + + (addColumnAtIndex + [self type value index] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value} index)))) + + (removeRow + [self index] + (let [id (get-data self :id)] + (st/emit! (dwsl/remove-layout-track #{id} :row index)))) + + (removeColumn + [self index] + (let [id (get-data self :id)] + (st/emit! (dwsl/remove-layout-track #{id} :column index)))) + + (setColumn + [self index type value] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/change-layout-track #{id} :column index (d/without-nils {:type type :value value}))))) + + (setRow + [self index type value] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/change-layout-track #{id} :row index (d/without-nils {:type type :value value}))))) + + (remove + [self] + (let [id (get-data self :id)] + (st/emit! (dwsl/remove-layout #{id}))))) + +(defn grid-layout-proxy + [data] + (-> (GridLayout. data) + (crc/add-properties! + {:name "dir" + :get #(get-state % :layout-grid-dir d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/grid-direction-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))} + + {:name "rows" + :get #(get-state % :layout-grid-rows make-tracks)} + + {:name "columns" + :get #(get-state % :layout-grid-columns make-tracks)} + + {:name "alignItems" + :get #(get-state % :layout-align-items d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/align-items-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))} + + {:name "alignContent" + :get #(get-state % :layout-align-content d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/align-content-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))} + + {:name "justifyItems" + :get #(get-state % :layout-justify-items d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/justify-items-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))} + + {:name "justifyContent" + :get #(get-state % :layout-justify-content d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/justify-content-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))} + + {:name "rowGap" + :get #(:row-gap (get-state % :layout-gap)) + :set + (fn [self value] + (let [id (get-data self :id)] + (when (us/safe-int? value) + (st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))} + + {:name "columnGap" + :get #(:column-gap (get-state % :layout-gap)) + :set + (fn [self value] + (let [id (get-data self :id)] + (when (us/safe-int? value) + (st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))} + + {:name "verticalPadding" + :get #(:p1 (get-state % :layout-padding)) + :set + (fn [self value] + (let [id (get-data self :id)] + (when (us/safe-int? value) + (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))} + + {:name "horizontalPadding" + :get #(:p2 (get-state % :layout-padding)) + :set + (fn [self value] + (let [id (get-data self :id)] + (when (us/safe-int? value) + (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))}))) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index b537109d7b..e4b466b2ed 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -7,7 +7,6 @@ (ns app.plugins.shape "RPC for plugins runtime." (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.record :as crc] @@ -16,9 +15,11 @@ [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.store :as st] - [app.plugins.utils :as utils :refer [get-data get-data-fn]] + [app.plugins.grid :as grid] + [app.plugins.utils :as utils :refer [get-data get-data-fn get-state]] [app.util.object :as obj])) (declare data->shape-proxy) @@ -40,23 +41,8 @@ (let [page-id (:current-page-id @st/state)] (dm/get-in @st/state [:workspace-data :pages-index page-id :objects shape-id]))) -(defn- get-state - ([self attr] - (let [id (get-data self :id) - page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))] - (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr]))) - ([self attr mapfn] - (-> (get-state self attr) - (mapfn)))) - -(deftype ShapeProxy [^:mutable #_:clj-kondo/ignore _data] +(deftype ShapeProxy [#_:clj-kondo/ignore _data] Object - (getChildren - [self] - (apply array (->> (get-state self :shapes) - (map locate-shape) - (map data->shape-proxy)))) - (resize [self width height] (let [id (get-data self :id)] @@ -76,6 +62,13 @@ (let [id (get-data self :id)] (st/emit! (dwsh/delete-shapes #{id})))) + ;; Only for frames + groups + booleans + (getChildren + [self] + (apply array (->> (get-state self :shapes) + (map locate-shape) + (map data->shape-proxy)))) + (appendChild [self child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] @@ -84,7 +77,16 @@ (insertChild [self index child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] - (st/emit! (udw/relocate-shapes #{child-id} parent-id index))))) + (st/emit! (udw/relocate-shapes #{child-id} parent-id index)))) + + ;; Only for frames + (addFlexLayout [self] + (let [id (get-data self :id)] + (st/emit! (dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false)))) + + (addGridLayout [self] + (let [id (get-data self :id)] + (st/emit! (dwsl/create-layout-from-id id :grid :from-frame? true :calculate-params? false))))) (crc/define-properties! ShapeProxy @@ -206,6 +208,32 @@ {:name "children" :get #(.getChildren ^js %)})) + (cond-> (not (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))) + (-> (obj/unset! "appendChild") + (obj/unset! "insertChild") + (obj/unset! "getChildren"))) + + (cond-> (cfh/frame-shape? data) + (-> (crc/add-properties! + {:name "grid" + :get + (fn [self] + (let [layout (get-state self :layout)] + (when (= :grid layout) + (grid/grid-layout-proxy data))))}) + + #_(crc/add-properties! + {:name "flex" + :get + (fn [self] + (let [layout (get-state self :layout)] + (when (= :flex layout) + (flex-layout-proxy data))))}))) + + (cond-> (not (cfh/frame-shape? data)) + (-> (obj/unset! "addGridLayout") + (obj/unset! "addFlexLayout"))) + (cond-> (cfh/text-shape? data) (crc/add-properties! {:name "characters" @@ -213,4 +241,3 @@ :set (fn [self value] (let [id (get-data self :id)] (st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))})))) - diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 23d8f276a9..35022c36e1 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -7,9 +7,11 @@ (ns app.plugins.utils "RPC for plugins runtime." (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.spec :as us] [app.common.uuid :as uuid] + [app.main.store :as st] [app.util.object :as obj] [cuerdas.core :as str] [promesa.core :as p])) @@ -32,22 +34,34 @@ (fn [self] (get-data self attr transform-fn)))) +(defn get-state + ([self attr] + (let [id (get-data self :id) + page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))] + (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr]))) + ([self attr mapfn] + (-> (get-state self attr) + (mapfn)))) + (defn from-js "Converts the object back to js" - [obj] - (let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})] - (reduce-kv - (fn [m k v] - (let [v (cond (map? v) - (from-js v) + ([obj] + (from-js obj identity)) + ([obj vfn] + (let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})] + (reduce-kv + (fn [m k v] + (let [k (keyword (str/kebab k)) + v (cond (map? v) + (from-js 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) - :else v)] - (assoc m (keyword (str/kebab k)) v))) - {} - ret))) + :else (vfn k v))] + (assoc m k v))) + {} + ret)))) (defn to-js "Converts to javascript an camelize the keys" From ca7f17efd1d2b9eaf98af68788875d6cf3532f99 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 6 May 2024 12:01:39 +0200 Subject: [PATCH 28/34] :sparkles: Add items to grid cells --- common/src/app/common/types/shape/layout.cljc | 36 +++++++++++++------ .../app/main/data/workspace/svg_upload.cljs | 1 - .../app/main/data/workspace/transforms.cljs | 23 ++++++++---- .../src/app/main/data/workspace/zoom.cljs | 1 - frontend/src/app/plugins/grid.cljs | 15 ++++++-- 5 files changed, 55 insertions(+), 21 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index d6c1178bd6..7f5e6e83a2 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -1281,6 +1281,21 @@ (let [cells+index (d/enumerate cells)] (d/seek #(in-cell? (second %) row column) cells+index))) +(defn free-cell-shapes + "Removes the shape-ids from the cells previously assigned." + [parent shape-ids] + (let [shape-ids (set shape-ids)] + (letfn [(free-cells + [cells] + (reduce-kv + (fn [m k v] + (if (some shape-ids (:shapes v)) + (assoc-in m [k :shapes] []) + m)) + cells + cells))] + (update parent :layout-grid-cells free-cells)))) + (defn push-into-cell "Push the shapes into the row/column cell and moves the rest" [parent shape-ids row column] @@ -1295,16 +1310,17 @@ ;; Move shift the `shapes` attribute between cells (->> (range start-index (inc to-index)) (map vector shape-ids) - (reduce (fn [[parent cells] [shape-id idx]] - ;; If the shape to put in a cell is the same that is already in the cell we do nothing - (if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0])) - [parent cells] - (let [[parent cells] (free-cell-push parent cells idx)] - [(update-in parent [:layout-grid-cells (get-in cells [idx :id])] - assoc :position :manual - :shapes [shape-id]) - cells]))) - [parent cells]) + (reduce + (fn [[parent cells] [shape-id idx]] + ;; If the shape to put in a cell is the same that is already in the cell we do nothing + (if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0])) + [parent cells] + (let [[parent cells] (free-cell-push parent cells idx)] + [(update-in parent [:layout-grid-cells (get-in cells [idx :id])] + assoc :position :manual + :shapes [shape-id]) + cells]))) + [parent cells]) (first))) parent))) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 2517a73aa0..d81d8ecb3f 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -112,7 +112,6 @@ (dwu/commit-undo-transaction undo-id))) (catch :default cause - (js/console.log (.-stack cause)) (rx/throw {:type :svg-parser :data cause}))))))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 05289294dc..4fc56d2cec 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -831,7 +831,7 @@ :ignore-constraints false :ignore-snap-pixel true})))))) -(defn- move-shapes-to-frame +(defn move-shapes-to-frame [ids frame-id drop-index [row column :as cell]] (ptk/reify ::move-shapes-to-frame ptk/WatchEvent @@ -923,24 +923,32 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) + ;; Remove layout-item properties when moving a shape outside a layout (cond-> (not (ctl/any-layout? objects frame-id)) (pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data)) + ;; Remove the swap slots if it is moving to a different component - (pcb/update-shapes child-heads - (fn [shape] - (cond-> shape - (not= component-main-frame (ctn/find-component-main objects shape false)) - (ctk/remove-swap-slot)))) + (pcb/update-shapes + child-heads + (fn [shape] + (cond-> shape + (not= component-main-frame (ctn/find-component-main objects shape false)) + (ctk/remove-swap-slot)))) + ;; Remove component-root property when moving a shape inside a component (cond-> (ctn/get-instance-root objects frame) (pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root))) + ;; Add component-root property when moving a component outside a component (cond-> (not (ctn/get-instance-root objects frame)) (pcb/update-shapes child-heads #(assoc % :component-root true))) + (pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))) (pcb/update-shapes shape-ids-to-detach ctk/detach-shape) (pcb/change-parent frame-id moving-shapes drop-index) + + ;; Change the grid cell in a grid layout (cond-> (ctl/grid-layout? objects frame-id) (-> (pcb/update-shapes [frame-id] @@ -948,7 +956,8 @@ (-> frame ;; Assign the cell when pushing into a specific grid cell (cond-> (some? cell) - (-> (ctl/push-into-cell moving-shapes-ids row column) + (-> (ctl/free-cell-shapes moving-shapes-ids) + (ctl/push-into-cell moving-shapes-ids row column) (ctl/assign-cells objects))) (ctl/assign-cell-positions objects))) {:with-objects? true}) diff --git a/frontend/src/app/main/data/workspace/zoom.cljs b/frontend/src/app/main/data/workspace/zoom.cljs index 8672544fd7..6499f93a2f 100644 --- a/frontend/src/app/main/data/workspace/zoom.cljs +++ b/frontend/src/app/main/data/workspace/zoom.cljs @@ -127,7 +127,6 @@ state (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - _ (prn "??" (->> ids (map #(get objects %)) (map :name))) srect (->> ids (map #(get objects %)) (gsh/shapes->rect))] diff --git a/frontend/src/app/plugins/grid.cljs b/frontend/src/app/plugins/grid.cljs index 7dc46bc9eb..07cb39c9a1 100644 --- a/frontend/src/app/plugins/grid.cljs +++ b/frontend/src/app/plugins/grid.cljs @@ -10,9 +10,13 @@ [app.common.record :as crc] [app.common.spec :as us] [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.transforms :as dwt] [app.main.store :as st] - [app.plugins.utils :as utils :refer [get-data get-state]])) + [app.plugins.utils :as utils :refer [get-data get-state]] + [app.util.object :as obj] + [potok.v2.core :as ptk])) (defn- make-tracks [tracks] @@ -72,7 +76,14 @@ (remove [self] (let [id (get-data self :id)] - (st/emit! (dwsl/remove-layout #{id}))))) + (st/emit! (dwsl/remove-layout #{id})))) + + (appendChild + [self child row column] + (let [parent-id (get-data self :id) + child-id (uuid/uuid (obj/get child "id"))] + (st/emit! (dwt/move-shapes-to-frame #{child-id} parent-id nil [row column]) + (ptk/data-event :layout/update {:ids [parent-id]}))))) (defn grid-layout-proxy [data] From 7280dfd3f7353fe136c8c1ba39fbe02b2a1a2675 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Mon, 6 May 2024 15:37:21 +0200 Subject: [PATCH 29/34] :sparkles: Tests for remove swap slot on move shapes to frame --- .../app/common/files/libraries_helpers.cljc | 128 +++++++++++++++ .../logic/comp_remove_swap_slots_test.cljc | 146 +++++++++++++++++- .../app/main/data/workspace/transforms.cljs | 133 +--------------- 3 files changed, 278 insertions(+), 129 deletions(-) diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index 9ac32219fc..6df368eb6e 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -2301,3 +2301,131 @@ ;; Resize parent containers that need to (pcb/resize-parents parents)))) + + +(defn generate-move-shapes-to-frame + [changes ids frame-id page-id objects drop-index [row column :as cell]] + (let [lookup (d/getf objects) + frame (get objects frame-id) + layout? (:layout frame) + + component-main-frame (ctn/find-component-main objects frame false) + + shapes (->> ids + (cfh/clean-loops objects) + (keep lookup) + ;;remove shapes inside copies, because we can't change the structure of copies + (remove #(ctk/in-component-copy? (get objects (:parent-id %))))) + + moving-shapes + (cond->> shapes + (not layout?) + (remove #(= (:frame-id %) frame-id)) + + layout? + (remove #(and (= (:frame-id %) frame-id) + (not= (:parent-id %) frame-id)))) + + ordered-indexes (cfh/order-by-indexed-shapes objects (map :id moving-shapes)) + moving-shapes (map (d/getf objects) ordered-indexes) + + all-parents + (reduce (fn [res id] + (into res (cfh/get-parent-ids objects id))) + (d/ordered-set) + ids) + + find-all-empty-parents + (fn recursive-find-empty-parents [empty-parents] + (let [all-ids (into empty-parents ids) + contains? (partial contains? all-ids) + xform (comp (map lookup) + (filter cfh/group-shape?) + (remove #(->> (:shapes %) (remove contains?) seq)) + (map :id)) + parents (into #{} xform all-parents)] + (if (= empty-parents parents) + empty-parents + (recursive-find-empty-parents parents)))) + + empty-parents + ;; Any empty parent whose children are moved to another frame should be deleted + (if (empty? moving-shapes) + #{} + (into (d/ordered-set) (find-all-empty-parents #{}))) + + ;; Not move absolute shapes that won't change parent + moving-shapes + (->> moving-shapes + (remove (fn [shape] + (and (ctl/position-absolute? shape) + (= frame-id (:parent-id shape)))))) + + frame-component + (ctn/get-component-shape objects frame) + + shape-ids-to-detach + (reduce (fn [result shape] + (if (and (some? shape) (ctk/in-component-copy-not-head? shape)) + (let [shape-component (ctn/get-component-shape objects shape)] + (if (= (:id frame-component) (:id shape-component)) + result + (into result (cfh/get-children-ids-with-self objects (:id shape))))) + result)) + #{} + moving-shapes) + + moving-shapes-ids + (map :id moving-shapes) + + moving-shapes-children-ids + (->> moving-shapes-ids + (mapcat #(cfh/get-children-ids-with-self objects %))) + + child-heads + (->> moving-shapes-ids + (mapcat #(ctn/get-child-heads objects %)) + (map :id))] + (-> changes + (pcb/with-page-id page-id) + (pcb/with-objects objects) + + ;; Remove layout-item properties when moving a shape outside a layout + (cond-> (not (ctl/any-layout? objects frame-id)) + (pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data)) + + ;; Remove the swap slots if it is moving to a different component + (pcb/update-shapes + child-heads + (fn [shape] + (cond-> shape + (not= component-main-frame (ctn/find-component-main objects shape false)) + (ctk/remove-swap-slot)))) + + ;; Remove component-root property when moving a shape inside a component + (cond-> (ctn/get-instance-root objects frame) + (pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root))) + + ;; Add component-root property when moving a component outside a component + (cond-> (not (ctn/get-instance-root objects frame)) + (pcb/update-shapes child-heads #(assoc % :component-root true))) + + (pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))) + (pcb/update-shapes shape-ids-to-detach ctk/detach-shape) + (pcb/change-parent frame-id moving-shapes drop-index) + + ;; Change the grid cell in a grid layout + (cond-> (ctl/grid-layout? objects frame-id) + (-> (pcb/update-shapes + [frame-id] + (fn [frame objects] + (-> frame + ;; Assign the cell when pushing into a specific grid cell + (cond-> (some? cell) + (-> (ctl/free-cell-shapes moving-shapes-ids) + (ctl/push-into-cell moving-shapes-ids row column) + (ctl/assign-cells objects))) + (ctl/assign-cell-positions objects))) + {:with-objects? true}) + (pcb/reorder-grid-children [frame-id]))) + (pcb/remove-objects empty-parents)))) diff --git a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc index 5773b1df05..ede14008da 100644 --- a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc +++ b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc @@ -60,7 +60,7 @@ changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) (:objects page) #{(:parent-id blue1)} ;; parents - uuid/zero ;; paremt-id + uuid/zero ;; parent-id (:id page) ;; page-id 0 ;; to-index #{(:id blue1)}) ;; ids @@ -77,6 +77,34 @@ (t/is (some? blue1')) (t/is (nil? (ctk/get-swap-slot blue1'))))) +(t/deftest test-keep-swap-slot-move-blue1-to-root + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + + ;; ============================== Action ============================== + changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id blue1)} ;; ids + uuid/zero ;; frame-id + (:id page) ;; page-id + (:objects page) ;; objects + 0 ;; drop-index + nil) ;; cell + + file' (thf/apply-changes file changes) + + ;; ============================== Get ================================= + blue1' (thf/get-shape file' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1')) + (t/is (nil? (ctk/get-swap-slot blue1'))))) + (t/deftest test-keep-swap-slot-relocating-blue1-to-b2 (let [;; ============================== Setup =============================== @@ -107,6 +135,36 @@ (t/is (some? blue1')) (t/is (nil? (ctk/get-swap-slot blue1'))))) +(t/deftest test-keep-swap-slot-move-blue1-to-b2 + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + b2 (thf/get-shape file :frame-b2) + + + ;; ============================== Action ============================== + changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id blue1)} ;; ids + (:id b2) ;; frame-id + (:id page) ;; page-id + (:objects page) ;; objects + 0 ;; drop-index + nil) ;; cell + + file' (thf/apply-changes file changes) + + ;; ============================== Get ================================= + blue1' (thf/get-shape file' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1')) + (t/is (nil? (ctk/get-swap-slot blue1'))))) + (t/deftest test-keep-swap-slot-relocating-yellow-to-root (let [;; ============================== Setup =============================== file (setup-file) @@ -149,6 +207,48 @@ (t/is (some? blue1'')) (t/is (nil? (ctk/get-swap-slot blue1''))))) +(t/deftest test-keep-swap-slot-move-yellow-to-root + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + yellow (thf/get-shape file :frame-yellow) + + ;; ============================== Action ============================== + ;; Move blue1 into yellow + changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id blue1)} ;; ids + (:id yellow) ;; frame-id + (:id page) ;; page-id + (:objects page) ;; objects + 0 ;; drop-index + nil) ;; cell + + file' (thf/apply-changes file changes) + page' (thf/current-page file') + yellow' (thf/get-shape file' :frame-yellow) + + ;; Move yellow into root + changes' (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id yellow')} ;; ids + uuid/zero ;; frame-id + (:id page') ;; page-id + (:objects page') ;; objects + 0 ;; drop-index + nil) ;; cell + file'' (thf/apply-changes file' changes') + + ;; ============================== Get ================================= + blue1'' (thf/get-shape file'' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1'')) + (t/is (nil? (ctk/get-swap-slot blue1''))))) + (t/deftest test-keep-swap-slot-relocating-yellow-to-b2 (let [;; ============================== Setup =============================== @@ -192,3 +292,47 @@ ;; blue1 has not swap-id after move (t/is (some? blue1'')) (t/is (nil? (ctk/get-swap-slot blue1''))))) + +(t/deftest test-keep-swap-slot-move-yellow-to-b2 + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + yellow (thf/get-shape file :frame-yellow) + + ;; ============================== Action ============================== + ;; Move blue1 into yellow + changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id blue1)} ;; ids + (:id yellow) ;; frame-id + (:id page) ;; page-id + (:objects page) ;; objects + 0 ;; drop-index + nil) ;; cell + + file' (thf/apply-changes file changes) + page' (thf/current-page file') + yellow' (thf/get-shape file' :frame-yellow) + b2' (thf/get-shape file' :frame-b2) + + ;; Move yellow into b2 + changes' (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id yellow')} ;; ids + (:id b2') ;; frame-id + (:id page') ;; page-id + (:objects page') ;; objects + 0 ;; drop-index + nil) ;; cell + + file'' (thf/apply-changes file' changes') + + ;; ============================== Get ================================= + blue1'' (thf/get-shape file'' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1'')) + (t/is (nil? (ctk/get-swap-slot blue1''))))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 4fc56d2cec..310a79c58f 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -11,6 +11,7 @@ [app.common.data.macros :as dm] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] + [app.common.files.libraries-helpers :as cflh] [app.common.geom.matrix :as gmt] [app.common.geom.modifiers :as gm] [app.common.geom.point :as gpt] @@ -832,137 +833,13 @@ :ignore-snap-pixel true})))))) (defn move-shapes-to-frame - [ids frame-id drop-index [row column :as cell]] + [ids frame-id drop-index cell] (ptk/reify ::move-shapes-to-frame ptk/WatchEvent (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - lookup (d/getf objects) - frame (get objects frame-id) - layout? (:layout frame) - - component-main-frame (ctn/find-component-main objects frame false) - - shapes (->> ids - (cfh/clean-loops objects) - (keep lookup) - ;;remove shapes inside copies, because we can't change the structure of copies - (remove #(ctk/in-component-copy? (get objects (:parent-id %))))) - - moving-shapes - (cond->> shapes - (not layout?) - (remove #(= (:frame-id %) frame-id)) - - layout? - (remove #(and (= (:frame-id %) frame-id) - (not= (:parent-id %) frame-id)))) - - ordered-indexes (cfh/order-by-indexed-shapes objects (map :id moving-shapes)) - moving-shapes (map (d/getf objects) ordered-indexes) - - all-parents - (reduce (fn [res id] - (into res (cfh/get-parent-ids objects id))) - (d/ordered-set) - ids) - - find-all-empty-parents - (fn recursive-find-empty-parents [empty-parents] - (let [all-ids (into empty-parents ids) - contains? (partial contains? all-ids) - xform (comp (map lookup) - (filter cfh/group-shape?) - (remove #(->> (:shapes %) (remove contains?) seq)) - (map :id)) - parents (into #{} xform all-parents)] - (if (= empty-parents parents) - empty-parents - (recursive-find-empty-parents parents)))) - - empty-parents - ;; Any empty parent whose children are moved to another frame should be deleted - (if (empty? moving-shapes) - #{} - (into (d/ordered-set) (find-all-empty-parents #{}))) - - ;; Not move absolute shapes that won't change parent - moving-shapes - (->> moving-shapes - (remove (fn [shape] - (and (ctl/position-absolute? shape) - (= frame-id (:parent-id shape)))))) - - frame-component - (ctn/get-component-shape objects frame) - - shape-ids-to-detach - (reduce (fn [result shape] - (if (and (some? shape) (ctk/in-component-copy-not-head? shape)) - (let [shape-component (ctn/get-component-shape objects shape)] - (if (= (:id frame-component) (:id shape-component)) - result - (into result (cfh/get-children-ids-with-self objects (:id shape))))) - result)) - #{} - moving-shapes) - - moving-shapes-ids - (map :id moving-shapes) - - moving-shapes-children-ids - (->> moving-shapes-ids - (mapcat #(cfh/get-children-ids-with-self objects %))) - - child-heads - (->> moving-shapes-ids - (mapcat #(ctn/get-child-heads objects %)) - (map :id)) - - changes - (-> (pcb/empty-changes it page-id) - (pcb/with-objects objects) - - ;; Remove layout-item properties when moving a shape outside a layout - (cond-> (not (ctl/any-layout? objects frame-id)) - (pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data)) - - ;; Remove the swap slots if it is moving to a different component - (pcb/update-shapes - child-heads - (fn [shape] - (cond-> shape - (not= component-main-frame (ctn/find-component-main objects shape false)) - (ctk/remove-swap-slot)))) - - ;; Remove component-root property when moving a shape inside a component - (cond-> (ctn/get-instance-root objects frame) - (pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root))) - - ;; Add component-root property when moving a component outside a component - (cond-> (not (ctn/get-instance-root objects frame)) - (pcb/update-shapes child-heads #(assoc % :component-root true))) - - (pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))) - (pcb/update-shapes shape-ids-to-detach ctk/detach-shape) - (pcb/change-parent frame-id moving-shapes drop-index) - - ;; Change the grid cell in a grid layout - (cond-> (ctl/grid-layout? objects frame-id) - (-> (pcb/update-shapes - [frame-id] - (fn [frame objects] - (-> frame - ;; Assign the cell when pushing into a specific grid cell - (cond-> (some? cell) - (-> (ctl/free-cell-shapes moving-shapes-ids) - (ctl/push-into-cell moving-shapes-ids row column) - (ctl/assign-cells objects))) - (ctl/assign-cell-positions objects))) - {:with-objects? true}) - (pcb/reorder-grid-children [frame-id]))) - (pcb/remove-objects empty-parents))] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes it) ids frame-id page-id objects drop-index cell)] (when (and (some? frame-id) (d/not-empty? changes)) (rx/of (dch/commit-changes changes) From 0b4fbc184deb1ba7afc5ddb689acbb08c6a87202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 6 May 2024 16:45:43 +0200 Subject: [PATCH 30/34] :recycle: Move generate-xx methods to logic module --- backend/src/app/features/components_v2.clj | 18 +- .../libraries.cljc} | 514 +---------------- common/src/app/common/logic/shapes.cljc | 519 ++++++++++++++++++ common/test/common_tests/helpers/files.cljc | 4 +- .../logic/comp_remove_swap_slots_test.cljc | 170 +++--- .../logic/component_creation_test.cljc | 18 +- .../logic/components_touched_test.cljc | 53 +- .../logic/swap_and_reset_test.cljc | 76 ++- frontend/src/app/main/data/workspace.cljs | 16 +- .../src/app/main/data/workspace/changes.cljs | 16 +- .../app/main/data/workspace/libraries.cljs | 47 +- .../app/main/data/workspace/selection.cljs | 28 +- .../src/app/main/data/workspace/shapes.cljs | 10 +- .../app/main/data/workspace/transforms.cljs | 4 +- .../test/frontend_tests/helpers/pages.cljs | 32 +- 15 files changed, 769 insertions(+), 756 deletions(-) rename common/src/app/common/{files/libraries_helpers.cljc => logic/libraries.cljc} (79%) create mode 100644 common/src/app/common/logic/shapes.cljc diff --git a/backend/src/app/features/components_v2.clj b/backend/src/app/features/components_v2.clj index 452b83bc59..47dc3fad00 100644 --- a/backend/src/app/features/components_v2.clj +++ b/backend/src/app/features/components_v2.clj @@ -12,7 +12,6 @@ [app.common.files.changes :as cp] [app.common.files.changes-builder :as fcb] [app.common.files.helpers :as cfh] - [app.common.files.libraries-helpers :as cflh] [app.common.files.migrations :as fmg] [app.common.files.shapes-helpers :as cfsh] [app.common.files.validate :as cfv] @@ -23,6 +22,7 @@ [app.common.geom.shapes :as gsh] [app.common.geom.shapes.path :as gshp] [app.common.logging :as l] + [app.common.logic.libraries :as cll] [app.common.math :as mth] [app.common.schema :as sm] [app.common.svg :as csvg] @@ -1451,14 +1451,14 @@ (cons shape children)) [_ _ changes] - (cflh/generate-add-component changes - [shape] - (:objects page) - (:id page) - file-id - true - nil - cfsh/prepare-create-artboard-from-selection)] + (cll/generate-add-component changes + [shape] + (:objects page) + (:id page) + file-id + true + nil + cfsh/prepare-create-artboard-from-selection)] (shape-cb shape) (:redo-changes changes))) diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/logic/libraries.cljc similarity index 79% rename from common/src/app/common/files/libraries_helpers.cljc rename to common/src/app/common/logic/libraries.cljc index 6df368eb6e..458bcb2ec7 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.common.files.libraries-helpers +(ns app.common.logic.libraries (:require [app.common.data :as d] [app.common.data.macros :as dm] @@ -14,6 +14,7 @@ [app.common.geom.shapes :as gsh] [app.common.geom.shapes.grid-layout :as gslg] [app.common.logging :as log] + [app.common.logic.shapes :as cls] [app.common.spec :as us] [app.common.text :as txt] [app.common.types.color :as ctc] @@ -21,10 +22,8 @@ [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] - [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] [app.common.types.shape-tree :as ctst] - [app.common.types.shape.interactions :as ctsi] [app.common.types.shape.layout :as ctl] [app.common.types.typography :as cty] [app.common.uuid :as uuid] @@ -34,23 +33,6 @@ ;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default (log/set-level! :warn) -(defn generate-update-shapes - [changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}] - (let [changes (reduce - (fn [changes id] - (let [opts {:attrs attrs - :ignore-geometry? (get ignore-tree id) - :ignore-touched ignore-touched - :with-objects? with-objects?}] - (pcb/update-shapes changes [id] update-fn (d/without-nils opts)))) - (-> changes - (pcb/with-objects objects)) - ids) - grid-ids (->> ids (filter (partial ctl/grid-layout? objects))) - changes (pcb/update-shapes changes grid-ids ctl/assign-cell-positions {:with-objects? true}) - changes (pcb/reorder-grid-children changes ids)] - changes)) - (declare generate-sync-container) (declare generate-sync-shape) (declare generate-sync-text-shape) @@ -58,7 +40,6 @@ (declare generate-sync-shape-direct) (declare generate-sync-shape-direct-recursive) -(declare generate-sync-shape-inverse) (declare generate-sync-shape-inverse-recursive) (declare compare-children) @@ -864,7 +845,6 @@ reset? components-v2)))) - (defn generate-rename-component "Generate the changes for rename the component with the given id, in the current file library." [changes id new-name library-data components-v2] @@ -884,9 +864,6 @@ (pcb/with-library-data library-data) (pcb/update-component id update-fn)))) - - - (defn generate-sync-shape-inverse "Generate changes to update the component a shape is linked to, from the values in the shape and all its children." @@ -1838,175 +1815,6 @@ (pcb/with-objects (:objects container)) (generate-detach-instance container libraries id)))) -(defn generate-update-shape-flags - [changes ids objects {:keys [blocked hidden] :as flags}] - (let [update-fn - (fn [obj] - (cond-> obj - (boolean? blocked) (assoc :blocked blocked) - (boolean? hidden) (assoc :hidden hidden))) - - ids (if (boolean? blocked) - (into ids (->> ids (mapcat #(cfh/get-children-ids objects %)))) - ids)] - (-> changes - (pcb/update-shapes ids update-fn {:attrs #{:blocked :hidden}})))) - -(defn generate-delete-shapes - [changes file page objects ids {:keys [components-v2 ignore-touched component-swap]}] - (let [ids (cfh/clean-loops objects ids) - - in-component-copy? - (fn [shape-id] - ;; Look for shapes that are inside a component copy, but are - ;; not the root. In this case, they must not be deleted, - ;; but hidden (to be able to recover them more easily). - ;; Unless we are doing a component swap, in which case we want - ;; to delete the old shape - (let [shape (get objects shape-id)] - (and (ctn/has-any-copy-parent? objects shape) - (not component-swap)))) - - [ids-to-delete ids-to-hide] - (if components-v2 - (loop [ids-seq (seq ids) - ids-to-delete [] - ids-to-hide []] - (let [id (first ids-seq)] - (if (nil? id) - [ids-to-delete ids-to-hide] - (if (in-component-copy? id) - (recur (rest ids-seq) - ids-to-delete - (conj ids-to-hide id)) - (recur (rest ids-seq) - (conj ids-to-delete id) - ids-to-hide))))) - [ids []]) - - changes (-> changes - (pcb/with-page page) - (pcb/with-objects objects) - (pcb/with-library-data file)) - lookup (d/getf objects) - groups-to-unmask - (reduce (fn [group-ids id] - ;; When the shape to delete is the mask of a masked group, - ;; the mask condition must be removed, and it must be - ;; converted to a normal group. - (let [obj (lookup id) - parent (lookup (:parent-id obj))] - (if (and (:masked-group parent) - (= id (first (:shapes parent)))) - (conj group-ids (:id parent)) - group-ids))) - #{} - ids-to-delete) - - interacting-shapes - (filter (fn [shape] - ;; If any of the deleted shapes is the destination of - ;; some interaction, this must be deleted, too. - (let [interactions (:interactions shape)] - (some #(and (ctsi/has-destination %) - (contains? ids-to-delete (:destination %))) - interactions))) - (vals objects)) - - ids-set (set ids-to-delete) - guides-to-remove - (->> (dm/get-in page [:options :guides]) - (vals) - (filter #(contains? ids-set (:frame-id %))) - (map :id)) - - guides - (->> guides-to-remove - (reduce dissoc (dm/get-in page [:options :guides]))) - - starting-flows - (filter (fn [flow] - ;; If any of the deleted is a frame that starts a flow, - ;; this must be deleted, too. - (contains? ids-to-delete (:starting-frame flow))) - (-> page :options :flows)) - - all-parents - (reduce (fn [res id] - ;; All parents of any deleted shape must be resized. - (into res (cfh/get-parent-ids objects id))) - (d/ordered-set) - ids-to-delete) - - all-children - (->> ids-to-delete ;; Children of deleted shapes must be also deleted. - (reduce (fn [res id] - (into res (cfh/get-children-ids objects id))) - []) - (reverse) - (into (d/ordered-set))) - - find-all-empty-parents - (fn recursive-find-empty-parents [empty-parents] - (let [all-ids (into empty-parents ids-to-delete) - contains? (partial contains? all-ids) - xform (comp (map lookup) - (filter #(or (cfh/group-shape? %) (cfh/bool-shape? %))) - (remove #(->> (:shapes %) (remove contains?) seq)) - (map :id)) - parents (into #{} xform all-parents)] - (if (= empty-parents parents) - empty-parents - (recursive-find-empty-parents parents)))) - - empty-parents - ;; Any parent whose children are all deleted, must be deleted too. - (into (d/ordered-set) (find-all-empty-parents #{})) - - components-to-delete - (if components-v2 - (reduce (fn [components id] - (let [shape (get objects id)] - (if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file - (:main-instance shape)) ;; but check anyway - (conj components (:component-id shape)) - components))) - [] - (into ids-to-delete all-children)) - []) - - changes (-> changes - (pcb/set-page-option :guides guides)) - - changes (reduce (fn [changes component-id] - ;; It's important to delete the component before the main instance, because we - ;; need to store the instance position if we want to restore it later. - (pcb/delete-component changes component-id (:id page))) - changes - components-to-delete) - changes (-> changes - (generate-update-shape-flags ids-to-hide objects {:hidden true}) - (pcb/remove-objects all-children {:ignore-touched true}) - (pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched}) - (pcb/remove-objects empty-parents) - (pcb/resize-parents all-parents) - (pcb/update-shapes groups-to-unmask - (fn [shape] - (assoc shape :masked-group false))) - (pcb/update-shapes (map :id interacting-shapes) - (fn [shape] - (d/update-when shape :interactions - (fn [interactions] - (into [] - (remove #(and (ctsi/has-destination %) - (contains? ids-to-delete (:destination %)))) - interactions))))) - (cond-> (seq starting-flows) - (pcb/update-page-option :flows (fn [flows] - (->> (map :id starting-flows) - (reduce ctp/remove-flow flows))))))] - [all-parents changes])) - (defn generate-new-shape-for-swap [changes shape file page libraries id-new-component index target-cell keep-props-values] (let [objects (:objects page) @@ -2057,8 +1865,8 @@ [changes objects shape file page libraries id-new-component index target-cell keep-props-values] (let [[all-parents changes] (-> changes - (generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:components-v2 true - :component-swap true})) + (cls/generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:components-v2 true + :component-swap true})) [new-shape changes] (-> changes (generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))] @@ -2115,317 +1923,3 @@ (cond-> changes (some? swap-slot) (generate-sync-head file-full libraries container id components-v2 true)))) - -(defn generate-relocate-shapes [changes objects parents parent-id page-id to-index ids] - (let [groups-to-delete - (loop [current-id (first parents) - to-check (rest parents) - removed-id? (set ids) - result #{}] - - (if-not current-id - ;; Base case, no next element - result - - (let [group (get objects current-id)] - (if (and (not= :frame (:type group)) - (not= current-id parent-id) - (empty? (remove removed-id? (:shapes group)))) - - ;; Adds group to the remove and check its parent - (let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])] - (recur (first to-check) - (rest to-check) - (conj removed-id? current-id) - (conj result current-id))) - - ;; otherwise recur - (recur (first to-check) - (rest to-check) - removed-id? - result))))) - - groups-to-unmask - (reduce (fn [group-ids id] - ;; When a masked group loses its mask shape, because it's - ;; moved outside the group, the mask condition must be - ;; removed, and it must be converted to a normal group. - (let [obj (get objects id) - parent (get objects (:parent-id obj))] - (if (and (:masked-group parent) - (= id (first (:shapes parent))) - (not= (:id parent) parent-id)) - (conj group-ids (:id parent)) - group-ids))) - #{} - ids) - - - ;; TODO: Probably implementing this using loop/recur will - ;; be more efficient than using reduce and continuous data - ;; desturcturing. - - ;; Sets the correct components metadata for the moved shapes - ;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside - ;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component - ;; `shapes-to-reroot` Adds a root flag when a nested component instance is moved outside - [shapes-to-detach shapes-to-deroot shapes-to-reroot] - (reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id] - (let [shape (get objects id) - parent (get objects parent-id) - component-shape (ctn/get-component-shape objects shape) - component-shape-parent (ctn/get-component-shape objects parent {:allow-main? true}) - root-parent (ctn/get-instance-root objects parent) - - detach? (and (ctk/in-component-copy-not-head? shape) - (not= (:id component-shape) - (:id component-shape-parent))) - deroot? (and (ctk/instance-root? shape) - root-parent) - reroot? (and (ctk/subinstance-head? shape) - (not component-shape-parent)) - - ids-to-detach (when detach? - (cons id (cfh/get-children-ids objects id)))] - - [(cond-> shapes-to-detach detach? (into ids-to-detach)) - (cond-> shapes-to-deroot deroot? (conj id)) - (cond-> shapes-to-reroot reroot? (conj id))])) - [[] [] []] - (->> ids - (mapcat #(ctn/get-child-heads objects %)) - (map :id))) - - shapes-to-unconstraint ids - - ordered-indexes (cfh/order-by-indexed-shapes objects ids) - shapes (map (d/getf objects) ordered-indexes) - parent (get objects parent-id) - component-main-parent (ctn/find-component-main objects parent false) - child-heads - (->> ordered-indexes - (mapcat #(ctn/get-child-heads objects %)) - (map :id))] - - (-> changes - (pcb/with-page-id page-id) - (pcb/with-objects objects) - - ;; Remove layout-item properties when moving a shape outside a layout - (cond-> (not (ctl/any-layout? parent)) - (pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) - - ;; Remove the hide in viewer flag - (cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent)) - (pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))) - - ;; Remove the swap slots if it is moving to a different component - (pcb/update-shapes child-heads - (fn [shape] - (cond-> shape - (not= component-main-parent (ctn/find-component-main objects shape false)) - (ctk/remove-swap-slot)))) - - ;; Add component-root property when moving a component outside a component - (cond-> (not (ctn/get-instance-root objects parent)) - (pcb/update-shapes child-heads #(assoc % :component-root true))) - - ;; Move the shapes - (pcb/change-parent parent-id - shapes - to-index) - - ;; Remove empty groups - (pcb/remove-objects groups-to-delete) - - ;; Unmask groups whose mask have moved outside - (pcb/update-shapes groups-to-unmask - (fn [shape] - (assoc shape :masked-group false))) - - ;; Detach shapes moved out of their component - (pcb/update-shapes shapes-to-detach ctk/detach-shape) - - ;; Make non root a component moved inside another one - (pcb/update-shapes shapes-to-deroot - (fn [shape] - (assoc shape :component-root nil))) - - ;; Make root a subcomponent moved outside its parent component - (pcb/update-shapes shapes-to-reroot - (fn [shape] - (assoc shape :component-root true))) - - ;; Reset constraints depending on the new parent - (pcb/update-shapes shapes-to-unconstraint - (fn [shape] - (let [frame-id (if (= (:type parent) :frame) - (:id parent) - (:frame-id parent)) - moved-shape (assoc shape - :parent-id parent-id - :frame-id frame-id)] - (assoc shape - :constraints-h (gsh/default-constraints-h moved-shape) - :constraints-v (gsh/default-constraints-v moved-shape)))) - {:ignore-touched true}) - - ;; Fix the sizing when moving a shape - (pcb/update-shapes parents - (fn [parent] - (if (ctl/flex-layout? parent) - (cond-> parent - (ctl/change-h-sizing? (:id parent) objects (:shapes parent)) - (assoc :layout-item-h-sizing :fix) - - (ctl/change-v-sizing? (:id parent) objects (:shapes parent)) - (assoc :layout-item-v-sizing :fix)) - parent))) - - ;; Update grid layout - (cond-> (ctl/grid-layout? objects parent-id) - (pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index))) - - (pcb/update-shapes parents - (fn [parent objects] - (cond-> parent - (ctl/grid-layout? parent) - (ctl/assign-cells objects))) - {:with-objects? true}) - - (pcb/reorder-grid-children parents) - - ;; If parent locked, lock the added shapes - (cond-> (:blocked parent) - (pcb/update-shapes ordered-indexes #(assoc % :blocked true))) - - ;; Resize parent containers that need to - (pcb/resize-parents parents)))) - - -(defn generate-move-shapes-to-frame - [changes ids frame-id page-id objects drop-index [row column :as cell]] - (let [lookup (d/getf objects) - frame (get objects frame-id) - layout? (:layout frame) - - component-main-frame (ctn/find-component-main objects frame false) - - shapes (->> ids - (cfh/clean-loops objects) - (keep lookup) - ;;remove shapes inside copies, because we can't change the structure of copies - (remove #(ctk/in-component-copy? (get objects (:parent-id %))))) - - moving-shapes - (cond->> shapes - (not layout?) - (remove #(= (:frame-id %) frame-id)) - - layout? - (remove #(and (= (:frame-id %) frame-id) - (not= (:parent-id %) frame-id)))) - - ordered-indexes (cfh/order-by-indexed-shapes objects (map :id moving-shapes)) - moving-shapes (map (d/getf objects) ordered-indexes) - - all-parents - (reduce (fn [res id] - (into res (cfh/get-parent-ids objects id))) - (d/ordered-set) - ids) - - find-all-empty-parents - (fn recursive-find-empty-parents [empty-parents] - (let [all-ids (into empty-parents ids) - contains? (partial contains? all-ids) - xform (comp (map lookup) - (filter cfh/group-shape?) - (remove #(->> (:shapes %) (remove contains?) seq)) - (map :id)) - parents (into #{} xform all-parents)] - (if (= empty-parents parents) - empty-parents - (recursive-find-empty-parents parents)))) - - empty-parents - ;; Any empty parent whose children are moved to another frame should be deleted - (if (empty? moving-shapes) - #{} - (into (d/ordered-set) (find-all-empty-parents #{}))) - - ;; Not move absolute shapes that won't change parent - moving-shapes - (->> moving-shapes - (remove (fn [shape] - (and (ctl/position-absolute? shape) - (= frame-id (:parent-id shape)))))) - - frame-component - (ctn/get-component-shape objects frame) - - shape-ids-to-detach - (reduce (fn [result shape] - (if (and (some? shape) (ctk/in-component-copy-not-head? shape)) - (let [shape-component (ctn/get-component-shape objects shape)] - (if (= (:id frame-component) (:id shape-component)) - result - (into result (cfh/get-children-ids-with-self objects (:id shape))))) - result)) - #{} - moving-shapes) - - moving-shapes-ids - (map :id moving-shapes) - - moving-shapes-children-ids - (->> moving-shapes-ids - (mapcat #(cfh/get-children-ids-with-self objects %))) - - child-heads - (->> moving-shapes-ids - (mapcat #(ctn/get-child-heads objects %)) - (map :id))] - (-> changes - (pcb/with-page-id page-id) - (pcb/with-objects objects) - - ;; Remove layout-item properties when moving a shape outside a layout - (cond-> (not (ctl/any-layout? objects frame-id)) - (pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data)) - - ;; Remove the swap slots if it is moving to a different component - (pcb/update-shapes - child-heads - (fn [shape] - (cond-> shape - (not= component-main-frame (ctn/find-component-main objects shape false)) - (ctk/remove-swap-slot)))) - - ;; Remove component-root property when moving a shape inside a component - (cond-> (ctn/get-instance-root objects frame) - (pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root))) - - ;; Add component-root property when moving a component outside a component - (cond-> (not (ctn/get-instance-root objects frame)) - (pcb/update-shapes child-heads #(assoc % :component-root true))) - - (pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))) - (pcb/update-shapes shape-ids-to-detach ctk/detach-shape) - (pcb/change-parent frame-id moving-shapes drop-index) - - ;; Change the grid cell in a grid layout - (cond-> (ctl/grid-layout? objects frame-id) - (-> (pcb/update-shapes - [frame-id] - (fn [frame objects] - (-> frame - ;; Assign the cell when pushing into a specific grid cell - (cond-> (some? cell) - (-> (ctl/free-cell-shapes moving-shapes-ids) - (ctl/push-into-cell moving-shapes-ids row column) - (ctl/assign-cells objects))) - (ctl/assign-cell-positions objects))) - {:with-objects? true}) - (pcb/reorder-grid-children [frame-id]))) - (pcb/remove-objects empty-parents)))) diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc new file mode 100644 index 0000000000..29d58dad64 --- /dev/null +++ b/common/src/app/common/logic/shapes.cljc @@ -0,0 +1,519 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.logic.shapes + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.files.changes-builder :as pcb] + [app.common.files.helpers :as cfh] + [app.common.geom.shapes :as gsh] + [app.common.types.component :as ctk] + [app.common.types.container :as ctn] + [app.common.types.page :as ctp] + [app.common.types.shape.interactions :as ctsi] + [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid])) + +(defn generate-update-shapes + [changes ids update-fn objects {:keys [attrs ignore-tree ignore-touched with-objects?]}] + (let [changes (reduce + (fn [changes id] + (let [opts {:attrs attrs + :ignore-geometry? (get ignore-tree id) + :ignore-touched ignore-touched + :with-objects? with-objects?}] + (pcb/update-shapes changes [id] update-fn (d/without-nils opts)))) + (-> changes + (pcb/with-objects objects)) + ids) + grid-ids (->> ids (filter (partial ctl/grid-layout? objects))) + changes (pcb/update-shapes changes grid-ids ctl/assign-cell-positions {:with-objects? true}) + changes (pcb/reorder-grid-children changes ids)] + changes)) + +(defn- generate-update-shape-flags + [changes ids objects {:keys [blocked hidden] :as flags}] + (let [update-fn + (fn [obj] + (cond-> obj + (boolean? blocked) (assoc :blocked blocked) + (boolean? hidden) (assoc :hidden hidden))) + + ids (if (boolean? blocked) + (into ids (->> ids (mapcat #(cfh/get-children-ids objects %)))) + ids)] + (-> changes + (pcb/update-shapes ids update-fn {:attrs #{:blocked :hidden}})))) + +(defn generate-delete-shapes + [changes file page objects ids {:keys [components-v2 ignore-touched component-swap]}] + (let [ids (cfh/clean-loops objects ids) + + in-component-copy? + (fn [shape-id] + ;; Look for shapes that are inside a component copy, but are + ;; not the root. In this case, they must not be deleted, + ;; but hidden (to be able to recover them more easily). + ;; Unless we are doing a component swap, in which case we want + ;; to delete the old shape + (let [shape (get objects shape-id)] + (and (ctn/has-any-copy-parent? objects shape) + (not component-swap)))) + + [ids-to-delete ids-to-hide] + (if components-v2 + (loop [ids-seq (seq ids) + ids-to-delete [] + ids-to-hide []] + (let [id (first ids-seq)] + (if (nil? id) + [ids-to-delete ids-to-hide] + (if (in-component-copy? id) + (recur (rest ids-seq) + ids-to-delete + (conj ids-to-hide id)) + (recur (rest ids-seq) + (conj ids-to-delete id) + ids-to-hide))))) + [ids []]) + + changes (-> changes + (pcb/with-page page) + (pcb/with-objects objects) + (pcb/with-library-data file)) + lookup (d/getf objects) + groups-to-unmask + (reduce (fn [group-ids id] + ;; When the shape to delete is the mask of a masked group, + ;; the mask condition must be removed, and it must be + ;; converted to a normal group. + (let [obj (lookup id) + parent (lookup (:parent-id obj))] + (if (and (:masked-group parent) + (= id (first (:shapes parent)))) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids-to-delete) + + interacting-shapes + (filter (fn [shape] + ;; If any of the deleted shapes is the destination of + ;; some interaction, this must be deleted, too. + (let [interactions (:interactions shape)] + (some #(and (ctsi/has-destination %) + (contains? ids-to-delete (:destination %))) + interactions))) + (vals objects)) + + ids-set (set ids-to-delete) + guides-to-remove + (->> (dm/get-in page [:options :guides]) + (vals) + (filter #(contains? ids-set (:frame-id %))) + (map :id)) + + guides + (->> guides-to-remove + (reduce dissoc (dm/get-in page [:options :guides]))) + + starting-flows + (filter (fn [flow] + ;; If any of the deleted is a frame that starts a flow, + ;; this must be deleted, too. + (contains? ids-to-delete (:starting-frame flow))) + (-> page :options :flows)) + + all-parents + (reduce (fn [res id] + ;; All parents of any deleted shape must be resized. + (into res (cfh/get-parent-ids objects id))) + (d/ordered-set) + ids-to-delete) + + all-children + (->> ids-to-delete ;; Children of deleted shapes must be also deleted. + (reduce (fn [res id] + (into res (cfh/get-children-ids objects id))) + []) + (reverse) + (into (d/ordered-set))) + + find-all-empty-parents + (fn recursive-find-empty-parents [empty-parents] + (let [all-ids (into empty-parents ids-to-delete) + contains? (partial contains? all-ids) + xform (comp (map lookup) + (filter #(or (cfh/group-shape? %) (cfh/bool-shape? %))) + (remove #(->> (:shapes %) (remove contains?) seq)) + (map :id)) + parents (into #{} xform all-parents)] + (if (= empty-parents parents) + empty-parents + (recursive-find-empty-parents parents)))) + + empty-parents + ;; Any parent whose children are all deleted, must be deleted too. + (into (d/ordered-set) (find-all-empty-parents #{})) + + components-to-delete + (if components-v2 + (reduce (fn [components id] + (let [shape (get objects id)] + (if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file + (:main-instance shape)) ;; but check anyway + (conj components (:component-id shape)) + components))) + [] + (into ids-to-delete all-children)) + []) + + changes (-> changes + (pcb/set-page-option :guides guides)) + + changes (reduce (fn [changes component-id] + ;; It's important to delete the component before the main instance, because we + ;; need to store the instance position if we want to restore it later. + (pcb/delete-component changes component-id (:id page))) + changes + components-to-delete) + changes (-> changes + (generate-update-shape-flags ids-to-hide objects {:hidden true}) + (pcb/remove-objects all-children {:ignore-touched true}) + (pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched}) + (pcb/remove-objects empty-parents) + (pcb/resize-parents all-parents) + (pcb/update-shapes groups-to-unmask + (fn [shape] + (assoc shape :masked-group false))) + (pcb/update-shapes (map :id interacting-shapes) + (fn [shape] + (d/update-when shape :interactions + (fn [interactions] + (into [] + (remove #(and (ctsi/has-destination %) + (contains? ids-to-delete (:destination %)))) + interactions))))) + (cond-> (seq starting-flows) + (pcb/update-page-option :flows (fn [flows] + (->> (map :id starting-flows) + (reduce ctp/remove-flow flows))))))] + [all-parents changes])) + +(defn generate-relocate-shapes [changes objects parents parent-id page-id to-index ids] + (let [groups-to-delete + (loop [current-id (first parents) + to-check (rest parents) + removed-id? (set ids) + result #{}] + + (if-not current-id + ;; Base case, no next element + result + + (let [group (get objects current-id)] + (if (and (not= :frame (:type group)) + (not= current-id parent-id) + (empty? (remove removed-id? (:shapes group)))) + + ;; Adds group to the remove and check its parent + (let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])] + (recur (first to-check) + (rest to-check) + (conj removed-id? current-id) + (conj result current-id))) + + ;; otherwise recur + (recur (first to-check) + (rest to-check) + removed-id? + result))))) + + groups-to-unmask + (reduce (fn [group-ids id] + ;; When a masked group loses its mask shape, because it's + ;; moved outside the group, the mask condition must be + ;; removed, and it must be converted to a normal group. + (let [obj (get objects id) + parent (get objects (:parent-id obj))] + (if (and (:masked-group parent) + (= id (first (:shapes parent))) + (not= (:id parent) parent-id)) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids) + + + ;; TODO: Probably implementing this using loop/recur will + ;; be more efficient than using reduce and continuous data + ;; desturcturing. + + ;; Sets the correct components metadata for the moved shapes + ;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside + ;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component + ;; `shapes-to-reroot` Adds a root flag when a nested component instance is moved outside + [shapes-to-detach shapes-to-deroot shapes-to-reroot] + (reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id] + (let [shape (get objects id) + parent (get objects parent-id) + component-shape (ctn/get-component-shape objects shape) + component-shape-parent (ctn/get-component-shape objects parent {:allow-main? true}) + root-parent (ctn/get-instance-root objects parent) + + detach? (and (ctk/in-component-copy-not-head? shape) + (not= (:id component-shape) + (:id component-shape-parent))) + deroot? (and (ctk/instance-root? shape) + root-parent) + reroot? (and (ctk/subinstance-head? shape) + (not component-shape-parent)) + + ids-to-detach (when detach? + (cons id (cfh/get-children-ids objects id)))] + + [(cond-> shapes-to-detach detach? (into ids-to-detach)) + (cond-> shapes-to-deroot deroot? (conj id)) + (cond-> shapes-to-reroot reroot? (conj id))])) + [[] [] []] + (->> ids + (mapcat #(ctn/get-child-heads objects %)) + (map :id))) + + shapes-to-unconstraint ids + + ordered-indexes (cfh/order-by-indexed-shapes objects ids) + shapes (map (d/getf objects) ordered-indexes) + parent (get objects parent-id) + component-main-parent (ctn/find-component-main objects parent false) + child-heads + (->> ordered-indexes + (mapcat #(ctn/get-child-heads objects %)) + (map :id))] + + (-> changes + (pcb/with-page-id page-id) + (pcb/with-objects objects) + + ;; Remove layout-item properties when moving a shape outside a layout + (cond-> (not (ctl/any-layout? parent)) + (pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) + + ;; Remove the hide in viewer flag + (cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent)) + (pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))) + + ;; Remove the swap slots if it is moving to a different component + (pcb/update-shapes child-heads + (fn [shape] + (cond-> shape + (not= component-main-parent (ctn/find-component-main objects shape false)) + (ctk/remove-swap-slot)))) + + ;; Add component-root property when moving a component outside a component + (cond-> (not (ctn/get-instance-root objects parent)) + (pcb/update-shapes child-heads #(assoc % :component-root true))) + + ;; Move the shapes + (pcb/change-parent parent-id + shapes + to-index) + + ;; Remove empty groups + (pcb/remove-objects groups-to-delete) + + ;; Unmask groups whose mask have moved outside + (pcb/update-shapes groups-to-unmask + (fn [shape] + (assoc shape :masked-group false))) + + ;; Detach shapes moved out of their component + (pcb/update-shapes shapes-to-detach ctk/detach-shape) + + ;; Make non root a component moved inside another one + (pcb/update-shapes shapes-to-deroot + (fn [shape] + (assoc shape :component-root nil))) + + ;; Make root a subcomponent moved outside its parent component + (pcb/update-shapes shapes-to-reroot + (fn [shape] + (assoc shape :component-root true))) + + ;; Reset constraints depending on the new parent + (pcb/update-shapes shapes-to-unconstraint + (fn [shape] + (let [frame-id (if (= (:type parent) :frame) + (:id parent) + (:frame-id parent)) + moved-shape (assoc shape + :parent-id parent-id + :frame-id frame-id)] + (assoc shape + :constraints-h (gsh/default-constraints-h moved-shape) + :constraints-v (gsh/default-constraints-v moved-shape)))) + {:ignore-touched true}) + + ;; Fix the sizing when moving a shape + (pcb/update-shapes parents + (fn [parent] + (if (ctl/flex-layout? parent) + (cond-> parent + (ctl/change-h-sizing? (:id parent) objects (:shapes parent)) + (assoc :layout-item-h-sizing :fix) + + (ctl/change-v-sizing? (:id parent) objects (:shapes parent)) + (assoc :layout-item-v-sizing :fix)) + parent))) + + ;; Update grid layout + (cond-> (ctl/grid-layout? objects parent-id) + (pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index))) + + (pcb/update-shapes parents + (fn [parent objects] + (cond-> parent + (ctl/grid-layout? parent) + (ctl/assign-cells objects))) + {:with-objects? true}) + + (pcb/reorder-grid-children parents) + + ;; If parent locked, lock the added shapes + (cond-> (:blocked parent) + (pcb/update-shapes ordered-indexes #(assoc % :blocked true))) + + ;; Resize parent containers that need to + (pcb/resize-parents parents)))) + + +(defn generate-move-shapes-to-frame + [changes ids frame-id page-id objects drop-index [row column :as cell]] + (let [lookup (d/getf objects) + frame (get objects frame-id) + layout? (:layout frame) + + component-main-frame (ctn/find-component-main objects frame false) + + shapes (->> ids + (cfh/clean-loops objects) + (keep lookup) + ;;remove shapes inside copies, because we can't change the structure of copies + (remove #(ctk/in-component-copy? (get objects (:parent-id %))))) + + moving-shapes + (cond->> shapes + (not layout?) + (remove #(= (:frame-id %) frame-id)) + + layout? + (remove #(and (= (:frame-id %) frame-id) + (not= (:parent-id %) frame-id)))) + + ordered-indexes (cfh/order-by-indexed-shapes objects (map :id moving-shapes)) + moving-shapes (map (d/getf objects) ordered-indexes) + + all-parents + (reduce (fn [res id] + (into res (cfh/get-parent-ids objects id))) + (d/ordered-set) + ids) + + find-all-empty-parents + (fn recursive-find-empty-parents [empty-parents] + (let [all-ids (into empty-parents ids) + contains? (partial contains? all-ids) + xform (comp (map lookup) + (filter cfh/group-shape?) + (remove #(->> (:shapes %) (remove contains?) seq)) + (map :id)) + parents (into #{} xform all-parents)] + (if (= empty-parents parents) + empty-parents + (recursive-find-empty-parents parents)))) + + empty-parents + ;; Any empty parent whose children are moved to another frame should be deleted + (if (empty? moving-shapes) + #{} + (into (d/ordered-set) (find-all-empty-parents #{}))) + + ;; Not move absolute shapes that won't change parent + moving-shapes + (->> moving-shapes + (remove (fn [shape] + (and (ctl/position-absolute? shape) + (= frame-id (:parent-id shape)))))) + + frame-component + (ctn/get-component-shape objects frame) + + shape-ids-to-detach + (reduce (fn [result shape] + (if (and (some? shape) (ctk/in-component-copy-not-head? shape)) + (let [shape-component (ctn/get-component-shape objects shape)] + (if (= (:id frame-component) (:id shape-component)) + result + (into result (cfh/get-children-ids-with-self objects (:id shape))))) + result)) + #{} + moving-shapes) + + moving-shapes-ids + (map :id moving-shapes) + + moving-shapes-children-ids + (->> moving-shapes-ids + (mapcat #(cfh/get-children-ids-with-self objects %))) + + child-heads + (->> moving-shapes-ids + (mapcat #(ctn/get-child-heads objects %)) + (map :id))] + (-> changes + (pcb/with-page-id page-id) + (pcb/with-objects objects) + + ;; Remove layout-item properties when moving a shape outside a layout + (cond-> (not (ctl/any-layout? objects frame-id)) + (pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data)) + + ;; Remove the swap slots if it is moving to a different component + (pcb/update-shapes + child-heads + (fn [shape] + (cond-> shape + (not= component-main-frame (ctn/find-component-main objects shape false)) + (ctk/remove-swap-slot)))) + + ;; Remove component-root property when moving a shape inside a component + (cond-> (ctn/get-instance-root objects frame) + (pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root))) + + ;; Add component-root property when moving a component outside a component + (cond-> (not (ctn/get-instance-root objects frame)) + (pcb/update-shapes child-heads #(assoc % :component-root true))) + + (pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))) + (pcb/update-shapes shape-ids-to-detach ctk/detach-shape) + (pcb/change-parent frame-id moving-shapes drop-index) + + ;; Change the grid cell in a grid layout + (cond-> (ctl/grid-layout? objects frame-id) + (-> (pcb/update-shapes + [frame-id] + (fn [frame objects] + (-> frame + ;; Assign the cell when pushing into a specific grid cell + (cond-> (some? cell) + (-> (ctl/free-cell-shapes moving-shapes-ids) + (ctl/push-into-cell moving-shapes-ids row column) + (ctl/assign-cells objects))) + (ctl/assign-cell-positions objects))) + {:with-objects? true}) + (pcb/reorder-grid-children [frame-id]))) + (pcb/remove-objects empty-parents)))) diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index b6349636b6..c7de668801 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -12,9 +12,9 @@ [app.common.files.changes :as cfc] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] - [app.common.files.libraries-helpers :as cflh] [app.common.files.validate :as cfv] [app.common.geom.point :as gpt] + [app.common.logic.libraries :as cll] [app.common.pprint :refer [pprint]] [app.common.types.color :as ctc] [app.common.types.colors-list :as ctcl] @@ -297,7 +297,7 @@ [new_shape _ changes] (-> (pcb/empty-changes nil (:id page)) - (cflh/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))] + (cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))] (thi/set-id! new-shape-label (:id new_shape)) (apply-changes file changes))) diff --git a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc index ede14008da..2fd559e0c3 100644 --- a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc +++ b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc @@ -7,7 +7,7 @@ (ns common-tests.logic.comp-remove-swap-slots-test (:require [app.common.files.changes-builder :as pcb] - [app.common.files.libraries-helpers :as cflh] + [app.common.logic.shapes :as cls] [app.common.types.component :as ctk] [app.common.uuid :as uuid] [clojure.test :as t] @@ -57,13 +57,13 @@ blue1 (thf/get-shape file :blue1) ;; ============================== Action ============================== - changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) - (:objects page) - #{(:parent-id blue1)} ;; parents - uuid/zero ;; parent-id - (:id page) ;; page-id - 0 ;; to-index - #{(:id blue1)}) ;; ids + changes (cls/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + uuid/zero ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids file' (thf/apply-changes file changes) ;; ============================== Get ================================= @@ -84,13 +84,13 @@ blue1 (thf/get-shape file :blue1) ;; ============================== Action ============================== - changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) - #{(:id blue1)} ;; ids - uuid/zero ;; frame-id - (:id page) ;; page-id - (:objects page) ;; objects - 0 ;; drop-index - nil) ;; cell + changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id blue1)} ;; ids + uuid/zero ;; frame-id + (:id page) ;; page-id + (:objects page) ;; objects + 0 ;; drop-index + nil) ;; cell file' (thf/apply-changes file changes) @@ -115,13 +115,13 @@ ;; ============================== Action ============================== - changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) - (:objects page) - #{(:parent-id blue1)} ;; parents - (:id b2) ;; parent-id - (:id page) ;; page-id - 0 ;; to-index - #{(:id blue1)}) ;; ids + changes (cls/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id b2) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids file' (thf/apply-changes file changes) ;; ============================== Get ================================= @@ -144,13 +144,13 @@ ;; ============================== Action ============================== - changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) - #{(:id blue1)} ;; ids - (:id b2) ;; frame-id - (:id page) ;; page-id - (:objects page) ;; objects - 0 ;; drop-index - nil) ;; cell + changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id blue1)} ;; ids + (:id b2) ;; frame-id + (:id page) ;; page-id + (:objects page) ;; objects + 0 ;; drop-index + nil) ;; cell file' (thf/apply-changes file changes) @@ -174,26 +174,26 @@ ;; ============================== Action ============================== ;; Move blue1 into yellow - changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) - (:objects page) - #{(:parent-id blue1)} ;; parents - (:id yellow) ;; parent-id - (:id page) ;; page-id - 0 ;; to-index - #{(:id blue1)}) ;; ids + changes (cls/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id yellow) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids file' (thf/apply-changes file changes) page' (thf/current-page file') yellow' (thf/get-shape file' :frame-yellow) ;; Move yellow into root - changes' (cflh/generate-relocate-shapes (pcb/empty-changes nil) - (:objects page') - #{(:parent-id yellow')} ;; parents - uuid/zero ;; parent-id - (:id page') ;; page-id - 0 ;; to-index - #{(:id yellow')}) ;; ids + changes' (cls/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page') + #{(:parent-id yellow')} ;; parents + uuid/zero ;; parent-id + (:id page') ;; page-id + 0 ;; to-index + #{(:id yellow')}) ;; ids file'' (thf/apply-changes file' changes') ;; ============================== Get ================================= @@ -216,26 +216,26 @@ ;; ============================== Action ============================== ;; Move blue1 into yellow - changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) - #{(:id blue1)} ;; ids - (:id yellow) ;; frame-id - (:id page) ;; page-id - (:objects page) ;; objects - 0 ;; drop-index - nil) ;; cell + changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id blue1)} ;; ids + (:id yellow) ;; frame-id + (:id page) ;; page-id + (:objects page) ;; objects + 0 ;; drop-index + nil) ;; cell file' (thf/apply-changes file changes) page' (thf/current-page file') yellow' (thf/get-shape file' :frame-yellow) ;; Move yellow into root - changes' (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) - #{(:id yellow')} ;; ids - uuid/zero ;; frame-id - (:id page') ;; page-id - (:objects page') ;; objects - 0 ;; drop-index - nil) ;; cell + changes' (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id yellow')} ;; ids + uuid/zero ;; frame-id + (:id page') ;; page-id + (:objects page') ;; objects + 0 ;; drop-index + nil) ;; cell file'' (thf/apply-changes file' changes') ;; ============================== Get ================================= @@ -259,13 +259,13 @@ ;; ============================== Action ============================== ;; Move blue1 into yellow - changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) - (:objects page) - #{(:parent-id blue1)} ;; parents - (:id yellow) ;; parent-id - (:id page) ;; page-id - 0 ;; to-index - #{(:id blue1)}) ;; ids + changes (cls/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id yellow) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids file' (thf/apply-changes file changes) page' (thf/current-page file') @@ -273,13 +273,13 @@ b2' (thf/get-shape file' :frame-b2) ;; Move yellow into b2 - changes' (cflh/generate-relocate-shapes (pcb/empty-changes nil) - (:objects page') - #{(:parent-id yellow')} ;; parents - (:id b2') ;; parent-id - (:id page') ;; page-id - 0 ;; to-index - #{(:id yellow')}) ;; ids + changes' (cls/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page') + #{(:parent-id yellow')} ;; parents + (:id b2') ;; parent-id + (:id page') ;; page-id + 0 ;; to-index + #{(:id yellow')}) ;; ids file'' (thf/apply-changes file' changes') ;; ============================== Get ================================= @@ -302,13 +302,13 @@ ;; ============================== Action ============================== ;; Move blue1 into yellow - changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) - #{(:id blue1)} ;; ids - (:id yellow) ;; frame-id - (:id page) ;; page-id - (:objects page) ;; objects - 0 ;; drop-index - nil) ;; cell + changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id blue1)} ;; ids + (:id yellow) ;; frame-id + (:id page) ;; page-id + (:objects page) ;; objects + 0 ;; drop-index + nil) ;; cell file' (thf/apply-changes file changes) page' (thf/current-page file') @@ -316,13 +316,13 @@ b2' (thf/get-shape file' :frame-b2) ;; Move yellow into b2 - changes' (cflh/generate-move-shapes-to-frame (pcb/empty-changes nil) - #{(:id yellow')} ;; ids - (:id b2') ;; frame-id - (:id page') ;; page-id - (:objects page') ;; objects - 0 ;; drop-index - nil) ;; cell + changes' (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) + #{(:id yellow')} ;; ids + (:id b2') ;; frame-id + (:id page') ;; page-id + (:objects page') ;; objects + 0 ;; drop-index + nil) ;; cell file'' (thf/apply-changes file' changes') diff --git a/common/test/common_tests/logic/component_creation_test.cljc b/common/test/common_tests/logic/component_creation_test.cljc index 3e6499fb2b..59ec298067 100644 --- a/common/test/common_tests/logic/component_creation_test.cljc +++ b/common/test/common_tests/logic/component_creation_test.cljc @@ -7,7 +7,7 @@ (ns common-tests.logic.component-creation-test (:require [app.common.files.changes-builder :as pcb] - [app.common.files.libraries-helpers :as cflh] + [app.common.logic.libraries :as cll] [clojure.test :as t] [common-tests.helpers.files :as thf] [common-tests.helpers.ids-map :as thi])) @@ -24,14 +24,14 @@ ;; Action [_ component-id changes] - (cflh/generate-add-component (pcb/empty-changes) - [shape1] - (:objects page) - (:id page) - (:id file) - true - nil - nil) + (cll/generate-add-component (pcb/empty-changes) + [shape1] + (:objects page) + (:id page) + (:id file) + true + nil + nil) file' (thf/apply-changes file changes) diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc index 4d912a6411..0aa14ca296 100644 --- a/common/test/common_tests/logic/components_touched_test.cljc +++ b/common/test/common_tests/logic/components_touched_test.cljc @@ -7,7 +7,8 @@ (ns common-tests.logic.components-touched-test (:require [app.common.files.changes-builder :as pcb] - [app.common.files.libraries-helpers :as cflh] + [app.common.logic.libraries :as cll] + [app.common.logic.shapes :as cls] [clojure.test :as t] [common-tests.helpers.compositions :as tho] [common-tests.helpers.files :as thf] @@ -31,11 +32,11 @@ update-fn (fn [shape] (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) - changes (cflh/generate-update-shapes (pcb/empty-changes nil (:id page)) - (:shapes copy-root) - update-fn - (:objects page) - {}) + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + (:shapes copy-root) + update-fn + (:objects page) + {}) file' (thf/apply-changes file changes) @@ -67,13 +68,13 @@ ;; Action ;; IMPORTANT: as modifying copies structure is now forbidden, this action ;; will not have any effect, and so the parent shape won't also be touched. - changes (cflh/generate-relocate-shapes (pcb/empty-changes) - (:objects page) - #{(:parent-id copy-root)} ; parents - (thi/id :copy-root) ; parent-id - (:id page) ; page-id - 0 ; to-index - #{(thi/id :free-shape)}) ; ids + changes (cls/generate-relocate-shapes (pcb/empty-changes) + (:objects page) + #{(:parent-id copy-root)} ; parents + (thi/id :copy-root) ; parent-id + (:id page) ; page-id + 0 ; to-index + #{(thi/id :free-shape)}) ; ids file' (thf/apply-changes file changes) @@ -100,12 +101,12 @@ ;; IMPORTANT: as modifying copies structure is now forbidden, this action will not ;; delete the child shape, but hide it (thus setting the visibility group). [_all-parents changes] - (cflh/generate-delete-shapes (pcb/empty-changes) - file - page - (:objects page) - (set (:shapes copy-root)) - {:components-v2 true}) + (cls/generate-delete-shapes (pcb/empty-changes) + file + page + (:objects page) + (set (:shapes copy-root)) + {:components-v2 true}) file' (thf/apply-changes file changes) @@ -133,13 +134,13 @@ ;; Action ;; IMPORTANT: as modifying copies structure is now forbidden, this action ;; will not have any effect, and so the parent shape won't also be touched. - changes (cflh/generate-relocate-shapes (pcb/empty-changes) - (:objects page) - #{(:parent-id copy-child1)} ; parents - (thi/id :copy-root) ; parent-id - (:id page) ; page-id - 2 ; to-index - #{(:id copy-child1)}) ; ids + changes (cls/generate-relocate-shapes (pcb/empty-changes) + (:objects page) + #{(:parent-id copy-child1)} ; parents + (thi/id :copy-root) ; parent-id + (:id page) ; page-id + 2 ; to-index + #{(:id copy-child1)}) ; ids file' (thf/apply-changes file changes) diff --git a/common/test/common_tests/logic/swap_and_reset_test.cljc b/common/test/common_tests/logic/swap_and_reset_test.cljc index 1ad70d9b7c..5353152973 100644 --- a/common/test/common_tests/logic/swap_and_reset_test.cljc +++ b/common/test/common_tests/logic/swap_and_reset_test.cljc @@ -7,11 +7,9 @@ (ns common-tests.logic.swap-and-reset-test (:require [app.common.files.changes-builder :as pcb] - [app.common.files.libraries-helpers :as cflh] - [app.common.pprint :as pp] + [app.common.logic.libraries :as cll] [app.common.types.component :as ctk] [app.common.types.file :as ctf] - [app.common.uuid :as uuid] [clojure.test :as t] [common-tests.helpers.compositions :as thc] [common-tests.helpers.files :as thf] @@ -34,16 +32,16 @@ ;; Action [new-shape all-parents changes] - (cflh/generate-component-swap (pcb/empty-changes) - (:objects page) - component-1-copy-root - (:data file) - page - {(:id file) file} - (:id component-2) - 0 - nil - {}) + (cll/generate-component-swap (pcb/empty-changes) + (:objects page) + component-1-copy-root + (:data file) + page + {(:id file) file} + (:id component-2) + 0 + nil + {}) file' (thf/apply-changes file changes) @@ -82,16 +80,16 @@ ;; Action [new-shape all-parents changes] - (cflh/generate-component-swap (pcb/empty-changes) - (:objects page) - copy - (:data file) - page - libraries - (:id component-2) - 0 - nil - {}) + (cll/generate-component-swap (pcb/empty-changes) + (:objects page) + copy + (:data file) + page + libraries + (:id component-2) + 0 + nil + {}) file' (thf/apply-changes file changes) libraries' {(:id file') file'} @@ -137,27 +135,27 @@ ;; Action [new-shape all-parents changes-swap] - (cflh/generate-component-swap (pcb/empty-changes) - (:objects page) - copy - (:data file) - page - {(:id file) file} - (:id component-2) - 0 - nil - {}) + (cll/generate-component-swap (pcb/empty-changes) + (:objects page) + copy + (:data file) + page + {(:id file) file} + (:id component-2) + 0 + nil + {}) file-swap (thf/apply-changes file changes-swap) page-swap (thf/current-page file-swap) changes - (cflh/generate-reset-component (pcb/empty-changes) - file-swap - {(:id file-swap) file-swap} - page-swap - (:id new-shape) - true) + (cll/generate-reset-component (pcb/empty-changes) + file-swap + {(:id file-swap) file-swap} + page-swap + (:id new-shape) + true) file' (thf/apply-changes file changes) page' (thf/current-page file') diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 078f6c1a32..bb88913c4d 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -13,13 +13,13 @@ [app.common.features :as cfeat] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] - [app.common.files.libraries-helpers :as cflh] [app.common.geom.align :as gal] [app.common.geom.point :as gpt] [app.common.geom.proportions :as gpp] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.grid-layout :as gslg] + [app.common.logic.shapes :as cls] [app.common.schema :as sm] [app.common.text :as txt] [app.common.transit :as t] @@ -809,13 +809,13 @@ all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids) parents (if ignore-parents? #{parent-id} all-parents) - changes (cflh/generate-relocate-shapes (pcb/empty-changes it) - objects - parents - parent-id - page-id - to-index - ids) + changes (cls/generate-relocate-shapes (pcb/empty-changes it) + objects + parents + parent-id + page-id + to-index + ids) undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index 87dec4048f..56fbb2411f 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -12,8 +12,8 @@ [app.common.files.changes :as cpc] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cph] - [app.common.files.libraries-helpers :as cflh] [app.common.logging :as log] + [app.common.logic.shapes :as cls] [app.common.schema :as sm] [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] @@ -77,13 +77,13 @@ changes (-> (pcb/empty-changes it page-id) (pcb/set-save-undo? save-undo?) (pcb/set-stack-undo? stack-undo?) - (cflh/generate-update-shapes ids - update-fn - objects - {:attrs attrs - :ignore-tree ignore-tree - :ignore-touched ignore-touched - :with-objects? with-objects?}) + (cls/generate-update-shapes ids + update-fn + objects + {:attrs attrs + :ignore-tree ignore-tree + :ignore-touched ignore-touched + :with-objects? with-objects?}) (cond-> undo-group (pcb/set-undo-group undo-group))) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index a542301e8d..585ab58a5b 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -11,10 +11,11 @@ [app.common.files.changes :as ch] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] - [app.common.files.libraries-helpers :as cflh] [app.common.files.shapes-helpers :as cfsh] [app.common.geom.point :as gpt] [app.common.logging :as log] + [app.common.logic.libraries :as cll] + [app.common.logic.shapes :as cls] [app.common.types.color :as ctc] [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] @@ -351,9 +352,9 @@ parents (into #{} (map :parent-id) shapes)] (when-not (empty? shapes) (let [[root _ changes] - (cflh/generate-add-component (pcb/empty-changes it) shapes objects page-id file-id components-v2 - dwg/prepare-create-group - cfsh/prepare-create-artboard-from-selection)] + (cll/generate-add-component (pcb/empty-changes it) shapes objects page-id file-id components-v2 + dwg/prepare-create-group + cfsh/prepare-create-artboard-from-selection)] (when-not (empty? (:redo-changes changes)) (rx/of (dch/commit-changes changes) (dws/select-shapes (d/ordered-set (:id root))) @@ -417,7 +418,7 @@ (let [library-data (get state :workspace-data) components-v2 (features/active-feature? state "components/v2") changes (-> (pcb/empty-changes it) - (cflh/generate-rename-component id new-name library-data components-v2))] + (cll/generate-rename-component id new-name library-data components-v2))] (rx/of (dch/commit-changes changes)))))))) @@ -452,7 +453,7 @@ library (get libraries library-id) components-v2 (features/active-feature? state "components/v2") changes (-> (pcb/empty-changes it nil) - (cflh/generate-duplicate-component library component-id components-v2))] + (cll/generate-duplicate-component library component-id components-v2))] (rx/of (dch/commit-changes changes)))))) @@ -478,9 +479,9 @@ [all-parents changes] (-> (pcb/empty-changes it page-id) ;; Deleting main root triggers component delete - (cflh/generate-delete-shapes file page objects #{root-id} {:components-v2 components-v2 - :undo-group undo-group - :undo-id undo-id}))] + (cls/generate-delete-shapes file page objects #{root-id} {:components-v2 components-v2 + :undo-group undo-group + :undo-id undo-id}))] (rx/of (dwu/start-undo-transaction undo-id) (dwt/clear-thumbnail (:current-file-id state) page-id root-id "component") @@ -508,7 +509,7 @@ library-data (wsh/get-file state library-id) objects (wsh/lookup-page-objects state page-id) changes (-> (pcb/empty-changes it) - (cflh/generate-restore-component library-data component-id library-id current-page objects))] + (cll/generate-restore-component library-data component-id library-id current-page objects))] (rx/of (dch/commit-changes changes)))))) @@ -545,13 +546,13 @@ (pcb/with-objects objects)) [new-shape changes] - (cflh/generate-instantiate-component changes - objects - file-id - component-id - position - page - libraries) + (cll/generate-instantiate-component changes + objects + file-id + component-id + position + page + libraries) undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) (dch/commit-changes changes) @@ -574,7 +575,7 @@ libraries (wsh/get-libraries state) changes (-> (pcb/empty-changes it) - (cflh/generate-detach-component id file page-id libraries))] + (cll/generate-detach-component id file page-id libraries))] (rx/of (dch/commit-changes changes)))))) @@ -610,7 +611,7 @@ changes (when can-detach? (reduce (fn [changes id] - (cflh/generate-detach-instance changes container libraries id)) + (cll/generate-detach-instance changes container libraries id)) (pcb/empty-changes it) selected))] @@ -696,7 +697,7 @@ changes (-> (pcb/empty-changes it) - (cflh/generate-reset-component file-full libraries container id components-v2))] + (cll/generate-reset-component file-full libraries container id components-v2))] (log/debug :msg "RESET-COMPONENT finished" :js/rchanges (log-changes (:redo-changes changes) @@ -751,7 +752,7 @@ (-> (pcb/empty-changes it) (pcb/set-undo-group undo-group) (pcb/with-container container) - (cflh/generate-sync-shape-inverse full-file libraries container id components-v2)) + (cll/generate-sync-shape-inverse full-file libraries container id components-v2)) file-id (:component-file shape) file (wsh/get-file state file-id) @@ -890,7 +891,7 @@ [new-shape all-parents changes] (-> (pcb/empty-changes it (:id page)) (pcb/set-undo-group undo-group) - (cflh/generate-component-swap objects shape file page libraries id-new-component index target-cell keep-props-values))] + (cll/generate-component-swap objects shape file page libraries id-new-component index target-cell keep-props-values))] (rx/of (dwu/start-undo-transaction undo-id) @@ -976,7 +977,7 @@ libraries (wsh/get-libraries state) current-file-id (:current-file-id state) - changes (cflh/generate-sync-file-changes + changes (cll/generate-sync-file-changes (pcb/empty-changes it) undo-group asset-type diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index b271f1ae6e..e0dee732f7 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -11,10 +11,10 @@ [app.common.files.changes-builder :as pcb] [app.common.files.focus :as cpf] [app.common.files.helpers :as cfh] - [app.common.files.libraries-helpers :as cflh] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] + [app.common.logic.libraries :as cll] [app.common.record :as cr] [app.common.types.component :as ctk] [app.common.types.container :as ctn] @@ -434,20 +434,20 @@ (gpt/subtract (-> origin-frame :selrect gpt/point))) instantiate-component - #(cflh/generate-instantiate-component changes - objects - file-id - (:component-id component-root) - pos - page - libraries - (:id component-root) - parent-id - frame-id - {}) + #(cll/generate-instantiate-component changes + objects + file-id + (:component-id component-root) + pos + page + libraries + (:id component-root) + parent-id + frame-id + {}) restore-component - #(let [restore (cflh/prepare-restore-component changes library-data (:component-id component-root) page delta (:id component-root) parent-id frame-id)] + #(let [restore (cll/prepare-restore-component changes library-data (:component-id component-root) page delta (:id component-root) parent-id frame-id)] [(:shape restore) (:changes restore)]) [_shape changes] @@ -498,7 +498,7 @@ regenerate-component (fn [changes shape] (let [components-v2 (dm/get-in library-data [:options :components-v2]) - [_ changes] (cflh/generate-add-component-changes changes shape objects file-id (:id page) components-v2)] + [_ changes] (cll/generate-add-component-changes changes shape objects file-id (:id page) components-v2)] changes)) new-obj diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 692ff93178..1918aa0f3d 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -10,8 +10,8 @@ [app.common.data.macros :as dm] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] - [app.common.files.libraries-helpers :as cflh] [app.common.files.shapes-helpers :as cfsh] + [app.common.logic.shapes :as cls] [app.common.schema :as sm] [app.common.types.container :as ctn] [app.common.types.shape :as cts] @@ -105,10 +105,10 @@ components-v2 (features/active-feature? state "components/v2") undo-id (or (:undo-id options) (js/Symbol)) [all-parents changes] (-> (pcb/empty-changes it (:id page)) - (cflh/generate-delete-shapes file page objects ids {:components-v2 components-v2 - :ignore-touched (:component-swap options) - :undo-group (:undo-group options) - :undo-id undo-id}))] + (cls/generate-delete-shapes file page objects ids {:components-v2 components-v2 + :ignore-touched (:component-swap options) + :undo-group (:undo-group options) + :undo-id undo-id}))] (rx/of (dwu/start-undo-transaction undo-id) (dc/detach-comment-thread ids) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 310a79c58f..e381b4bee6 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -11,7 +11,6 @@ [app.common.data.macros :as dm] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] - [app.common.files.libraries-helpers :as cflh] [app.common.geom.matrix :as gmt] [app.common.geom.modifiers :as gm] [app.common.geom.point :as gpt] @@ -19,6 +18,7 @@ [app.common.geom.shapes :as gsh] [app.common.geom.shapes.flex-layout :as gslf] [app.common.geom.shapes.grid-layout :as gslg] + [app.common.logic.shapes :as cls] [app.common.math :as mth] [app.common.types.component :as ctk] [app.common.types.container :as ctn] @@ -839,7 +839,7 @@ (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - changes (cflh/generate-move-shapes-to-frame (pcb/empty-changes it) ids frame-id page-id objects drop-index cell)] + changes (cls/generate-move-shapes-to-frame (pcb/empty-changes it) ids frame-id page-id objects drop-index cell)] (when (and (some? frame-id) (d/not-empty? changes)) (rx/of (dch/commit-changes changes) diff --git a/frontend/test/frontend_tests/helpers/pages.cljs b/frontend/test/frontend_tests/helpers/pages.cljs index e650c3d281..9289d992a5 100644 --- a/frontend/test/frontend_tests/helpers/pages.cljs +++ b/frontend/test/frontend_tests/helpers/pages.cljs @@ -9,9 +9,9 @@ [app.common.files.changes :as cp] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] - [app.common.files.libraries-helpers :as cflh] [app.common.files.shapes-helpers :as cfsh] [app.common.geom.point :as gpt] + [app.common.logic.libraries :as cll] [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.main.data.workspace.groups :as dwg] @@ -132,14 +132,14 @@ shapes (dwg/shapes-for-grouping objects shape-ids) [group component-id changes] - (cflh/generate-add-component (pcb/empty-changes nil) - shapes - (:objects page) - (:id page) - current-file-id - true - dwg/prepare-create-group - cfsh/prepare-create-artboard-from-selection)] + (cll/generate-add-component (pcb/empty-changes nil) + shapes + (:objects page) + (:id page) + current-file-id + true + dwg/prepare-create-group + cfsh/prepare-create-artboard-from-selection)] (swap! idmap assoc instance-label (:id group) component-label component-id) @@ -158,13 +158,13 @@ (pcb/with-objects objects)) [new-shape changes] - (cflh/generate-instantiate-component changes - objects - file-id - component-id - (gpt/point 100 100) - page - libraries)] + (cll/generate-instantiate-component changes + objects + file-id + component-id + (gpt/point 100 100) + page + libraries)] (swap! idmap assoc label (:id new-shape)) (update state :workspace-data From 832c1db63ba379653d36e3e2ca5341a158822ee3 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Mon, 6 May 2024 17:29:27 +0200 Subject: [PATCH 31/34] :sparkles: Add login page as Page Object Model --- .../get-profile-anonymous.json | 0 .../get-built-in-templates-empty.json | 0 .../get-font-variants-empty.json | 0 .../logged-in-user/get-profile-logged-in.json | 0 .../get-profiles-for-file-comments-empty.json | 0 .../logged-in-user/get-projects-default.json | 0 .../get-team-members-your-penpot.json | 0 .../get-team-recent-files-empty.json | 0 .../get-team-users-single-user.json | 0 .../logged-in-user/get-teams-default.json | 0 .../get-unread-comment-threads-empty.json | 0 .../login-with-password-success.json | 0 .../login-with-password-error.json | 0 frontend/playwright/helpers/index.js | 8 +- frontend/playwright/helpers/intercepts.js | 8 ++ frontend/playwright/login.spec.js | 80 ------------------- frontend/playwright/ui/pages/login-page.js | 76 ++++++++++++++++++ .../playwright/{ => ui/specs}/example.spec.js | 2 +- frontend/playwright/ui/specs/login.spec.js | 54 +++++++++++++ 19 files changed, 143 insertions(+), 85 deletions(-) rename frontend/playwright/{fixtures => data}/get-profile-anonymous.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-built-in-templates-empty.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-font-variants-empty.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-profile-logged-in.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-profiles-for-file-comments-empty.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-projects-default.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-team-members-your-penpot.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-team-recent-files-empty.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-team-users-single-user.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-teams-default.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/get-unread-comment-threads-empty.json (100%) rename frontend/playwright/{fixtures => data}/logged-in-user/login-with-password-success.json (100%) rename frontend/playwright/{fixtures => data}/login-with-password-error.json (100%) create mode 100644 frontend/playwright/helpers/intercepts.js delete mode 100644 frontend/playwright/login.spec.js create mode 100644 frontend/playwright/ui/pages/login-page.js rename frontend/playwright/{ => ui/specs}/example.spec.js (84%) create mode 100644 frontend/playwright/ui/specs/login.spec.js diff --git a/frontend/playwright/fixtures/get-profile-anonymous.json b/frontend/playwright/data/get-profile-anonymous.json similarity index 100% rename from frontend/playwright/fixtures/get-profile-anonymous.json rename to frontend/playwright/data/get-profile-anonymous.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-built-in-templates-empty.json b/frontend/playwright/data/logged-in-user/get-built-in-templates-empty.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-built-in-templates-empty.json rename to frontend/playwright/data/logged-in-user/get-built-in-templates-empty.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-font-variants-empty.json b/frontend/playwright/data/logged-in-user/get-font-variants-empty.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-font-variants-empty.json rename to frontend/playwright/data/logged-in-user/get-font-variants-empty.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-profile-logged-in.json b/frontend/playwright/data/logged-in-user/get-profile-logged-in.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-profile-logged-in.json rename to frontend/playwright/data/logged-in-user/get-profile-logged-in.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-profiles-for-file-comments-empty.json b/frontend/playwright/data/logged-in-user/get-profiles-for-file-comments-empty.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-profiles-for-file-comments-empty.json rename to frontend/playwright/data/logged-in-user/get-profiles-for-file-comments-empty.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-projects-default.json b/frontend/playwright/data/logged-in-user/get-projects-default.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-projects-default.json rename to frontend/playwright/data/logged-in-user/get-projects-default.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-team-members-your-penpot.json b/frontend/playwright/data/logged-in-user/get-team-members-your-penpot.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-team-members-your-penpot.json rename to frontend/playwright/data/logged-in-user/get-team-members-your-penpot.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-team-recent-files-empty.json b/frontend/playwright/data/logged-in-user/get-team-recent-files-empty.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-team-recent-files-empty.json rename to frontend/playwright/data/logged-in-user/get-team-recent-files-empty.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-team-users-single-user.json b/frontend/playwright/data/logged-in-user/get-team-users-single-user.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-team-users-single-user.json rename to frontend/playwright/data/logged-in-user/get-team-users-single-user.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-teams-default.json b/frontend/playwright/data/logged-in-user/get-teams-default.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-teams-default.json rename to frontend/playwright/data/logged-in-user/get-teams-default.json diff --git a/frontend/playwright/fixtures/logged-in-user/get-unread-comment-threads-empty.json b/frontend/playwright/data/logged-in-user/get-unread-comment-threads-empty.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/get-unread-comment-threads-empty.json rename to frontend/playwright/data/logged-in-user/get-unread-comment-threads-empty.json diff --git a/frontend/playwright/fixtures/logged-in-user/login-with-password-success.json b/frontend/playwright/data/logged-in-user/login-with-password-success.json similarity index 100% rename from frontend/playwright/fixtures/logged-in-user/login-with-password-success.json rename to frontend/playwright/data/logged-in-user/login-with-password-success.json diff --git a/frontend/playwright/fixtures/login-with-password-error.json b/frontend/playwright/data/login-with-password-error.json similarity index 100% rename from frontend/playwright/fixtures/login-with-password-error.json rename to frontend/playwright/data/login-with-password-error.json diff --git a/frontend/playwright/helpers/index.js b/frontend/playwright/helpers/index.js index 419dfa9184..ac8108f81f 100644 --- a/frontend/playwright/helpers/index.js +++ b/frontend/playwright/helpers/index.js @@ -1,14 +1,14 @@ export const interceptRPC = async (page, path, jsonFilename, options = {}) => { const interceptConfig = { status: 200, - ...options + ...options, }; - await page.route(`**/api/rpc/command/${path}`, (route) => { - route.fulfill({ + await page.route(`**/api/rpc/command/${path}`, async (route) => { + await route.fulfill({ ...interceptConfig, contentType: "application/transit+json", - path: `playwright/fixtures/${jsonFilename}`, + path: `playwright/data/${jsonFilename}`, }); }); }; diff --git a/frontend/playwright/helpers/intercepts.js b/frontend/playwright/helpers/intercepts.js new file mode 100644 index 0000000000..ecb46b8177 --- /dev/null +++ b/frontend/playwright/helpers/intercepts.js @@ -0,0 +1,8 @@ +import { interceptRPC } from "./index"; + + +export const setupNotLogedIn = async (page) => { + await interceptRPC(page, "get-profile", "get-profile-anonymous.json"); + +}; + diff --git a/frontend/playwright/login.spec.js b/frontend/playwright/login.spec.js deleted file mode 100644 index 5b10ca2ceb..0000000000 --- a/frontend/playwright/login.spec.js +++ /dev/null @@ -1,80 +0,0 @@ -import { test, expect } from "@playwright/test"; -import { interceptRPC } from "./helpers"; - -const setupLoggedOutUser = async (page) => { - await interceptRPC(page, "get-profile", "get-profile-anonymous.json"); - await interceptRPC(page, "login-with-password", "logged-in-user/login-with-password-success.json"); -}; - -// TODO: maybe Playwright's fixtures are the right way to do this? -const setupDashboardUser = async (page) => { - await interceptRPC(page, "get-profile", "logged-in-user/get-profile-logged-in.json"); - await interceptRPC(page, "get-teams", "logged-in-user/get-teams-default.json"); - await interceptRPC(page, "get-font-variants?team-id=*", "logged-in-user/get-font-variants-empty.json"); - await interceptRPC(page, "get-projects?team-id=*", "logged-in-user/get-projects-default.json"); - await interceptRPC(page, "get-team-members?team-id=*", "logged-in-user/get-team-members-your-penpot.json"); - await interceptRPC(page, "get-team-users?team-id=*", "logged-in-user/get-team-users-single-user.json"); - await interceptRPC( - page, - "get-unread-comment-threads?team-id=*", - "logged-in-user/get-team-users-single-user.json", - ); - await interceptRPC( - page, - "get-team-recent-files?team-id=*", - "logged-in-user/get-team-recent-files-empty.json", - ); - await interceptRPC( - page, - "get-profiles-for-file-comments", - "logged-in-user/get-profiles-for-file-comments-empty.json", - ); -}; - -test("Shows login page when going to index and user is logged out", async ({ page }) => { - setupLoggedOutUser(page); - - await page.goto("/"); - - await expect(page).toHaveURL(/auth\/login$/); - await expect(page.getByRole("heading", { name: "Log into my account" } )).toBeVisible(); -}); - -test("User submit a wrong formated email ", async ({ page }) => { - await interceptRPC(page, "get-profile", "get-profile-anonymous.json"); - await page.goto("/"); - await page.getByLabel("Email").fill("foo"); - - await expect(page).toHaveURL(/auth\/login$/); - await expect(page.getByText("Enter a valid email please")).toBeVisible(); -}); - -test("User logs in by filling the login form", async ({ page }) => { - setupLoggedOutUser(page); - - await page.goto("/#/auth/login"); - - setupDashboardUser(page); - - await page.getByLabel("Email").fill("foo@example.com"); - await page.getByLabel("Password").fill("loremipsum"); - - await page.getByRole("button", { name: "Login" }).click(); - - await expect(page).toHaveURL(/dashboard/); -}); - -test("User submits wrong credentials", async ({ page }) => { - await interceptRPC(page, "get-profile", "get-profile-anonymous.json"); - await interceptRPC(page, "login-with-password", "login-with-password-error.json", { status: 400 }); - - await page.goto("/"); - - await page.getByLabel("Email").fill("foo123@example.com"); - await page.getByLabel("Password").fill("aaaa"); - - await page.getByRole("button", { name: "Login" }).click(); - - await expect(page.getByText("Email or password is incorrect")).toBeVisible(); - await expect(page).toHaveURL(/auth\/login$/); -}); diff --git a/frontend/playwright/ui/pages/login-page.js b/frontend/playwright/ui/pages/login-page.js new file mode 100644 index 0000000000..1358f4ab3e --- /dev/null +++ b/frontend/playwright/ui/pages/login-page.js @@ -0,0 +1,76 @@ +import { interceptRPC } from "../../helpers/index"; + +class LoginPage { + constructor(page) { + this.page = page; + this.loginButton = page.getByRole("button", { name: "Login" }); + this.password = page.getByLabel("Password"); + this.userName = page.getByLabel("Email"); + this.message = page.getByText("Email or password is incorrect"); + this.badLoginMsg = page.getByText("Enter a valid email please"); + this.initialHeading = page.getByRole("heading", { name: "Log into my account" }); + } + + url() { + return this.page.url(); + } + + context() { + return this.page.context(); + } + + async fillEmailAndPasswordInputs(email, password) { + await this.userName.fill(email); + await this.password.fill(password); + } + + async clickLoginButton() { + await this.loginButton.click(); + } + + async setupAllowedUser() { + await interceptRPC(this.page, "get-profile", "logged-in-user/get-profile-logged-in.json"); + await interceptRPC(this.page, "get-teams", "logged-in-user/get-teams-default.json"); + await interceptRPC( + this.page, + "get-font-variants?team-id=*", + "logged-in-user/get-font-variants-empty.json", + ); + await interceptRPC(this.page, "get-projects?team-id=*", "logged-in-user/get-projects-default.json"); + await interceptRPC( + this.page, + "get-team-members?team-id=*", + "logged-in-user/get-team-members-your-penpot.json", + ); + await interceptRPC( + this.page, + "get-team-users?team-id=*", + "logged-in-user/get-team-users-single-user.json", + ); + await interceptRPC( + this.page, + "get-unread-comment-threads?team-id=*", + "logged-in-user/get-team-users-single-user.json", + ); + await interceptRPC( + this.page, + "get-team-recent-files?team-id=*", + "logged-in-user/get-team-recent-files-empty.json", + ); + await interceptRPC( + this.page, + "get-profiles-for-file-comments", + "logged-in-user/get-profiles-for-file-comments-empty.json", + ); + } + + async setupLoginSuccess() { + await interceptRPC(this.page, "login-with-password", "logged-in-user/login-with-password-success.json"); + } + + async setupLoginError() { + await interceptRPC(this.page, "login-with-password", "login-with-password-error.json", { status: 400 }); + } +} + +export default LoginPage; diff --git a/frontend/playwright/example.spec.js b/frontend/playwright/ui/specs/example.spec.js similarity index 84% rename from frontend/playwright/example.spec.js rename to frontend/playwright/ui/specs/example.spec.js index f2450fa36f..ad5e4712dd 100644 --- a/frontend/playwright/example.spec.js +++ b/frontend/playwright/ui/specs/example.spec.js @@ -5,7 +5,7 @@ test("Has title", async ({ page }) => { route.fulfill({ status: 200, contentType: "application/transit+json", - path: "playwright/fixtures/get-profile-anonymous.json", + path: "playwright/data/get-profile-anonymous.json", }); }); await page.goto("/"); diff --git a/frontend/playwright/ui/specs/login.spec.js b/frontend/playwright/ui/specs/login.spec.js new file mode 100644 index 0000000000..08d3753c4b --- /dev/null +++ b/frontend/playwright/ui/specs/login.spec.js @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; +import { setupNotLogedIn } from "../../helpers/intercepts"; + +import LoginPage from "../pages/login-page"; + +test.beforeEach(async ({ page }) => { + await setupNotLogedIn(page); + await page.goto("/#/auth/login"); +}); + +test("Shows login page when going to index and user is logged out", async ({ page }) => { + const loginPage = new LoginPage(page); + + await loginPage.setupAllowedUser(); + + await expect(loginPage.url()).toMatch(/auth\/login$/); + await expect(loginPage.initialHeading).toBeVisible(); +}); + +test("User submit a wrong formated email ", async ({ page }) => { + const loginPage = new LoginPage(page); + + await loginPage.setupLoginSuccess(); + + await loginPage.fillEmailAndPasswordInputs("foo", "lorenIpsum"); + + await expect(loginPage.badLoginMsg).toBeVisible(); +}); + +test("User logs in by filling the login form", async ({ page }) => { + const loginPage = new LoginPage(page); + + await loginPage.setupLoginSuccess(); + await loginPage.setupAllowedUser(); + + await loginPage.fillEmailAndPasswordInputs("foo@example.com", "loremipsum"); + await loginPage.clickLoginButton(); + + await page.waitForURL('**/dashboard/**'); + await expect(page).toHaveURL(/dashboard/); + // await expect(loginPage.url()).toMatch(/dashboard/); +}); + +test("User submits wrong credentials", async ({ page }) => { + const loginPage = new LoginPage(page); + + await loginPage.setupLoginError(); + + await loginPage.fillEmailAndPasswordInputs("test@example.com", "loremipsum"); + await loginPage.clickLoginButton(); + + await expect(loginPage.message).toBeVisible(); + await expect(loginPage.url()).toMatch(/auth\/login$/); +}); From b68bc9a08c89281f0c09c4897f1c59e658052547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 6 May 2024 14:53:44 +0200 Subject: [PATCH 32/34] :white_check_mark: Move more touched tests to common --- .../common_tests/helpers/compositions.cljc | 105 +++++- common/test/common_tests/helpers/debug.cljc | 44 ++- .../logic/components_touched_test.cljc | 79 ++++- .../state_components_sync_test.cljs | 331 ------------------ 4 files changed, 206 insertions(+), 353 deletions(-) diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc index d8b9cbe8b2..50e367afde 100644 --- a/common/test/common_tests/helpers/compositions.cljc +++ b/common/test/common_tests/helpers/compositions.cljc @@ -11,6 +11,8 @@ (defn add-rect [file rect-label & {:keys [] :as params}] + ;; Generated shape tree: + ;; :rect-label [:type :rect :name: Rect1] (thf/add-sample-shape file rect-label (merge {:type :rect :name "Rect1"} @@ -18,6 +20,8 @@ (defn add-frame [file frame-label & {:keys [] :as params}] + ;; Generated shape tree: + ;; :frame-label [:type :frame :name: Frame1] (thf/add-sample-shape file frame-label (merge {:type :frame :name "Frame1"} @@ -25,6 +29,9 @@ (defn add-frame-with-child [file frame-label child-label & {:keys [frame-params child-params]}] + ;; Generated shape tree: + ;; :frame-label [:name: Frame1] + ;; :child-label [:name: Rect1] (-> file (add-frame frame-label frame-params) (thf/add-sample-shape child-label @@ -36,6 +43,9 @@ (defn add-simple-component [file component-label root-label child-label & {:keys [component-params root-params child-params]}] + ;; Generated shape tree: + ;; {:root-label} [:name: Frame1] # [Component :component-label] + ;; :child-label [:name: Rect1] (-> file (add-frame-with-child root-label child-label :frame-params root-params :child-params child-params) (thf/make-component component-label root-label component-params))) @@ -43,6 +53,12 @@ (defn add-simple-component-with-copy [file component-label main-root-label main-child-label copy-root-label & {:keys [component-params main-root-params main-child-params copy-root-params]}] + ;; Generated shape tree: + ;; {:main-root-label} [:name: Frame1] # [Component :component-label] + ;; :main-child-label [:name: Rect1] + ;; + ;; :copy-root-label [:name: Frame1] #--> [Component :component-label] :main-root-label + ;; [:name: Rect1] ---> :main-child-label (-> file (add-simple-component component-label main-root-label @@ -55,27 +71,98 @@ (defn add-component-with-many-children [file component-label root-label child-labels & {:keys [component-params root-params child-params-list]}] + ;; Generated shape tree: + ;; {:root-label} [:name: Frame1] # [Component :component-label] + ;; :child1-label [:name: Rect1] + ;; :child2-label [:name: Rect2] + ;; :child3-label [:name: Rect3] (as-> file $ (add-frame $ root-label root-params) - (reduce (fn [file [label params]] + (reduce (fn [file [index [label params]]] (thf/add-sample-shape file label (merge {:type :rect - :name "Rect1" + :name (str "Rect" (inc index)) :parent-label root-label} params))) $ - (d/zip-all child-labels child-params-list)) + (d/enumerate (d/zip-all child-labels child-params-list))) (thf/make-component $ component-label root-label component-params))) (defn add-component-with-many-children-and-copy - [file component-label root-label child-labels copy-root-label - & {:keys [component-params root-params child-params-list copy-root-params]}] + [file component-label main-root-label main-child-labels copy-root-label + & {:keys [component-params main-root-params main-child-params-list copy-root-params]}] + ;; Generated shape tree: + ;; {:root-label} [:name: Frame1] # [Component :component-label] + ;; :child1-label [:name: Rect1] + ;; :child2-label [:name: Rect2] + ;; :child3-label [:name: Rect3] + ;; + ;; :copy-root-label [:name: Frame1] #--> [Component :component-label] :root-label + ;; [:name: Rect1] ---> :child1-label + ;; [:name: Rect2] ---> :child2-label + ;; [:name: Rect3] ---> :child3-label (-> file (add-component-with-many-children component-label - root-label - child-labels + main-root-label + main-child-labels :component-params component-params - :root-params root-params - :child-params-list child-params-list) + :root-params main-root-params + :child-params-list main-child-params-list) (thf/instantiate-component component-label copy-root-label copy-root-params))) + +(defn add-nested-component + [file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label + & {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params]}] + ;; Generated shape tree: + ;; {:main1-root-label} [:name: Frame1] # [Component :component1-label] + ;; :main1-child-label [:name: Rect1] + ;; + ;; {:main2-root-label} [:name: Frame2] # [Component :component2-label] + ;; :nested-head-label [:name: Frame1] @--> [Component :component1-label] :main1-root-label + ;; [:name: Rect1] ---> :main1-child-label + (-> file + (add-simple-component component1-label + main1-root-label + main1-child-label + :component-params component1-params + :root-params root1-params + :child-params main1-child-params) + (add-frame main2-root-label (merge {:name "Frame2"} + main2-root-params)) + (thf/instantiate-component component1-label + nested-head-label + (assoc nested-head-params + :parent-label main2-root-label)) + (thf/make-component component2-label + main2-root-label + component2-params))) + +(defn add-nested-component-with-copy + [file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label copy2-label + & {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params copy2-params]}] + ;; Generated shape tree: + ;; {:main1-root-label} [:name: Frame1] # [Component :component1-label] + ;; :main1-child-label [:name: Rect1] + ;; + ;; {:main2-root-label} [:name: Frame2] # [Component :component2-label] + ;; :nested-head-label [:name: Frame1] @--> [Component :component1-label] :main1-root-label + ;; [:name: Rect1] ---> :main1-child-label + ;; + ;; :copy2-label [:name: Frame2] #--> [Component :component2-label] :main2-root-label + ;; [:name: Frame1] @--> [Component :component1-label] :nested-head-label + ;; [:name: Rect1] ---> + (-> file + (add-nested-component component1-label + main1-root-label + main1-child-label + component2-label + main2-root-label + nested-head-label + :component1-params component1-params + :root1-params root1-params + :main1-child-params main1-child-params + :component2-params component2-params + :main2-root-params main2-root-params + :nested-head-params nested-head-params) + (thf/instantiate-component component2-label copy2-label copy2-params))) \ No newline at end of file diff --git a/common/test/common_tests/helpers/debug.cljc b/common/test/common_tests/helpers/debug.cljc index 984e47159c..5ecdcdb715 100644 --- a/common/test/common_tests/helpers/debug.cljc +++ b/common/test/common_tests/helpers/debug.cljc @@ -1,7 +1,9 @@ (ns common-tests.helpers.debug (:require + [app.common.data :as d] [app.common.uuid :as uuid] - [common-tests.helpers.ids-map :as thi])) + [common-tests.helpers.ids-map :as thi] + [cuerdas.core :as str])) (defn dump-shape "Dumps a shape, with each attribute in a line" @@ -15,19 +17,37 @@ (defn- stringify-keys [m keys] (apply str (interpose ", " (map #(str % ": " (get m %)) keys)))) +(defn- dump-page-shape + [shape keys padding] + (println (str/pad (str padding + (when (:main-instance shape) "{") + (or (thi/label (:id shape)) "") + (when (:main-instance shape) "}") + (when keys + (str " [" (stringify-keys shape keys) "]"))) + {:length 40 :type :right}) + (if (nil? (:shape-ref shape)) + (if (:component-root shape) + (str "# [Component " (or (thi/label (:component-id shape)) "") "]") + "") + (str/format "%s--> %s%s" + (cond (:component-root shape) "#" + (:component-id shape) "@" + :else "-") + (if (:component-root shape) + (str "[Component " (or (thi/label (:component-id shape)) "") "] ") + "") + (or (thi/label (:shape-ref shape)) ""))))) + (defn dump-page "Dumps the layer tree of the page. Prints the label of each shape, and the specified keys. Example: (thd/dump-page (thf/current-page file) [:id :touched])" ([page keys] (dump-page page uuid/zero "" keys)) - ([page id padding keys] - (let [objects (vals (:objects page)) - root-objects (filter #(and - (= (:parent-id %) id) - (not= (:id %) id)) - objects)] - (doseq [val root-objects] - (println padding (thi/label (:id val)) - (when keys - (str "[" (stringify-keys val keys) "]"))) - (dump-page page (:id val) (str padding " ") keys))))) + ([page root-id padding keys] + (let [lookupf (d/getf (:objects page)) + root-shape (lookupf root-id) + shapes (map lookupf (:shapes root-shape))] + (doseq [shape shapes] + (dump-page-shape shape keys padding) + (dump-page page (:id shape) (str padding " ") keys))))) diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc index 0aa14ca296..e20827c1ec 100644 --- a/common/test/common_tests/logic/components_touched_test.cljc +++ b/common/test/common_tests/logic/components_touched_test.cljc @@ -7,7 +7,6 @@ (ns common-tests.logic.components-touched-test (:require [app.common.files.changes-builder :as pcb] - [app.common.logic.libraries :as cll] [app.common.logic.shapes :as cls] [clojure.test :as t] [common-tests.helpers.compositions :as tho] @@ -151,3 +150,81 @@ ;; Check (t/is (= (:touched copy-root') nil)) (t/is (= (:touched copy-child') nil)))) + +(t/deftest test-touched-when-changing-upper + (let [;; Setup + file (-> (thf/sample-file :file1) + (tho/add-nested-component-with-copy :component1 + :main1-root + :main1-child + :component2 + :main2-root + :main2-nested-head + :copy2-root + :root2-params {:fills (thf/sample-fills-color + :fill-color "#abcdef")})) + page (thf/current-page file) + copy2-root (thf/get-shape file :copy2-root) + + ;; Action + update-fn (fn [shape] + (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id copy2-root)} + update-fn + (:objects page) + {}) + + file' (thf/apply-changes file changes) + + ;; Get + copy2-root' (thf/get-shape file' :copy2-root) + fills' (:fills copy2-root') + fill' (first fills')] + + ;; Check + (t/is (= (count fills') 1)) + (t/is (= (:fill-color fill') "#fabada")) + (t/is (= (:fill-opacity fill') 1)) + (t/is (= (:touched copy2-root') #{:fill-group})))) + +(t/deftest test-touched-when-changing-lower + (let [;; Setup + file (-> (thf/sample-file :file1) + (tho/add-nested-component-with-copy :component1 + :main1-root + :main1-child + :component2 + :main2-root + :main2-nested-head + :copy2-root + :nested-head-params {:fills (thf/sample-fills-color + :fill-color "#abcdef")})) + page (thf/current-page file) + copy2-root (thf/get-shape file :copy2-root) + + ;; Action + update-fn (fn [shape] + (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + (:shapes copy2-root) + update-fn + (:objects page) + {}) + + file' (thf/apply-changes file changes) + + ;; Get + copy2-root' (thf/get-shape file' :copy2-root) + copy2-child' (thf/get-shape-by-id file' (first (:shapes copy2-root'))) + fills' (:fills copy2-child') + fill' (first fills')] + + ;; Check + (t/is (= (count fills') 1)) + (t/is (= (:fill-color fill') "#fabada")) + (t/is (= (:fill-opacity fill') 1)) + (t/is (= (:touched copy2-root') nil)) + (t/is (= (:touched copy2-child') #{:fill-group})))) \ No newline at end of file diff --git a/frontend/test/frontend_tests/state_components_sync_test.cljs b/frontend/test/frontend_tests/state_components_sync_test.cljs index 8ae88562a1..7907f4dddf 100644 --- a/frontend/test/frontend_tests/state_components_sync_test.cljs +++ b/frontend/test/frontend_tests/state_components_sync_test.cljs @@ -21,337 +21,6 @@ (t/use-fixtures :each {:before thp/reset-idmap!}) -;; === Test touched ====================== - -(t/deftest test-touched-from-lib - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/move-to-library :lib1 "Library 1") - (thp/sample-page) - (thp/instantiate-component :instance1 - (thp/id :component1) - (thp/id :lib1))) - - [_group1 shape1'] - (thl/resolve-instance state (thp/id :instance1)) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 #--> Rect 1 - ;; Rect 1* ---> Rect 1 - ;; #{:fill-group} - ;; - (let [[[group shape1] [c-group c-shape1] _component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] - - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) #{:fill-group})) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/white)) - (t/is (= (:fill-opacity c-shape1) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape1')] - (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5}))) - :the/end)))) - -(t/deftest test-touched-nested-upper - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 - (thp/id :component1)) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/frame-shapes :frame1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :main2 :component2 - [(thp/id :frame1)]) - (thp/instantiate-component :instance2 - (thp/id :component2))) - - [_instance2 _instance1 shape1' _shape2'] - (thl/resolve-instance state (thp/id :instance2)) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Group - ;; Rect 1 #--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; Circle 1 - ;; Group #--> Group - ;; Rect 1 @--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; Circle 1* ---> Circle 1 - ;; #{:fill-group} - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - ;; [Group] - ;; page1 / Group - ;; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] _component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] - - (t/is (= (:name instance2) "Board")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) #{:fill-group})) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/white)) - (t/is (= (:fill-opacity shape2) 1)) - - (t/is (= (:name c-instance2) "Board")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/white)) - (t/is (= (:fill-opacity c-shape2) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape1')] - (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5}))) - :the/end)))) - -(t/deftest test-touched-nested-lower-near - (t/async done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 - (thp/id :component1)) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/frame-shapes :frame1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component2 - [(thp/id :frame1)]) - (thp/instantiate-component :instance2 - (thp/id :component2))) - - [_instance2 _instance1 _shape1' shape2'] - (thl/resolve-instance state (thp/id :instance2)) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Group - ;; Rect 1 #--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; Circle 1 - ;; Group #--> Group - ;; Rect 1 @--> Rect 1 - ;; Rect 1* ---> Rect 1 - ;; #{:fill-group} - ;; Circle 1 ---> Circle 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - ;; [Group] - ;; page1 / Group - ;; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] _component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] - - (t/is (= (:name instance2) "Board")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) #{:fill-group})) - (t/is (= (:fill-color shape2) clr/test)) - (t/is (= (:fill-opacity shape2) 0.5)) - - (t/is (= (:name c-instance2) "Board")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/white)) - (t/is (= (:fill-opacity c-shape2) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape2')] - (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5}))) - :the/end)))) - -(t/deftest test-touched-nested-lower-remote - (t/async done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :main1 :component1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance1 - (thp/id :component1)) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/frame-shapes :frame1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component2 - [(thp/id :frame1)]) - (thp/instantiate-component :instance2 - (thp/id :component2))) - - [instance2 _instance1 _shape1' shape2'] - (thl/resolve-instance state (thp/id :instance2)) - - store (the/prepare-store state done - (fn [new-state] - ;; Expected shape tree: - ;; - ;; [Page] - ;; Root Frame - ;; Rect 1 - ;; Rect 1 - ;; Group - ;; Rect 1 #--> Rect 1 - ;; Rect 1* ---> Rect 1 - ;; #{:fill-group} - ;; Circle 1 - ;; Group #--> Group - ;; Rect 1 @--> Rect 1 - ;; Rect 1 ---> Rect 1 - ;; Circle 1 ---> Circle 1 - ;; - ;; [Rect 1] - ;; page1 / Rect 1 - ;; - ;; [Group] - ;; page1 / Group - ;; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] _component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] - - (t/is (= (:name instance2) "Board")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) #{:fill-group})) - (t/is (= (:fill-color shape2) clr/test)) - (t/is (= (:fill-opacity shape2) 0.5)) - (t/is (= (:name c-instance2) "Board")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) #{:fill-group})))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape2')] - (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5}))) - (dwl/update-component (:id instance2)) - :the/end)))) - ;; === Test reset changes ====================== (t/deftest test-reset-changes From 97e34d6e28e6fbd52b671a33ecb665c6b4ab6fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 7 May 2024 11:35:01 +0200 Subject: [PATCH 33/34] :lipstick: Unify comments --- .../logic/comp_remove_swap_slots_test.cljc | 76 +++++++++++-------- .../logic/component_creation_test.cljc | 8 +- .../logic/components_touched_test.cljc | 51 +++++++------ .../logic/swap_and_reset_test.cljc | 51 ++++++------- 4 files changed, 100 insertions(+), 86 deletions(-) diff --git a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc index 2fd559e0c3..dcbd5cc482 100644 --- a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc +++ b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc @@ -51,12 +51,13 @@ (thf/make-component :b2 :frame-b2))) (t/deftest test-keep-swap-slot-relocating-blue1-to-root - (let [;; ============================== Setup =============================== + (let [;; ==== Setup file (setup-file) + page (thf/current-page file) blue1 (thf/get-shape file :blue1) - ;; ============================== Action ============================== + ;; ==== Action changes (cls/generate-relocate-shapes (pcb/empty-changes nil) (:objects page) #{(:parent-id blue1)} ;; parents @@ -66,10 +67,11 @@ #{(:id blue1)}) ;; ids file' (thf/apply-changes file changes) - ;; ============================== Get ================================= + ;; ==== Get blue1' (thf/get-shape file' :blue1)] - ;; ================================== Check =============================== + ;; ==== Check + ;; blue1 had swap-id before move (t/is (some? (ctk/get-swap-slot blue1))) @@ -78,12 +80,12 @@ (t/is (nil? (ctk/get-swap-slot blue1'))))) (t/deftest test-keep-swap-slot-move-blue1-to-root - (let [;; ============================== Setup =============================== + (let [;; ==== Setup file (setup-file) page (thf/current-page file) blue1 (thf/get-shape file :blue1) - ;; ============================== Action ============================== + ;; ==== Action changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) #{(:id blue1)} ;; ids uuid/zero ;; frame-id @@ -94,10 +96,11 @@ file' (thf/apply-changes file changes) - ;; ============================== Get ================================= + ;; ==== Get blue1' (thf/get-shape file' :blue1)] - ;; ================================== Check =============================== + ;; ==== Check + ;; blue1 had swap-id before move (t/is (some? (ctk/get-swap-slot blue1))) @@ -107,14 +110,14 @@ (t/deftest test-keep-swap-slot-relocating-blue1-to-b2 - (let [;; ============================== Setup =============================== + (let [;; ==== Setup file (setup-file) page (thf/current-page file) blue1 (thf/get-shape file :blue1) b2 (thf/get-shape file :frame-b2) - ;; ============================== Action ============================== + ;; ==== Action changes (cls/generate-relocate-shapes (pcb/empty-changes nil) (:objects page) #{(:parent-id blue1)} ;; parents @@ -124,10 +127,11 @@ #{(:id blue1)}) ;; ids file' (thf/apply-changes file changes) - ;; ============================== Get ================================= + ;; ==== Get blue1' (thf/get-shape file' :blue1)] - ;; ================================== Check =============================== + ;; ==== Check + ;; blue1 had swap-id before move (t/is (some? (ctk/get-swap-slot blue1))) @@ -136,14 +140,14 @@ (t/is (nil? (ctk/get-swap-slot blue1'))))) (t/deftest test-keep-swap-slot-move-blue1-to-b2 - (let [;; ============================== Setup =============================== + (let [;; ==== Setup file (setup-file) page (thf/current-page file) blue1 (thf/get-shape file :blue1) b2 (thf/get-shape file :frame-b2) - ;; ============================== Action ============================== + ;; ==== Action changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) #{(:id blue1)} ;; ids (:id b2) ;; frame-id @@ -154,10 +158,11 @@ file' (thf/apply-changes file changes) - ;; ============================== Get ================================= + ;; ==== Get blue1' (thf/get-shape file' :blue1)] - ;; ================================== Check =============================== + ;; ==== Check + ;; blue1 had swap-id before move (t/is (some? (ctk/get-swap-slot blue1))) @@ -166,13 +171,13 @@ (t/is (nil? (ctk/get-swap-slot blue1'))))) (t/deftest test-keep-swap-slot-relocating-yellow-to-root - (let [;; ============================== Setup =============================== + (let [;; ==== Setup file (setup-file) page (thf/current-page file) blue1 (thf/get-shape file :blue1) yellow (thf/get-shape file :frame-yellow) - ;; ============================== Action ============================== + ;; ==== Action ;; Move blue1 into yellow changes (cls/generate-relocate-shapes (pcb/empty-changes nil) (:objects page) @@ -196,10 +201,11 @@ #{(:id yellow')}) ;; ids file'' (thf/apply-changes file' changes') - ;; ============================== Get ================================= + ;; ==== Get blue1'' (thf/get-shape file'' :blue1)] - ;; ================================== Check =============================== + ;; ==== Check + ;; blue1 had swap-id before move (t/is (some? (ctk/get-swap-slot blue1))) @@ -208,13 +214,14 @@ (t/is (nil? (ctk/get-swap-slot blue1''))))) (t/deftest test-keep-swap-slot-move-yellow-to-root - (let [;; ============================== Setup =============================== + (let [;; ==== Setup file (setup-file) page (thf/current-page file) blue1 (thf/get-shape file :blue1) yellow (thf/get-shape file :frame-yellow) - ;; ============================== Action ============================== + ;; ==== Action + ;; Move blue1 into yellow changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) #{(:id blue1)} ;; ids @@ -238,10 +245,11 @@ nil) ;; cell file'' (thf/apply-changes file' changes') - ;; ============================== Get ================================= + ;; ==== Get blue1'' (thf/get-shape file'' :blue1)] - ;; ================================== Check =============================== + ;; ==== Check + ;; blue1 had swap-id before move (t/is (some? (ctk/get-swap-slot blue1))) @@ -251,13 +259,14 @@ (t/deftest test-keep-swap-slot-relocating-yellow-to-b2 - (let [;; ============================== Setup =============================== + (let [;; ==== Setup file (setup-file) page (thf/current-page file) blue1 (thf/get-shape file :blue1) yellow (thf/get-shape file :frame-yellow) - ;; ============================== Action ============================== + ;; ==== Action + ;; Move blue1 into yellow changes (cls/generate-relocate-shapes (pcb/empty-changes nil) (:objects page) @@ -282,10 +291,11 @@ #{(:id yellow')}) ;; ids file'' (thf/apply-changes file' changes') - ;; ============================== Get ================================= + ;; ==== Get blue1'' (thf/get-shape file'' :blue1)] - ;; ================================== Check =============================== + ;; ==== Check + ;; blue1 had swap-id before move (t/is (some? (ctk/get-swap-slot blue1))) @@ -294,13 +304,14 @@ (t/is (nil? (ctk/get-swap-slot blue1''))))) (t/deftest test-keep-swap-slot-move-yellow-to-b2 - (let [;; ============================== Setup =============================== + (let [;; ==== Setup file (setup-file) page (thf/current-page file) blue1 (thf/get-shape file :blue1) yellow (thf/get-shape file :frame-yellow) - ;; ============================== Action ============================== + ;; ==== Action + ;; Move blue1 into yellow changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) #{(:id blue1)} ;; ids @@ -326,10 +337,11 @@ file'' (thf/apply-changes file' changes') - ;; ============================== Get ================================= + ;; ==== Get blue1'' (thf/get-shape file'' :blue1)] - ;; ================================== Check =============================== + ;; ==== Check + ;; blue1 had swap-id before move (t/is (some? (ctk/get-swap-slot blue1))) diff --git a/common/test/common_tests/logic/component_creation_test.cljc b/common/test/common_tests/logic/component_creation_test.cljc index 59ec298067..daf1565f8f 100644 --- a/common/test/common_tests/logic/component_creation_test.cljc +++ b/common/test/common_tests/logic/component_creation_test.cljc @@ -15,14 +15,14 @@ (t/use-fixtures :each thi/test-fixture) (t/deftest test-add-component-from-single-shape - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (thf/add-sample-shape :shape1 :type :frame)) page (thf/current-page file) shape1 (thf/get-shape file :shape1) - ;; Action + ;; ==== Action [_ component-id changes] (cll/generate-add-component (pcb/empty-changes) [shape1] @@ -35,11 +35,11 @@ file' (thf/apply-changes file changes) - ;; Get + ;; ==== Get component (thf/get-component-by-id file' component-id) root (thf/get-shape-by-id file' (:main-instance-id component))] - ;; Check + ;; ==== Check (t/is (some? component)) (t/is (some? root)) (t/is (= (:component-id root) (:id component))))) diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc index e20827c1ec..d409c8d5f4 100644 --- a/common/test/common_tests/logic/components_touched_test.cljc +++ b/common/test/common_tests/logic/components_touched_test.cljc @@ -16,7 +16,7 @@ (t/use-fixtures :each thi/test-fixture) (t/deftest test-touched-when-changing-attribute - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (tho/add-simple-component-with-copy :component1 :main-root @@ -27,7 +27,7 @@ page (thf/current-page file) copy-root (thf/get-shape file :copy-root) - ;; Action + ;; ==== Action update-fn (fn [shape] (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) @@ -39,13 +39,13 @@ file' (thf/apply-changes file changes) - ;; Get + ;; ==== Get copy-root' (thf/get-shape file' :copy-root) copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root'))) fills' (:fills copy-child') fill' (first fills')] - ;; Check + ;; ==== Check (t/is (= (count fills') 1)) (t/is (= (:fill-color fill') "#fabada")) (t/is (= (:fill-opacity fill') 1)) @@ -53,7 +53,7 @@ (t/is (= (:touched copy-child') #{:fill-group})))) (t/deftest test-not-touched-when-adding-shape - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (tho/add-simple-component-with-copy :component1 :main-root @@ -64,7 +64,8 @@ page (thf/current-page file) copy-root (thf/get-shape file :copy-root) - ;; Action + ;; ==== Action + ;; IMPORTANT: as modifying copies structure is now forbidden, this action ;; will not have any effect, and so the parent shape won't also be touched. changes (cls/generate-relocate-shapes (pcb/empty-changes) @@ -77,16 +78,16 @@ file' (thf/apply-changes file changes) - ;; Get + ;; ==== Get copy-root' (thf/get-shape file' :copy-root) copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] - ;; Check + ;; ==== Check (t/is (= (:touched copy-root') nil)) (t/is (= (:touched copy-child') nil)))) (t/deftest test-touched-when-deleting-shape - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (tho/add-simple-component-with-copy :component1 :main-root @@ -96,7 +97,8 @@ page (thf/current-page file) copy-root (thf/get-shape file :copy-root) - ;; Action + ;; ==== Action + ;; IMPORTANT: as modifying copies structure is now forbidden, this action will not ;; delete the child shape, but hide it (thus setting the visibility group). [_all-parents changes] @@ -109,16 +111,16 @@ file' (thf/apply-changes file changes) - ;; Get + ;; ==== Get copy-root' (thf/get-shape file' :copy-root) copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] - ;; Check + ;; ==== Check (t/is (= (:touched copy-root') nil)) (t/is (= (:touched copy-child') #{:visibility-group})))) (t/deftest test-not-touched-when-moving-shape - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (tho/add-component-with-many-children-and-copy :component1 :main-root @@ -130,7 +132,8 @@ copy-root (thf/get-shape file :copy-root) copy-child1 (thf/get-shape-by-id file (first (:shapes copy-root))) - ;; Action + ;; ==== Action + ;; IMPORTANT: as modifying copies structure is now forbidden, this action ;; will not have any effect, and so the parent shape won't also be touched. changes (cls/generate-relocate-shapes (pcb/empty-changes) @@ -143,16 +146,16 @@ file' (thf/apply-changes file changes) - ;; Get + ;; ==== Get copy-root' (thf/get-shape file' :copy-root) copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] - ;; Check + ;; ==== Check (t/is (= (:touched copy-root') nil)) (t/is (= (:touched copy-child') nil)))) (t/deftest test-touched-when-changing-upper - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (tho/add-nested-component-with-copy :component1 :main1-root @@ -166,7 +169,7 @@ page (thf/current-page file) copy2-root (thf/get-shape file :copy2-root) - ;; Action + ;; ==== Action update-fn (fn [shape] (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) @@ -178,19 +181,19 @@ file' (thf/apply-changes file changes) - ;; Get + ;; ==== Get copy2-root' (thf/get-shape file' :copy2-root) fills' (:fills copy2-root') fill' (first fills')] - ;; Check + ;; ==== Check (t/is (= (count fills') 1)) (t/is (= (:fill-color fill') "#fabada")) (t/is (= (:fill-opacity fill') 1)) (t/is (= (:touched copy2-root') #{:fill-group})))) (t/deftest test-touched-when-changing-lower - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (tho/add-nested-component-with-copy :component1 :main1-root @@ -204,7 +207,7 @@ page (thf/current-page file) copy2-root (thf/get-shape file :copy2-root) - ;; Action + ;; ==== Action update-fn (fn [shape] (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) @@ -216,13 +219,13 @@ file' (thf/apply-changes file changes) - ;; Get + ;; ==== Get copy2-root' (thf/get-shape file' :copy2-root) copy2-child' (thf/get-shape-by-id file' (first (:shapes copy2-root'))) fills' (:fills copy2-child') fill' (first fills')] - ;; Check + ;; ==== Check (t/is (= (count fills') 1)) (t/is (= (:fill-color fill') "#fabada")) (t/is (= (:fill-opacity fill') 1)) diff --git a/common/test/common_tests/logic/swap_and_reset_test.cljc b/common/test/common_tests/logic/swap_and_reset_test.cljc index 5353152973..ba9746c3a0 100644 --- a/common/test/common_tests/logic/swap_and_reset_test.cljc +++ b/common/test/common_tests/logic/swap_and_reset_test.cljc @@ -19,19 +19,22 @@ ;; Related .penpot file: common/test/cases/swap-and-reset.penpot (t/deftest test-simple-swap - (let [;; Setup - file - (-> (thf/sample-file :file1) - (thc/add-simple-component-with-copy :component-1 :component-1-main-root :component-1-main-child :component-1-copy-root) - (thc/add-simple-component :component-2 :component-2-root :component-2-child)) + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thc/add-simple-component-with-copy :component-1 + :component-1-main-root + :component-1-main-child + :component-1-copy-root) + (thc/add-simple-component :component-2 + :component-2-root + :component-2-child)) component-1-copy-root (thf/get-shape file :component-1-copy-root) - component-1 (thf/get-component file :component-1) component-2 (thf/get-component file :component-2) page (thf/current-page file) - ;; Action - [new-shape all-parents changes] + ;; ==== Action + [new-shape _all-parents changes] (cll/generate-component-swap (pcb/empty-changes) (:objects page) component-1-copy-root @@ -45,16 +48,16 @@ file' (thf/apply-changes file changes) - ;; Get + ;; ==== Get swapped (thf/get-shape-by-id file' (:id new-shape))] - ;; Check + ;; ==== Check (t/is (not= (:component-id component-1-copy-root) (:component-id swapped))) (t/is (= (:id component-2) (:component-id swapped))) (t/is (= (:id file) (:component-file swapped))))) (t/deftest test-swap-nested - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (thc/add-simple-component :component-1 :component-1-main-root :component-1-main-child) @@ -65,9 +68,7 @@ (thc/add-simple-component :component-2 :component-2-main-root :component-2-main-child)) page (thf/current-page file) - component-1 (thf/get-component file :component-1) component-2 (thf/get-component file :component-2) - component-3 (thf/get-component file :component-3) copy (->> @@ -78,8 +79,8 @@ libraries {(:id file) file} - ;; Action - [new-shape all-parents changes] + ;; ==== Action + [new-shape _all-parents changes] (cll/generate-component-swap (pcb/empty-changes) (:objects page) copy @@ -95,7 +96,7 @@ libraries' {(:id file') file'} page' (thf/current-page file') - ;; Get + ;; ==== Get swapped (thf/get-shape-by-id file' (:id new-shape)) component-1-copy-root (thf/get-shape file' :component-1-copy-root) slot (-> (ctf/find-swap-slot swapped @@ -104,7 +105,7 @@ libraries') (ctk/build-swap-slot-group))] - ;; Check + ;; ==== Check (t/is (not= (:component-id copy) (:component-id swapped))) (t/is (= (:id component-2) (:component-id swapped))) (t/is (= (:id file) (:component-file swapped))) @@ -112,7 +113,7 @@ (t/is (= (ctk/get-swap-slot swapped) (:id component-1-copy-root))))) (t/deftest test-swap-and-reset-override - (let [;; Setup + (let [;; ==== Setup file (-> (thf/sample-file :file1) (thc/add-simple-component :component-1 :component-1-main-root :component-1-main-child) @@ -133,8 +134,8 @@ first (thf/get-shape-by-id file)) - ;; Action - [new-shape all-parents changes-swap] + ;; ==== Action + [new-shape _all-parents changes-swap] (cll/generate-component-swap (pcb/empty-changes) (:objects page) copy @@ -158,17 +159,15 @@ true) file' (thf/apply-changes file changes) - page' (thf/current-page file') - ;; Get + + ;; ==== Get reset (->> (thf/get-shape file' :component-container-instance) :shapes first - (thf/get-shape-by-id file')) + (thf/get-shape-by-id file'))] - component-1-copy-root (thf/get-shape file' :component-1-copy-root)] - - ;; Check + ;; ==== Check (t/is (= (:id component-1) (:component-id reset))) (t/is (nil? (ctk/get-swap-slot reset))))) From c937d49ce9187e3fd9f20e8c1d284367e2684d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 7 May 2024 12:50:32 +0200 Subject: [PATCH 34/34] :lipstick: Split helpers in separated files --- .../test/common_tests/helpers/components.cljc | 153 +++++++++ .../common_tests/helpers/compositions.cljc | 25 +- common/test/common_tests/helpers/debug.cljc | 53 --- common/test/common_tests/helpers/files.cljc | 312 ++++-------------- common/test/common_tests/helpers/shapes.cljc | 101 ++++++ .../logic/comp_remove_swap_slots_test.cljc | 83 +++-- .../logic/component_creation_test.cljc | 12 +- .../logic/components_touched_test.cljc | 55 +-- .../logic/swap_and_reset_test.cljc | 62 ++-- .../types/types_libraries_test.cljc | 32 +- 10 files changed, 459 insertions(+), 429 deletions(-) create mode 100644 common/test/common_tests/helpers/components.cljc delete mode 100644 common/test/common_tests/helpers/debug.cljc create mode 100644 common/test/common_tests/helpers/shapes.cljc diff --git a/common/test/common_tests/helpers/components.cljc b/common/test/common_tests/helpers/components.cljc new file mode 100644 index 0000000000..56ebf488d7 --- /dev/null +++ b/common/test/common_tests/helpers/components.cljc @@ -0,0 +1,153 @@ +;; 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 common-tests.helpers.components + (:require + [app.common.data.macros :as dm] + [app.common.files.changes-builder :as pcb] + [app.common.files.helpers :as cfh] + [app.common.geom.point :as gpt] + [app.common.logic.libraries :as cll] + [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape-tree :as ctst] + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi] + [common-tests.helpers.shapes :as ths])) + +(defn make-component + [file label root-label & {:keys [] :as params}] + (let [page (thf/current-page file) + root (ths/get-shape file root-label)] + + (dm/assert! + "Need that root is already a frame" + (cfh/frame-shape? root)) + + (let [[_new-root _new-shapes updated-shapes] + (ctn/convert-shape-in-component root (:objects page) (:id file)) + + updated-root (first updated-shapes)] ; Can't use new-root because it has a new id + + (thi/set-id! label (:component-id updated-root)) + + (ctf/update-file-data + file + (fn [file-data] + (as-> file-data $ + (reduce (fn [file-data shape] + (ctpl/update-page file-data + (:id page) + #(update % :objects assoc (:id shape) shape))) + $ + updated-shapes) + (ctkl/add-component $ (assoc params + :id (:component-id updated-root) + :name (:name updated-root) + :main-instance-id (:id updated-root) + :main-instance-page (:id page) + :shapes updated-shapes)))))))) + +(defn get-component + [file label] + (ctkl/get-component (:data file) (thi/id label))) + +(defn get-component-by-id + [file id] + (ctkl/get-component (:data file) id)) + +(defn set-child-label + [file shape-label child-idx label] + (let [id (-> (ths/get-shape file shape-label) + :shapes + (nth child-idx))] + (when id + (thi/set-id! label id)))) + +(defn instantiate-component + [file component-label copy-root-label & {:keys [parent-label library children-labels] :as params}] + (let [page (thf/current-page file) + library (or library file) + component (get-component library component-label) + parent-id (when parent-label + (thi/id parent-label)) + parent (when parent-id + (ctst/get-shape page parent-id)) + frame-id (if (cfh/frame-shape? parent) + (:id parent) + (:frame-id parent)) + + [copy-root copy-shapes] + (ctn/make-component-instance page + component + (:data library) + (gpt/point 100 100) + true + {:force-id (thi/new-id! copy-root-label) + :force-frame-id frame-id}) + + copy-root' (cond-> copy-root + (some? parent) + (assoc :parent-id parent-id) + + (some? frame-id) + (assoc :frame-id frame-id) + + (and (some? parent) (ctn/in-any-component? (:objects page) parent)) + (dissoc :component-root)) + file' (ctf/update-file-data + file + (fn [file-data] + (as-> file-data $ + (ctpl/update-page $ + (:id page) + #(ctst/add-shape (:id copy-root') + copy-root' + % + frame-id + parent-id + nil + true)) + (reduce (fn [file-data shape] + (ctpl/update-page file-data + (:id page) + #(ctst/add-shape (:id shape) + shape + % + (:parent-id shape) + (:frame-id shape) + nil + true))) + $ + (remove #(= (:id %) (:did copy-root')) copy-shapes)))))] + (when children-labels + (dotimes [idx (count children-labels)] + (set-child-label file' copy-root-label idx (nth children-labels idx)))) + file')) + +(defn component-swap + [file shape-label new-component-label new-shape-label & {:keys [library] :as params}] + (let [shape (ths/get-shape file shape-label) + library (or library file) + libraries {(:id library) library} + page (thf/current-page file) + objects (:objects page) + id-new-component (-> (get-component library new-component-label) + :id) + + ;; Store the properties that need to be maintained when the component is swapped + keep-props-values (select-keys shape ctk/swap-keep-attrs) + + + [new_shape _ changes] + (-> (pcb/empty-changes nil (:id page)) + (cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))] + + (thi/set-id! new-shape-label (:id new_shape)) + (thf/apply-changes file changes))) diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc index 50e367afde..cf1a02d9a1 100644 --- a/common/test/common_tests/helpers/compositions.cljc +++ b/common/test/common_tests/helpers/compositions.cljc @@ -7,13 +7,14 @@ (ns common-tests.helpers.compositions (:require [app.common.data :as d] - [common-tests.helpers.files :as thf])) + [common-tests.helpers.components :as thc] + [common-tests.helpers.shapes :as ths])) (defn add-rect [file rect-label & {:keys [] :as params}] ;; Generated shape tree: ;; :rect-label [:type :rect :name: Rect1] - (thf/add-sample-shape file rect-label + (ths/add-sample-shape file rect-label (merge {:type :rect :name "Rect1"} params))) @@ -22,7 +23,7 @@ [file frame-label & {:keys [] :as params}] ;; Generated shape tree: ;; :frame-label [:type :frame :name: Frame1] - (thf/add-sample-shape file frame-label + (ths/add-sample-shape file frame-label (merge {:type :frame :name "Frame1"} params))) @@ -34,7 +35,7 @@ ;; :child-label [:name: Rect1] (-> file (add-frame frame-label frame-params) - (thf/add-sample-shape child-label + (ths/add-sample-shape child-label (merge {:type :rect :name "Rect1" :parent-label frame-label} @@ -48,7 +49,7 @@ ;; :child-label [:name: Rect1] (-> file (add-frame-with-child root-label child-label :frame-params root-params :child-params child-params) - (thf/make-component component-label root-label component-params))) + (thc/make-component component-label root-label component-params))) (defn add-simple-component-with-copy [file component-label main-root-label main-child-label copy-root-label @@ -66,7 +67,7 @@ :component-params component-params :root-params main-root-params :child-params main-child-params) - (thf/instantiate-component component-label copy-root-label copy-root-params))) + (thc/instantiate-component component-label copy-root-label copy-root-params))) (defn add-component-with-many-children [file component-label root-label child-labels @@ -79,7 +80,7 @@ (as-> file $ (add-frame $ root-label root-params) (reduce (fn [file [index [label params]]] - (thf/add-sample-shape file + (ths/add-sample-shape file label (merge {:type :rect :name (str "Rect" (inc index)) @@ -87,7 +88,7 @@ params))) $ (d/enumerate (d/zip-all child-labels child-params-list))) - (thf/make-component $ component-label root-label component-params))) + (thc/make-component $ component-label root-label component-params))) (defn add-component-with-many-children-and-copy [file component-label main-root-label main-child-labels copy-root-label @@ -109,7 +110,7 @@ :component-params component-params :root-params main-root-params :child-params-list main-child-params-list) - (thf/instantiate-component component-label copy-root-label copy-root-params))) + (thc/instantiate-component component-label copy-root-label copy-root-params))) (defn add-nested-component [file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label @@ -130,11 +131,11 @@ :child-params main1-child-params) (add-frame main2-root-label (merge {:name "Frame2"} main2-root-params)) - (thf/instantiate-component component1-label + (thc/instantiate-component component1-label nested-head-label (assoc nested-head-params :parent-label main2-root-label)) - (thf/make-component component2-label + (thc/make-component component2-label main2-root-label component2-params))) @@ -165,4 +166,4 @@ :component2-params component2-params :main2-root-params main2-root-params :nested-head-params nested-head-params) - (thf/instantiate-component component2-label copy2-label copy2-params))) \ No newline at end of file + (thc/instantiate-component component2-label copy2-label copy2-params))) \ No newline at end of file diff --git a/common/test/common_tests/helpers/debug.cljc b/common/test/common_tests/helpers/debug.cljc deleted file mode 100644 index 5ecdcdb715..0000000000 --- a/common/test/common_tests/helpers/debug.cljc +++ /dev/null @@ -1,53 +0,0 @@ -(ns common-tests.helpers.debug - (:require - [app.common.data :as d] - [app.common.uuid :as uuid] - [common-tests.helpers.ids-map :as thi] - [cuerdas.core :as str])) - -(defn dump-shape - "Dumps a shape, with each attribute in a line" - [shape] - (println "{") - (doseq [[k v] (sort shape)] - (when (some? v) - (println (str " " k " : " v)))) - (println "}")) - -(defn- stringify-keys [m keys] - (apply str (interpose ", " (map #(str % ": " (get m %)) keys)))) - -(defn- dump-page-shape - [shape keys padding] - (println (str/pad (str padding - (when (:main-instance shape) "{") - (or (thi/label (:id shape)) "") - (when (:main-instance shape) "}") - (when keys - (str " [" (stringify-keys shape keys) "]"))) - {:length 40 :type :right}) - (if (nil? (:shape-ref shape)) - (if (:component-root shape) - (str "# [Component " (or (thi/label (:component-id shape)) "") "]") - "") - (str/format "%s--> %s%s" - (cond (:component-root shape) "#" - (:component-id shape) "@" - :else "-") - (if (:component-root shape) - (str "[Component " (or (thi/label (:component-id shape)) "") "] ") - "") - (or (thi/label (:shape-ref shape)) ""))))) - -(defn dump-page - "Dumps the layer tree of the page. Prints the label of each shape, and the specified keys. - Example: (thd/dump-page (thf/current-page file) [:id :touched])" - ([page keys] - (dump-page page uuid/zero "" keys)) - ([page root-id padding keys] - (let [lookupf (d/getf (:objects page)) - root-shape (lookupf root-id) - shapes (map lookupf (:shapes root-shape))] - (doseq [shape shapes] - (dump-page-shape shape keys padding) - (dump-page page (:id shape) (str padding " ") keys))))) diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index c7de668801..2d893104ff 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -6,29 +6,17 @@ (ns common-tests.helpers.files (:require - [app.common.colors :as clr] - [app.common.data.macros :as dm] + [app.common.data :as d] [app.common.features :as ffeat] [app.common.files.changes :as cfc] - [app.common.files.changes-builder :as pcb] - [app.common.files.helpers :as cfh] [app.common.files.validate :as cfv] - [app.common.geom.point :as gpt] - [app.common.logic.libraries :as cll] [app.common.pprint :refer [pprint]] - [app.common.types.color :as ctc] - [app.common.types.colors-list :as ctcl] - [app.common.types.component :as ctk] - [app.common.types.components-list :as ctkl] - [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] - [app.common.types.shape :as cts] - [app.common.types.shape-tree :as ctst] - [app.common.types.typographies-list :as cttl] - [app.common.types.typography :as ctt] - [common-tests.helpers.ids-map :as thi])) + [app.common.uuid :as uuid] + [common-tests.helpers.ids-map :as thi] + [cuerdas.core :as str])) ;; ----- Files @@ -68,25 +56,6 @@ (validate-file! file') file')) -(declare current-page-id) -(declare get-page) - -(defn dump-file - [file & {:keys [page-label libraries] :as params}] - (let [params (-> params - (or {:show-ids true :show-touched true}) - (dissoc page-label libraries)) - page (if (some? page-label) - (:id (get-page file page-label)) - (current-page-id file)) - libraries (or libraries {})] - - (ctf/dump-tree file page libraries params))) - -(defn pprint-file - [file & {:keys [level length] :or {level 10 length 1000}}] - (pprint file {:level level :length length})) - ;; ----- Pages (defn sample-page @@ -116,221 +85,74 @@ [file label] (vary-meta file assoc :current-page-id (thi/id label))) -;; ----- Shapes +;; ----- Debug -(defn sample-shape - [label & {:keys [type] :as params}] - (let [params (cond-> params - label - (assoc :id (thi/new-id! label)) +(defn dump-file-type + "Dump a file using dump-tree function in common.types.file." + [file & {:keys [page-label libraries] :as params}] + (let [params (-> params + (or {:show-ids true :show-touched true}) + (dissoc page-label libraries)) + page (if (some? page-label) + (:id (get-page file page-label)) + (current-page-id file)) + libraries (or libraries {})] - (nil? type) - (assoc :type :rect))] + (ctf/dump-tree file page libraries params))) - (cts/setup-shape params))) +(defn pprint-file + "Pretry print a file trying to limit the quantity of info shown." + [file & {:keys [level length] :or {level 10 length 1000}}] + (pprint file {:level level :length length})) -(defn add-sample-shape - [file label & {:keys [parent-label] :as params}] - (let [page (current-page file) - shape (sample-shape label (dissoc params :parent-label)) - parent-id (when parent-label - (thi/id parent-label)) - parent (when parent-id - (ctst/get-shape page parent-id)) - frame-id (if (cfh/frame-shape? parent) - (:id parent) - (:frame-id parent))] - (ctf/update-file-data - file - (fn [file-data] - (ctpl/update-page file-data - (:id page) - #(ctst/add-shape (:id shape) - shape - % - frame-id - parent-id - nil - true)))))) +(defn dump-shape + "Dump a shape, with each attribute in a line." + [shape] + (println "{") + (doseq [[k v] (sort shape)] + (when (some? v) + (println (str " " k " : " v)))) + (println "}")) -(defn get-shape - [file label & {:keys [page-label]}] - (let [page (if page-label - (get-page file page-label) - (current-page file))] - (ctst/get-shape page (thi/id label)))) +(defn- stringify-keys [m keys] + (apply str (interpose ", " (map #(str % ": " (get m %)) keys)))) -(defn get-shape-by-id - [file id & {:keys [page-label]}] - (let [page (if page-label - (get-page file page-label) - (current-page file))] - (ctst/get-shape page id))) +(defn- dump-page-shape + [shape keys padding] + (println (str/pad (str padding + (when (:main-instance shape) "{") + (or (thi/label (:id shape)) "") + (when (:main-instance shape) "}") + (when keys + (str " [" (stringify-keys shape keys) "]"))) + {:length 40 :type :right}) + (if (nil? (:shape-ref shape)) + (if (:component-root shape) + (str "# [Component " (or (thi/label (:component-id shape)) "") "]") + "") + (str/format "%s--> %s%s" + (cond (:component-root shape) "#" + (:component-id shape) "@" + :else "-") + (if (:component-root shape) + (str "[Component " (or (thi/label (:component-id shape)) "") "] ") + "") + (or (thi/label (:shape-ref shape)) ""))))) -;; ----- Components +(defn dump-page + "Dump the layer tree of the page. Print the label of each shape, and the specified keys." + ([page keys] + (dump-page page uuid/zero "" keys)) + ([page root-id padding keys] + (let [lookupf (d/getf (:objects page)) + root-shape (lookupf root-id) + shapes (map lookupf (:shapes root-shape))] + (doseq [shape shapes] + (dump-page-shape shape keys padding) + (dump-page page (:id shape) (str padding " ") keys))))) -(defn make-component - [file label root-label & {:keys [] :as params}] - (let [page (current-page file) - root (get-shape file root-label)] - - (dm/assert! - "Need that root is already a frame" - (cfh/frame-shape? root)) - - (let [[_new-root _new-shapes updated-shapes] - (ctn/convert-shape-in-component root (:objects page) (:id file)) - - updated-root (first updated-shapes)] ; Can't use new-root because it has a new id - - (thi/set-id! label (:component-id updated-root)) - - (ctf/update-file-data - file - (fn [file-data] - (as-> file-data $ - (reduce (fn [file-data shape] - (ctpl/update-page file-data - (:id page) - #(update % :objects assoc (:id shape) shape))) - $ - updated-shapes) - (ctkl/add-component $ (assoc params - :id (:component-id updated-root) - :name (:name updated-root) - :main-instance-id (:id updated-root) - :main-instance-page (:id page) - :shapes updated-shapes)))))))) - -(defn get-component - [file label] - (ctkl/get-component (:data file) (thi/id label))) - -(defn get-component-by-id - [file id] - (ctkl/get-component (:data file) id)) - -(defn set-child-label - [file shape-label child-idx label] - (let [id (-> (get-shape file shape-label) - :shapes - (nth child-idx))] - (when id - (thi/set-id! label id)))) - -(defn instantiate-component - [file component-label copy-root-label & {:keys [parent-label library children-labels] :as params}] - (let [page (current-page file) - library (or library file) - component (get-component library component-label) - parent-id (when parent-label - (thi/id parent-label)) - parent (when parent-id - (ctst/get-shape page parent-id)) - frame-id (if (cfh/frame-shape? parent) - (:id parent) - (:frame-id parent)) - - [copy-root copy-shapes] - (ctn/make-component-instance page - component - (:data library) - (gpt/point 100 100) - true - {:force-id (thi/new-id! copy-root-label) - :force-frame-id frame-id}) - - copy-root' (cond-> copy-root - (some? parent) - (assoc :parent-id parent-id) - - (some? frame-id) - (assoc :frame-id frame-id) - - (and (some? parent) (ctn/in-any-component? (:objects page) parent)) - (dissoc :component-root)) - file' (ctf/update-file-data - file - (fn [file-data] - (as-> file-data $ - (ctpl/update-page $ - (:id page) - #(ctst/add-shape (:id copy-root') - copy-root' - % - frame-id - parent-id - nil - true)) - (reduce (fn [file-data shape] - (ctpl/update-page file-data - (:id page) - #(ctst/add-shape (:id shape) - shape - % - (:parent-id shape) - (:frame-id shape) - nil - true))) - $ - (remove #(= (:id %) (:did copy-root')) copy-shapes)))))] - (when children-labels - (dotimes [idx (count children-labels)] - (set-child-label file' copy-root-label idx (nth children-labels idx)))) - file')) - - - -(defn component-swap - [file shape-label new-component-label new-shape-label & {:keys [library] :as params}] - (let [shape (get-shape file shape-label) - library (or library file) - libraries {(:id library) library} - page (current-page file) - objects (:objects page) - id-new-component (-> (get-component library new-component-label) - :id) - - ;; Store the properties that need to be maintained when the component is swapped - keep-props-values (select-keys shape ctk/swap-keep-attrs) - - - [new_shape _ changes] - (-> (pcb/empty-changes nil (:id page)) - (cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))] - - (thi/set-id! new-shape-label (:id new_shape)) - (apply-changes file changes))) - - -(defn sample-color - [label & {:keys [] :as params}] - (ctc/make-color (assoc params :id (thi/new-id! label)))) - -(defn sample-fill-color - [& {:keys [fill-color fill-opacity] :as params}] - (let [params (cond-> params - (nil? fill-color) - (assoc :fill-color clr/black) - - (nil? fill-opacity) - (assoc :fill-opacity 1))] - params)) - -(defn sample-fills-color - [& {:keys [] :as params}] - [(sample-fill-color params)]) - -(defn add-sample-library-color - [file label & {:keys [] :as params}] - (let [color (sample-color label params)] - (ctf/update-file-data file #(ctcl/add-color % color)))) - -(defn sample-typography - [label & {:keys [] :as params}] - (ctt/make-typography (assoc params :id (thi/new-id! label)))) - -(defn add-sample-typography - [file label & {:keys [] :as params}] - (let [typography (sample-typography label params)] - (ctf/update-file-data file #(cttl/add-typography % typography)))) +(defn dump-file + "Dump the current page of the file, using dump-page above. + Example: (thf/dump-file file [:id :touched])" + ([file] (dump-file file [])) + ([file keys] (dump-page (current-page file) keys))) diff --git a/common/test/common_tests/helpers/shapes.cljc b/common/test/common_tests/helpers/shapes.cljc new file mode 100644 index 0000000000..53521cbe54 --- /dev/null +++ b/common/test/common_tests/helpers/shapes.cljc @@ -0,0 +1,101 @@ +;; 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 common-tests.helpers.shapes + (:require + [app.common.colors :as clr] + [app.common.files.helpers :as cfh] + [app.common.types.color :as ctc] + [app.common.types.colors-list :as ctcl] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctst] + [app.common.types.typographies-list :as cttl] + [app.common.types.typography :as ctt] + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) + +(defn sample-shape + [label & {:keys [type] :as params}] + (let [params (cond-> params + label + (assoc :id (thi/new-id! label)) + + (nil? type) + (assoc :type :rect))] + + (cts/setup-shape params))) + +(defn add-sample-shape + [file label & {:keys [parent-label] :as params}] + (let [page (thf/current-page file) + shape (sample-shape label (dissoc params :parent-label)) + parent-id (when parent-label + (thi/id parent-label)) + parent (when parent-id + (ctst/get-shape page parent-id)) + frame-id (if (cfh/frame-shape? parent) + (:id parent) + (:frame-id parent))] + (ctf/update-file-data + file + (fn [file-data] + (ctpl/update-page file-data + (:id page) + #(ctst/add-shape (:id shape) + shape + % + frame-id + parent-id + nil + true)))))) + +(defn get-shape + [file label & {:keys [page-label]}] + (let [page (if page-label + (thf/get-page file page-label) + (thf/current-page file))] + (ctst/get-shape page (thi/id label)))) + +(defn get-shape-by-id + [file id & {:keys [page-label]}] + (let [page (if page-label + (thf/get-page file page-label) + (thf/current-page file))] + (ctst/get-shape page id))) + +(defn sample-color + [label & {:keys [] :as params}] + (ctc/make-color (assoc params :id (thi/new-id! label)))) + +(defn sample-fill-color + [& {:keys [fill-color fill-opacity] :as params}] + (let [params (cond-> params + (nil? fill-color) + (assoc :fill-color clr/black) + + (nil? fill-opacity) + (assoc :fill-opacity 1))] + params)) + +(defn sample-fills-color + [& {:keys [] :as params}] + [(sample-fill-color params)]) + +(defn add-sample-library-color + [file label & {:keys [] :as params}] + (let [color (sample-color label params)] + (ctf/update-file-data file #(ctcl/add-color % color)))) + +(defn sample-typography + [label & {:keys [] :as params}] + (ctt/make-typography (assoc params :id (thi/new-id! label)))) + +(defn add-sample-typography + [file label & {:keys [] :as params}] + (let [typography (sample-typography label params)] + (ctf/update-file-data file #(cttl/add-typography % typography)))) diff --git a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc index dcbd5cc482..331ee0e8aa 100644 --- a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc +++ b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc @@ -11,9 +11,11 @@ [app.common.types.component :as ctk] [app.common.uuid :as uuid] [clojure.test :as t] + [common-tests.helpers.components :as thc] [common-tests.helpers.compositions :as tho] [common-tests.helpers.files :as thf] - [common-tests.helpers.ids-map :as thi])) + [common-tests.helpers.ids-map :as thi] + [common-tests.helpers.shapes :as ths])) (t/use-fixtures :each thi/test-fixture) @@ -34,28 +36,28 @@ (-> (thf/sample-file :file1) (tho/add-frame :frame-red) - (thf/make-component :red :frame-red) + (thc/make-component :red :frame-red) (tho/add-frame :frame-blue) - (thf/make-component :blue :frame-blue) + (thc/make-component :blue :frame-blue) (tho/add-frame :frame-green) - (thf/make-component :green :frame-green) - (thf/instantiate-component :red :red-copy-green :parent-label :frame-green) + (thc/make-component :green :frame-green) + (thc/instantiate-component :red :red-copy-green :parent-label :frame-green) (tho/add-frame :frame-b1) - (thf/make-component :b1 :frame-b1) + (thc/make-component :b1 :frame-b1) (tho/add-frame :frame-yellow :parent-label :frame-b1) - (thf/instantiate-component :red :red-copy :parent-label :frame-b1) - (thf/component-swap :red-copy :blue :blue1) - (thf/instantiate-component :green :green-copy :parent-label :frame-b1 :children-labels [:red-copy-in-green-copy]) - (thf/component-swap :red-copy-in-green-copy :blue :blue-copy-in-green-copy) + (thc/instantiate-component :red :red-copy :parent-label :frame-b1) + (thc/component-swap :red-copy :blue :blue1) + (thc/instantiate-component :green :green-copy :parent-label :frame-b1 :children-labels [:red-copy-in-green-copy]) + (thc/component-swap :red-copy-in-green-copy :blue :blue-copy-in-green-copy) (tho/add-frame :frame-b2) - (thf/make-component :b2 :frame-b2))) + (thc/make-component :b2 :frame-b2))) (t/deftest test-keep-swap-slot-relocating-blue1-to-root (let [;; ==== Setup file (setup-file) page (thf/current-page file) - blue1 (thf/get-shape file :blue1) + blue1 (ths/get-shape file :blue1) ;; ==== Action changes (cls/generate-relocate-shapes (pcb/empty-changes nil) @@ -68,7 +70,7 @@ file' (thf/apply-changes file changes) ;; ==== Get - blue1' (thf/get-shape file' :blue1)] + blue1' (ths/get-shape file' :blue1)] ;; ==== Check @@ -83,7 +85,7 @@ (let [;; ==== Setup file (setup-file) page (thf/current-page file) - blue1 (thf/get-shape file :blue1) + blue1 (ths/get-shape file :blue1) ;; ==== Action changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) @@ -97,7 +99,7 @@ file' (thf/apply-changes file changes) ;; ==== Get - blue1' (thf/get-shape file' :blue1)] + blue1' (ths/get-shape file' :blue1)] ;; ==== Check @@ -113,8 +115,8 @@ (let [;; ==== Setup file (setup-file) page (thf/current-page file) - blue1 (thf/get-shape file :blue1) - b2 (thf/get-shape file :frame-b2) + blue1 (ths/get-shape file :blue1) + b2 (ths/get-shape file :frame-b2) ;; ==== Action @@ -128,7 +130,7 @@ file' (thf/apply-changes file changes) ;; ==== Get - blue1' (thf/get-shape file' :blue1)] + blue1' (ths/get-shape file' :blue1)] ;; ==== Check @@ -143,8 +145,8 @@ (let [;; ==== Setup file (setup-file) page (thf/current-page file) - blue1 (thf/get-shape file :blue1) - b2 (thf/get-shape file :frame-b2) + blue1 (ths/get-shape file :blue1) + b2 (ths/get-shape file :frame-b2) ;; ==== Action @@ -159,7 +161,7 @@ file' (thf/apply-changes file changes) ;; ==== Get - blue1' (thf/get-shape file' :blue1)] + blue1' (ths/get-shape file' :blue1)] ;; ==== Check @@ -174,8 +176,8 @@ (let [;; ==== Setup file (setup-file) page (thf/current-page file) - blue1 (thf/get-shape file :blue1) - yellow (thf/get-shape file :frame-yellow) + blue1 (ths/get-shape file :blue1) + yellow (ths/get-shape file :frame-yellow) ;; ==== Action ;; Move blue1 into yellow @@ -189,7 +191,7 @@ file' (thf/apply-changes file changes) page' (thf/current-page file') - yellow' (thf/get-shape file' :frame-yellow) + yellow' (ths/get-shape file' :frame-yellow) ;; Move yellow into root changes' (cls/generate-relocate-shapes (pcb/empty-changes nil) @@ -202,7 +204,7 @@ file'' (thf/apply-changes file' changes') ;; ==== Get - blue1'' (thf/get-shape file'' :blue1)] + blue1'' (ths/get-shape file'' :blue1)] ;; ==== Check @@ -217,11 +219,10 @@ (let [;; ==== Setup file (setup-file) page (thf/current-page file) - blue1 (thf/get-shape file :blue1) - yellow (thf/get-shape file :frame-yellow) + blue1 (ths/get-shape file :blue1) + yellow (ths/get-shape file :frame-yellow) ;; ==== Action - ;; Move blue1 into yellow changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) #{(:id blue1)} ;; ids @@ -233,7 +234,7 @@ file' (thf/apply-changes file changes) page' (thf/current-page file') - yellow' (thf/get-shape file' :frame-yellow) + yellow' (ths/get-shape file' :frame-yellow) ;; Move yellow into root changes' (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) @@ -246,7 +247,7 @@ file'' (thf/apply-changes file' changes') ;; ==== Get - blue1'' (thf/get-shape file'' :blue1)] + blue1'' (ths/get-shape file'' :blue1)] ;; ==== Check @@ -262,11 +263,10 @@ (let [;; ==== Setup file (setup-file) page (thf/current-page file) - blue1 (thf/get-shape file :blue1) - yellow (thf/get-shape file :frame-yellow) + blue1 (ths/get-shape file :blue1) + yellow (ths/get-shape file :frame-yellow) ;; ==== Action - ;; Move blue1 into yellow changes (cls/generate-relocate-shapes (pcb/empty-changes nil) (:objects page) @@ -278,8 +278,8 @@ file' (thf/apply-changes file changes) page' (thf/current-page file') - yellow' (thf/get-shape file' :frame-yellow) - b2' (thf/get-shape file' :frame-b2) + yellow' (ths/get-shape file' :frame-yellow) + b2' (ths/get-shape file' :frame-b2) ;; Move yellow into b2 changes' (cls/generate-relocate-shapes (pcb/empty-changes nil) @@ -292,7 +292,7 @@ file'' (thf/apply-changes file' changes') ;; ==== Get - blue1'' (thf/get-shape file'' :blue1)] + blue1'' (ths/get-shape file'' :blue1)] ;; ==== Check @@ -307,11 +307,10 @@ (let [;; ==== Setup file (setup-file) page (thf/current-page file) - blue1 (thf/get-shape file :blue1) - yellow (thf/get-shape file :frame-yellow) + blue1 (ths/get-shape file :blue1) + yellow (ths/get-shape file :frame-yellow) ;; ==== Action - ;; Move blue1 into yellow changes (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) #{(:id blue1)} ;; ids @@ -323,8 +322,8 @@ file' (thf/apply-changes file changes) page' (thf/current-page file') - yellow' (thf/get-shape file' :frame-yellow) - b2' (thf/get-shape file' :frame-b2) + yellow' (ths/get-shape file' :frame-yellow) + b2' (ths/get-shape file' :frame-b2) ;; Move yellow into b2 changes' (cls/generate-move-shapes-to-frame (pcb/empty-changes nil) @@ -338,7 +337,7 @@ file'' (thf/apply-changes file' changes') ;; ==== Get - blue1'' (thf/get-shape file'' :blue1)] + blue1'' (ths/get-shape file'' :blue1)] ;; ==== Check diff --git a/common/test/common_tests/logic/component_creation_test.cljc b/common/test/common_tests/logic/component_creation_test.cljc index daf1565f8f..13a7533b85 100644 --- a/common/test/common_tests/logic/component_creation_test.cljc +++ b/common/test/common_tests/logic/component_creation_test.cljc @@ -9,18 +9,20 @@ [app.common.files.changes-builder :as pcb] [app.common.logic.libraries :as cll] [clojure.test :as t] + [common-tests.helpers.components :as thc] [common-tests.helpers.files :as thf] - [common-tests.helpers.ids-map :as thi])) + [common-tests.helpers.ids-map :as thi] + [common-tests.helpers.shapes :as ths])) (t/use-fixtures :each thi/test-fixture) (t/deftest test-add-component-from-single-shape (let [;; ==== Setup file (-> (thf/sample-file :file1) - (thf/add-sample-shape :shape1 :type :frame)) + (ths/add-sample-shape :shape1 :type :frame)) page (thf/current-page file) - shape1 (thf/get-shape file :shape1) + shape1 (ths/get-shape file :shape1) ;; ==== Action [_ component-id changes] @@ -36,8 +38,8 @@ file' (thf/apply-changes file changes) ;; ==== Get - component (thf/get-component-by-id file' component-id) - root (thf/get-shape-by-id file' (:main-instance-id component))] + component (thc/get-component-by-id file' component-id) + root (ths/get-shape-by-id file' (:main-instance-id component))] ;; ==== Check (t/is (some? component)) diff --git a/common/test/common_tests/logic/components_touched_test.cljc b/common/test/common_tests/logic/components_touched_test.cljc index d409c8d5f4..c7bc5bd620 100644 --- a/common/test/common_tests/logic/components_touched_test.cljc +++ b/common/test/common_tests/logic/components_touched_test.cljc @@ -11,7 +11,8 @@ [clojure.test :as t] [common-tests.helpers.compositions :as tho] [common-tests.helpers.files :as thf] - [common-tests.helpers.ids-map :as thi])) + [common-tests.helpers.ids-map :as thi] + [common-tests.helpers.shapes :as ths])) (t/use-fixtures :each thi/test-fixture) @@ -22,14 +23,14 @@ :main-root :main-child :copy-root - :main-child-params {:fills (thf/sample-fills-color + :main-child-params {:fills (ths/sample-fills-color :fill-color "#abcdef")})) page (thf/current-page file) - copy-root (thf/get-shape file :copy-root) + copy-root (ths/get-shape file :copy-root) ;; ==== Action update-fn (fn [shape] - (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) + (assoc shape :fills (ths/sample-fills-color :fill-color "#fabada"))) changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) (:shapes copy-root) @@ -40,8 +41,8 @@ file' (thf/apply-changes file changes) ;; ==== Get - copy-root' (thf/get-shape file' :copy-root) - copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root'))) + copy-root' (ths/get-shape file' :copy-root) + copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root'))) fills' (:fills copy-child') fill' (first fills')] @@ -59,10 +60,10 @@ :main-root :main-child :copy-root) - (thf/add-sample-shape :free-shape)) + (ths/add-sample-shape :free-shape)) page (thf/current-page file) - copy-root (thf/get-shape file :copy-root) + copy-root (ths/get-shape file :copy-root) ;; ==== Action @@ -79,8 +80,8 @@ file' (thf/apply-changes file changes) ;; ==== Get - copy-root' (thf/get-shape file' :copy-root) - copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] + copy-root' (ths/get-shape file' :copy-root) + copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))] ;; ==== Check (t/is (= (:touched copy-root') nil)) @@ -95,7 +96,7 @@ :copy-root)) page (thf/current-page file) - copy-root (thf/get-shape file :copy-root) + copy-root (ths/get-shape file :copy-root) ;; ==== Action @@ -112,8 +113,8 @@ file' (thf/apply-changes file changes) ;; ==== Get - copy-root' (thf/get-shape file' :copy-root) - copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] + copy-root' (ths/get-shape file' :copy-root) + copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))] ;; ==== Check (t/is (= (:touched copy-root') nil)) @@ -126,11 +127,11 @@ :main-root [:main-child1 :main-child2 :main-child3] :copy-root) - (thf/add-sample-shape :free-shape)) + (ths/add-sample-shape :free-shape)) page (thf/current-page file) - copy-root (thf/get-shape file :copy-root) - copy-child1 (thf/get-shape-by-id file (first (:shapes copy-root))) + copy-root (ths/get-shape file :copy-root) + copy-child1 (ths/get-shape-by-id file (first (:shapes copy-root))) ;; ==== Action @@ -147,8 +148,8 @@ file' (thf/apply-changes file changes) ;; ==== Get - copy-root' (thf/get-shape file' :copy-root) - copy-child' (thf/get-shape-by-id file' (first (:shapes copy-root')))] + copy-root' (ths/get-shape file' :copy-root) + copy-child' (ths/get-shape-by-id file' (first (:shapes copy-root')))] ;; ==== Check (t/is (= (:touched copy-root') nil)) @@ -164,14 +165,14 @@ :main2-root :main2-nested-head :copy2-root - :root2-params {:fills (thf/sample-fills-color + :root2-params {:fills (ths/sample-fills-color :fill-color "#abcdef")})) page (thf/current-page file) - copy2-root (thf/get-shape file :copy2-root) + copy2-root (ths/get-shape file :copy2-root) ;; ==== Action update-fn (fn [shape] - (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) + (assoc shape :fills (ths/sample-fills-color :fill-color "#fabada"))) changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) #{(:id copy2-root)} @@ -182,7 +183,7 @@ file' (thf/apply-changes file changes) ;; ==== Get - copy2-root' (thf/get-shape file' :copy2-root) + copy2-root' (ths/get-shape file' :copy2-root) fills' (:fills copy2-root') fill' (first fills')] @@ -202,14 +203,14 @@ :main2-root :main2-nested-head :copy2-root - :nested-head-params {:fills (thf/sample-fills-color + :nested-head-params {:fills (ths/sample-fills-color :fill-color "#abcdef")})) page (thf/current-page file) - copy2-root (thf/get-shape file :copy2-root) + copy2-root (ths/get-shape file :copy2-root) ;; ==== Action update-fn (fn [shape] - (assoc shape :fills (thf/sample-fills-color :fill-color "#fabada"))) + (assoc shape :fills (ths/sample-fills-color :fill-color "#fabada"))) changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) (:shapes copy2-root) @@ -220,8 +221,8 @@ file' (thf/apply-changes file changes) ;; ==== Get - copy2-root' (thf/get-shape file' :copy2-root) - copy2-child' (thf/get-shape-by-id file' (first (:shapes copy2-root'))) + copy2-root' (ths/get-shape file' :copy2-root) + copy2-child' (ths/get-shape-by-id file' (first (:shapes copy2-root'))) fills' (:fills copy2-child') fill' (first fills')] diff --git a/common/test/common_tests/logic/swap_and_reset_test.cljc b/common/test/common_tests/logic/swap_and_reset_test.cljc index ba9746c3a0..40d3c7ef8c 100644 --- a/common/test/common_tests/logic/swap_and_reset_test.cljc +++ b/common/test/common_tests/logic/swap_and_reset_test.cljc @@ -11,9 +11,11 @@ [app.common.types.component :as ctk] [app.common.types.file :as ctf] [clojure.test :as t] - [common-tests.helpers.compositions :as thc] + [common-tests.helpers.components :as thc] + [common-tests.helpers.compositions :as tho] [common-tests.helpers.files :as thf] - [common-tests.helpers.ids-map :as thi])) + [common-tests.helpers.ids-map :as thi] + [common-tests.helpers.shapes :as ths])) (t/use-fixtures :each thi/test-fixture) @@ -21,16 +23,16 @@ (t/deftest test-simple-swap (let [;; ==== Setup file (-> (thf/sample-file :file1) - (thc/add-simple-component-with-copy :component-1 + (tho/add-simple-component-with-copy :component-1 :component-1-main-root :component-1-main-child :component-1-copy-root) - (thc/add-simple-component :component-2 + (tho/add-simple-component :component-2 :component-2-root :component-2-child)) - component-1-copy-root (thf/get-shape file :component-1-copy-root) - component-2 (thf/get-component file :component-2) + component-1-copy-root (ths/get-shape file :component-1-copy-root) + component-2 (thc/get-component file :component-2) page (thf/current-page file) ;; ==== Action @@ -49,7 +51,7 @@ file' (thf/apply-changes file changes) ;; ==== Get - swapped (thf/get-shape-by-id file' (:id new-shape))] + swapped (ths/get-shape-by-id file' (:id new-shape))] ;; ==== Check (t/is (not= (:component-id component-1-copy-root) (:component-id swapped))) @@ -60,22 +62,22 @@ (let [;; ==== Setup file (-> (thf/sample-file :file1) - (thc/add-simple-component :component-1 :component-1-main-root :component-1-main-child) - (thc/add-frame :component-container) - (thf/instantiate-component :component-1 :component-1-copy-root :parent-label :component-container) - (thf/make-component :component-container-main :component-container) - (thf/instantiate-component :component-container-main :component-container-instance) - (thc/add-simple-component :component-2 :component-2-main-root :component-2-main-child)) + (tho/add-simple-component :component-1 :component-1-main-root :component-1-main-child) + (tho/add-frame :component-container) + (thc/instantiate-component :component-1 :component-1-copy-root :parent-label :component-container) + (thc/make-component :component-container-main :component-container) + (thc/instantiate-component :component-container-main :component-container-instance) + (tho/add-simple-component :component-2 :component-2-main-root :component-2-main-child)) page (thf/current-page file) - component-2 (thf/get-component file :component-2) + component-2 (thc/get-component file :component-2) copy (->> - (thf/get-shape file :component-container-instance) + (ths/get-shape file :component-container-instance) :shapes first - (thf/get-shape-by-id file)) + (ths/get-shape-by-id file)) libraries {(:id file) file} @@ -97,8 +99,8 @@ page' (thf/current-page file') ;; ==== Get - swapped (thf/get-shape-by-id file' (:id new-shape)) - component-1-copy-root (thf/get-shape file' :component-1-copy-root) + swapped (ths/get-shape-by-id file' (:id new-shape)) + component-1-copy-root (ths/get-shape file' :component-1-copy-root) slot (-> (ctf/find-swap-slot swapped page' file' @@ -116,23 +118,23 @@ (let [;; ==== Setup file (-> (thf/sample-file :file1) - (thc/add-simple-component :component-1 :component-1-main-root :component-1-main-child) - (thc/add-frame :component-container) - (thf/instantiate-component :component-1 :component-1-copy-root :parent-label :component-container) - (thf/make-component :component-container-main :component-container) - (thf/instantiate-component :component-container-main :component-container-instance) - (thc/add-simple-component :component-2 :component-2-main-root :component-2-main-child)) + (tho/add-simple-component :component-1 :component-1-main-root :component-1-main-child) + (tho/add-frame :component-container) + (thc/instantiate-component :component-1 :component-1-copy-root :parent-label :component-container) + (thc/make-component :component-container-main :component-container) + (thc/instantiate-component :component-container-main :component-container-instance) + (tho/add-simple-component :component-2 :component-2-main-root :component-2-main-child)) page (thf/current-page file) - component-1 (thf/get-component file :component-1) - component-2 (thf/get-component file :component-2) + component-1 (thc/get-component file :component-1) + component-2 (thc/get-component file :component-2) copy (->> - (thf/get-shape file :component-container-instance) + (ths/get-shape file :component-container-instance) :shapes first - (thf/get-shape-by-id file)) + (ths/get-shape-by-id file)) ;; ==== Action [new-shape _all-parents changes-swap] @@ -163,10 +165,10 @@ ;; ==== Get reset (->> - (thf/get-shape file' :component-container-instance) + (ths/get-shape file' :component-container-instance) :shapes first - (thf/get-shape-by-id file'))] + (ths/get-shape-by-id file'))] ;; ==== Check (t/is (= (:id component-1) (:component-id reset))) diff --git a/common/test/common_tests/types/types_libraries_test.cljc b/common/test/common_tests/types/types_libraries_test.cljc index d95232824d..744a395187 100644 --- a/common/test/common_tests/types/types_libraries_test.cljc +++ b/common/test/common_tests/types/types_libraries_test.cljc @@ -15,9 +15,11 @@ [app.common.types.pages-list :as ctpl] [app.common.types.typographies-list :as ctyl] [clojure.test :as t] + [common-tests.helpers.components :as thc] [common-tests.helpers.compositions :as tho] [common-tests.helpers.files :as thf] - [common-tests.helpers.ids-map :as thi])) + [common-tests.helpers.ids-map :as thi] + [common-tests.helpers.shapes :as ths])) (t/use-fixtures :each thi/test-fixture) @@ -27,14 +29,14 @@ f3 (thf/sample-file :file3 :name "testing file") f4 (-> (thf/sample-file :file4 :page-label :page2) (thf/add-sample-page :page3 :name "testing page") - (thf/add-sample-shape :shape1)) + (ths/add-sample-shape :shape1)) f5 (-> f4 - (thf/add-sample-shape :shape2) + (ths/add-sample-shape :shape2) (thf/switch-to-page :page2) - (thf/add-sample-shape :shape3 :name "testing shape" :width 100)) - s1 (thf/get-shape f4 :shape1) - s2 (thf/get-shape f5 :shape2 :page-label :page3) - s3 (thf/get-shape f5 :shape3)] + (ths/add-sample-shape :shape3 :name "testing shape" :width 100)) + s1 (ths/get-shape f4 :shape1) + s2 (ths/get-shape f5 :shape2 :page-label :page3) + s3 (ths/get-shape f5 :shape3)] ;; (thf/pprint-file f4) @@ -75,7 +77,7 @@ (tho/add-simple-component :component1 :main-root :rect1)) file (-> (thf/sample-file :file) - (thf/instantiate-component :component1 :copy-root :library library)) + (thc/instantiate-component :component1 :copy-root :library library)) ;; Action file' (ctf/update-file-data @@ -89,7 +91,7 @@ components' (ctkl/components-seq (ctf/file-data file')) component' (first components') - copy-root' (thf/get-shape file' :copy-root) + copy-root' (ths/get-shape file' :copy-root) main-root' (ctf/get-ref-shape (ctf/file-data file') component' copy-root')] ;; Check @@ -106,11 +108,11 @@ (t/deftest test-absorb-colors (let [;; Setup library (-> (thf/sample-file :library :is-shared true) - (thf/add-sample-library-color :color1 {:name "Test color" + (ths/add-sample-library-color :color1 {:name "Test color" :color "#abcdef"})) file (-> (thf/sample-file :file) - (thf/add-sample-shape :shape1 + (ths/add-sample-shape :shape1 :type :rect :name "Rect1" :fills [{:fill-color "#abcdef" @@ -127,7 +129,7 @@ ;; Get colors' (ctcl/colors-seq (ctf/file-data file')) - shape1' (thf/get-shape file' :shape1) + shape1' (ths/get-shape file' :shape1) fill' (first (:fills shape1'))] ;; Check @@ -143,10 +145,10 @@ (t/deftest test-absorb-typographies (let [;; Setup library (-> (thf/sample-file :library :is-shared true) - (thf/add-sample-typography :typography1 {:name "Test typography"})) + (ths/add-sample-typography :typography1 {:name "Test typography"})) file (-> (thf/sample-file :file) - (thf/add-sample-shape :shape1 + (ths/add-sample-shape :shape1 :type :text :name "Text1" :content {:type "root" @@ -178,7 +180,7 @@ ;; Get typographies' (ctyl/typographies-seq (ctf/file-data file')) - shape1' (thf/get-shape file' :shape1) + shape1' (ths/get-shape file' :shape1) text-node' (d/seek #(some? (:text %)) (txt/node-seq (:content shape1')))] ;; Check