Compare commits
84 Commits
azazeln28-
...
2.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed5875f29a | ||
|
|
ad38a21053 | ||
|
|
adffac4eec | ||
|
|
73dfe12ec9 | ||
|
|
ff2e845f2c | ||
|
|
8e0a6e4123 | ||
|
|
0131cd6f8b | ||
|
|
288a7b21d6 | ||
|
|
32bd08533d | ||
|
|
c1aae12327 | ||
|
|
23a6f4b7c1 | ||
|
|
133e6e1e68 | ||
|
|
6abd045273 | ||
|
|
778a608854 | ||
|
|
a76a9fae41 | ||
|
|
f7cfbdd229 | ||
|
|
e28d2842f6 | ||
|
|
ccc3ca0948 | ||
|
|
515cbf7bef | ||
|
|
c320cbc47b | ||
|
|
46969585ed | ||
|
|
47882c5419 | ||
|
|
019d5e083a | ||
|
|
85f6cf32ae | ||
|
|
ded8e39e73 | ||
|
|
e730200873 | ||
|
|
4501d13961 | ||
|
|
baa1cfb2f8 | ||
|
|
905699d15a | ||
|
|
fe53869308 | ||
|
|
50076bac83 | ||
|
|
44bc4b7fa4 | ||
|
|
5c14f486d7 | ||
|
|
0cbd980b68 | ||
|
|
95dda2b1af | ||
|
|
5170872961 | ||
|
|
871ca68e1e | ||
|
|
0ab896fc76 | ||
|
|
6a4b548457 | ||
|
|
695a399941 | ||
|
|
a32463fada | ||
|
|
5d44c88988 | ||
|
|
ce87d797d1 | ||
|
|
7fde1436e1 | ||
|
|
e1c5a32fcb | ||
|
|
b262e6a46f | ||
|
|
2e726b62c3 | ||
|
|
02acd81c2c | ||
|
|
bae2de75ff | ||
|
|
b68c426cd1 | ||
|
|
5161ef15bf | ||
|
|
36d3d94ec9 | ||
|
|
17447d7610 | ||
|
|
708a40bff1 | ||
|
|
efaf6573bd | ||
|
|
001bcbce59 | ||
|
|
c195c07a3f | ||
|
|
f5298f51e7 | ||
|
|
46c440fef2 | ||
|
|
e77f8b572a | ||
|
|
ade5eecf80 | ||
|
|
97fc7702b8 | ||
|
|
54fcd58531 | ||
|
|
b7a8747f00 | ||
|
|
d00de7d5a4 | ||
|
|
5ae4dde222 | ||
|
|
2fbd4b07e0 | ||
|
|
58a843ea23 | ||
|
|
f6b97af148 | ||
|
|
76b7287bf1 | ||
|
|
019bc2f183 | ||
|
|
8c96a617be | ||
|
|
1f15e9b81e | ||
|
|
f7627e515a | ||
|
|
d08c94d5a6 | ||
|
|
01896501c1 | ||
|
|
3f9a1525ca | ||
|
|
52c1e227d5 | ||
|
|
955538b12a | ||
|
|
8254af27cb | ||
|
|
f76391ecbb | ||
|
|
c49e9fbf18 | ||
|
|
122701ee7b | ||
|
|
351362bb50 |
105
.github/workflows/build-bundles.yml
vendored
@@ -1,28 +1,14 @@
|
||||
name: Build and Upload Penpot Bundles non-prod
|
||||
name: Build and Upload Penpot Bundles
|
||||
|
||||
on:
|
||||
# Create bundler for every tag
|
||||
push:
|
||||
tags:
|
||||
- '**' # Pattern matched against refs/tags
|
||||
# Create bundler every hour between 5:00 and 20:00 on working days
|
||||
schedule:
|
||||
- cron: '0 5-20 * * 1-5'
|
||||
# Create bundler from manual action
|
||||
# Create bundle from manual action
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
zip_mode:
|
||||
# zip_mode defines how the build artifacts are packaged:
|
||||
# - 'individual': creates one ZIP file per component (frontend, backend, exporter)
|
||||
# - 'all': creates a single ZIP containing all components
|
||||
# - null: for the rest of cases (non-manual events)
|
||||
description: 'Bundle packaging mode'
|
||||
required: false
|
||||
default: 'individual'
|
||||
type: choice
|
||||
options:
|
||||
- individual
|
||||
- all
|
||||
gh_ref:
|
||||
description: 'Name of the branch'
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build-bundles:
|
||||
@@ -38,15 +24,15 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
- name: Extract somer useful variables
|
||||
- name: Extract some useful variables
|
||||
id: vars
|
||||
run: |
|
||||
echo "commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "gh_branch=${{ github.base_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
|
||||
# Set up Docker Buildx for multi-arch build
|
||||
- name: Set up Docker Buildx
|
||||
- name: Set up Docker Buildx for multi-arch build
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Run manage.sh build-bundle from host
|
||||
@@ -57,73 +43,22 @@ jobs:
|
||||
mkdir zips
|
||||
mv bundles penpot
|
||||
|
||||
- name: Create zip bundles for zip_mode == 'all'
|
||||
if: ${{ github.event.inputs.zip_mode == 'all' }}
|
||||
- name: Create zip bundles
|
||||
run: |
|
||||
echo "📦 Packaging Penpot 'all' bundles..."
|
||||
zip -r zips/penpot-all-bundles.zip penpot
|
||||
echo "📦 Packaging Penpot bundles..."
|
||||
zip -r zips/penpot.zip penpot
|
||||
|
||||
- name: Create zip bundles for zip_mode != 'all'
|
||||
if: ${{ github.event.inputs.zip_mode != 'all' }}
|
||||
- name: Upload Penpot bundle to S3
|
||||
run: |
|
||||
echo "📦 Packaging Penpot 'individual' bundles..."
|
||||
zip -r zips/penpot-frontend.zip penpot/frontend
|
||||
zip -r zips/penpot-backend.zip penpot/backend
|
||||
zip -r zips/penpot-exporter.zip penpot/exporter
|
||||
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.gh_branch}}-latest.zip
|
||||
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.commit_hash }}.zip
|
||||
|
||||
- name: Upload unified 'all' bundle
|
||||
if: ${{ github.event.inputs.zip_mode == 'all' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: penpot-all-bundles
|
||||
path: zips/penpot-all-bundles.zip
|
||||
|
||||
- name: Upload individual bundles
|
||||
if: ${{ github.event.inputs.zip_mode != 'all' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: penpot-individual-bundles
|
||||
path: |
|
||||
zips/penpot-frontend.zip
|
||||
zips/penpot-backend.zip
|
||||
zips/penpot-exporter.zip
|
||||
|
||||
- name: Upload unified 'all' bundle to S3
|
||||
if: ${{ github.event.inputs.zip_mode == 'all' }}
|
||||
run: |
|
||||
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.gh_branch}}.zip
|
||||
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.commit_hash }}.zip
|
||||
|
||||
- name: Upload 'individual' bundles to S3
|
||||
if: ${{ github.event.inputs.zip_mode != 'all' }}
|
||||
run: |
|
||||
for name in penpot-frontend penpot-backend penpot-exporter; do
|
||||
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.gh_branch }}-latest.zip
|
||||
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.commit_hash }}.zip
|
||||
done
|
||||
|
||||
- name: Notify Mattermost about automatic bundles
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Notify Mattermost
|
||||
if: failure()
|
||||
uses: mattermost/action-mattermost-notify@master
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
TEXT: |
|
||||
📦 *Penpot bundle automatically generated*
|
||||
📄 PR: ${{ github.event.pull_request.title }}
|
||||
🔁 From: \`${{ github.head_ref }}\` to \`{{ github.base_ref }}\`
|
||||
❌ *[PENPOT] Error during the execution of the job*
|
||||
📄 Triggered from ref: `${{ steps.vars.outputs.gh_branch}}`
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
- name: Notify Mattermost about manual bundles
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: mattermost/action-mattermost-notify@master
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
TEXT: |
|
||||
📦 *Penpot bundle manually generated*
|
||||
📄 Triggered from branch: `${{ github.ref_name}}`
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
- name: Print artifact summary URL
|
||||
run: |
|
||||
echo "📦 Artifacts available at:"
|
||||
echo "🔗 https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
12
.github/workflows/build-develop.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Build and Upload Penpot DEVELOP Bundles
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '16 5-20 * * 1-5'
|
||||
|
||||
jobs:
|
||||
build-develop-bundle:
|
||||
uses: ./.github/workflows/build-bundles.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "develop"
|
||||
12
.github/workflows/build-staging.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Build and Upload Penpot STAGING Bundles
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 5 * * 1-5'
|
||||
|
||||
jobs:
|
||||
build-staging-bundle:
|
||||
uses: ./.github/workflows/build-bundles.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "staging"
|
||||
2
.github/workflows/commit-checker.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Check Commit Type
|
||||
uses: gsactions/commit-message-checker@v2
|
||||
with:
|
||||
pattern: '^(Merge|:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle):)\s[A-Z].*[^.]$'
|
||||
pattern: '^(Merge|Revert|:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle):)\s[A-Z].*[^.]$'
|
||||
flags: 'gm'
|
||||
error: 'Commit should match CONTRIBUTING.md guideline'
|
||||
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
|
||||
|
||||
33
CHANGES.md
@@ -1,18 +1,5 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.10.0 (Unreleased)
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
|
||||
## 2.9.0 (Unreleased)
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
@@ -38,9 +25,11 @@
|
||||
- Hide bounding box while editing visual effects [Taiga #11576](https://tree.taiga.io/project/penpot/issue/11576)
|
||||
- Improved text layer resizing: Allow double-click on text bounding box to set auto-width/auto-height [Taiga #11577](https://tree.taiga.io/project/penpot/issue/11577)
|
||||
- Improve text layer auto-resize: auto-width switches to auto-height on horizontal resize, and only switches to fixed on vertical resize [Taiga #11578](https://tree.taiga.io/project/penpot/issue/11578)
|
||||
- Highlight first font in font selector search. Apply only on Enter or click. [Taiga #11579](https://tree.taiga.io/project/penpot/issue/11579)
|
||||
- Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871)
|
||||
- Improve the application of tokens with object specific tokens [Taiga #10209](https://tree.taiga.io/project/penpot/us/10209)
|
||||
- Add info to apply-token event [Taiga #11710](https://tree.taiga.io/project/penpot/task/11710)
|
||||
- Fix double click on set name input [Taiga #11747](https://tree.taiga.io/project/penpot/issue/11747)
|
||||
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
@@ -54,18 +43,32 @@
|
||||
- Keep color data when copying from info tab into CSS [Taiga #11144](https://tree.taiga.io/project/penpot/issue/11144)
|
||||
- Update HSL values to modern syntax as defined in W3C CSS Color Module Level 4 [Taiga #11144](https://tree.taiga.io/project/penpot/issue/11144)
|
||||
- Fix main component receives focus and is selected when using 'Show Main Component' [Taiga #11402](https://tree.taiga.io/project/penpot/issue/11402)
|
||||
- Fix UI theme selection from main menu [Taiga #11567](https://tree.taiga.io/project/penpot/issue/11567)
|
||||
- Fix duplicating pages with mainInstance shapes nested inside groups [Taiga #10774](https://tree.taiga.io/project/penpot/issue/10774)
|
||||
- Fix ESC key not closing Add/Manage Libraries modal [Taiga #11523](https://tree.taiga.io/project/penpot/issue/11523)
|
||||
- Fix copying a shadow color from info tab [Taiga #11211](https://tree.taiga.io/project/penpot/issue/11211)
|
||||
- Fix remove color button in the gradient editor [Taiga #11623](https://tree.taiga.io/project/penpot/issue/11623)
|
||||
- Fix "Copy as SVG" generates different code from the Inspect panel [Taiga #11519](https://tree.taiga.io/project/penpot/issue/11519)
|
||||
- Fix overriden tokens in text copies are not preserved [Taiga #11486](https://tree.taiga.io/project/penpot/issue/11486)
|
||||
- Fix problem when changing between flex/grid layout [Taiga #11625](https://tree.taiga.io/project/penpot/issue/11625)
|
||||
- Fix opacity on stroke gradients [Taiga #11646](https://tree.taiga.io/project/penpot/issue/11646)
|
||||
- Fix change from gradient to solid color [Taiga #11648](https://tree.taiga.io/project/penpot/issue/11648)
|
||||
- Fix the context menu always closes after any action [Taiga #11624](https://tree.taiga.io/project/penpot/issue/11624)
|
||||
- Fix X & Y position do not sincronize with tokens [Taiga #11617](https://tree.taiga.io/project/penpot/issue/11617)
|
||||
- Fix tooltip position after first time [Taiga #11688](https://tree.taiga.io/project/penpot/issue/11688)
|
||||
- Fix inconsistent ordering of pinned projects on dashboard sidebar [Taiga #11674](https://tree.taiga.io/project/penpot/issue/11674)
|
||||
- Fix export button width on inspect tab [Taiga #11394](https://tree.taiga.io/project/penpot/issue/11394)
|
||||
- Fix stroke width token application [Taiga #11724](https://tree.taiga.io/project/penpot/issue/11724)
|
||||
- Fix number token application on shape [Taiga #11331](https://tree.taiga.io/project/penpot/task/11331)
|
||||
- Fix auto height is fixed in the HTML inspect tab for text elements [Taiga #11680](https://tree.taiga.io/project/penpot/task/11680)
|
||||
|
||||
## 2.8.1 (Unreleased)
|
||||
## 2.8.1
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix unexpected exception on processing old texts [Github #6889](https://github.com/penpot/penpot/pull/6889)
|
||||
- Fix error on inspect tab when selecting multiple shapes [Taiga #11655](https://tree.taiga.io/project/penpot/issue/11655)
|
||||
- Fix missing package for the penport_exporter Docker image [GitHub #7205](https://github.com/penpot/penpot/issues/7025)
|
||||
|
||||
## 2.8.0
|
||||
|
||||
|
||||
@@ -10,18 +10,19 @@
|
||||
[app.config :as cf]
|
||||
[app.util.time :as dt]))
|
||||
|
||||
(def ^:private canceled-status
|
||||
#{"canceled" "unpaid"})
|
||||
|
||||
(defn get-deletion-delay
|
||||
"Calculate the next deleted-at for a resource (file, team, etc) in function
|
||||
of team settings"
|
||||
[team]
|
||||
(if-let [subscription (get team :subscription)]
|
||||
(if-let [{:keys [type status]} (get team :subscription)]
|
||||
(cond
|
||||
(and (= (:type subscription) "unlimited")
|
||||
(= (:status subscription) "active"))
|
||||
(and (= "unlimited" type) (not (contains? canceled-status status)))
|
||||
(dt/duration {:days 30})
|
||||
|
||||
(and (= (:type subscription) "enterprise")
|
||||
(= (:status subscription) "active"))
|
||||
(and (= "enterprise" type) (not (contains? canceled-status status)))
|
||||
(dt/duration {:days 90})
|
||||
|
||||
:else
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
[app.rpc :as-alias rpc]
|
||||
[app.rpc.doc :as-alias rpc.doc]
|
||||
[app.setup :as-alias setup]
|
||||
[app.worker :as wrk]
|
||||
[integrant.core :as ig]
|
||||
[promesa.exec :as px]
|
||||
[reitit.core :as r]
|
||||
@@ -63,15 +64,16 @@
|
||||
(assert (sm/check schema:server-params params)))
|
||||
|
||||
(defmethod ig/init-key ::server
|
||||
[_ {:keys [::handler ::router ::host ::port] :as cfg}]
|
||||
[_ {:keys [::handler ::router ::host ::port ::wrk/executor] :as cfg}]
|
||||
(l/info :hint "starting http server" :port port :host host)
|
||||
(let [options {:http/port port
|
||||
:http/host host
|
||||
:http/max-body-size (::max-body-size cfg)
|
||||
:http/max-multipart-body-size (::max-multipart-body-size cfg)
|
||||
:xnio/direct-buffers false
|
||||
:xnio/io-threads (or (::io-threads cfg)
|
||||
(max 3 (px/get-available-processors)))
|
||||
:xnio/dispatch :virtual
|
||||
:xnio/dispatch executor
|
||||
:ring/compat :ring2
|
||||
:socket/backlog 4069}
|
||||
|
||||
|
||||
@@ -231,7 +231,8 @@
|
||||
::http/router (ig/ref ::http/router)
|
||||
::http/io-threads (cf/get :http-server-io-threads)
|
||||
::http/max-body-size (cf/get :http-server-max-body-size)
|
||||
::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)}
|
||||
::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)
|
||||
::wrk/executor (ig/ref ::wrk/executor)}
|
||||
|
||||
::ldap/provider
|
||||
{:host (cf/get :ldap-host)
|
||||
|
||||
@@ -438,7 +438,10 @@
|
||||
:fn (mg/resource "app/migrations/sql/0138-mod-file-data-fragment-table.sql")}
|
||||
|
||||
{:name "0139-mod-file-change-table.sql"
|
||||
:fn (mg/resource "app/migrations/sql/0139-mod-file-change-table.sql")}])
|
||||
:fn (mg/resource "app/migrations/sql/0139-mod-file-change-table.sql")}
|
||||
|
||||
{:name "0140-mod-file-change-table.sql"
|
||||
:fn (mg/resource "app/migrations/sql/0140-mod-file-change-table.sql")}])
|
||||
|
||||
(defn apply-migrations!
|
||||
[pool name migrations]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE file_change
|
||||
ADD COLUMN migrations text[];
|
||||
@@ -178,12 +178,12 @@
|
||||
(measure metrics mlabels stats nil)
|
||||
(log "enqueued" req-id stats limit-id limit-label limit-params nil))
|
||||
|
||||
(px/invoke! limiter (fn []
|
||||
(let [elapsed (tpoint)
|
||||
stats (pbh/get-stats limiter)]
|
||||
(measure metrics mlabels stats elapsed)
|
||||
(log "acquired" req-id stats limit-id limit-label limit-params elapsed)
|
||||
(handler))))
|
||||
(pbh/invoke! limiter (fn []
|
||||
(let [elapsed (tpoint)
|
||||
stats (pbh/get-stats limiter)]
|
||||
(measure metrics mlabels stats elapsed)
|
||||
(log "acquired" req-id stats limit-id limit-label limit-params elapsed)
|
||||
(handler))))
|
||||
|
||||
(catch ExceptionInfo cause
|
||||
(let [{:keys [type code]} (ex-data cause)]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.binfile.common :as bfc]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.files.migrations :as fmg]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.uuid :as uuid]
|
||||
@@ -15,6 +16,7 @@
|
||||
[app.db :as db]
|
||||
[app.db.sql :as-alias sql]
|
||||
[app.features.fdata :as feat.fdata]
|
||||
[app.features.file-migrations :refer [reset-migrations!]]
|
||||
[app.main :as-alias main]
|
||||
[app.msgbus :as mbus]
|
||||
[app.rpc :as-alias rpc]
|
||||
@@ -27,6 +29,13 @@
|
||||
[app.util.time :as dt]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(defn decode-row
|
||||
[{:keys [migrations] :as row}]
|
||||
(when row
|
||||
(cond-> row
|
||||
(some? migrations)
|
||||
(assoc :migrations (db/decode-pgarray migrations)))))
|
||||
|
||||
(def sql:get-file-snapshots
|
||||
"WITH changes AS (
|
||||
SELECT id, label, revn, created_at, created_by, profile_id
|
||||
@@ -74,10 +83,7 @@
|
||||
(assert (#{:system :user :admin} created-by)
|
||||
"expected valid keyword for created-by")
|
||||
|
||||
(let [conn
|
||||
(db/get-connection cfg)
|
||||
|
||||
created-by
|
||||
(let [created-by
|
||||
(name created-by)
|
||||
|
||||
deleted-at
|
||||
@@ -101,12 +107,15 @@
|
||||
(blob/encode (:data file))
|
||||
|
||||
features
|
||||
(db/encode-pgarray (:features file) conn "text")]
|
||||
(into-array (:features file))
|
||||
|
||||
(l/debug :hint "creating file snapshot"
|
||||
:file-id (str (:id file))
|
||||
:id (str snapshot-id)
|
||||
:label label)
|
||||
migrations
|
||||
(into-array (:migrations file))]
|
||||
|
||||
(l/dbg :hint "creating file snapshot"
|
||||
:file-id (str (:id file))
|
||||
:id (str snapshot-id)
|
||||
:label label)
|
||||
|
||||
(db/insert! cfg :file-change
|
||||
{:id snapshot-id
|
||||
@@ -114,6 +123,7 @@
|
||||
:data data
|
||||
:version (:version file)
|
||||
:features features
|
||||
:migrations migrations
|
||||
:profile-id profile-id
|
||||
:file-id (:id file)
|
||||
:label label
|
||||
@@ -159,7 +169,17 @@
|
||||
{:file-id file-id
|
||||
:id snapshot-id}
|
||||
{::db/for-share true})
|
||||
(feat.fdata/resolve-file-data cfg))]
|
||||
(feat.fdata/resolve-file-data cfg)
|
||||
(decode-row))
|
||||
|
||||
;; If snapshot has tracked applied migrations, we reuse them,
|
||||
;; if not we take a safest set of migrations as starting
|
||||
;; point. This is because, at the time of implementing
|
||||
;; snapshots, migrations were not taken into account so we
|
||||
;; need to make this backward compatible in some way.
|
||||
file (assoc file :migrations
|
||||
(or (:migrations snapshot)
|
||||
(fmg/generate-migrations-from-version 67)))]
|
||||
|
||||
(when-not snapshot
|
||||
(ex/raise :type :not-found
|
||||
@@ -180,12 +200,16 @@
|
||||
:label (:label snapshot)
|
||||
:snapshot-id (str (:id snapshot)))
|
||||
|
||||
;; If the file was already offloaded, on restring the snapshot
|
||||
;; we are going to replace the file data, so we need to touch
|
||||
;; the old referenced storage object and avoid possible leaks
|
||||
;; If the file was already offloaded, on restoring the snapshot we
|
||||
;; are going to replace the file data, so we need to touch the old
|
||||
;; referenced storage object and avoid possible leaks
|
||||
(when (feat.fdata/offloaded? file)
|
||||
(sto/touch-object! storage (:data-ref-id file)))
|
||||
|
||||
;; In the same way, on reseting the file data, we need to restore
|
||||
;; the applied migrations on the moment of taking the snapshot
|
||||
(reset-migrations! conn file)
|
||||
|
||||
(db/update! conn :file
|
||||
{:data (:data snapshot)
|
||||
:revn (inc (:revn file))
|
||||
@@ -253,7 +277,7 @@
|
||||
:deleted-at nil}
|
||||
{:id snapshot-id}
|
||||
{::db/return-keys true})
|
||||
(dissoc :data :features)))
|
||||
(dissoc :data :features :migrations)))
|
||||
|
||||
(defn- get-snapshot
|
||||
"Get a minimal snapshot from database and lock for update"
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
[:map {:title "PartialFile"}
|
||||
[:id ::sm/uuid]
|
||||
[:revn {:min 0} ::sm/int]
|
||||
[:page :any]])
|
||||
[:page [:map-of :keyword ::sm/any]]])
|
||||
|
||||
(sv/defmethod ::get-file-data-for-thumbnail
|
||||
"Retrieves the data for generate the thumbnail of the file. Used
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"canceled"
|
||||
"incomplete"
|
||||
"incomplete_expired"
|
||||
"pass_due"
|
||||
"past_due"
|
||||
"paused"
|
||||
"trialing"
|
||||
"unpaid"]]
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||
funcool/cuerdas {:mvn/version "2025.06.16-414"}
|
||||
funcool/promesa
|
||||
{:git/sha "f52f58cfacf62f59eab717e2637f37729d0cc383"
|
||||
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
|
||||
:git/url "https://github.com/funcool/promesa"}
|
||||
|
||||
funcool/datoteka
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
(:require
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.text :as txt]))
|
||||
[app.common.text :as txt]))
|
||||
|
||||
(defn- get-attr
|
||||
[obj attr]
|
||||
|
||||
@@ -74,13 +74,6 @@
|
||||
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
|
||||
`(.put ~target ~offset (unchecked-byte ~value)))))
|
||||
|
||||
(defmacro write-bool
|
||||
[target offset value]
|
||||
(if (:ns &env)
|
||||
`(.setInt8 ~target ~offset (if ~value 0x01 0x00) true)
|
||||
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
|
||||
`(.put ~target ~offset (unchecked-byte (if ~value 0x01 0x00))))))
|
||||
|
||||
(defmacro write-short
|
||||
[target offset value]
|
||||
(if (:ns &env)
|
||||
|
||||
@@ -356,7 +356,7 @@
|
||||
(first children)
|
||||
(last children))
|
||||
fills (if (and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
(types.path/get-default-bool-fills)
|
||||
types.path/default-bool-fills
|
||||
(get head :fills))]
|
||||
(-> bool-shape
|
||||
(assoc :fills fills)
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.grid :as ctg]
|
||||
[app.common.types.library :as ctl]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.path :as path]
|
||||
@@ -419,7 +418,14 @@
|
||||
[:type [:= :set-token-set]]
|
||||
[:set-name :string]
|
||||
[:group? :boolean]
|
||||
[:token-set [:maybe [:fn ctob/token-set?]]]]]
|
||||
|
||||
;; FIXME: we should not pass private types as part of changes
|
||||
;; protocol, the changes protocol should reflect a
|
||||
;; method/protocol for perform surgical operations on file data,
|
||||
;; this has nothing todo with internal types of a file data
|
||||
;; structure.
|
||||
[:token-set {:gen/gen (sg/generator ctob/schema:token-set)}
|
||||
[:maybe [:fn ctob/token-set?]]]]]
|
||||
|
||||
[:set-token
|
||||
[:map {:title "SetTokenChange"}
|
||||
@@ -928,15 +934,15 @@
|
||||
|
||||
(defmethod process-change :add-color
|
||||
[data {:keys [color]}]
|
||||
(ctl/add-color data color))
|
||||
(ctc/add-color data color))
|
||||
|
||||
(defmethod process-change :mod-color
|
||||
[data {:keys [color]}]
|
||||
(ctl/set-color data color))
|
||||
(ctc/set-color data color))
|
||||
|
||||
(defmethod process-change :del-color
|
||||
[data {:keys [id]}]
|
||||
(ctl/delete-color data id))
|
||||
(ctc/delete-color data id))
|
||||
|
||||
;; DEPRECATED: remove before 2.3
|
||||
(defmethod process-change :add-recent-color
|
||||
|
||||
@@ -21,17 +21,18 @@
|
||||
[app.common.math :as mth]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.svg :as csvg]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.color :as types.color]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.types.fill :as types.fill]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.segment :as path.segment]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.types.text :as types.text]
|
||||
[app.common.types.text :as cttx]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]))
|
||||
@@ -80,7 +81,7 @@
|
||||
(update :migrations set/union diff)
|
||||
(vary-meta assoc ::migrated (not-empty diff)))))
|
||||
|
||||
(defn- generate-migrations-from-version
|
||||
(defn generate-migrations-from-version
|
||||
"A function that generates new format migration from the old,
|
||||
version based migration system"
|
||||
[version]
|
||||
@@ -622,7 +623,7 @@
|
||||
(let [invalid-node? (complement valid-node?)]
|
||||
(cond-> object
|
||||
(cfh/text-shape? object)
|
||||
(update :content #(types.text/transform-nodes invalid-node? fix-node %)))))
|
||||
(update :content #(txt/transform-nodes invalid-node? fix-node %)))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
@@ -729,7 +730,7 @@
|
||||
(let [shape (update-object shape)]
|
||||
(if (cfh/text-shape? shape)
|
||||
(-> shape
|
||||
(update :content (partial types.text/transform-nodes identity update-fill))
|
||||
(update :content (partial txt/transform-nodes identity update-fill))
|
||||
(d/update-when :position-data #(mapv update-object %)))
|
||||
shape)))
|
||||
|
||||
@@ -837,7 +838,7 @@
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(def ^:private valid-fill?
|
||||
(sm/lazy-validator types.fills/schema:fill))
|
||||
(sm/lazy-validator types.fill/schema:fill))
|
||||
|
||||
(defmethod migrate-data "legacy-43"
|
||||
[data _]
|
||||
@@ -855,7 +856,7 @@
|
||||
|
||||
(update-object [object]
|
||||
(if (cfh/text-shape? object)
|
||||
(update object :content #(types.text/transform-nodes types.text/is-content-node? update-text-node %))
|
||||
(update object :content #(txt/transform-nodes txt/is-content-node? update-text-node %))
|
||||
object))
|
||||
|
||||
(update-container [container]
|
||||
@@ -1104,7 +1105,7 @@
|
||||
;; The text shape also can has fills on the text
|
||||
;; fragments so we need to fix fills there
|
||||
(cond-> (cfh/text-shape? object)
|
||||
(update :content (partial types.text/transform-nodes types.text/is-content-node? fix-fills)))))
|
||||
(update :content (partial txt/transform-nodes txt/is-content-node? fix-fills)))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
@@ -1422,7 +1423,7 @@
|
||||
|
||||
(update-object [object]
|
||||
(if (cfh/text-shape? object)
|
||||
(update object :content (partial types.text/transform-nodes types.text/is-content-node? fix-fills))
|
||||
(update object :content (partial txt/transform-nodes txt/is-content-node? fix-fills))
|
||||
object))
|
||||
|
||||
(update-container [container]
|
||||
@@ -1456,7 +1457,7 @@
|
||||
|
||||
;; Fixes shapes with nested :fills in the :fills attribute
|
||||
;; introduced in a migration `0006-fix-old-texts-fills` when
|
||||
;; types.text/transform-nodes with identity pred was broken
|
||||
;; txt/transform-nodes with identity pred was broken
|
||||
(remove-nested-fills [[fill :as fills]]
|
||||
(if (and (= 1 (count fills))
|
||||
(contains? fill :fills))
|
||||
@@ -1465,7 +1466,7 @@
|
||||
|
||||
(clear-fill [fill]
|
||||
(-> fill
|
||||
(select-keys types.fills/fill-attrs)
|
||||
(select-keys types.fill/fill-attrs)
|
||||
(d/update-when :fill-image clear-color-image)
|
||||
(d/update-when :fill-color-gradient clear-color-gradient)))
|
||||
|
||||
@@ -1482,8 +1483,8 @@
|
||||
|
||||
(fix-text-content [content]
|
||||
(->> content
|
||||
(types.text/transform-nodes types.text/is-content-node? fix-object)
|
||||
(types.text/transform-nodes types.text/is-paragraph-set-node? #(dissoc % :fills))))
|
||||
(txt/transform-nodes txt/is-content-node? fix-object)
|
||||
(txt/transform-nodes txt/is-paragraph-set-node? #(dissoc % :fills))))
|
||||
|
||||
(update-shape [object]
|
||||
(-> object
|
||||
@@ -1538,7 +1539,7 @@
|
||||
ref-shape (ctf/find-ref-shape file page libs object
|
||||
{:include-deleted? true :with-context? true})
|
||||
partial-touched (when ref-shape
|
||||
(types.text/get-diff-type (:content object) (:content ref-shape)))]
|
||||
(cttx/get-diff-type (:content object) (:content ref-shape)))]
|
||||
(if (seq partial-touched)
|
||||
(update object :touched (fn [touched]
|
||||
(reduce #(ctk/set-touched-group %1 %2)
|
||||
@@ -1617,4 +1618,4 @@
|
||||
"0007-clear-invalid-strokes-and-fills-v2"
|
||||
"0008-fix-library-colors-v4"
|
||||
"0009-clean-library-colors"
|
||||
"0009-add-partial-text-touched-flags"]))
|
||||
#_"0009-add-partial-text-touched-flags"]))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.common.files.shapes-builder
|
||||
"A SVG to Shapes builder."
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
@@ -20,7 +21,6 @@
|
||||
[app.common.math :as mth]
|
||||
[app.common.schema :as sm :refer [max-safe-int min-safe-int]]
|
||||
[app.common.svg :as csvg]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.segment :as path.segm]
|
||||
[app.common.types.shape :as cts]
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def ^:dynamic *current* #{})
|
||||
|
||||
(def login
|
||||
"Flags related to login features"
|
||||
#{;; Allows registration with login / password
|
||||
|
||||
@@ -17,18 +17,19 @@
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.logic.variant-properties :as clvp]
|
||||
[app.common.spec :as us]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.color :as ctc]
|
||||
[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.library :as ctl]
|
||||
[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.shape.interactions :as ctsi]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.text :as cttx]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.typography :as cty]
|
||||
[app.common.types.variant :as ctv]
|
||||
@@ -214,8 +215,8 @@
|
||||
[(:frame-id new-main-instance-shape)]
|
||||
(fn [shape objects]
|
||||
(cond-> shape
|
||||
(ctsl/grid-layout? shape)
|
||||
(ctsl/assign-cells objects)))
|
||||
(ctl/grid-layout? shape)
|
||||
(ctl/assign-cells objects)))
|
||||
{:with-objects? true}))]))
|
||||
|
||||
|
||||
@@ -276,7 +277,7 @@
|
||||
(->> ids-map vals (some #(= % (:parent-id first-shape))))
|
||||
|
||||
changes
|
||||
(if (and (ctsl/grid-layout? objects (:parent-id first-shape)) (not duplicated-parent?))
|
||||
(if (and (ctl/grid-layout? objects (:parent-id first-shape)) (not duplicated-parent?))
|
||||
(let [target-cell (-> position meta :cell)
|
||||
|
||||
[row column]
|
||||
@@ -287,10 +288,10 @@
|
||||
[(:parent-id first-shape)]
|
||||
(fn [shape objects]
|
||||
(-> shape
|
||||
(ctsl/assign-cells objects)
|
||||
(ctl/assign-cells objects)
|
||||
(cond-> (and (some? row) (some? column))
|
||||
(-> (ctsl/push-into-cell [(:id first-shape)] row column)
|
||||
(ctsl/assign-cells objects)))))
|
||||
(-> (ctl/push-into-cell [(:id first-shape)] row column)
|
||||
(ctl/assign-cells objects)))))
|
||||
{:with-objects? true})
|
||||
(pcb/reorder-grid-children [(:parent-id first-shape)])))
|
||||
changes)
|
||||
@@ -575,8 +576,8 @@
|
||||
(defmethod uses-assets? :colors
|
||||
[_ color-id shape library-id]
|
||||
(if (nil? color-id)
|
||||
(cts/uses-library-colors? shape library-id)
|
||||
(cts/uses-library-color? shape library-id color-id)))
|
||||
(ctc/uses-library-colors? shape library-id)
|
||||
(ctc/uses-library-color? shape library-id color-id)))
|
||||
|
||||
(defmethod uses-assets? :typographies
|
||||
[_ typography-id shape library-id]
|
||||
@@ -604,7 +605,7 @@
|
||||
(let [library-colors (get-in libraries [library-id :data :colors])]
|
||||
(pcb/update-shapes changes
|
||||
[(:id shape)]
|
||||
#(ctl/sync-colors % library-id library-colors))))
|
||||
#(ctc/sync-shape-colors % library-id library-colors))))
|
||||
|
||||
(defmethod generate-sync-shape :typographies
|
||||
[_ changes library-id container shape libraries _]
|
||||
@@ -853,7 +854,7 @@
|
||||
container
|
||||
omit-touched?)
|
||||
|
||||
(ctsl/flex-layout? shape-main)
|
||||
(ctl/flex-layout? shape-main)
|
||||
(update-flex-child-copy-attrs shape-main
|
||||
shape-inst
|
||||
library
|
||||
@@ -972,7 +973,7 @@
|
||||
|
||||
changes
|
||||
(cond-> changes
|
||||
(ctsl/grid-layout? shape-inst)
|
||||
(ctl/grid-layout? shape-inst)
|
||||
(update-grid-copy-attrs
|
||||
(:id shape-inst)
|
||||
shape-main
|
||||
@@ -1062,14 +1063,14 @@
|
||||
component-container
|
||||
{:copy-touched? true}))
|
||||
|
||||
(ctsl/flex-layout? shape-main)
|
||||
(ctl/flex-layout? shape-main)
|
||||
(update-flex-child-main-attrs shape-main
|
||||
shape-inst
|
||||
component-container
|
||||
container
|
||||
omit-touched?)
|
||||
|
||||
(ctsl/grid-layout? shape-main)
|
||||
(ctl/grid-layout? shape-main)
|
||||
(update-grid-main-attrs shape-main
|
||||
shape-inst
|
||||
component-container
|
||||
@@ -1669,16 +1670,16 @@
|
||||
untouched-content ;; The :content of the main component
|
||||
touched]
|
||||
|
||||
(let [main-comps-diff (txt/get-diff-type touched-content untouched-content)
|
||||
(let [main-comps-diff (cttx/get-diff-type touched-content untouched-content)
|
||||
diff-structure? (contains? main-comps-diff :text-content-structure)
|
||||
|
||||
touched-attrs (txt/get-first-paragraph-text-attrs touched-content)
|
||||
touched-attrs (cttx/get-first-paragraph-text-attrs touched-content)
|
||||
;; Have touched content an uniform style?
|
||||
thed-unif-style? (txt/equal-attrs? touched-content touched-attrs)
|
||||
thed-unif-style? (cttx/equal-attrs? touched-content touched-attrs)
|
||||
|
||||
untouched-attrs (txt/get-first-paragraph-text-attrs untouched-content)
|
||||
untouched-attrs (cttx/get-first-paragraph-text-attrs untouched-content)
|
||||
;; Have untouched content an uniform style?
|
||||
untched-unif-style? (txt/equal-attrs? untouched-content untouched-attrs)]
|
||||
untched-unif-style? (cttx/equal-attrs? untouched-content untouched-attrs)]
|
||||
(cond
|
||||
;; Both text and attrs has been touched, keep the
|
||||
;; touched-content
|
||||
@@ -1691,14 +1692,14 @@
|
||||
;; and both have uniform attributes, we keep the touched-content structure and
|
||||
;; texts, updating its attrs to make them like the untouched-content
|
||||
(if (and (not (touched :text-content-attribute)) thed-unif-style? untched-unif-style?)
|
||||
(txt/copy-attrs-keys touched-content untouched-attrs)
|
||||
(cttx/copy-attrs-keys touched-content untouched-attrs)
|
||||
;; In other case, we keep the touched content
|
||||
touched-content)
|
||||
|
||||
(touched :text-content-text)
|
||||
;; Keep the texts touched in touched-content, so copy the
|
||||
;; texts from touched-content into untouched-content
|
||||
(txt/copy-text-keys touched-content untouched-content)
|
||||
(cttx/copy-text-keys touched-content untouched-content)
|
||||
|
||||
(touched :text-content-attribute)
|
||||
;; The untouched content has a different structure, but the touched content had't
|
||||
@@ -1707,19 +1708,20 @@
|
||||
;; If both have uniform attributes, we keep the untouched-content structure and
|
||||
;; texts, updating its attrs to make them like the touched-content
|
||||
(if (and thed-unif-style? untched-unif-style?)
|
||||
(txt/copy-attrs-keys untouched-content touched-attrs)
|
||||
(cttx/copy-attrs-keys untouched-content touched-attrs)
|
||||
;; In other case, we keep the touched content
|
||||
touched-content)
|
||||
|
||||
;; Keep the attrs touched in touched-content, so copy the
|
||||
;; texts from untouched-content into touched-content
|
||||
(txt/copy-text-keys untouched-content touched-content))
|
||||
(cttx/copy-text-keys untouched-content touched-content))
|
||||
|
||||
|
||||
;; Nothing is touched
|
||||
:else
|
||||
untouched-content)))
|
||||
|
||||
|
||||
(defn- add-update-attr-operations
|
||||
[attr dest-shape roperations uoperations attr-val]
|
||||
(let [roperation {:type :set
|
||||
@@ -1850,20 +1852,20 @@
|
||||
(let [;; We need the differences between the contents on the main
|
||||
;; components. current-content is the content of a clean copy,
|
||||
;; so for all effects its the same as the content on its main
|
||||
main-comps-diff (txt/get-diff-type ref-content current-content)
|
||||
main-comps-diff (cttx/get-diff-type ref-content current-content)
|
||||
can-keep-text? (not (contains? main-comps-diff :text-content-text))
|
||||
can-keep-attr? (not (contains? main-comps-diff :text-content-attribute))
|
||||
main-diff-structure? (contains? main-comps-diff :text-content-structure)
|
||||
|
||||
current-attrs (txt/get-first-paragraph-text-attrs current-content)
|
||||
current-attrs (cttx/get-first-paragraph-text-attrs current-content)
|
||||
;; Have current content an uniform style?
|
||||
curr-unif-style? (txt/equal-attrs? current-content current-attrs)
|
||||
prev-attrs (txt/get-first-paragraph-text-attrs prev-content)
|
||||
curr-unif-style? (cttx/equal-attrs? current-content current-attrs)
|
||||
prev-attrs (cttx/get-first-paragraph-text-attrs prev-content)
|
||||
;; Have prev content an uniform style?
|
||||
prev-unif-style? (txt/equal-attrs? prev-content prev-attrs)
|
||||
ref-attrs (txt/get-first-paragraph-text-attrs ref-content)
|
||||
prev-unif-style? (cttx/equal-attrs? prev-content prev-attrs)
|
||||
ref-attrs (cttx/get-first-paragraph-text-attrs ref-content)
|
||||
;; Have ref content an uniform style?
|
||||
ref-unif-style? (txt/equal-attrs? ref-content ref-attrs)]
|
||||
ref-unif-style? (cttx/equal-attrs? ref-content ref-attrs)]
|
||||
(cond
|
||||
;; When the main components have a difference in structure
|
||||
;; (different number of paragraph or text entries)
|
||||
@@ -1877,7 +1879,7 @@
|
||||
ref-unif-style?
|
||||
prev-unif-style?
|
||||
(= ref-attrs current-attrs))
|
||||
(txt/copy-attrs-keys current-content prev-attrs)
|
||||
(cttx/copy-attrs-keys current-content prev-attrs)
|
||||
;; In any other case of structure change, we discard all
|
||||
;; the overrides and keep the content of the current-shape
|
||||
current-content)
|
||||
@@ -1901,8 +1903,8 @@
|
||||
curr-unif-style?
|
||||
prev-unif-style?)
|
||||
(if can-keep-text?
|
||||
(txt/copy-attrs-keys prev-content current-attrs)
|
||||
(txt/copy-attrs-keys current-content prev-attrs))
|
||||
(cttx/copy-attrs-keys prev-content current-attrs)
|
||||
(cttx/copy-attrs-keys current-content prev-attrs))
|
||||
|
||||
;; In any other case of structure change, we discard all
|
||||
;; the overrides and keep the content of the current-shape
|
||||
@@ -1914,14 +1916,14 @@
|
||||
;; previous-shape over the attrs of current-shape
|
||||
(and
|
||||
(touched :text-content-text) can-keep-text?)
|
||||
(txt/copy-text-keys prev-content current-content)
|
||||
(cttx/copy-text-keys prev-content current-content)
|
||||
|
||||
;; When there is a change on :text-content-attribute,
|
||||
;; and we can keep it, we copy the texts from current-shape
|
||||
;; over the attrs of previous-shape
|
||||
(and
|
||||
(touched :text-content-attribute) can-keep-attr?)
|
||||
(txt/copy-text-keys current-content prev-content)
|
||||
(cttx/copy-text-keys current-content prev-content)
|
||||
|
||||
;; In any other case, we discard all the overrides
|
||||
;; and keep the content of the current-shape
|
||||
@@ -2157,11 +2159,11 @@
|
||||
(update cell :shapes #(filterv child? %)))))))
|
||||
;; Take cells from main and remap the shapes to assign it to the copy
|
||||
copy-cells (-> shape-copy :layout-grid-cells (remove-orphan-cells shape-copy))
|
||||
main-cells (-> shape-main (ctsl/remap-grid-cells ids-map) :layout-grid-cells)]
|
||||
main-cells (-> shape-main (ctl/remap-grid-cells ids-map) :layout-grid-cells)]
|
||||
(-> shape-copy
|
||||
(assoc :layout-grid-cells
|
||||
(ctsl/merge-cells main-cells copy-cells omit-touched?))
|
||||
(ctsl/assign-cells objects))))
|
||||
(ctl/merge-cells main-cells copy-cells omit-touched?))
|
||||
(ctl/assign-cells objects))))
|
||||
{:ignore-touched true :with-objects? true})))
|
||||
|
||||
(defn- update-grid-main-attrs
|
||||
@@ -2185,7 +2187,7 @@
|
||||
[(:id shape-main)]
|
||||
(fn [shape-main]
|
||||
;; Take cells from copy and remap the shapes to assign it to the copy
|
||||
(let [new-cells (-> (ctsl/remap-grid-cells shape-copy ids-map) :layout-grid-cells)]
|
||||
(let [new-cells (-> (ctl/remap-grid-cells shape-copy ids-map) :layout-grid-cells)]
|
||||
(assoc shape-main :layout-grid-cells new-cells)))
|
||||
{:ignore-touched true}))]
|
||||
(pcb/concat-changes changes new-changes)))
|
||||
@@ -2298,8 +2300,8 @@
|
||||
parent-id (:parent-id shape)
|
||||
|
||||
insert-before?
|
||||
(and (ctsl/flex-layout? objects parent-id)
|
||||
(not (ctsl/reverse? objects parent-id)))
|
||||
(and (ctl/flex-layout? objects parent-id)
|
||||
(not (ctl/reverse? objects parent-id)))
|
||||
|
||||
objects
|
||||
(-> objects
|
||||
@@ -2315,7 +2317,7 @@
|
||||
(pcb/with-objects objects)
|
||||
(pcb/resize-parents new-objects-ids)
|
||||
;; Fix the order of the children inside the parent
|
||||
(cond-> (ctsl/any-layout? objects parent-id)
|
||||
(cond-> (ctl/any-layout? objects parent-id)
|
||||
(pcb/reorder-children parent-id (get-in objects [parent-id :shapes]))))]
|
||||
(assoc changes :file-id library-id)))
|
||||
|
||||
@@ -2652,8 +2654,8 @@
|
||||
(gsh/move delta)
|
||||
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects))
|
||||
|
||||
(cond-> (ctsl/grid-layout? obj)
|
||||
(ctsl/remap-grid-cells ids-map))
|
||||
(cond-> (ctl/grid-layout? obj)
|
||||
(ctl/remap-grid-cells ids-map))
|
||||
|
||||
(cond-> (ctk/is-variant-container? parent)
|
||||
(assoc :variant-id parent-id))
|
||||
@@ -2669,8 +2671,8 @@
|
||||
;; We want the first added object to touch it's parent, but not subsequent children
|
||||
changes (-> (pcb/add-object changes new-obj {:ignore-touched (and duplicating-component? child?)})
|
||||
(pcb/amend-last-change #(assoc % :old-id (:id obj)))
|
||||
(cond-> (ctsl/grid-layout? objects (:parent-id obj))
|
||||
(-> (pcb/update-shapes [(:parent-id obj)] ctsl/assign-cells {:with-objects? true})
|
||||
(cond-> (ctl/grid-layout? objects (:parent-id obj))
|
||||
(-> (pcb/update-shapes [(:parent-id obj)] ctl/assign-cells {:with-objects? true})
|
||||
(pcb/reorder-grid-children [(:parent-id obj)]))))
|
||||
|
||||
changes (cond-> changes
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
(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.logic.variant-properties :as clvp]
|
||||
[app.common.text :as ct]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
@@ -21,9 +23,12 @@
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(def text-typography-attrs (set ct/text-typography-attrs))
|
||||
|
||||
(defn- generate-unapply-tokens
|
||||
"When updating attributes that have a token applied, we must unapply it, because the value
|
||||
of the attribute now has been given directly, and does not come from the token."
|
||||
of the attribute now has been given directly, and does not come from the token.
|
||||
When applying a typography asset style we also unapply any typographic tokens."
|
||||
[changes objects changed-sub-attr]
|
||||
(let [new-objects (pcb/get-objects changes)
|
||||
mod-obj-changes (->> (:redo-changes changes)
|
||||
@@ -32,29 +37,38 @@
|
||||
text-changed-attrs
|
||||
(fn [shape]
|
||||
(let [new-shape (get new-objects (:id shape))
|
||||
attrs (ctt/get-diff-attrs (:content shape) (:content new-shape))]
|
||||
attrs (ctt/get-diff-attrs (:content shape) (:content new-shape))
|
||||
|
||||
;; Unapply token when applying typography asset style
|
||||
attrs (if (seq (set/intersection text-typography-attrs attrs))
|
||||
(into attrs cto/typography-keys)
|
||||
attrs)]
|
||||
(apply set/union (map cto/shape-attr->token-attrs attrs))))
|
||||
|
||||
check-attr (fn [shape changes attr]
|
||||
(let [tokens (get shape :applied-tokens {})
|
||||
token-attrs (if (or (not= (:type shape) :text) (not= attr :content))
|
||||
(cto/shape-attr->token-attrs attr changed-sub-attr)
|
||||
(text-changed-attrs shape))]
|
||||
(if (some #(contains? tokens %) token-attrs)
|
||||
(pcb/update-shapes changes [(:id shape)] #(cto/unapply-token-id % token-attrs))
|
||||
changes)))
|
||||
check-attr
|
||||
(fn [shape changes attr]
|
||||
(let [shape-id (dm/get-prop shape :id)
|
||||
tokens (get shape :applied-tokens {})
|
||||
token-attrs (if (and (cfh/text-shape? shape) (= attr :content))
|
||||
(text-changed-attrs shape)
|
||||
(cto/shape-attr->token-attrs attr changed-sub-attr))]
|
||||
|
||||
check-shape (fn [changes mod-obj-change]
|
||||
(let [shape (get objects (:id mod-obj-change))
|
||||
xf (comp (filter #(= (:type %) :set))
|
||||
(map :attr))
|
||||
attrs (into [] xf (:operations mod-obj-change))]
|
||||
(reduce (partial check-attr shape)
|
||||
changes
|
||||
attrs)))]
|
||||
(reduce check-shape
|
||||
changes
|
||||
mod-obj-changes)))
|
||||
(if (some #(contains? tokens %) token-attrs)
|
||||
(pcb/update-shapes changes [shape-id] #(cto/unapply-token-id % token-attrs))
|
||||
changes)))
|
||||
|
||||
check-shape
|
||||
(fn [changes mod-obj-change]
|
||||
(let [shape (get objects (:id mod-obj-change))
|
||||
attrs (into []
|
||||
(comp (filter #(= (:type %) :set))
|
||||
(map :attr))
|
||||
(:operations mod-obj-change))]
|
||||
(reduce (partial check-attr shape)
|
||||
changes
|
||||
attrs)))]
|
||||
|
||||
(reduce check-shape changes mod-obj-changes)))
|
||||
|
||||
(defn generate-update-shapes
|
||||
[changes ids update-fn objects {:keys [attrs changed-sub-attr ignore-tree ignore-touched with-objects?]}]
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.text :as txt]))
|
||||
[app.common.types.shape :as cts]))
|
||||
|
||||
;; ----- File building
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
(defn add-text
|
||||
[file text-label content & {:keys [text-params] :as text}]
|
||||
(let [shape (-> (cts/setup-shape {:type :text :x 0 :y 0})
|
||||
(update :content txt/change-text content))]
|
||||
(txt/change-text content))]
|
||||
(ths/add-sample-shape file text-label
|
||||
(merge shape
|
||||
text-params))))
|
||||
@@ -74,7 +74,7 @@
|
||||
(defn add-frame-with-text
|
||||
[file frame-label child-label text & {:keys [frame-params child-params]}]
|
||||
(let [shape (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
|
||||
(update :content txt/change-text text)
|
||||
(txt/change-text text)
|
||||
(assoc :position-data nil
|
||||
:parent-label frame-label))]
|
||||
(-> file
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
|
||||
(ns app.common.test-helpers.shapes
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.library :as ctl]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.typographies-list :as cttl]
|
||||
[app.common.types.typography :as ctt]))
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
(defn add-sample-library-color
|
||||
[file label & {:keys [] :as params}]
|
||||
(let [color (sample-library-color label params)]
|
||||
(update file :data ctl/add-color color)))
|
||||
(update file :data ctc/add-color color)))
|
||||
|
||||
(defn sample-typography
|
||||
[label & {:keys [] :as params}]
|
||||
@@ -149,4 +149,4 @@
|
||||
(fn [file-data]
|
||||
(ctpl/update-page file-data
|
||||
(:id page)
|
||||
#(ctst/set-shape % (assoc origin :interactions interactions)))))))
|
||||
#(ctst/set-shape % (assoc origin :interactions interactions)))))))
|
||||
@@ -9,8 +9,8 @@
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.text :as txt]))
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.shape :as cts]))
|
||||
|
||||
(defn add-variant
|
||||
[file variant-label component1-label root1-label component2-label root2-label
|
||||
@@ -60,11 +60,11 @@
|
||||
[file variant-label component1-label root1-label component2-label root2-label child1-label child2-label text1 text2
|
||||
& {:keys [text1-params text2-params]}]
|
||||
(let [text1 (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
|
||||
(update :content txt/change-text text1)
|
||||
(txt/change-text text1)
|
||||
(assoc :position-data nil
|
||||
:parent-label root1-label))
|
||||
text2 (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
|
||||
(update :content txt/change-text text2)
|
||||
(txt/change-text text2)
|
||||
(assoc :position-data nil
|
||||
:parent-label root2-label))
|
||||
|
||||
|
||||
@@ -5,16 +5,186 @@
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.text
|
||||
"Legacy editor helpers (draftjs).
|
||||
|
||||
NOTE: this namespace should be not used for new code related to texts"
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.text :as types.text]
|
||||
[clojure.walk :as walk]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; -- Attrs
|
||||
|
||||
(def text-typography-attrs
|
||||
[:typography-ref-id
|
||||
:typography-ref-file])
|
||||
|
||||
(def text-fill-attrs
|
||||
[:fill-color
|
||||
:fill-opacity
|
||||
:fill-color-ref-id
|
||||
:fill-color-ref-file
|
||||
:fill-color-gradient])
|
||||
|
||||
(def text-font-attrs
|
||||
[:font-id
|
||||
:font-family
|
||||
:font-variant-id
|
||||
:font-size
|
||||
:font-weight
|
||||
:font-style])
|
||||
|
||||
(def text-align-attrs
|
||||
[:text-align])
|
||||
|
||||
(def text-direction-attrs
|
||||
[:text-direction])
|
||||
|
||||
(def text-spacing-attrs
|
||||
[:line-height
|
||||
:letter-spacing])
|
||||
|
||||
(def text-valign-attrs
|
||||
[:vertical-align])
|
||||
|
||||
(def text-decoration-attrs
|
||||
[:text-decoration])
|
||||
|
||||
(def text-transform-attrs
|
||||
[:text-transform])
|
||||
|
||||
(def text-fills
|
||||
[:fills])
|
||||
|
||||
(def shape-attrs
|
||||
[:grow-type])
|
||||
|
||||
(def root-attrs
|
||||
text-valign-attrs)
|
||||
|
||||
(def paragraph-attrs
|
||||
(d/concat-vec
|
||||
text-align-attrs
|
||||
text-direction-attrs))
|
||||
|
||||
(def text-node-attrs
|
||||
(d/concat-vec
|
||||
text-typography-attrs
|
||||
text-font-attrs
|
||||
text-spacing-attrs
|
||||
text-decoration-attrs
|
||||
text-transform-attrs
|
||||
text-fills))
|
||||
|
||||
(def text-all-attrs (d/concat-set shape-attrs root-attrs paragraph-attrs text-node-attrs))
|
||||
|
||||
(def text-style-attrs
|
||||
(d/concat-vec root-attrs paragraph-attrs text-node-attrs))
|
||||
|
||||
(def default-root-attrs
|
||||
{:vertical-align "top"})
|
||||
|
||||
(def default-text-attrs
|
||||
{:typography-ref-file nil
|
||||
:typography-ref-id nil
|
||||
:font-id "sourcesanspro"
|
||||
:font-family "sourcesanspro"
|
||||
:font-variant-id "regular"
|
||||
:font-size "14"
|
||||
:font-weight "400"
|
||||
:font-style "normal"
|
||||
:line-height "1.2"
|
||||
:letter-spacing "0"
|
||||
:text-transform "none"
|
||||
:text-align "left"
|
||||
:text-decoration "none"
|
||||
:text-direction "ltr"
|
||||
:fills [{:fill-color clr/black
|
||||
:fill-opacity 1}]})
|
||||
|
||||
(def default-attrs
|
||||
(merge default-root-attrs default-text-attrs))
|
||||
|
||||
(def typography-fields
|
||||
[:font-id
|
||||
:font-family
|
||||
:font-variant-id
|
||||
:font-size
|
||||
:font-weight
|
||||
:font-style
|
||||
:line-height
|
||||
:letter-spacing
|
||||
:text-transform])
|
||||
|
||||
(def default-typography
|
||||
(merge
|
||||
{:name "Source Sans Pro Regular"}
|
||||
(select-keys default-text-attrs typography-fields)))
|
||||
|
||||
(defn node-seq
|
||||
([root] (node-seq identity root))
|
||||
([match? root]
|
||||
(->> (tree-seq map? :children root)
|
||||
(filter match?)
|
||||
(seq))))
|
||||
|
||||
(defn is-text-node?
|
||||
[node]
|
||||
(and (nil? (:type node))
|
||||
(string? (:text node))))
|
||||
|
||||
(defn is-paragraph-set-node?
|
||||
[node]
|
||||
(= "paragraph-set" (:type node)))
|
||||
|
||||
(defn is-paragraph-node?
|
||||
[node]
|
||||
(= "paragraph" (:type node)))
|
||||
|
||||
(defn is-root-node?
|
||||
[node]
|
||||
(= "root" (:type node)))
|
||||
|
||||
(defn is-node?
|
||||
[node]
|
||||
(or ^boolean (is-text-node? node)
|
||||
^boolean (is-paragraph-node? node)
|
||||
^boolean (is-paragraph-set-node? node)
|
||||
^boolean (is-root-node? node)))
|
||||
|
||||
(defn is-content-node?
|
||||
"Only matches content nodes, ignoring the paragraph-set nodes."
|
||||
[node]
|
||||
(or ^boolean (is-text-node? node)
|
||||
^boolean (is-paragraph-node? node)
|
||||
^boolean (is-root-node? node)))
|
||||
|
||||
(defn transform-nodes
|
||||
([transform root]
|
||||
(transform-nodes identity transform root))
|
||||
([pred transform root]
|
||||
(walk/postwalk
|
||||
(fn [item]
|
||||
(if (and (is-node? item) (pred item))
|
||||
(transform item)
|
||||
item))
|
||||
root)))
|
||||
|
||||
(defn update-text-content
|
||||
[shape pred-fn update-fn attrs]
|
||||
(let [update-attrs-fn #(update-fn % attrs)
|
||||
transform #(transform-nodes pred-fn update-attrs-fn %)]
|
||||
(-> shape
|
||||
(update :content transform))))
|
||||
|
||||
(defn generate-shape-name
|
||||
[text]
|
||||
(subs text 0 (min 280 (count text))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; DraftJS <-> Penpot Conversion
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn encode-style-value
|
||||
[v]
|
||||
(t/encode-str v))
|
||||
@@ -122,7 +292,7 @@
|
||||
(fix-gradients)
|
||||
(assoc :text text))))
|
||||
|
||||
(split-texts [text styles]
|
||||
(split-texts [text styles data]
|
||||
(let [cpoints (text->code-points text)
|
||||
children (->> (parse-draft-styles styles)
|
||||
(build-style-index (count cpoints))
|
||||
@@ -131,7 +301,7 @@
|
||||
(mapv #(extract-text cpoints %)))]
|
||||
(cond-> children
|
||||
(empty? children)
|
||||
(conj {:text ""}))))
|
||||
(conj (assoc data :text "")))))
|
||||
|
||||
(build-paragraph [block]
|
||||
(let [key (get block :key)
|
||||
@@ -142,7 +312,7 @@
|
||||
(-> data
|
||||
(assoc :key key)
|
||||
(assoc :type "paragraph")
|
||||
(assoc :children (split-texts text styles)))))]
|
||||
(assoc :children (split-texts text styles data)))))]
|
||||
|
||||
{:type "root"
|
||||
:children
|
||||
@@ -199,7 +369,7 @@
|
||||
:entityRanges []
|
||||
:inlineStyleRanges (calc-ranges paragraph)})]
|
||||
|
||||
{:blocks (reduce #(conj %1 (build-block %2)) [] (types.text/node-seq #(= (:type %) "paragraph") root))
|
||||
{:blocks (reduce #(conj %1 (build-block %2)) [] (node-seq #(= (:type %) "paragraph") root))
|
||||
:entityMap {}}))
|
||||
|
||||
(defn content->text+styles
|
||||
@@ -207,13 +377,13 @@
|
||||
[node]
|
||||
(letfn
|
||||
[(rec-style-text-map [acc node style]
|
||||
(let [node-style (merge style (select-keys node types.text/text-all-attrs))
|
||||
(let [node-style (merge style (select-keys node text-all-attrs))
|
||||
head (or (-> acc first) [{} ""])
|
||||
[head-style head-text] head
|
||||
|
||||
new-acc
|
||||
(cond
|
||||
(not (types.text/is-text-node? node))
|
||||
(not (is-text-node? node))
|
||||
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
|
||||
|
||||
(not= head-style node-style)
|
||||
@@ -233,6 +403,82 @@
|
||||
(-> (rec-style-text-map [] node {})
|
||||
reverse)))
|
||||
|
||||
(defn content-range->text+styles
|
||||
"Given a root node of a text content extracts the texts with its associated styles"
|
||||
[node start end]
|
||||
(let [sss (content->text+styles node)]
|
||||
(loop [styles (seq sss)
|
||||
taking? false
|
||||
acc 0
|
||||
result []]
|
||||
(if styles
|
||||
(let [[node-style text] (first styles)
|
||||
from acc
|
||||
to (+ acc (count text))
|
||||
taking? (or taking? (and (<= from start) (< start to)))
|
||||
text (subs text (max 0 (- start acc)) (- end acc))
|
||||
result (cond-> result
|
||||
(and taking? (d/not-empty? text))
|
||||
(conj (assoc node-style :text text)))
|
||||
continue? (or (> from end) (>= end to))]
|
||||
(recur (when continue? (rest styles)) taking? to result))
|
||||
result))))
|
||||
|
||||
(defn content->text
|
||||
"Given a root node of a text content extracts the texts with its associated styles"
|
||||
[content]
|
||||
(letfn [(add-node [acc node]
|
||||
(cond
|
||||
(is-paragraph-node? node)
|
||||
(conj acc [])
|
||||
|
||||
(is-text-node? node)
|
||||
(let [i (dec (count acc))]
|
||||
(update acc i conj (:text node)))
|
||||
|
||||
:else
|
||||
acc))]
|
||||
(->> (node-seq content)
|
||||
(reduce add-node [])
|
||||
(map #(str/join "" %))
|
||||
(str/join "\n"))))
|
||||
|
||||
(defn change-text
|
||||
"Changes the content of the text shape to use the text as argument. Will use the styles of the
|
||||
first paragraph and text that is present in the shape (and override the rest)"
|
||||
[shape text]
|
||||
(let [content (:content shape)
|
||||
|
||||
root-styles (select-keys content root-attrs)
|
||||
|
||||
paragraph-style (merge
|
||||
default-text-attrs
|
||||
(select-keys (->> content (node-seq is-paragraph-node?) first) text-all-attrs))
|
||||
text-style (merge
|
||||
default-text-attrs
|
||||
(select-keys (->> content (node-seq is-text-node?) first) text-all-attrs))
|
||||
|
||||
paragraph-texts (str/split text "\n")
|
||||
|
||||
paragraphs
|
||||
(->> paragraph-texts
|
||||
(mapv
|
||||
(fn [pt]
|
||||
(merge
|
||||
paragraph-style
|
||||
{:type "paragraph"
|
||||
:children [(merge {:text pt} text-style)]}))))
|
||||
|
||||
new-content
|
||||
(d/patch-object
|
||||
{:type "root"
|
||||
:children
|
||||
[{:type "paragraph-set"
|
||||
:children paragraphs}]}
|
||||
root-styles)]
|
||||
|
||||
(assoc shape :content new-content)))
|
||||
|
||||
(defn index-content
|
||||
"Adds a property `$id` that identifies the current node inside"
|
||||
([content]
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.types.color
|
||||
(:refer-clojure :exclude [test])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.math :as mth]
|
||||
[app.common.media :as cm]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.schema.openapi :as-alias oapi]
|
||||
[app.common.text :as txt]
|
||||
[app.common.time :as dt]
|
||||
[app.common.types.plugins :as ctpg]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]))
|
||||
@@ -163,183 +163,11 @@
|
||||
(def check-color
|
||||
(sm/check-fn schema:color :hint "expected valid color"))
|
||||
|
||||
;: FIXME: maybe declare it under types.library ?
|
||||
(def check-library-color
|
||||
(sm/check-fn schema:library-color :hint "expected valid color"))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; CONSTANTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def ^:const black "#000000")
|
||||
(def ^:const default-layout "#DE4762")
|
||||
(def ^:const gray-20 "#B1B2B5")
|
||||
(def ^:const info "#59B9E2")
|
||||
(def ^:const test "#fabada")
|
||||
(def ^:const white "#FFFFFF")
|
||||
(def ^:const warning "#FC8802")
|
||||
|
||||
;; new-css-system colors
|
||||
(def ^:const new-primary "#7efff5")
|
||||
(def ^:const new-danger "#ff3277")
|
||||
(def ^:const new-warning "#fe4811")
|
||||
(def ^:const new-primary-light "#6911d4")
|
||||
(def ^:const background-quaternary "#2e3434")
|
||||
(def ^:const background-quaternary-light "#eef0f2")
|
||||
(def ^:const canvas "#E8E9EA")
|
||||
|
||||
(def names
|
||||
{"aliceblue" "#f0f8ff"
|
||||
"antiquewhite" "#faebd7"
|
||||
"aqua" "#00ffff"
|
||||
"aquamarine" "#7fffd4"
|
||||
"azure" "#f0ffff"
|
||||
"beige" "#f5f5dc"
|
||||
"bisque" "#ffe4c4"
|
||||
"black" "#000000"
|
||||
"blanchedalmond" "#ffebcd"
|
||||
"blue" "#0000ff"
|
||||
"blueviolet" "#8a2be2"
|
||||
"brown" "#a52a2a"
|
||||
"burlywood" "#deb887"
|
||||
"cadetblue" "#5f9ea0"
|
||||
"chartreuse" "#7fff00"
|
||||
"chocolate" "#d2691e"
|
||||
"coral" "#ff7f50"
|
||||
"cornflowerblue" "#6495ed"
|
||||
"cornsilk" "#fff8dc"
|
||||
"crimson" "#dc143c"
|
||||
"cyan" "#00ffff"
|
||||
"darkblue" "#00008b"
|
||||
"darkcyan" "#008b8b"
|
||||
"darkgoldenrod" "#b8860b"
|
||||
"darkgray" "#a9a9a9"
|
||||
"darkgreen" "#006400"
|
||||
"darkgrey" "#a9a9a9"
|
||||
"darkkhaki" "#bdb76b"
|
||||
"darkmagenta" "#8b008b"
|
||||
"darkolivegreen" "#556b2f"
|
||||
"darkorange" "#ff8c00"
|
||||
"darkorchid" "#9932cc"
|
||||
"darkred" "#8b0000"
|
||||
"darksalmon" "#e9967a"
|
||||
"darkseagreen" "#8fbc8f"
|
||||
"darkslateblue" "#483d8b"
|
||||
"darkslategray" "#2f4f4f"
|
||||
"darkslategrey" "#2f4f4f"
|
||||
"darkturquoise" "#00ced1"
|
||||
"darkviolet" "#9400d3"
|
||||
"deeppink" "#ff1493"
|
||||
"deepskyblue" "#00bfff"
|
||||
"dimgray" "#696969"
|
||||
"dimgrey" "#696969"
|
||||
"dodgerblue" "#1e90ff"
|
||||
"firebrick" "#b22222"
|
||||
"floralwhite" "#fffaf0"
|
||||
"forestgreen" "#228b22"
|
||||
"fuchsia" "#ff00ff"
|
||||
"gainsboro" "#dcdcdc"
|
||||
"ghostwhite" "#f8f8ff"
|
||||
"gold" "#ffd700"
|
||||
"goldenrod" "#daa520"
|
||||
"gray" "#808080"
|
||||
"green" "#008000"
|
||||
"greenyellow" "#adff2f"
|
||||
"grey" "#808080"
|
||||
"honeydew" "#f0fff0"
|
||||
"hotpink" "#ff69b4"
|
||||
"indianred" "#cd5c5c"
|
||||
"indigo" "#4b0082"
|
||||
"ivory" "#fffff0"
|
||||
"khaki" "#f0e68c"
|
||||
"lavender" "#e6e6fa"
|
||||
"lavenderblush" "#fff0f5"
|
||||
"lawngreen" "#7cfc00"
|
||||
"lemonchiffon" "#fffacd"
|
||||
"lightblue" "#add8e6"
|
||||
"lightcoral" "#f08080"
|
||||
"lightcyan" "#e0ffff"
|
||||
"lightgoldenrodyellow" "#fafad2"
|
||||
"lightgray" "#d3d3d3"
|
||||
"lightgreen" "#90ee90"
|
||||
"lightgrey" "#d3d3d3"
|
||||
"lightpink" "#ffb6c1"
|
||||
"lightsalmon" "#ffa07a"
|
||||
"lightseagreen" "#20b2aa"
|
||||
"lightskyblue" "#87cefa"
|
||||
"lightslategray" "#778899"
|
||||
"lightslategrey" "#778899"
|
||||
"lightsteelblue" "#b0c4de"
|
||||
"lightyellow" "#ffffe0"
|
||||
"lime" "#00ff00"
|
||||
"limegreen" "#32cd32"
|
||||
"linen" "#faf0e6"
|
||||
"magenta" "#ff00ff"
|
||||
"maroon" "#800000"
|
||||
"mediumaquamarine" "#66cdaa"
|
||||
"mediumblue" "#0000cd"
|
||||
"mediumorchid" "#ba55d3"
|
||||
"mediumpurple" "#9370db"
|
||||
"mediumseagreen" "#3cb371"
|
||||
"mediumslateblue" "#7b68ee"
|
||||
"mediumspringgreen" "#00fa9a"
|
||||
"mediumturquoise" "#48d1cc"
|
||||
"mediumvioletred" "#c71585"
|
||||
"midnightblue" "#191970"
|
||||
"mintcream" "#f5fffa"
|
||||
"mistyrose" "#ffe4e1"
|
||||
"moccasin" "#ffe4b5"
|
||||
"navajowhite" "#ffdead"
|
||||
"navy" "#000080"
|
||||
"oldlace" "#fdf5e6"
|
||||
"olive" "#808000"
|
||||
"olivedrab" "#6b8e23"
|
||||
"orange" "#ffa500"
|
||||
"orangered" "#ff4500"
|
||||
"orchid" "#da70d6"
|
||||
"palegoldenrod" "#eee8aa"
|
||||
"palegreen" "#98fb98"
|
||||
"paleturquoise" "#afeeee"
|
||||
"palevioletred" "#db7093"
|
||||
"papayawhip" "#ffefd5"
|
||||
"peachpuff" "#ffdab9"
|
||||
"peru" "#cd853f"
|
||||
"pink" "#ffc0cb"
|
||||
"plum" "#dda0dd"
|
||||
"powderblue" "#b0e0e6"
|
||||
"purple" "#800080"
|
||||
"red" "#ff0000"
|
||||
"rosybrown" "#bc8f8f"
|
||||
"royalblue" "#4169e1"
|
||||
"saddlebrown" "#8b4513"
|
||||
"salmon" "#fa8072"
|
||||
"sandybrown" "#f4a460"
|
||||
"seagreen" "#2e8b57"
|
||||
"seashell" "#fff5ee"
|
||||
"sienna" "#a0522d"
|
||||
"silver" "#c0c0c0"
|
||||
"skyblue" "#87ceeb"
|
||||
"slateblue" "#6a5acd"
|
||||
"slategray" "#708090"
|
||||
"slategrey" "#708090"
|
||||
"snow" "#fffafa"
|
||||
"springgreen" "#00ff7f"
|
||||
"steelblue" "#4682b4"
|
||||
"tan" "#d2b48c"
|
||||
"teal" "#008080"
|
||||
"thistle" "#d8bfd8"
|
||||
"tomato" "#ff6347"
|
||||
"turquoise" "#40e0d0"
|
||||
"violet" "#ee82ee"
|
||||
"wheat" "#f5deb3"
|
||||
"white" "#ffffff"
|
||||
"whitesmoke" "#f5f5f5"
|
||||
"yellow" "#ffff00"
|
||||
"yellowgreen" "#9acd32"})
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS (FIXME: this helpers are not in the correct place)
|
||||
;; HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn library-color->color
|
||||
@@ -353,6 +181,42 @@
|
||||
:path (get lcolor :path)
|
||||
:name (get lcolor :name))))
|
||||
|
||||
;; --- fill
|
||||
|
||||
(defn fill->color
|
||||
[fill]
|
||||
(d/without-nils
|
||||
{:color (:fill-color fill)
|
||||
:opacity (:fill-opacity fill)
|
||||
:gradient (:fill-color-gradient fill)
|
||||
:image (:fill-image fill)
|
||||
:ref-id (:fill-color-ref-id fill)
|
||||
:ref-file (:fill-color-ref-file fill)}))
|
||||
|
||||
(defn set-fill-color
|
||||
[shape position color opacity gradient image]
|
||||
(update-in shape [:fills position]
|
||||
(fn [fill]
|
||||
(d/without-nils (assoc fill
|
||||
:fill-color color
|
||||
:fill-opacity opacity
|
||||
:fill-color-gradient gradient
|
||||
:fill-image image)))))
|
||||
|
||||
(defn attach-fill-color
|
||||
[shape position ref-id ref-file]
|
||||
(d/update-in-when shape [:fills position]
|
||||
(fn [fill]
|
||||
(-> fill
|
||||
(assoc :fill-color-ref-file ref-file)
|
||||
(assoc :fill-color-ref-id ref-id)))))
|
||||
|
||||
(defn detach-fill-color
|
||||
[shape position]
|
||||
(d/update-in-when shape [:fills position] dissoc :fill-color-ref-id :fill-color-ref-file))
|
||||
|
||||
;; stroke
|
||||
|
||||
(defn stroke->color
|
||||
[stroke]
|
||||
(d/without-nils
|
||||
@@ -363,10 +227,59 @@
|
||||
:ref-id (:stroke-color-ref-id stroke)
|
||||
:ref-file (:stroke-color-ref-file stroke)}))
|
||||
|
||||
(defn set-stroke-color
|
||||
[shape position color opacity gradient image]
|
||||
(d/update-in-when shape [:strokes position]
|
||||
(fn [stroke]
|
||||
(-> stroke
|
||||
(assoc :stroke-color color)
|
||||
(assoc :stroke-opacity opacity)
|
||||
(assoc :stroke-color-gradient gradient)
|
||||
(assoc :stroke-image image)
|
||||
(d/without-nils)))))
|
||||
|
||||
(defn attach-stroke-color
|
||||
[shape position ref-id ref-file]
|
||||
(d/update-in-when shape [:strokes position]
|
||||
(fn [stroke]
|
||||
(-> stroke
|
||||
(assoc :stroke-color-ref-id ref-id)
|
||||
(assoc :stroke-color-ref-file ref-file)))))
|
||||
|
||||
(defn detach-stroke-color
|
||||
[shape position]
|
||||
(d/update-in-when shape [:strokes position] dissoc :stroke-color-ref-id :stroke-color-ref-file))
|
||||
|
||||
;; shadow
|
||||
|
||||
(defn shadow->color
|
||||
[shadow]
|
||||
(:color shadow))
|
||||
|
||||
(defn set-shadow-color
|
||||
[shape position color opacity gradient]
|
||||
(d/update-in-when shape [:shadow position :color]
|
||||
(fn [shadow-color]
|
||||
(-> shadow-color
|
||||
(assoc :color color)
|
||||
(assoc :opacity opacity)
|
||||
(assoc :gradient gradient)
|
||||
(d/without-nils)))))
|
||||
|
||||
(defn attach-shadow-color
|
||||
[shape position ref-id ref-file]
|
||||
(d/update-in-when shape [:shadow position :color]
|
||||
(fn [color]
|
||||
(-> color
|
||||
(assoc :ref-id ref-id)
|
||||
(assoc :ref-file ref-file)))))
|
||||
|
||||
(defn detach-shadow-color
|
||||
[shape position]
|
||||
(d/update-in-when shape [:shadow position :color] dissoc :ref-id :ref-file))
|
||||
|
||||
;; grid
|
||||
|
||||
;: FIXME: revisit colors...... WTF
|
||||
(defn grid->color
|
||||
[grid]
|
||||
@@ -378,374 +291,291 @@
|
||||
:ref-id (-> color :id)
|
||||
:ref-file (-> color :file-id)})))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
(defn set-grid-color
|
||||
[shape position color opacity gradient]
|
||||
(d/update-in-when shape [:grids position :params :color]
|
||||
(fn [grid-color]
|
||||
(-> grid-color
|
||||
(assoc :color color)
|
||||
(assoc :opacity opacity)
|
||||
(assoc :gradient gradient)
|
||||
(d/without-nils)))))
|
||||
|
||||
(def ^:private hex-color-re
|
||||
#"\#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})")
|
||||
(defn attach-grid-color
|
||||
[shape position ref-id ref-file]
|
||||
(d/update-in-when shape [:grids position :params :color]
|
||||
(fn [color]
|
||||
(-> color
|
||||
(assoc :ref-id ref-id)
|
||||
(assoc :ref-file ref-file)))))
|
||||
|
||||
(def ^:private rgb-color-re
|
||||
#"(?:|rgb)\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\)")
|
||||
(defn detach-grid-color
|
||||
[shape position]
|
||||
(d/update-in-when shape [:grids position :params :color] dissoc :ref-id :ref-file))
|
||||
|
||||
(defn valid-hex-color?
|
||||
;; --- Helpers for all colors in a shape
|
||||
|
||||
(defn get-text-node-colors
|
||||
"Get all colors used by a node of a text shape"
|
||||
[node]
|
||||
(concat (map fill->color (:fills node))
|
||||
(map stroke->color (:strokes node))))
|
||||
|
||||
(defn get-all-colors
|
||||
"Get all colors used by a shape, in any section."
|
||||
[shape]
|
||||
(concat (map fill->color (:fills shape))
|
||||
(map stroke->color (:strokes shape))
|
||||
(map shadow->color (:shadow shape))
|
||||
(when (= (:type shape) :frame)
|
||||
(map grid->color (:grids shape)))
|
||||
(when (= (:type shape) :text)
|
||||
(reduce (fn [colors node]
|
||||
(concat colors (get-text-node-colors node)))
|
||||
()
|
||||
(txt/node-seq (:content shape))))))
|
||||
|
||||
(defn uses-library-colors?
|
||||
"Check if the shape uses any color in the given library."
|
||||
[shape library-id]
|
||||
(let [all-colors (get-all-colors shape)]
|
||||
(some #(and (some? (:ref-id %))
|
||||
(= (:ref-file %) library-id))
|
||||
all-colors)))
|
||||
|
||||
(defn uses-library-color?
|
||||
"Check if the shape uses the given library color."
|
||||
[shape library-id color-id]
|
||||
(let [all-colors (get-all-colors shape)]
|
||||
(some #(and (= (:ref-id %) color-id)
|
||||
(= (:ref-file %) library-id))
|
||||
all-colors)))
|
||||
|
||||
(defn- process-shape-colors
|
||||
"Execute an update function on all colors of a shape."
|
||||
[shape process-fn]
|
||||
(let [process-fill (fn [shape [position fill]]
|
||||
(process-fn shape
|
||||
position
|
||||
(fill->color fill)
|
||||
set-fill-color
|
||||
attach-fill-color
|
||||
detach-fill-color))
|
||||
|
||||
process-stroke (fn [shape [position stroke]]
|
||||
(process-fn shape
|
||||
position
|
||||
(stroke->color stroke)
|
||||
set-stroke-color
|
||||
attach-stroke-color
|
||||
detach-stroke-color))
|
||||
|
||||
process-shadow (fn [shape [position shadow]]
|
||||
(process-fn shape
|
||||
position
|
||||
(shadow->color shadow)
|
||||
set-shadow-color
|
||||
attach-shadow-color
|
||||
detach-shadow-color))
|
||||
|
||||
process-grid (fn [shape [position grid]]
|
||||
(process-fn shape
|
||||
position
|
||||
(grid->color grid)
|
||||
set-grid-color
|
||||
attach-grid-color
|
||||
detach-grid-color))
|
||||
|
||||
process-text-node (fn [node]
|
||||
(as-> node $
|
||||
(reduce process-fill $ (d/enumerate (:fills $)))
|
||||
(reduce process-stroke $ (d/enumerate (:strokes $)))))
|
||||
|
||||
process-text (fn [shape]
|
||||
(let [content (:content shape)
|
||||
new-content (txt/transform-nodes process-text-node content)]
|
||||
(if (not= content new-content)
|
||||
(assoc shape :content new-content)
|
||||
shape)))]
|
||||
|
||||
(as-> shape $
|
||||
(reduce process-fill $ (d/enumerate (:fills $)))
|
||||
(reduce process-stroke $ (d/enumerate (:strokes $)))
|
||||
(reduce process-shadow $ (d/enumerate (:shadow $)))
|
||||
(reduce process-grid $ (d/enumerate (:grids $)))
|
||||
(process-text $))))
|
||||
|
||||
(defn remap-colors
|
||||
"Change the shape so that any use of the given color now points to
|
||||
the given library."
|
||||
[shape library-id color]
|
||||
(letfn [(remap-color [shape position shape-color _ attach-fn _]
|
||||
(if (= (:ref-id shape-color) (:id color))
|
||||
(attach-fn shape
|
||||
position
|
||||
(:id color)
|
||||
library-id)
|
||||
shape))]
|
||||
|
||||
(process-shape-colors shape remap-color)))
|
||||
|
||||
(defn sync-shape-colors
|
||||
"Look for usage of any color of the given library inside the shape,
|
||||
and, in this case, copy the library color into the shape."
|
||||
[shape library-id library-colors]
|
||||
(letfn [(sync-color [shape position shape-color set-fn _ detach-fn]
|
||||
(if (= (:ref-file shape-color) library-id)
|
||||
(let [library-color (get library-colors (:ref-id shape-color))]
|
||||
(if (some? library-color)
|
||||
(set-fn shape
|
||||
position
|
||||
(:color library-color)
|
||||
(:opacity library-color)
|
||||
(:gradient library-color)
|
||||
(:image library-color))
|
||||
(detach-fn shape position)))
|
||||
shape))]
|
||||
|
||||
(process-shape-colors shape sync-color)))
|
||||
|
||||
(defn- stroke->color-att
|
||||
[stroke file-id libraries]
|
||||
(let [ref-file (:stroke-color-ref-file stroke)
|
||||
ref-id (:stroke-color-ref-id stroke)
|
||||
shared-colors (dm/get-in libraries [ref-file :data :colors])
|
||||
is-shared? (contains? shared-colors ref-id)
|
||||
has-color? (or (:stroke-color stroke)
|
||||
(:stroke-color-gradient stroke))
|
||||
attrs (cond-> (stroke->color stroke)
|
||||
(not (or is-shared? (= ref-file file-id)))
|
||||
(dissoc :ref-id :ref-file))]
|
||||
(when has-color?
|
||||
{:attrs attrs
|
||||
:prop :stroke
|
||||
:shape-id (:shape-id stroke)
|
||||
:index (:index stroke)})))
|
||||
|
||||
(defn- shadow->color-att
|
||||
[shadow file-id libraries]
|
||||
(let [color (get shadow :color)
|
||||
ref-file (get color :ref-file)
|
||||
ref-id (get color :ref-id)
|
||||
shared-colors (dm/get-in libraries [ref-file :data :colors])
|
||||
is-shared? (contains? shared-colors ref-id)
|
||||
attrs (cond-> (shadow->color shadow)
|
||||
(not (or is-shared? (= ref-file file-id)))
|
||||
(dissoc :ref-file :ref-id))]
|
||||
{:attrs attrs
|
||||
:prop :shadow
|
||||
:shape-id (:shape-id shadow)
|
||||
:index (:index shadow)}))
|
||||
|
||||
(defn- text->color-att
|
||||
[fill file-id libraries]
|
||||
(let [ref-file (:fill-color-ref-file fill)
|
||||
ref-id (:fill-color-ref-id fill)
|
||||
shared-colors (dm/get-in libraries [ref-file :data :colors])
|
||||
is-shared? (contains? shared-colors ref-id)
|
||||
attrs (cond-> (fill->color fill)
|
||||
(not (or is-shared? (= ref-file file-id)))
|
||||
(dissoc :ref-file :ref-id))]
|
||||
|
||||
{:attrs attrs
|
||||
:prop :content
|
||||
:shape-id (:shape-id fill)
|
||||
:index (:index fill)}))
|
||||
|
||||
(defn- treat-node
|
||||
[node shape-id]
|
||||
(map-indexed #(assoc %2 :shape-id shape-id :index %1) node))
|
||||
|
||||
(defn- extract-text-colors
|
||||
[text file-id libraries]
|
||||
(->> (txt/node-seq txt/is-text-node? (:content text))
|
||||
(map :fills)
|
||||
(mapcat #(treat-node % (:id text)))
|
||||
(map #(text->color-att % file-id libraries))))
|
||||
|
||||
(defn- fill->color-att
|
||||
[fill file-id libraries]
|
||||
(let [ref-file (:fill-color-ref-file fill)
|
||||
ref-id (:fill-color-ref-id fill)
|
||||
shared-colors (dm/get-in libraries [ref-file :data :colors])
|
||||
is-shared? (contains? shared-colors ref-id)
|
||||
has-color? (or (:fill-color fill)
|
||||
(:fill-color-gradient fill))
|
||||
attrs (cond-> (fill->color fill)
|
||||
(not (or is-shared? (= ref-file file-id)))
|
||||
(dissoc :ref-file :ref-id))]
|
||||
|
||||
(when has-color?
|
||||
{:attrs attrs
|
||||
:prop :fill
|
||||
:shape-id (:shape-id fill)
|
||||
:index (:index fill)})))
|
||||
|
||||
(defn extract-all-colors
|
||||
[shapes file-id libraries]
|
||||
(reduce
|
||||
(fn [result shape]
|
||||
(let [fill-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:fills shape))
|
||||
stroke-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:strokes shape))
|
||||
shadow-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:shadow shape))]
|
||||
(if (= :text (:type shape))
|
||||
(-> result
|
||||
(into (map #(stroke->color-att % file-id libraries)) stroke-obj)
|
||||
(into (map #(shadow->color-att % file-id libraries)) shadow-obj)
|
||||
(into (extract-text-colors shape file-id libraries)))
|
||||
|
||||
(-> result
|
||||
(into (map #(fill->color-att % file-id libraries)) fill-obj)
|
||||
(into (map #(stroke->color-att % file-id libraries)) stroke-obj)
|
||||
(into (map #(shadow->color-att % file-id libraries)) shadow-obj)))))
|
||||
[]
|
||||
shapes))
|
||||
|
||||
(defn colors-seq
|
||||
[file-data]
|
||||
(vals (:colors file-data)))
|
||||
|
||||
(defn- touch
|
||||
[color]
|
||||
(and (string? color)
|
||||
(some? (re-matches hex-color-re color))))
|
||||
(assoc color :modified-at (dt/now)))
|
||||
|
||||
(defn parse-rgb
|
||||
[color]
|
||||
(let [result (re-matches rgb-color-re color)]
|
||||
(when (some? result)
|
||||
(let [r (parse-long (nth result 1))
|
||||
g (parse-long (nth result 2))
|
||||
b (parse-long (nth result 3))]
|
||||
(when (and (<= 0 r 255) (<= 0 g 255) (<= 0 b 255))
|
||||
[r g b])))))
|
||||
(defn add-color
|
||||
[file-data color]
|
||||
(update file-data :colors assoc (:id color) (touch color)))
|
||||
|
||||
(defn valid-rgb-color?
|
||||
[color]
|
||||
(if (string? color)
|
||||
(let [result (parse-rgb color)]
|
||||
(some? result))
|
||||
false))
|
||||
(defn get-color
|
||||
[file-data color-id]
|
||||
(get-in file-data [:colors color-id]))
|
||||
|
||||
(defn- normalize-hex
|
||||
[color]
|
||||
(if (= (count color) 4) ; of the form #RGB
|
||||
(-> color
|
||||
(str/replace #"\#(.)(.)(.)" "#$1$1$2$2$3$3")
|
||||
(str/lower))
|
||||
(str/lower color)))
|
||||
(defn get-ref-color
|
||||
[library-data color]
|
||||
(when (= (:ref-file color) (:id library-data))
|
||||
(get-color library-data (:ref-id color))))
|
||||
|
||||
(defn rgb->str
|
||||
[[r g b a]]
|
||||
(if (some? a)
|
||||
(str/ffmt "rgba(%,%,%,%)" r g b a)
|
||||
(str/ffmt "rgb(%,%,%)" r g b)))
|
||||
(defn set-color
|
||||
[file-data color]
|
||||
(d/assoc-in-when file-data [:colors (:id color)] (touch color)))
|
||||
|
||||
(defn rgb->hsv
|
||||
[[red green blue]]
|
||||
(let [max (d/max red green blue)
|
||||
min (d/min red green blue)
|
||||
val max]
|
||||
(if (= min max)
|
||||
[0 0 val]
|
||||
(let [delta (- max min)
|
||||
sat (/ delta max)
|
||||
hue (if (= red max)
|
||||
(/ (- green blue) delta)
|
||||
(if (= green max)
|
||||
(+ 2 (/ (- blue red) delta))
|
||||
(+ 4 (/ (- red green) delta))))
|
||||
hue (* 60 hue)
|
||||
hue (if (< hue 0)
|
||||
(+ hue 360)
|
||||
hue)
|
||||
hue (if (> hue 360)
|
||||
(- hue 360)
|
||||
hue)]
|
||||
[hue sat val]))))
|
||||
(defn update-color
|
||||
[file-data color-id f & args]
|
||||
(d/update-in-when file-data [:colors color-id] #(-> (apply f % args)
|
||||
(touch))))
|
||||
|
||||
(defn hsv->rgb
|
||||
[[h s brightness]]
|
||||
(if (= s 0)
|
||||
[brightness brightness brightness]
|
||||
(let [sextant (int (mth/floor (/ h 60)))
|
||||
remainder (- (/ h 60) sextant)
|
||||
brightness (d/nilv brightness 0)
|
||||
val1 (int (* brightness (- 1 s)))
|
||||
val2 (int (* brightness (- 1 (* s remainder))))
|
||||
val3 (int (* brightness (- 1 (* s (- 1 remainder)))))]
|
||||
(case sextant
|
||||
1 [val2 brightness val1]
|
||||
2 [val1 brightness val3]
|
||||
3 [val1 val2 brightness]
|
||||
4 [val3 val1 brightness]
|
||||
5 [brightness val1 val2]
|
||||
6 [brightness val3 val1]
|
||||
0 [brightness val3 val1]))))
|
||||
(defn delete-color
|
||||
[file-data color-id]
|
||||
(update file-data :colors dissoc color-id))
|
||||
|
||||
(defn hex->rgb
|
||||
[color]
|
||||
(try
|
||||
(let [rgb #?(:clj (Integer/parseInt (subs color 1) 16)
|
||||
:cljs (js/parseInt (subs color 1) 16))
|
||||
r (bit-shift-right rgb 16)
|
||||
g (bit-and (bit-shift-right rgb 8) 255)
|
||||
b (bit-and rgb 255)]
|
||||
[r g b])
|
||||
(catch #?(:clj Throwable :cljs :default) _cause
|
||||
[0 0 0])))
|
||||
(defn used-colors-changed-since
|
||||
"Find all usages of any color in the library by the given shape, of colors
|
||||
that have ben modified after the date."
|
||||
[shape library since-date]
|
||||
(->> (get-all-colors shape)
|
||||
(keep #(get-ref-color (:data library) %))
|
||||
(remove #(< (:modified-at %) since-date)) ;; Note that :modified-at may be nil
|
||||
(map (fn [color] {:shape-id (:id shape)
|
||||
:asset-id (:id color)
|
||||
:asset-type :color}))))
|
||||
|
||||
(defn hex->lum
|
||||
[color]
|
||||
(let [[r g b] (hex->rgb color)]
|
||||
(mth/sqrt (+ (* 0.241 r)
|
||||
(* 0.691 g)
|
||||
(* 0.068 b)))))
|
||||
|
||||
(defn- int->hex
|
||||
"Convert integer to hex string"
|
||||
[v]
|
||||
#?(:clj (Integer/toHexString v)
|
||||
:cljs (.toString v 16)))
|
||||
|
||||
(defn rgb->hex
|
||||
[[r g b]]
|
||||
(let [r (int r)
|
||||
g (int g)
|
||||
b (int b)]
|
||||
(if (or (not= r (bit-and r 255))
|
||||
(not= g (bit-and g 255))
|
||||
(not= b (bit-and b 255)))
|
||||
(throw (ex-info "not valid rgb" {:r r :g g :b b}))
|
||||
(let [rgb (bit-or (bit-shift-left r 16)
|
||||
(bit-shift-left g 8) b)]
|
||||
(if (< r 16)
|
||||
(dm/str "#" (subs (int->hex (bit-or 0x1000000 rgb)) 1))
|
||||
(dm/str "#" (int->hex rgb)))))))
|
||||
|
||||
(defn rgb->hsl
|
||||
[[r g b]]
|
||||
(let [norm-r (/ r 255.0)
|
||||
norm-g (/ g 255.0)
|
||||
norm-b (/ b 255.0)
|
||||
max (d/max norm-r norm-g norm-b)
|
||||
min (d/min norm-r norm-g norm-b)
|
||||
l (/ (+ max min) 2.0)
|
||||
h (if (= max min) 0
|
||||
(if (= max norm-r)
|
||||
(* 60 (/ (- norm-g norm-b) (- max min)))
|
||||
(if (= max norm-g)
|
||||
(+ 120 (* 60 (/ (- norm-b norm-r) (- max min))))
|
||||
(+ 240 (* 60 (/ (- norm-r norm-g) (- max min)))))))
|
||||
s (if (and (> l 0) (<= l 0.5))
|
||||
(/ (- max min) (* 2 l))
|
||||
(/ (- max min) (- 2 (* 2 l))))]
|
||||
[(mod (+ h 360) 360) s l]))
|
||||
|
||||
(defn hex->hsv
|
||||
[v]
|
||||
(-> v hex->rgb rgb->hsv))
|
||||
|
||||
(defn hex->rgba
|
||||
[data opacity]
|
||||
(-> (hex->rgb data)
|
||||
(conj opacity)))
|
||||
|
||||
(defn hex->hsl [hex]
|
||||
(try
|
||||
(-> hex hex->rgb rgb->hsl)
|
||||
(catch #?(:clj Throwable :cljs :default) _e
|
||||
[0 0 0])))
|
||||
|
||||
(defn hex->hsla
|
||||
[data opacity]
|
||||
(-> (hex->hsl data)
|
||||
(conj opacity)))
|
||||
|
||||
(defn format-hsla
|
||||
[[h s l a]]
|
||||
(let [precision 2
|
||||
rounded-h (int h)
|
||||
rounded-s (d/format-number (* 100 s) precision)
|
||||
rounded-l (d/format-number (* 100 l) precision)
|
||||
rounded-a (d/format-number a precision)]
|
||||
(str/concat "" rounded-h ", " rounded-s "%, " rounded-l "%, " rounded-a)))
|
||||
|
||||
(defn format-rgba
|
||||
[[r g b a]]
|
||||
(let [precision 2
|
||||
rounded-a (d/format-number a precision)]
|
||||
(str/ffmt "%, %, %, %" r g b rounded-a)))
|
||||
|
||||
(defn- hue->rgb
|
||||
"Helper for hsl->rgb"
|
||||
[v1 v2 vh]
|
||||
(let [vh (if (< vh 0)
|
||||
(+ vh 1)
|
||||
(if (> vh 1)
|
||||
(- vh 1)
|
||||
vh))]
|
||||
(cond
|
||||
(< (* 6 vh) 1) (+ v1 (* (- v2 v1) 6 vh))
|
||||
(< (* 2 vh) 1) v2
|
||||
(< (* 3 vh) 2) (+ v1 (* (- v2 v1) (- (/ 2 3) vh) 6))
|
||||
:else v1)))
|
||||
|
||||
(defn hsl->rgb
|
||||
[[h s l]]
|
||||
(if (= s 0)
|
||||
(let [o (* l 255)]
|
||||
[o o o])
|
||||
(let [norm-h (/ h 360.0)
|
||||
temp2 (if (< l 0.5)
|
||||
(* l (+ 1 s))
|
||||
(- (+ l s)
|
||||
(* s l)))
|
||||
temp1 (- (* l 2) temp2)]
|
||||
|
||||
[(mth/round (* 255 (hue->rgb temp1 temp2 (+ norm-h (/ 1 3)))))
|
||||
(mth/round (* 255 (hue->rgb temp1 temp2 norm-h)))
|
||||
(mth/round (* 255 (hue->rgb temp1 temp2 (- norm-h (/ 1 3)))))])))
|
||||
|
||||
(defn hsl->hex
|
||||
[v]
|
||||
(-> v hsl->rgb rgb->hex))
|
||||
|
||||
(defn hsl->hsv
|
||||
[hsl]
|
||||
(-> hsl hsl->rgb rgb->hsv))
|
||||
|
||||
(defn hsv->hex
|
||||
[hsv]
|
||||
(-> hsv hsv->rgb rgb->hex))
|
||||
|
||||
(defn hsv->hsl
|
||||
[hsv]
|
||||
(-> hsv hsv->hex hex->hsl))
|
||||
|
||||
(defn expand-hex
|
||||
[v]
|
||||
(cond
|
||||
(re-matches #"^[0-9A-Fa-f]$" v)
|
||||
(dm/str v v v v v v)
|
||||
|
||||
(re-matches #"^[0-9A-Fa-f]{2}$" v)
|
||||
(dm/str v v v)
|
||||
|
||||
(re-matches #"^[0-9A-Fa-f]{3}$" v)
|
||||
(let [a (nth v 0)
|
||||
b (nth v 1)
|
||||
c (nth v 2)]
|
||||
(dm/str a a b b c c))
|
||||
|
||||
:else
|
||||
v))
|
||||
|
||||
(defn prepend-hash
|
||||
[color]
|
||||
(if (= "#" (subs color 0 1))
|
||||
color
|
||||
(dm/str "#" color)))
|
||||
|
||||
(defn remove-hash
|
||||
[color]
|
||||
(if (str/starts-with? color "#")
|
||||
(subs color 1)
|
||||
color))
|
||||
|
||||
(defn color-string?
|
||||
[color]
|
||||
(and (string? color)
|
||||
(or (valid-hex-color? color)
|
||||
(valid-rgb-color? color)
|
||||
(contains? names color))))
|
||||
|
||||
(defn parse
|
||||
[color]
|
||||
(when (string? color)
|
||||
(if (or (valid-hex-color? color)
|
||||
(valid-hex-color? (dm/str "#" color)))
|
||||
(normalize-hex color)
|
||||
(or (some-> (parse-rgb color) (rgb->hex))
|
||||
(get names (str/lower color))))))
|
||||
|
||||
(def color-names
|
||||
(into [] (keys names)))
|
||||
|
||||
(def empty-color
|
||||
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity]))
|
||||
|
||||
(defn next-rgb
|
||||
"Given a color in rgb returns the next color"
|
||||
[[r g b]]
|
||||
(cond
|
||||
(and (= 255 r) (= 255 g) (= 255 b))
|
||||
(throw (ex-info "cannot get next color" {:r r :g g :b b}))
|
||||
|
||||
(and (= 255 g) (= 255 b))
|
||||
[(inc r) 0 0]
|
||||
|
||||
(= 255 b)
|
||||
[r (inc g) 0]
|
||||
|
||||
:else
|
||||
[r g (inc b)]))
|
||||
|
||||
(defn reduce-range
|
||||
[value range]
|
||||
(/ (mth/floor (* value range)) range))
|
||||
|
||||
(defn sort-colors
|
||||
[a b]
|
||||
(let [[ah _ av] (hex->hsv (:color a))
|
||||
[bh _ bv] (hex->hsv (:color b))
|
||||
ah (reduce-range (/ ah 60) 8)
|
||||
bh (reduce-range (/ bh 60) 8)
|
||||
av (/ av 255)
|
||||
bv (/ bv 255)
|
||||
a (+ (* ah 100) (* av 10))
|
||||
b (+ (* bh 100) (* bv 10))]
|
||||
(compare a b)))
|
||||
|
||||
(defn interpolate-color
|
||||
[c1 c2 offset]
|
||||
(cond
|
||||
(<= offset (:offset c1)) (assoc c1 :offset offset)
|
||||
(>= offset (:offset c2)) (assoc c2 :offset offset)
|
||||
|
||||
:else
|
||||
(let [tr-offset (/ (- offset (:offset c1)) (- (:offset c2) (:offset c1)))
|
||||
[r1 g1 b1] (hex->rgb (:color c1))
|
||||
[r2 g2 b2] (hex->rgb (:color c2))
|
||||
a1 (:opacity c1)
|
||||
a2 (:opacity c2)
|
||||
r (+ r1 (* (- r2 r1) tr-offset))
|
||||
g (+ g1 (* (- g2 g1) tr-offset))
|
||||
b (+ b1 (* (- b2 b1) tr-offset))
|
||||
a (+ a1 (* (- a2 a1) tr-offset))]
|
||||
{:color (rgb->hex [r g b])
|
||||
:opacity a
|
||||
:r r
|
||||
:g g
|
||||
:b b
|
||||
:alpha a
|
||||
:offset offset})))
|
||||
|
||||
(defn- offset-spread
|
||||
[from to num]
|
||||
(->> (range 0 num)
|
||||
(map #(mth/precision (+ from (* (/ (- to from) (dec num)) %)) 2))))
|
||||
|
||||
(defn uniform-spread?
|
||||
"Checks if the gradient stops are spread uniformly"
|
||||
[stops]
|
||||
(let [cs (count stops)
|
||||
from (first stops)
|
||||
to (last stops)
|
||||
expect-vals (offset-spread (:offset from) (:offset to) cs)
|
||||
|
||||
calculate-expected
|
||||
(fn [expected-offset stop]
|
||||
(and (mth/close? (:offset stop) expected-offset)
|
||||
(let [ec (interpolate-color from to expected-offset)]
|
||||
(and (= (:color ec) (:color stop))
|
||||
(= (:opacity ec) (:opacity stop))))))]
|
||||
(->> (map calculate-expected expect-vals stops)
|
||||
(every? true?))))
|
||||
|
||||
(defn uniform-spread
|
||||
"Assign an uniform spread to the offset values for the gradient"
|
||||
[from to num-stops]
|
||||
(->> (offset-spread (:offset from) (:offset to) num-stops)
|
||||
(mapv (fn [offset]
|
||||
(interpolate-color from to offset)))))
|
||||
|
||||
(defn interpolate-gradient
|
||||
[stops offset]
|
||||
(let [idx (d/index-of-pred stops #(<= offset (:offset %)))
|
||||
start (if (= idx 0) (first stops) (get stops (dec idx)))
|
||||
end (if (nil? idx) (last stops) (get stops idx))]
|
||||
(interpolate-color start end offset)))
|
||||
|
||||
@@ -16,17 +16,15 @@
|
||||
[app.common.geom.shapes.tree-seq :as gsts]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.text :as ct]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.library :as ctlb]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.plugins :refer [schema:plugin-data]]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.tokens-lib :refer [schema:tokens-lib]]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[app.common.types.typography :as cty]
|
||||
@@ -523,7 +521,7 @@
|
||||
|
||||
(defmethod uses-asset? :color
|
||||
[_ shape library-id color]
|
||||
(cts/uses-library-color? shape library-id (:id color)))
|
||||
(ctc/uses-library-color? shape library-id (:id color)))
|
||||
|
||||
(defmethod uses-asset? :typography
|
||||
[_ shape library-id typography]
|
||||
@@ -535,10 +533,10 @@
|
||||
|
||||
Returns a list ((asset ((container shapes) (container shapes)...))...)"
|
||||
[file-data library-data asset-type]
|
||||
(let [assets (case asset-type
|
||||
:component (ctkl/components-seq library-data)
|
||||
:color (vals (ctlb/get-colors library-data))
|
||||
:typography (ctyl/typographies-seq library-data))
|
||||
(let [assets-seq (case asset-type
|
||||
:component (ctkl/components-seq library-data)
|
||||
:color (ctc/colors-seq library-data)
|
||||
:typography (ctyl/typographies-seq library-data))
|
||||
|
||||
find-usages-in-container
|
||||
(fn [container asset]
|
||||
@@ -555,7 +553,7 @@
|
||||
(let [instances (find-asset-usages file-data asset)]
|
||||
(when (d/not-empty? instances)
|
||||
[[asset instances]])))
|
||||
assets)))
|
||||
assets-seq)))
|
||||
|
||||
(defn used-in?
|
||||
"Checks if a specific asset is used in a given file (by any shape in its pages or in
|
||||
@@ -576,7 +574,7 @@
|
||||
(letfn [(used-assets-shape [shape]
|
||||
(concat
|
||||
(ctkl/used-components-changed-since shape library since-date)
|
||||
(ctlb/used-colors-changed-since shape library since-date)
|
||||
(ctc/used-colors-changed-since shape library since-date)
|
||||
(ctyl/used-typographies-changed-since shape library since-date)))
|
||||
|
||||
(used-assets-container [container]
|
||||
@@ -695,12 +693,11 @@
|
||||
|
||||
(add-component-grid file-data (sort-by #(:name (first %)) used-components))))
|
||||
|
||||
;: FIXME: this can be moved to library
|
||||
(defn- absorb-colors
|
||||
[file-data used-colors]
|
||||
(let [absorb-color
|
||||
(fn [file-data [color usages]]
|
||||
(let [remap-shape #(cts/remap-colors % (:id file-data) color)
|
||||
(let [remap-shape #(ctc/remap-colors % (:id file-data) color)
|
||||
|
||||
remap-shapes
|
||||
(fn [file-data [container shapes]]
|
||||
@@ -713,7 +710,7 @@
|
||||
%
|
||||
shapes)))]
|
||||
(as-> file-data $
|
||||
(ctlb/add-color $ color)
|
||||
(ctc/add-color $ color)
|
||||
(reduce remap-shapes $ usages))))]
|
||||
|
||||
(reduce absorb-color
|
||||
@@ -1049,7 +1046,7 @@
|
||||
(let [detach-text
|
||||
(fn [content]
|
||||
(->> content
|
||||
(txt/transform-nodes
|
||||
(ct/transform-nodes
|
||||
#(cond-> %
|
||||
(not= file-id (:fill-color-ref-file %))
|
||||
(dissoc :fill-color-ref-id :fill-color-ref-file)
|
||||
|
||||
68
common/src/app/common/types/fill.cljc
Normal file
@@ -0,0 +1,68 @@
|
||||
;; 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.types.fill
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.color :as types.color]
|
||||
[app.common.types.fill.impl :as impl]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(def ^:const MAX-GRADIENT-STOPS impl/MAX-GRADIENT-STOPS)
|
||||
(def ^:const MAX-FILLS impl/MAX-FILLS)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMAS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def schema:fill-attrs
|
||||
[:map {:title "FillAttrs" :closed true}
|
||||
[:fill-color-ref-file {:optional true} ::sm/uuid]
|
||||
[:fill-color-ref-id {:optional true} ::sm/uuid]
|
||||
[:fill-opacity {:optional true} [::sm/number {:min 0 :max 1}]]
|
||||
[:fill-color {:optional true} types.color/schema:hex-color]
|
||||
[:fill-color-gradient {:optional true} types.color/schema:gradient]
|
||||
[:fill-image {:optional true} types.color/schema:image]])
|
||||
|
||||
(def fill-attrs
|
||||
"A set of attrs that corresponds to fill data type"
|
||||
(sm/keys schema:fill-attrs))
|
||||
|
||||
(def valid-fill-attrs
|
||||
"A set used for proper check if color should contain only one of the
|
||||
attrs listed in this set."
|
||||
#{:fill-image :fill-color :fill-color-gradient})
|
||||
|
||||
(defn has-valid-fill-attrs?
|
||||
"Check if color has correct color attrs"
|
||||
[color]
|
||||
(let [attrs (set (keys color))
|
||||
result (set/intersection attrs valid-fill-attrs)]
|
||||
(= 1 (count result))))
|
||||
|
||||
(def schema:fill
|
||||
[:and schema:fill-attrs
|
||||
[:fn has-valid-fill-attrs?]])
|
||||
|
||||
(def check-fill
|
||||
(sm/check-fn schema:fill))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; CONSTRUCTORS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn from-plain
|
||||
[o]
|
||||
(assert (every? check-fill o) "expected valid fills vector")
|
||||
(impl/from-plain o))
|
||||
|
||||
(defn fills?
|
||||
[o]
|
||||
(impl/fills? o))
|
||||
@@ -4,14 +4,13 @@
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.types.fills.impl
|
||||
(ns app.common.types.fill.impl
|
||||
(:require
|
||||
#?(:clj [clojure.data.json :as json])
|
||||
#?(:cljs [app.common.weak-map :as weak-map])
|
||||
[app.common.buffer :as buf]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.math :as mth]
|
||||
[app.common.transit :as t]))
|
||||
|
||||
@@ -23,7 +22,7 @@
|
||||
(def ^:const GRADIENT-STOP-SIZE 8)
|
||||
(def ^:const GRADIENT-BYTE-SIZE 156)
|
||||
(def ^:const SOLID-BYTE-SIZE 4)
|
||||
(def ^:const IMAGE-BYTE-SIZE 36)
|
||||
(def ^:const IMAGE-BYTE-SIZE 28)
|
||||
(def ^:const METADATA-BYTE-SIZE 36)
|
||||
(def ^:const FILL-BYTE-SIZE
|
||||
(+ 4 (mth/max GRADIENT-BYTE-SIZE
|
||||
@@ -36,13 +35,6 @@
|
||||
(def ^:private xf:take-fills
|
||||
(take MAX-FILLS))
|
||||
|
||||
(defprotocol IHeapWritable
|
||||
(-write-to [_ buffer offset] "write the content to the specified buffer")
|
||||
(-get-byte-size [_] "get byte size"))
|
||||
|
||||
(defprotocol IBinaryFills
|
||||
(-get-image-ids [_] "get referenced image ids"))
|
||||
|
||||
(defn- hex->rgb
|
||||
"Encode an hex string as rgb (int32)"
|
||||
[hex]
|
||||
@@ -72,16 +64,16 @@
|
||||
n (unsigned-bit-shift-right n 24)]
|
||||
(mth/precision (/ (float n) 0xff) 2)))
|
||||
|
||||
(defn write-solid-fill
|
||||
[offset buffer opacity color]
|
||||
(defn- write-solid-fill
|
||||
[offset buffer color alpha]
|
||||
(buf/write-byte buffer (+ offset 0) 0x00)
|
||||
(buf/write-int buffer (+ offset 4)
|
||||
(-> (hex->rgb color)
|
||||
(rgb->rgba opacity)))
|
||||
(rgb->rgba alpha)))
|
||||
(+ offset FILL-BYTE-SIZE))
|
||||
|
||||
(defn write-gradient-fill
|
||||
[offset buffer opacity gradient]
|
||||
(defn- write-gradient-fill
|
||||
[offset buffer gradient opacity]
|
||||
(let [start-x (:start-x gradient)
|
||||
start-y (:start-y gradient)
|
||||
end-x (:end-x gradient)
|
||||
@@ -116,18 +108,16 @@
|
||||
(+ offset' GRADIENT-STOP-SIZE)))
|
||||
(+ offset FILL-BYTE-SIZE)))))
|
||||
|
||||
(defn write-image-fill
|
||||
(defn- write-image-fill
|
||||
[offset buffer opacity image]
|
||||
(let [image-id (get image :id)
|
||||
image-width (get image :width)
|
||||
image-height (get image :height)
|
||||
keep-aspect-ratio (get image :keep-aspect-ratio false)]
|
||||
(let [image-id (get image :id)
|
||||
image-width (get image :width)
|
||||
image-height (get image :height)]
|
||||
(buf/write-byte buffer (+ offset 0) 0x03)
|
||||
(buf/write-uuid buffer (+ offset 4) image-id)
|
||||
(buf/write-float buffer (+ offset 20) opacity)
|
||||
(buf/write-int buffer (+ offset 24) image-width)
|
||||
(buf/write-int buffer (+ offset 28) image-height)
|
||||
(buf/write-bool buffer (+ offset 32) keep-aspect-ratio)
|
||||
(+ offset FILL-BYTE-SIZE)))
|
||||
|
||||
(defn- write-metadata
|
||||
@@ -138,21 +128,21 @@
|
||||
|
||||
(when mtype
|
||||
(let [val (case mtype
|
||||
"image/jpeg" 0x01
|
||||
"image/png" 0x02
|
||||
"image/gif" 0x03
|
||||
"image/webp" 0x04
|
||||
"image/jpeg" 0x01
|
||||
"image/png" 0x02
|
||||
"image/gif" 0x03
|
||||
"image/webp" 0x04
|
||||
"image/svg+xml" 0x05)]
|
||||
(buf/write-short buffer (+ offset 2) val)))
|
||||
|
||||
(if (and (some? ref-file)
|
||||
(some? ref-id))
|
||||
(do
|
||||
(buf/write-bool buffer (+ offset 0) true)
|
||||
(buf/write-byte buffer (+ offset 0) 0x01)
|
||||
(buf/write-uuid buffer (+ offset 4) ref-file)
|
||||
(buf/write-uuid buffer (+ offset 20) ref-id))
|
||||
(do
|
||||
(buf/write-bool buffer (+ offset 0) false)))))
|
||||
(buf/write-byte buffer (+ offset 0) 0x00)))))
|
||||
|
||||
(defn- read-stop
|
||||
[buffer offset]
|
||||
@@ -203,8 +193,7 @@
|
||||
:type type}})
|
||||
|
||||
3
|
||||
(let [ratio (buf/read-bool dbuffer (+ doffset 32))
|
||||
id (buf/read-uuid dbuffer (+ doffset 4))
|
||||
(let [id (buf/read-uuid dbuffer (+ doffset 4))
|
||||
alpha (buf/read-float dbuffer (+ doffset 20))
|
||||
width (buf/read-int dbuffer (+ doffset 24))
|
||||
height (buf/read-int dbuffer (+ doffset 28))
|
||||
@@ -220,7 +209,6 @@
|
||||
:width width
|
||||
:height height
|
||||
:mtype mtype
|
||||
:keep-aspect-ratio ratio
|
||||
;; FIXME: we are not encodign the name, looks useless
|
||||
:name "sample"}}))]
|
||||
|
||||
@@ -290,20 +278,7 @@
|
||||
|
||||
:cljs
|
||||
#_:clj-kondo/ignore
|
||||
(deftype Fills [size dbuffer mbuffer image-ids cache ^:mutable __hash]
|
||||
|
||||
IHeapWritable
|
||||
(-get-byte-size [_]
|
||||
(- (.-byteLength dbuffer) 4))
|
||||
|
||||
(-write-to [_ heap offset]
|
||||
(let [buffer' (.-buffer ^js/DataView dbuffer)]
|
||||
(.set heap (js/Uint32Array. buffer' 4) offset)))
|
||||
|
||||
IBinaryFills
|
||||
(-get-image-ids [_]
|
||||
image-ids)
|
||||
|
||||
(deftype Fills [size dbuffer mbuffer cache ^:mutable __hash]
|
||||
cljs.core/ISequential
|
||||
cljs.core/IEquiv
|
||||
(-equiv [this other]
|
||||
@@ -378,26 +353,7 @@
|
||||
(when (< i size)
|
||||
(cons (read-fill dbuffer mbuffer i)
|
||||
(lazy-seq (next-seq (inc i))))))
|
||||
0)))
|
||||
|
||||
cljs.core/IPrintWithWriter
|
||||
(-pr-writer [this writer _]
|
||||
(binding [*print-dup* true]
|
||||
(cljs.core/-write writer (str "#penpot/fills \"" (pr-str (vec this)) "\""))))))
|
||||
|
||||
#?(:clj
|
||||
(defmethod print-method Fills
|
||||
[o ^java.io.Writer writer]
|
||||
(.write writer "#penpot/fills \"")
|
||||
(print-dup (vec o) writer)
|
||||
(.write writer "\"")))
|
||||
|
||||
#?(:clj
|
||||
(defmethod print-dup Fills
|
||||
[o ^java.io.Writer writer]
|
||||
(.write writer "#penpot/fills \"")
|
||||
(print-dup (vec o) writer)
|
||||
(.write writer "\"")))
|
||||
0)))))
|
||||
|
||||
(defn from-plain
|
||||
[fills]
|
||||
@@ -408,9 +364,8 @@
|
||||
|
||||
(buf/write-byte dbuffer 0 total)
|
||||
|
||||
(loop [index 0
|
||||
image-ids #{}]
|
||||
(if (< index total)
|
||||
(loop [index 0]
|
||||
(when (< index total)
|
||||
(let [fill (nth fills index)
|
||||
doffset (+ 4 (* index FILL-BYTE-SIZE))
|
||||
moffset (* index METADATA-BYTE-SIZE)
|
||||
@@ -418,26 +373,23 @@
|
||||
|
||||
(if-let [color (get fill :fill-color)]
|
||||
(do
|
||||
(write-solid-fill doffset dbuffer opacity color)
|
||||
(write-solid-fill doffset dbuffer color opacity)
|
||||
(write-metadata moffset mbuffer fill)
|
||||
(recur (inc index) image-ids))
|
||||
(recur (inc index)))
|
||||
(if-let [gradient (get fill :fill-color-gradient)]
|
||||
(do
|
||||
(write-gradient-fill doffset dbuffer opacity gradient)
|
||||
(write-gradient-fill doffset dbuffer gradient opacity)
|
||||
(write-metadata moffset mbuffer fill)
|
||||
(recur (inc index) image-ids))
|
||||
(recur (inc index)))
|
||||
(if-let [image (get fill :fill-image)]
|
||||
(do
|
||||
(write-image-fill doffset dbuffer opacity image)
|
||||
(write-metadata moffset mbuffer fill)
|
||||
(recur (inc index)
|
||||
(conj image-ids (get image :id))))
|
||||
(ex/raise :type :internal
|
||||
:code :invalid-fill
|
||||
:hint "found invalid fill on encoding fills to binary format")))))
|
||||
(recur (inc index)))
|
||||
(recur (inc index))))))))
|
||||
|
||||
#?(:cljs (Fills. total dbuffer mbuffer image-ids (weak-map/create) nil)
|
||||
:clj (Fills. total dbuffer mbuffer nil))))))
|
||||
#?(:cljs (Fills. total dbuffer mbuffer (weak-map/create) nil)
|
||||
:clj (Fills. total dbuffer mbuffer nil))))
|
||||
|
||||
(defn fills?
|
||||
[o]
|
||||
@@ -1,156 +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 app.common.types.fills
|
||||
(:refer-clojure :exclude [assoc update])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.flags :as flags]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.color :as types.color]
|
||||
[app.common.types.fills.impl :as impl]
|
||||
[clojure.core :as c]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(def ^:const MAX-GRADIENT-STOPS impl/MAX-GRADIENT-STOPS)
|
||||
(def ^:const MAX-FILLS impl/MAX-FILLS)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMAS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def schema:fill-attrs
|
||||
[:map {:title "FillAttrs" :closed true}
|
||||
[:fill-color-ref-file {:optional true} ::sm/uuid]
|
||||
[:fill-color-ref-id {:optional true} ::sm/uuid]
|
||||
[:fill-opacity {:optional true} [::sm/number {:min 0 :max 1}]]
|
||||
[:fill-color {:optional true} types.color/schema:hex-color]
|
||||
[:fill-color-gradient {:optional true} types.color/schema:gradient]
|
||||
[:fill-image {:optional true} types.color/schema:image]])
|
||||
|
||||
(def fill-attrs
|
||||
"A set of attrs that corresponds to fill data type"
|
||||
(sm/keys schema:fill-attrs))
|
||||
|
||||
(def valid-fill-attrs
|
||||
"A set used for proper check if color should contain only one of the
|
||||
attrs listed in this set."
|
||||
#{:fill-image :fill-color :fill-color-gradient})
|
||||
|
||||
(defn has-valid-fill-attrs?
|
||||
"Check if color has correct color attrs"
|
||||
[color]
|
||||
(let [attrs (set (keys color))
|
||||
result (set/intersection attrs valid-fill-attrs)]
|
||||
(= 1 (count result))))
|
||||
|
||||
(def schema:fill
|
||||
[:and schema:fill-attrs
|
||||
[:fn has-valid-fill-attrs?]])
|
||||
|
||||
(def check-fill
|
||||
(sm/check-fn schema:fill))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; CONSTRUCTORS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn from-plain
|
||||
[o]
|
||||
(assert (every? check-fill o) "expected valid fills vector")
|
||||
(impl/from-plain o))
|
||||
|
||||
(defn fills?
|
||||
[o]
|
||||
(impl/fills? o))
|
||||
|
||||
(defn coerce
|
||||
[o]
|
||||
(cond
|
||||
(nil? o)
|
||||
(impl/from-plain [])
|
||||
|
||||
(impl/fills? o)
|
||||
o
|
||||
|
||||
(vector? o)
|
||||
(impl/from-plain o)
|
||||
|
||||
:else
|
||||
(ex/raise :type :internal
|
||||
:code :invalid-type
|
||||
:hint (str "cannot coerce " (pr-str o) " to fills"))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TYPE HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn get-image-ids
|
||||
[fills]
|
||||
(if (vector? fills)
|
||||
(into #{}
|
||||
(comp (keep :fill-image)
|
||||
(map :id))
|
||||
fills)
|
||||
(impl/-get-image-ids fills)))
|
||||
|
||||
(defn get-byte-size
|
||||
[fills]
|
||||
(impl/-get-byte-size fills))
|
||||
|
||||
(defn write-to
|
||||
[fills buffer offset]
|
||||
(impl/-write-to fills buffer offset))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TRANSFORMATION & CREATION HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn assoc
|
||||
[fills position fill]
|
||||
(if (contains? flags/*current* :frontend-binary-fills)
|
||||
(if (nil? fills)
|
||||
(impl/from-plain [fill])
|
||||
(-> (coerce fills)
|
||||
(c/assoc position fill)))
|
||||
(if (nil? fills)
|
||||
[fill]
|
||||
(-> (coerce fills)
|
||||
(c/assoc position fill)))))
|
||||
|
||||
(defn update
|
||||
[fills f & args]
|
||||
(let [fills (vec fills)
|
||||
fills (apply f fills args)]
|
||||
(if (contains? flags/*current* :frontend-binary-fills)
|
||||
(impl/from-plain fills)
|
||||
(vec fills))))
|
||||
|
||||
(defn create
|
||||
[& elements]
|
||||
(let [fills (vec elements)]
|
||||
(if (contains? flags/*current* :frontend-binary-fills)
|
||||
(impl/from-plain fills)
|
||||
fills)))
|
||||
|
||||
(defn prepend
|
||||
"Prepend a fill to existing fills"
|
||||
[fills fill]
|
||||
(let [fills (into [fill] fills)]
|
||||
(if (contains? flags/*current* :frontend-binary-fills)
|
||||
(impl/from-plain fills)
|
||||
fills)))
|
||||
|
||||
(defn fill->color
|
||||
[fill]
|
||||
(d/without-nils
|
||||
{:color (:fill-color fill)
|
||||
:opacity (:fill-opacity fill)
|
||||
:gradient (:fill-color-gradient fill)
|
||||
:image (:fill-image fill)
|
||||
:ref-id (:fill-color-ref-id fill)
|
||||
:ref-file (:fill-color-ref-file fill)}))
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
(ns app.common.types.grid
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.color :as clr]))
|
||||
[app.common.types.color :refer [schema:hex-color]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMA
|
||||
@@ -15,7 +16,7 @@
|
||||
|
||||
(def schema:grid-color
|
||||
[:map {:title "PageGridColor"}
|
||||
[:color clr/schema:hex-color]
|
||||
[:color schema:hex-color]
|
||||
[:opacity ::sm/safe-number]])
|
||||
|
||||
(def schema:column-params
|
||||
|
||||
@@ -1,87 +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 app.common.types.library
|
||||
"Exposes file library type data helpers.
|
||||
|
||||
WARNING: It belongs to FILE types in hierarchy of types so: file
|
||||
types can import this ns but, but this ns can't import file types."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.time :as dt]
|
||||
[app.common.types.shape :as types.shape]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; COLOR LIBRARY
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn get-colors
|
||||
[file-data]
|
||||
(:colors file-data))
|
||||
|
||||
(defn get-color
|
||||
[file-data color-id]
|
||||
(dm/get-in file-data [:colors color-id]))
|
||||
|
||||
(defn get-ref-color
|
||||
[library-data color]
|
||||
(when (= (:ref-file color) (:id library-data))
|
||||
(get-color library-data (:ref-id color))))
|
||||
|
||||
(defn- touch
|
||||
[color]
|
||||
(assoc color :modified-at (dt/now)))
|
||||
|
||||
(defn add-color
|
||||
[file-data color]
|
||||
(update file-data :colors assoc (:id color) (touch color)))
|
||||
|
||||
(defn set-color
|
||||
[file-data color]
|
||||
(d/assoc-in-when file-data [:colors (:id color)] (touch color)))
|
||||
|
||||
(defn update-color
|
||||
[file-data color-id f & args]
|
||||
(d/update-in-when file-data [:colors color-id] #(-> (apply f % args)
|
||||
(touch))))
|
||||
(defn delete-color
|
||||
[file-data color-id]
|
||||
(update file-data :colors dissoc color-id))
|
||||
|
||||
(defn used-colors-changed-since
|
||||
"Find all usages of any color in the library by the given shape, of colors
|
||||
that have ben modified after the date."
|
||||
[shape library since-date]
|
||||
(->> (types.shape/get-all-colors shape)
|
||||
(keep #(get-ref-color (:data library) %))
|
||||
(remove #(< (:modified-at %) since-date)) ;; Note that :modified-at may be nil
|
||||
(map (fn [color]
|
||||
{:shape-id (:id shape)
|
||||
:asset-id (:id color)
|
||||
:asset-type :color}))))
|
||||
|
||||
;: FIXME: revisit the API of this, i think we should pass the whole
|
||||
;; library data here instead of only colors
|
||||
(defn sync-colors
|
||||
"Look for usage of any color of the given library inside the shape,
|
||||
and, in this case, copy the library color into the shape."
|
||||
[shape library-id library-colors]
|
||||
(letfn [(sync-color [shape position shape-color set-fn _ detach-fn]
|
||||
(if (= (:ref-file shape-color) library-id)
|
||||
(let [library-color (get library-colors (:ref-id shape-color))]
|
||||
(if (some? library-color)
|
||||
(set-fn shape
|
||||
position
|
||||
(:color library-color)
|
||||
(:opacity library-color)
|
||||
(:gradient library-color)
|
||||
(:image library-color))
|
||||
(detach-fn shape position)))
|
||||
shape))]
|
||||
|
||||
(types.shape/process-shape-colors shape sync-color)))
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
[app.common.geom.shapes.effects :as gse]
|
||||
[app.common.geom.shapes.strokes :as gss]
|
||||
[app.common.math :as mth]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.text :as txt]
|
||||
[clojure.core :as c]))
|
||||
|
||||
;; --- Modifiers
|
||||
|
||||
@@ -25,10 +25,7 @@
|
||||
|
||||
(def ^:cosnt bool-group-style-properties bool/group-style-properties)
|
||||
(def ^:const bool-style-properties bool/style-properties)
|
||||
|
||||
(defn get-default-bool-fills
|
||||
[]
|
||||
(bool/get-default-fills))
|
||||
(def ^:const default-bool-fills bool/default-fills)
|
||||
|
||||
(def schema:content impl/schema:content)
|
||||
(def schema:segments impl/schema:segments)
|
||||
|
||||
@@ -6,23 +6,17 @@
|
||||
|
||||
(ns app.common.types.path.bool
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.flags :as flags]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.types.path.helpers :as helpers]
|
||||
[app.common.types.path.segment :as segment]
|
||||
[app.common.types.path.subpath :as subpath]))
|
||||
|
||||
(defn get-default-fills
|
||||
[]
|
||||
(let [fills [{:fill-color clr/black}]]
|
||||
(if (contains? flags/*current* :frontend-binary-fills)
|
||||
(types.fills/from-plain fills)
|
||||
fills)))
|
||||
(def default-fills
|
||||
[{:fill-color clr/black}])
|
||||
|
||||
(def group-style-properties
|
||||
#{:shadow :blur})
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.common.types.shape
|
||||
(:require
|
||||
#?(:clj [app.common.fressian :as fres])
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
@@ -17,9 +18,10 @@
|
||||
[app.common.record :as cr]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.text :as txt]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.fills :refer [schema:fill fill->color]]
|
||||
[app.common.types.color :as types.color]
|
||||
[app.common.types.fill :refer [schema:fill]]
|
||||
[app.common.types.grid :as ctg]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.segment :as path.segment]
|
||||
@@ -31,7 +33,6 @@
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.shape.shadow :as ctss]
|
||||
[app.common.types.shape.text :as ctsx]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
@@ -147,9 +148,9 @@
|
||||
[::sm/one-of stroke-caps]]
|
||||
[:stroke-cap-end {:optional true}
|
||||
[::sm/one-of stroke-caps]]
|
||||
[:stroke-color {:optional true} clr/schema:hex-color]
|
||||
[:stroke-color-gradient {:optional true} clr/schema:gradient]
|
||||
[:stroke-image {:optional true} clr/schema:image]])
|
||||
[:stroke-color {:optional true} types.color/schema:hex-color]
|
||||
[:stroke-color-gradient {:optional true} types.color/schema:gradient]
|
||||
[:stroke-image {:optional true} types.color/schema:image]])
|
||||
|
||||
(def stroke-attrs
|
||||
"A set of attrs that corresponds to stroke data type"
|
||||
@@ -415,14 +416,12 @@
|
||||
|
||||
;; Valid attributes
|
||||
|
||||
(def ^:private allowed-shape-attrs
|
||||
#{:page-id :component-id :component-file :component-root :main-instance
|
||||
:remote-synced :shape-ref :touched :blocked :collapsed :locked
|
||||
:hidden :masked-group :fills :proportion :proportion-lock :constraints-h
|
||||
:constraints-v :fixed-scroll :r1 :r2 :r3 :r4 :opacity :grids :exports
|
||||
:strokes :blend-mode :interactions :shadow :blur :grow-type :applied-tokens
|
||||
:plugin-data})
|
||||
|
||||
(def ^:private allowed-shape-attrs #{:page-id :component-id :component-file :component-root :main-instance
|
||||
:remote-synced :shape-ref :touched :blocked :collapsed :locked
|
||||
:hidden :masked-group :fills :proportion :proportion-lock :constraints-h
|
||||
:constraints-v :fixed-scroll :r1 :r2 :r3 :r4 :opacity :grids :exports
|
||||
:strokes :blend-mode :interactions :shadow :blur :grow-type :applied-tokens
|
||||
:plugin-data})
|
||||
(def ^:private allowed-shape-geom-attrs #{:x :y :width :height})
|
||||
(def ^:private allowed-shape-base-attrs #{:id :name :type :selrect :points :transform :transform-inverse :parent-id :frame-id})
|
||||
(def ^:private allowed-bool-attrs #{:shapes :bool-type :content})
|
||||
@@ -756,201 +755,3 @@
|
||||
(d/patch-object (select-keys props basic-extract-props))
|
||||
(cond-> (cfh/text-shape? shape) (patch-text-props props))
|
||||
(cond-> (cfh/frame-shape? shape) (patch-layout-props props)))))
|
||||
|
||||
|
||||
|
||||
(defn- set-fill-color
|
||||
[shape position color opacity gradient image]
|
||||
(update-in shape [:fills position]
|
||||
(fn [fill]
|
||||
(d/without-nils (assoc fill
|
||||
:fill-color color
|
||||
:fill-opacity opacity
|
||||
:fill-color-gradient gradient
|
||||
:fill-image image)))))
|
||||
|
||||
|
||||
(defn- attach-fill-color
|
||||
[shape position ref-id ref-file]
|
||||
(d/update-in-when shape [:fills position]
|
||||
(fn [fill]
|
||||
(-> fill
|
||||
(assoc :fill-color-ref-file ref-file)
|
||||
(assoc :fill-color-ref-id ref-id)))))
|
||||
|
||||
(defn- detach-fill-color
|
||||
[shape position]
|
||||
(d/update-in-when shape [:fills position] dissoc :fill-color-ref-id :fill-color-ref-file))
|
||||
|
||||
|
||||
(defn- set-stroke-color
|
||||
[shape position color opacity gradient image]
|
||||
(d/update-in-when shape [:strokes position]
|
||||
(fn [stroke]
|
||||
(-> stroke
|
||||
(assoc :stroke-color color)
|
||||
(assoc :stroke-opacity opacity)
|
||||
(assoc :stroke-color-gradient gradient)
|
||||
(assoc :stroke-image image)
|
||||
(d/without-nils)))))
|
||||
|
||||
(defn- attach-stroke-color
|
||||
[shape position ref-id ref-file]
|
||||
(d/update-in-when shape [:strokes position]
|
||||
(fn [stroke]
|
||||
(-> stroke
|
||||
(assoc :stroke-color-ref-id ref-id)
|
||||
(assoc :stroke-color-ref-file ref-file)))))
|
||||
|
||||
(defn- detach-stroke-color
|
||||
[shape position]
|
||||
(d/update-in-when shape [:strokes position] dissoc :stroke-color-ref-id :stroke-color-ref-file))
|
||||
|
||||
(defn- set-shadow-color
|
||||
[shape position color opacity gradient]
|
||||
(d/update-in-when shape [:shadow position :color]
|
||||
(fn [shadow-color]
|
||||
(-> shadow-color
|
||||
(assoc :color color)
|
||||
(assoc :opacity opacity)
|
||||
(assoc :gradient gradient)
|
||||
(d/without-nils)))))
|
||||
|
||||
(defn- attach-shadow-color
|
||||
[shape position ref-id ref-file]
|
||||
(d/update-in-when shape [:shadow position :color]
|
||||
(fn [color]
|
||||
(-> color
|
||||
(assoc :ref-id ref-id)
|
||||
(assoc :ref-file ref-file)))))
|
||||
|
||||
(defn- detach-shadow-color
|
||||
[shape position]
|
||||
(d/update-in-when shape [:shadow position :color] dissoc :ref-id :ref-file))
|
||||
|
||||
(defn- set-grid-color
|
||||
[shape position color opacity gradient]
|
||||
(d/update-in-when shape [:grids position :params :color]
|
||||
(fn [grid-color]
|
||||
(-> grid-color
|
||||
(assoc :color color)
|
||||
(assoc :opacity opacity)
|
||||
(assoc :gradient gradient)
|
||||
(d/without-nils)))))
|
||||
|
||||
(defn- attach-grid-color
|
||||
[shape position ref-id ref-file]
|
||||
(d/update-in-when shape [:grids position :params :color]
|
||||
(fn [color]
|
||||
(-> color
|
||||
(assoc :ref-id ref-id)
|
||||
(assoc :ref-file ref-file)))))
|
||||
|
||||
(defn- detach-grid-color
|
||||
[shape position]
|
||||
(d/update-in-when shape [:grids position :params :color] dissoc :ref-id :ref-file))
|
||||
|
||||
(defn process-shape-colors
|
||||
"Execute an update function on all colors of a shape."
|
||||
[shape process-fn]
|
||||
(let [process-fill (fn [shape [position fill]]
|
||||
(process-fn shape
|
||||
position
|
||||
(fill->color fill)
|
||||
set-fill-color
|
||||
attach-fill-color
|
||||
detach-fill-color))
|
||||
|
||||
process-stroke (fn [shape [position stroke]]
|
||||
(process-fn shape
|
||||
position
|
||||
(clr/stroke->color stroke)
|
||||
set-stroke-color
|
||||
attach-stroke-color
|
||||
detach-stroke-color))
|
||||
|
||||
process-shadow (fn [shape [position shadow]]
|
||||
(process-fn shape
|
||||
position
|
||||
(clr/shadow->color shadow)
|
||||
set-shadow-color
|
||||
attach-shadow-color
|
||||
detach-shadow-color))
|
||||
|
||||
process-grid (fn [shape [position grid]]
|
||||
(process-fn shape
|
||||
position
|
||||
(clr/grid->color grid)
|
||||
set-grid-color
|
||||
attach-grid-color
|
||||
detach-grid-color))
|
||||
|
||||
process-text-node (fn [node]
|
||||
(as-> node $
|
||||
(reduce process-fill $ (d/enumerate (:fills $)))
|
||||
(reduce process-stroke $ (d/enumerate (:strokes $)))))
|
||||
|
||||
process-text (fn [shape]
|
||||
(let [content (:content shape)
|
||||
new-content (txt/transform-nodes process-text-node content)]
|
||||
(if (not= content new-content)
|
||||
(assoc shape :content new-content)
|
||||
shape)))]
|
||||
|
||||
(as-> shape $
|
||||
(reduce process-fill $ (d/enumerate (:fills $)))
|
||||
(reduce process-stroke $ (d/enumerate (:strokes $)))
|
||||
(reduce process-shadow $ (d/enumerate (:shadow $)))
|
||||
(reduce process-grid $ (d/enumerate (:grids $)))
|
||||
(process-text $))))
|
||||
|
||||
(defn- get-text-node-colors
|
||||
"Get all colors used by a node of a text shape"
|
||||
[node]
|
||||
(concat (map fill->color (:fills node))
|
||||
(map clr/stroke->color (:strokes node))))
|
||||
|
||||
(defn get-all-colors
|
||||
"Get all colors used by a shape, in any section."
|
||||
[shape]
|
||||
;; FIXME: all this functions should be really in color?
|
||||
(concat (map fill->color (:fills shape))
|
||||
(map clr/stroke->color (:strokes shape))
|
||||
(map clr/shadow->color (:shadow shape))
|
||||
(when (= (:type shape) :frame)
|
||||
(map clr/grid->color (:grids shape)))
|
||||
(when (= (:type shape) :text)
|
||||
(reduce (fn [colors node]
|
||||
(concat colors (get-text-node-colors node)))
|
||||
()
|
||||
(txt/node-seq (:content shape))))))
|
||||
|
||||
(defn uses-library-color?
|
||||
"Check if the shape uses the given library color."
|
||||
[shape library-id color-id]
|
||||
(let [all-colors (get-all-colors shape)]
|
||||
(some #(and (= (:ref-id %) color-id)
|
||||
(= (:ref-file %) library-id))
|
||||
all-colors)))
|
||||
|
||||
(defn uses-library-colors?
|
||||
"Check if the shape uses any color in the given library."
|
||||
[shape library-id]
|
||||
(let [all-colors (get-all-colors shape)]
|
||||
(some #(and (some? (:ref-id %))
|
||||
(= (:ref-file %) library-id))
|
||||
all-colors)))
|
||||
|
||||
(defn remap-colors
|
||||
"Change the shape so that any use of the given color now points to
|
||||
the given library."
|
||||
[shape library-id color]
|
||||
(letfn [(remap-color [shape position shape-color _ attach-fn _]
|
||||
(if (= (:ref-id shape-color) (:id color))
|
||||
(attach-fn shape
|
||||
position
|
||||
(:id color)
|
||||
library-id)
|
||||
shape))]
|
||||
|
||||
(process-shape-colors shape remap-color)))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
(ns app.common.types.shape.attrs
|
||||
(:require
|
||||
[app.common.types.color :as clr]))
|
||||
[app.common.colors :as clr]))
|
||||
|
||||
(def default-color clr/gray-20)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
(ns app.common.types.shape.text
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.fills :refer [schema:fill]]
|
||||
[app.common.types.fill :refer [schema:fill]]
|
||||
[app.common.types.shape :as-alias shape]
|
||||
[app.common.types.shape.text.position-data :as-alias position-data]))
|
||||
|
||||
|
||||
20
common/src/app/common/types/stroke.cljc
Normal file
@@ -0,0 +1,20 @@
|
||||
;; 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.types.stroke
|
||||
(:require
|
||||
[app.common.colors :as clr]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMAS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def default-stroke
|
||||
{:stroke-alignment :inner
|
||||
:stroke-style :solid
|
||||
:stroke-color clr/black
|
||||
:stroke-opacity 1
|
||||
:stroke-width 1})
|
||||
@@ -6,194 +6,8 @@
|
||||
|
||||
(ns app.common.types.text
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.flags :as flags]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[clojure.set :as set]
|
||||
[clojure.walk :as walk]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; -- Attrs
|
||||
|
||||
(def text-typography-attrs
|
||||
[:typography-ref-id
|
||||
:typography-ref-file])
|
||||
|
||||
(def text-fill-attrs
|
||||
[:fill-color
|
||||
:fill-opacity
|
||||
:fill-color-ref-id
|
||||
:fill-color-ref-file
|
||||
:fill-color-gradient])
|
||||
|
||||
(def text-font-attrs
|
||||
[:font-id
|
||||
:font-family
|
||||
:font-variant-id
|
||||
:font-size
|
||||
:font-weight
|
||||
:font-style])
|
||||
|
||||
(def text-align-attrs
|
||||
[:text-align])
|
||||
|
||||
(def text-direction-attrs
|
||||
[:text-direction])
|
||||
|
||||
(def text-spacing-attrs
|
||||
[:line-height
|
||||
:letter-spacing])
|
||||
|
||||
(def text-valign-attrs
|
||||
[:vertical-align])
|
||||
|
||||
(def text-decoration-attrs
|
||||
[:text-decoration])
|
||||
|
||||
(def text-transform-attrs
|
||||
[:text-transform])
|
||||
|
||||
(def text-fills
|
||||
[:fills])
|
||||
|
||||
(def shape-attrs
|
||||
[:grow-type])
|
||||
|
||||
(def root-attrs
|
||||
text-valign-attrs)
|
||||
|
||||
(def paragraph-attrs
|
||||
(d/concat-vec
|
||||
text-align-attrs
|
||||
text-direction-attrs))
|
||||
|
||||
(def text-node-attrs
|
||||
(d/concat-vec
|
||||
text-typography-attrs
|
||||
text-font-attrs
|
||||
text-spacing-attrs
|
||||
text-decoration-attrs
|
||||
text-transform-attrs
|
||||
text-fills))
|
||||
|
||||
(def text-all-attrs (d/concat-set shape-attrs root-attrs paragraph-attrs text-node-attrs))
|
||||
|
||||
(def text-style-attrs
|
||||
(d/concat-vec root-attrs paragraph-attrs text-node-attrs))
|
||||
|
||||
(def default-root-attrs
|
||||
{:vertical-align "top"})
|
||||
|
||||
(def default-text-fills
|
||||
[{:fill-color clr/black
|
||||
:fill-opacity 1}])
|
||||
|
||||
(def default-text-attrs
|
||||
{:font-id "sourcesanspro"
|
||||
:font-family "sourcesanspro"
|
||||
:font-variant-id "regular"
|
||||
:font-size "14"
|
||||
:font-weight "400"
|
||||
:font-style "normal"
|
||||
:line-height "1.2"
|
||||
:letter-spacing "0"
|
||||
:text-transform "none"
|
||||
:text-align "left"
|
||||
:text-decoration "none"
|
||||
:text-direction "ltr"})
|
||||
|
||||
(defn get-default-text-fills
|
||||
"Return calculated default text fills"
|
||||
[]
|
||||
(if (contains? flags/*current* :frontend-binary-fills)
|
||||
(types.fills/from-plain default-text-fills)
|
||||
default-text-fills))
|
||||
|
||||
(defn get-default-text-attrs
|
||||
"Return calculated default text attrs.
|
||||
|
||||
NOTE: is implemented as function because it needs resolve at runtime
|
||||
the activated flag for properly encode the fills"
|
||||
[]
|
||||
(assoc default-text-attrs :fills (get-default-text-fills)))
|
||||
|
||||
(def typography-fields
|
||||
[:font-id
|
||||
:font-family
|
||||
:font-variant-id
|
||||
:font-size
|
||||
:font-weight
|
||||
:font-style
|
||||
:line-height
|
||||
:letter-spacing
|
||||
:text-transform])
|
||||
|
||||
(def default-typography
|
||||
(-> default-text-attrs
|
||||
(select-keys typography-fields)
|
||||
(assoc :name "Source Sans Pro Regular")))
|
||||
|
||||
(defn node-seq
|
||||
([root] (node-seq identity root))
|
||||
([match? root]
|
||||
(->> (tree-seq map? :children root)
|
||||
(filter match?)
|
||||
(seq))))
|
||||
|
||||
(defn is-text-node?
|
||||
[node]
|
||||
(and (nil? (:type node))
|
||||
(string? (:text node))))
|
||||
|
||||
(defn is-paragraph-set-node?
|
||||
[node]
|
||||
(= "paragraph-set" (:type node)))
|
||||
|
||||
(defn is-paragraph-node?
|
||||
[node]
|
||||
(= "paragraph" (:type node)))
|
||||
|
||||
(defn is-root-node?
|
||||
[node]
|
||||
(= "root" (:type node)))
|
||||
|
||||
(defn is-node?
|
||||
[node]
|
||||
(or ^boolean (is-text-node? node)
|
||||
^boolean (is-paragraph-node? node)
|
||||
^boolean (is-paragraph-set-node? node)
|
||||
^boolean (is-root-node? node)))
|
||||
|
||||
(defn is-content-node?
|
||||
"Only matches content nodes, ignoring the paragraph-set nodes."
|
||||
[node]
|
||||
(or ^boolean (is-text-node? node)
|
||||
^boolean (is-paragraph-node? node)
|
||||
^boolean (is-root-node? node)))
|
||||
|
||||
(defn transform-nodes
|
||||
([transform root]
|
||||
(transform-nodes identity transform root))
|
||||
([pred transform root]
|
||||
(walk/postwalk
|
||||
(fn [item]
|
||||
(if (and (is-node? item) (pred item))
|
||||
(transform item)
|
||||
item))
|
||||
root)))
|
||||
|
||||
(defn update-text-content
|
||||
[shape pred-fn update-fn attrs]
|
||||
(let [update-attrs-fn #(update-fn % attrs)
|
||||
transform #(transform-nodes pred-fn update-attrs-fn %)]
|
||||
(-> shape
|
||||
(update :content transform))))
|
||||
|
||||
(defn generate-shape-name
|
||||
[text]
|
||||
(subs text 0 (min 280 (count text))))
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defn- compare-text-content
|
||||
"Given two content text structures, conformed by maps and vectors,
|
||||
@@ -344,91 +158,3 @@
|
||||
(if (= :children k)
|
||||
[k (vec (map #(copy-attrs-keys %1 attrs) v))]
|
||||
[k (get attrs k v)]))))
|
||||
|
||||
|
||||
(defn content->text
|
||||
"Given a root node of a text content extracts the texts with its associated styles"
|
||||
[content]
|
||||
(letfn [(add-node [acc node]
|
||||
(cond
|
||||
(is-paragraph-node? node)
|
||||
(conj acc [])
|
||||
|
||||
(is-text-node? node)
|
||||
(let [i (dec (count acc))]
|
||||
(update acc i conj (:text node)))
|
||||
|
||||
:else
|
||||
acc))]
|
||||
(->> (node-seq content)
|
||||
(reduce add-node [])
|
||||
(map #(str/join "" %))
|
||||
(str/join "\n"))))
|
||||
|
||||
(defn content->text+styles
|
||||
"Given a root node of a text content extracts the texts with its associated styles"
|
||||
[node]
|
||||
(letfn
|
||||
[(rec-style-text-map [acc node style]
|
||||
(let [node-style (merge style (select-keys node text-all-attrs))
|
||||
head (or (-> acc first) [{} ""])
|
||||
[head-style head-text] head
|
||||
|
||||
new-acc
|
||||
(cond
|
||||
(not (is-text-node? node))
|
||||
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
|
||||
|
||||
(not= head-style node-style)
|
||||
(cons [node-style (:text node "")] acc)
|
||||
|
||||
:else
|
||||
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
|
||||
|
||||
;; We add an end-of-line when finish a paragraph
|
||||
new-acc
|
||||
(if (= (:type node) "paragraph")
|
||||
(let [[hs ht] (first new-acc)]
|
||||
(cons [hs (dm/str ht "\n")] (rest new-acc)))
|
||||
new-acc)]
|
||||
new-acc))]
|
||||
|
||||
(-> (rec-style-text-map [] node {})
|
||||
reverse)))
|
||||
|
||||
(defn change-text
|
||||
"Changes the content of the text shape to use the text as argument. Will use the styles of the
|
||||
first paragraph and text that is present in the shape (and override the rest)"
|
||||
[content text]
|
||||
(let [root-styles (select-keys content root-attrs)
|
||||
|
||||
paragraph-style
|
||||
(merge
|
||||
default-text-attrs
|
||||
(select-keys (->> content (node-seq is-paragraph-node?) first) text-all-attrs))
|
||||
|
||||
text-style
|
||||
(merge
|
||||
default-text-attrs
|
||||
(select-keys (->> content (node-seq is-text-node?) first) text-all-attrs))
|
||||
|
||||
paragraph-texts
|
||||
(str/split text "\n")
|
||||
|
||||
paragraphs
|
||||
(->> paragraph-texts
|
||||
(mapv
|
||||
(fn [pt]
|
||||
(merge
|
||||
paragraph-style
|
||||
{:type "paragraph"
|
||||
:children [(merge {:text pt} text-style)]}))))]
|
||||
|
||||
|
||||
(d/patch-object
|
||||
{:type "root"
|
||||
:children
|
||||
[{:type "paragraph-set"
|
||||
:children paragraphs}]}
|
||||
root-styles)))
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
[app.common.schema :as sm]
|
||||
[clojure.data :as data]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]
|
||||
[malli.util :as mu]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -34,7 +33,6 @@
|
||||
:border-radius "borderRadius"
|
||||
:color "color"
|
||||
:dimensions "dimension"
|
||||
:font-family "fontFamilies"
|
||||
:font-size "fontSizes"
|
||||
:letter-spacing "letterSpacing"
|
||||
:number "number"
|
||||
@@ -94,19 +92,32 @@
|
||||
|
||||
(def opacity-keys (schema-keys schema:opacity))
|
||||
|
||||
(def ^:private schema:spacing
|
||||
(def ^:private schema:spacing-gap
|
||||
[:map
|
||||
[:row-gap {:optional true} token-name-ref]
|
||||
[:column-gap {:optional true} token-name-ref]
|
||||
[:column-gap {:optional true} token-name-ref]])
|
||||
|
||||
(def ^:private schema:spacing-padding
|
||||
[:map
|
||||
[:p1 {:optional true} token-name-ref]
|
||||
[:p2 {:optional true} token-name-ref]
|
||||
[:p3 {:optional true} token-name-ref]
|
||||
[:p4 {:optional true} token-name-ref]
|
||||
[:p4 {:optional true} token-name-ref]])
|
||||
|
||||
(def ^:private schema:spacing-margin
|
||||
[:map
|
||||
[:m1 {:optional true} token-name-ref]
|
||||
[:m2 {:optional true} token-name-ref]
|
||||
[:m3 {:optional true} token-name-ref]
|
||||
[:m4 {:optional true} token-name-ref]])
|
||||
|
||||
(def ^:private schema:spacing
|
||||
(reduce mu/union [schema:spacing-gap
|
||||
schema:spacing-padding
|
||||
schema:spacing-margin]))
|
||||
|
||||
(def spacing-margin-keys (schema-keys schema:spacing-margin))
|
||||
|
||||
(def spacing-keys (schema-keys schema:spacing))
|
||||
|
||||
(def ^:private schema:dimensions
|
||||
@@ -117,6 +128,15 @@
|
||||
|
||||
(def dimensions-keys (schema-keys schema:dimensions))
|
||||
|
||||
(def ^:private schema:axis
|
||||
[:map
|
||||
[:x {:optional true} token-name-ref]
|
||||
[:y {:optional true} token-name-ref]])
|
||||
|
||||
(def axis-keys (schema-keys schema:axis))
|
||||
|
||||
|
||||
|
||||
(def ^:private schema:rotation
|
||||
[:map
|
||||
[:rotation {:optional true} token-name-ref]])
|
||||
@@ -135,13 +155,7 @@
|
||||
|
||||
(def letter-spacing-keys (schema-keys schema:letter-spacing))
|
||||
|
||||
(def ^:private schema:font-family
|
||||
[:map
|
||||
[:font-family {:optional true} token-name-ref]])
|
||||
|
||||
(def font-family-keys (schema-keys schema:font-family))
|
||||
|
||||
(def typography-keys (set/union font-size-keys letter-spacing-keys font-family-keys))
|
||||
(def typography-keys (set/union font-size-keys letter-spacing-keys))
|
||||
|
||||
;; TODO: Created to extract the font-size feature from the typography feature flag.
|
||||
;; Delete this once the typography feature flag is removed.
|
||||
@@ -160,6 +174,7 @@
|
||||
opacity-keys
|
||||
spacing-keys
|
||||
dimensions-keys
|
||||
axis-keys
|
||||
rotation-keys
|
||||
typography-keys
|
||||
number-keys))
|
||||
@@ -177,7 +192,6 @@
|
||||
schema:number
|
||||
schema:font-size
|
||||
schema:letter-spacing
|
||||
schema:font-family
|
||||
schema:dimensions])
|
||||
|
||||
(defn shape-attr->token-attrs
|
||||
@@ -207,13 +221,13 @@
|
||||
|
||||
(font-size-keys shape-attr) #{shape-attr}
|
||||
(letter-spacing-keys shape-attr) #{shape-attr}
|
||||
(font-family-keys shape-attr) #{shape-attr}
|
||||
(border-radius-keys shape-attr) #{shape-attr}
|
||||
(sizing-keys shape-attr) #{shape-attr}
|
||||
(opacity-keys shape-attr) #{shape-attr}
|
||||
(spacing-keys shape-attr) #{shape-attr}
|
||||
(rotation-keys shape-attr) #{shape-attr}
|
||||
(number-keys shape-attr) #{shape-attr})))
|
||||
(number-keys shape-attr) #{shape-attr}
|
||||
(axis-keys shape-attr) #{shape-attr})))
|
||||
|
||||
(defn token-attr->shape-attr
|
||||
[token-attr]
|
||||
@@ -308,23 +322,3 @@
|
||||
|
||||
(defn unapply-token-id [shape attributes]
|
||||
(update shape :applied-tokens d/without-keys attributes))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TYPOGRAPHY
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn split-font-family
|
||||
"Splits font family `value` string from into vector of font families.
|
||||
|
||||
Doesn't handle possible edge-case of font-families with `,` in their font family name."
|
||||
[font-value]
|
||||
(let [families (str/split font-value ",")
|
||||
xform (comp
|
||||
(map str/trim)
|
||||
(remove str/empty?))]
|
||||
(into [] xform families)))
|
||||
|
||||
(defn join-font-family
|
||||
"Joins font family `value` into a string to be edited with a single input."
|
||||
[font-families]
|
||||
(str/join ", " font-families))
|
||||
|
||||
@@ -167,6 +167,11 @@
|
||||
(get-tokens [_] "return an ordered sequence of all tokens in the set")
|
||||
(get-tokens-map [_] "return a map of tokens in the set, indexed by token-name"))
|
||||
|
||||
;; TODO: this structure is temporary. It's needed to be able to migrate TokensLib
|
||||
;; from 1.2 to 1.3 when TokenSet datatype was changed to a deftype. This should
|
||||
;; be removed after migrations are consolidated.
|
||||
(defrecord TokenSetLegacy [id name description modified-at tokens])
|
||||
|
||||
(deftype TokenSet [id name description modified-at tokens]
|
||||
#?@(:clj [clojure.lang.IDeref
|
||||
(deref [_] {:id id
|
||||
@@ -181,6 +186,10 @@
|
||||
:modified-at modified-at
|
||||
:tokens tokens})])
|
||||
|
||||
#?@(:clj
|
||||
[json/JSONWriter
|
||||
(-write [this writter options] (json/-write (deref this) writter options))])
|
||||
|
||||
#?@(:cljs [cljs.core/IEncodeJS
|
||||
(-clj->js [_] (js-obj "id" (clj->js id)
|
||||
"name" (clj->js name)
|
||||
@@ -255,6 +264,10 @@
|
||||
[o]
|
||||
(instance? TokenSet o))
|
||||
|
||||
(defn token-set-legacy?
|
||||
[o]
|
||||
(instance? TokenSetLegacy o))
|
||||
|
||||
(def schema:token-set-attrs
|
||||
[:map {:title "TokenSet"}
|
||||
[:id ::sm/uuid]
|
||||
@@ -283,7 +296,9 @@
|
||||
(declare make-token-set)
|
||||
|
||||
(def schema:token-set
|
||||
(sm/required-keys schema:token-set-attrs))
|
||||
[:schema {:gen/gen (->> (sg/generator schema:token-set-attrs)
|
||||
(sg/fmap #(make-token-set %)))}
|
||||
(sm/required-keys schema:token-set-attrs)])
|
||||
|
||||
(sm/register! ::token-set schema:token-set) ;; need to register for the recursive schema of token-sets
|
||||
|
||||
@@ -1333,25 +1348,13 @@ Will return a value that matches this schema:
|
||||
(walk/postwalk
|
||||
(fn [node]
|
||||
(cond-> node
|
||||
;; Handle sequential values that are objects with type
|
||||
(and (map? node)
|
||||
(contains? node "value")
|
||||
(sequential? (get node "value"))
|
||||
(map? (first (get node "value"))))
|
||||
(sequential? (get node "value")))
|
||||
(update "value"
|
||||
(fn [seq-value]
|
||||
(map #(set/rename-keys % {"type" "$type"}) seq-value)))
|
||||
|
||||
;; Keep array of font families
|
||||
(and (map? node)
|
||||
(contains? node "type")
|
||||
(= "fontFamilies" (get node "type"))
|
||||
(contains? node "value")
|
||||
(sequential? (get node "value"))
|
||||
(not (map? (first (get node "value")))))
|
||||
identity
|
||||
|
||||
;; Rename keys for all token nodes
|
||||
(and (map? node)
|
||||
(and (contains? node "type")
|
||||
(contains? node "value")))
|
||||
@@ -1383,16 +1386,7 @@ Will return a value that matches this schema:
|
||||
(assoc tokens child-path (make-token
|
||||
:name child-path
|
||||
:type token-type
|
||||
:value (cond-> (get v "$value")
|
||||
;; Split string of font-families
|
||||
(and (= :font-family token-type)
|
||||
(string? (get v "$value")))
|
||||
cto/split-font-family
|
||||
|
||||
;; Keep array of font-families
|
||||
(and (= :font-family token-type)
|
||||
(sequential? (get v "$value")))
|
||||
identity)
|
||||
:value (get v "$value")
|
||||
:description (get v "$description")))
|
||||
;; Discard unknown type tokens
|
||||
tokens)))))
|
||||
@@ -1738,10 +1732,11 @@ Will return a value that matches this schema:
|
||||
|
||||
migrate-sets-node
|
||||
(fn recurse [node]
|
||||
(if (token-set? node)
|
||||
(assoc node
|
||||
:id (uuid/next)
|
||||
:tokens (d/update-vals (:tokens node) migrate-token))
|
||||
(if (token-set-legacy? node)
|
||||
(make-token-set
|
||||
(assoc node
|
||||
:id (uuid/next)
|
||||
:tokens (d/update-vals (:tokens node) migrate-token)))
|
||||
(d/update-vals node recurse)))
|
||||
|
||||
sets
|
||||
@@ -1769,6 +1764,26 @@ Will return a value that matches this schema:
|
||||
|
||||
(->TokensLib sets themes active-themes))))
|
||||
|
||||
#?(:clj
|
||||
(defn- read-tokens-lib-v1-3
|
||||
"Reads the tokens lib data structure and removes the TokenSetLegacy data type,
|
||||
needed for a temporary migration step."
|
||||
[r]
|
||||
(let [sets (fres/read-object! r)
|
||||
themes (fres/read-object! r)
|
||||
active-themes (fres/read-object! r)
|
||||
|
||||
migrate-sets-node
|
||||
(fn recurse [node]
|
||||
(if (token-set-legacy? node)
|
||||
(make-token-set node)
|
||||
(d/update-vals node recurse)))
|
||||
|
||||
sets
|
||||
(d/update-vals sets migrate-sets-node)]
|
||||
|
||||
(->TokensLib sets themes active-themes))))
|
||||
|
||||
#?(:clj
|
||||
(defn- write-tokens-lib
|
||||
[n w ^TokensLib o]
|
||||
@@ -1797,6 +1812,11 @@ Will return a value that matches this schema:
|
||||
(make-token obj)))}
|
||||
|
||||
{:name "penpot/token-set/v1"
|
||||
:rfn (fn [r]
|
||||
(let [obj (fres/read-object! r)]
|
||||
(map->TokenSetLegacy obj)))}
|
||||
|
||||
{:name "penpot/token-set/v2"
|
||||
:class TokenSet
|
||||
:wfn (fn [n w o]
|
||||
(fres/write-tag! w n 1)
|
||||
@@ -1824,8 +1844,11 @@ Will return a value that matches this schema:
|
||||
{:name "penpot/tokens-lib/v1.2"
|
||||
:rfn read-tokens-lib-v1-2}
|
||||
|
||||
;; CURRENT TOKENS LIB READER & WRITTER
|
||||
{:name "penpot/tokens-lib/v1.3"
|
||||
:rfn read-tokens-lib-v1-3}
|
||||
|
||||
;; CURRENT TOKENS LIB READER & WRITTER
|
||||
{:name "penpot/tokens-lib/v1.4"
|
||||
:class TokensLib
|
||||
:wfn write-tokens-lib
|
||||
:rfn read-tokens-lib}))
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
(ns app.common.types.typographies-list
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.time :as dt]
|
||||
[app.common.types.text :as txt]))
|
||||
[app.common.text :as txt]
|
||||
[app.common.time :as dt]))
|
||||
|
||||
(defn typographies-seq
|
||||
[file-data]
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.plugins :as ctpg]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -59,8 +59,6 @@
|
||||
:text-transform (or text-transform "none")}
|
||||
(d/without-nils)))
|
||||
|
||||
|
||||
;; FIXME: this function should not be here it belongs to shape and not typography
|
||||
(defn uses-library-typographies?
|
||||
"Check if the shape uses any typography in the given library."
|
||||
[shape library-id]
|
||||
@@ -72,7 +70,6 @@
|
||||
#(and (some? (:typography-ref-id %))
|
||||
(= (:typography-ref-file %) library-id))))))
|
||||
|
||||
;; FIXME: this function should not be here it belongs to shape and not typography
|
||||
(defn uses-library-typography?
|
||||
"Check if the shape uses the given library typography."
|
||||
[shape library-id typography-id]
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
(ns common-tests.colors-test
|
||||
(:require
|
||||
#?(:cljs [goog.color :as gcolors])
|
||||
[app.common.types.color :as colors]
|
||||
[app.common.colors :as colors]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest valid-hex-color
|
||||
@@ -51,8 +51,8 @@
|
||||
(t/is (= [1 2 3] (colors/hex->rgb "#010203"))))
|
||||
|
||||
(t/deftest format-hsla
|
||||
(t/is (= "210, 50%, 0.78%, 1" (colors/format-hsla [210.0 0.5 0.00784313725490196 1])))
|
||||
(t/is (= "220, 5%, 30%, 0.8" (colors/format-hsla [220.0 0.05 0.3 0.8]))))
|
||||
(t/is (= "210 50% 0.78% / 1" (colors/format-hsla [210.0 0.5 0.00784313725490196 1])))
|
||||
(t/is (= "220 5% 30% / 0.8" (colors/format-hsla [220.0 0.05 0.3 0.8]))))
|
||||
|
||||
(t/deftest format-rgba
|
||||
(t/is (= "210, 199, 12, 0.08" (colors/format-rgba [210 199 12 0.08])))
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
|
||||
(t/deftest sync-unchanged-copy-when-changed-attribute
|
||||
(t/deftest test-sync-unchanged-copy-when-changed-attribute
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -55,7 +55,7 @@
|
||||
(t/is (= "32" (:font-size line)))
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
|
||||
(t/deftest sync-unchanged-copy-when-changed-text
|
||||
(t/deftest test-sync-unchanged-copy-when-changed-text
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -91,7 +91,7 @@
|
||||
(t/is (= "14" (:font-size line)))
|
||||
(t/is (= "Bye" (:text line)))))
|
||||
|
||||
(t/deftest sync-unchanged-copy-when-changed-both
|
||||
(t/deftest test-sync-unchanged-copy-when-changed-both
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -129,7 +129,7 @@
|
||||
(t/is (= "32" (:font-size line)))
|
||||
(t/is (= "Bye" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-attr-copy-when-changed-attribute
|
||||
(t/deftest test-sync-updated-attr-copy-when-changed-attribute
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -176,7 +176,7 @@
|
||||
(t/is (= "14" (:font-size line)))
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-attr-copy-when-changed-text
|
||||
(t/deftest test-sync-updated-attr-copy-when-changed-text
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -223,7 +223,7 @@
|
||||
;; The text is updated because only attrs were touched
|
||||
(t/is (= "Bye" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-attr-copy-when-changed-both
|
||||
(t/deftest test-sync-updated-attr-copy-when-changed-both
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -273,7 +273,7 @@
|
||||
;; The text is updated because only attrs were touched
|
||||
(t/is (= "Bye" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-text-copy-when-changed-attribute
|
||||
(t/deftest test-sync-updated-text-copy-when-changed-attribute
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -320,7 +320,7 @@
|
||||
(t/is (= "32" (:font-size line)))
|
||||
(t/is (= "Hi" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-text-copy-when-changed-text
|
||||
(t/deftest test-sync-updated-text-copy-when-changed-text
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -367,7 +367,7 @@
|
||||
;; The text doesn't change, because it was touched
|
||||
(t/is (= "Hi" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-text-copy-when-changed-both
|
||||
(t/deftest test-sync-updated-text-copy-when-changed-both
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -417,7 +417,7 @@
|
||||
;; The text doesn't change, because it was touched
|
||||
(t/is (= "Hi" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-both-copy-when-changed-attribute
|
||||
(t/deftest test-sync-updated-both-copy-when-changed-attribute
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -466,7 +466,7 @@
|
||||
(t/is (= "14" (:font-size line)))
|
||||
(t/is (= "Hi" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-both-copy-when-changed-text
|
||||
(t/deftest test-sync-updated-both-copy-when-changed-text
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -515,7 +515,7 @@
|
||||
;; The text doesn't change, because it was touched
|
||||
(t/is (= "Hi" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-both-copy-when-changed-both
|
||||
(t/deftest test-sync-updated-both-copy-when-changed-both
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -567,7 +567,7 @@
|
||||
;; The text doesn't change, because it was touched
|
||||
(t/is (= "Hi" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-structure-same-attrs-copy-when-changed-attribute
|
||||
(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-attribute
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -619,7 +619,7 @@
|
||||
(t/is (= "32" (:font-size line)))
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-structure-same-attrs-copy-when-changed-text
|
||||
(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-text
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -668,7 +668,7 @@
|
||||
;; The text doesn't change, because the structure was touched
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-structure-same-attrs-copy-when-changed-both
|
||||
(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-both
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -722,7 +722,7 @@
|
||||
;; The text doesn't change, because the structure was touched
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-structure-diff-attrs-copy-when-changed-attribute
|
||||
(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-attribute
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -775,7 +775,7 @@
|
||||
(t/is (= "14" (:font-size line)))
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-structure-diff-attrs-copy-when-changed-text
|
||||
(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-text
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -825,7 +825,7 @@
|
||||
;; The text doesn't change, because the structure was touched
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
|
||||
(t/deftest sync-updated-structure-diff-attrs-copy-when-changed-both
|
||||
(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-both
|
||||
(let [;; ==== Setup
|
||||
file0 (-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-text :main-root :main-child "hello world")
|
||||
@@ -878,4 +878,4 @@
|
||||
;; The attr doesn't change, because not all the attrs on the structure are equal
|
||||
(t/is (= "14" (:font-size line)))
|
||||
;; The text doesn't change, because the structure was touched
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
(t/is (= "hello world" (:text line)))))
|
||||
@@ -14,8 +14,8 @@
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.test-helpers.tokens :as tht]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[clojure.test :as t]))
|
||||
@@ -62,11 +62,7 @@
|
||||
(ctob/add-token-in-set "test-token-set"
|
||||
(ctob/make-token :name "token-letter-spacing"
|
||||
:type :letter-spacing
|
||||
:value 2))
|
||||
(ctob/add-token-in-set "test-token-set"
|
||||
(ctob/make-token :name "token-font-family"
|
||||
:type :font-family
|
||||
:value ["Helvetica" "Arial" "sans-serif"]))))
|
||||
:value 2))))
|
||||
(tho/add-frame :frame1)
|
||||
(tho/add-text :text1 "Hello World!")))
|
||||
|
||||
@@ -81,8 +77,7 @@
|
||||
(tht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#00ff00")
|
||||
(tht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100)
|
||||
(tht/apply-token-to-shape :text1 "token-font-size" [:font-size] [:font-size] 24)
|
||||
(tht/apply-token-to-shape :text1 "token-letter-spacing" [:letter-spacing] [:letter-spacing] 2)
|
||||
(tht/apply-token-to-shape :text1 "token-font-family" [:font-family] [:font-family] ["Helvetica" "Arial" "sans-serif"])))
|
||||
(tht/apply-token-to-shape :text1 "token-letter-spacing" [:letter-spacing] [:letter-spacing] 2)))
|
||||
|
||||
(t/deftest apply-tokens-to-shape
|
||||
(let [;; ==== Setup
|
||||
@@ -98,7 +93,6 @@
|
||||
token-dimensions (tht/get-token file "test-token-set" "token-dimensions")
|
||||
token-font-size (tht/get-token file "test-token-set" "token-font-size")
|
||||
token-letter-spacing (tht/get-token file "test-token-set" "token-letter-spacing")
|
||||
token-font-family (tht/get-token file "test-token-set" "token-font-family")
|
||||
|
||||
;; ==== Action
|
||||
changes (-> (-> (pcb/empty-changes nil)
|
||||
@@ -138,10 +132,7 @@
|
||||
:attributes [:font-size]})
|
||||
(cto/apply-token-to-shape {:token token-letter-spacing
|
||||
:shape $
|
||||
:attributes [:letter-spacing]})
|
||||
(cto/apply-token-to-shape {:token token-font-family
|
||||
:shape $
|
||||
:attributes [:font-family]})))
|
||||
:attributes [:letter-spacing]})))
|
||||
(:objects page)
|
||||
{}))
|
||||
|
||||
@@ -166,10 +157,9 @@
|
||||
(t/is (= (:fill applied-tokens') "token-color"))
|
||||
(t/is (= (:width applied-tokens') "token-dimensions"))
|
||||
(t/is (= (:height applied-tokens') "token-dimensions"))
|
||||
(t/is (= (count text1-applied-tokens) 3))
|
||||
(t/is (= (count text1-applied-tokens) 2))
|
||||
(t/is (= (:font-size text1-applied-tokens) "token-font-size"))
|
||||
(t/is (= (:letter-spacing text1-applied-tokens) "token-letter-spacing"))
|
||||
(t/is (= (:font-family text1-applied-tokens) "token-font-family"))))
|
||||
(t/is (= (:letter-spacing text1-applied-tokens) "token-letter-spacing"))))
|
||||
|
||||
(t/deftest unapply-tokens-from-shape
|
||||
(let [;; ==== Setup
|
||||
@@ -199,8 +189,7 @@
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(cto/unapply-token-id [:font-size])
|
||||
(cto/unapply-token-id [:letter-spacing])
|
||||
(cto/unapply-token-id [:font-family])))
|
||||
(cto/unapply-token-id [:letter-spacing])))
|
||||
(:objects page)
|
||||
{}))
|
||||
|
||||
@@ -251,8 +240,7 @@
|
||||
d/txt-merge
|
||||
{:fills (ths/sample-fills-color :fill-color "#fabada")
|
||||
:font-size "1"
|
||||
:letter-spacing "0"
|
||||
:font-family "Arial"}))
|
||||
:letter-spacing "0"}))
|
||||
(:objects page)
|
||||
{}))
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.library :as ctl]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[clojure.test :as t]))
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
_ (thf/validate-file! file')
|
||||
|
||||
;; Get
|
||||
colors' (vals (ctl/get-colors (ctf/file-data file')))
|
||||
colors' (ctc/colors-seq (ctf/file-data file'))
|
||||
shape1' (ths/get-shape file' :shape1)
|
||||
fill' (first (:fills shape1'))]
|
||||
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
|
||||
(ns common-tests.types.fill-test
|
||||
(:require
|
||||
#?(:clj [app.common.fressian :as fres])
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.schema.test :as smt]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.transit :as trans]
|
||||
[app.common.types.fill :as types.fill]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
@@ -80,8 +85,8 @@
|
||||
:fill-opacity 0.7})
|
||||
|
||||
(t/deftest build-from-plain-1
|
||||
(let [fills (types.fills/from-plain [sample-fill-1])]
|
||||
(t/is (types.fills/fills? fills))
|
||||
(let [fills (types.fill/from-plain [sample-fill-1])]
|
||||
(t/is (types.fill/fills? fills))
|
||||
(t/is (= 1 (count fills)))
|
||||
(t/is (equivalent-fill? (first fills) sample-fill-1))))
|
||||
|
||||
@@ -94,8 +99,8 @@
|
||||
:keep-aspect-ratio false}})
|
||||
|
||||
(t/deftest build-from-plain-2
|
||||
(let [fills (types.fills/from-plain [sample-fill-2])]
|
||||
(t/is (types.fills/fills? fills))
|
||||
(let [fills (types.fill/from-plain [sample-fill-2])]
|
||||
(t/is (types.fill/fills? fills))
|
||||
(t/is (= 1 (count fills)))
|
||||
(t/is (equivalent-fill? (first fills) sample-fill-2))))
|
||||
|
||||
@@ -112,8 +117,8 @@
|
||||
:stops [{:color "#631aa8", :offset 0.5}]}})
|
||||
|
||||
(t/deftest build-from-plain-3
|
||||
(let [fills (types.fills/from-plain [sample-fill-3])]
|
||||
(t/is (types.fills/fills? fills))
|
||||
(let [fills (types.fill/from-plain [sample-fill-3])]
|
||||
(t/is (types.fill/fills? fills))
|
||||
(t/is (= 1 (count fills)))
|
||||
(t/is (equivalent-fill? (first fills) sample-fill-3))))
|
||||
|
||||
@@ -131,8 +136,8 @@
|
||||
:fill-color-ref-id #uuid "2eef07f1-e38a-8062-8006-3aa264d5b785"})
|
||||
|
||||
(t/deftest build-from-plain-4
|
||||
(let [fills (types.fills/from-plain [sample-fill-4])]
|
||||
(t/is (types.fills/fills? fills))
|
||||
(let [fills (types.fill/from-plain [sample-fill-4])]
|
||||
(t/is (types.fill/fills? fills))
|
||||
(t/is (= 1 (count fills)))
|
||||
(t/is (equivalent-fill? (first fills) sample-fill-4))))
|
||||
|
||||
@@ -149,8 +154,8 @@
|
||||
:stops [{:color "#bba1aa", :opacity 0.37, :offset 0.84}]}})
|
||||
|
||||
(t/deftest build-from-plain-5
|
||||
(let [fills (types.fills/from-plain [sample-fill-5])]
|
||||
(t/is (types.fills/fills? fills))
|
||||
(let [fills (types.fill/from-plain [sample-fill-5])]
|
||||
(t/is (types.fill/fills? fills))
|
||||
(t/is (= 1 (count fills)))
|
||||
(t/is (equivalent-fill? (first fills) sample-fill-5))))
|
||||
|
||||
@@ -165,14 +170,14 @@
|
||||
:stops [{:color "#e15610", :offset 0.4} {:color "#005a9e", :opacity 0.62, :offset 0.81}]}})
|
||||
|
||||
(t/deftest build-from-plain-6
|
||||
(let [fills (types.fills/from-plain [sample-fill-6])]
|
||||
(t/is (types.fills/fills? fills))
|
||||
(let [fills (types.fill/from-plain [sample-fill-6])]
|
||||
(t/is (types.fill/fills? fills))
|
||||
(t/is (= 1 (count fills)))
|
||||
(t/is (equivalent-fill? (first fills) sample-fill-6))))
|
||||
|
||||
(t/deftest fills-datatype-roundtrip
|
||||
(smt/check!
|
||||
(smt/for [fill (->> (sg/generator types.fills/schema:fill)
|
||||
(smt/for [fill (->> (sg/generator types.fill/schema:fill)
|
||||
(sg/fmap d/without-nils)
|
||||
(sg/fmap (fn [fill]
|
||||
(cond-> fill
|
||||
@@ -182,27 +187,27 @@
|
||||
(contains? fill :fill-color-ref-file)))
|
||||
(-> (assoc :fill-color-ref-file (uuid/next))
|
||||
(assoc :fill-color-ref-id (uuid/next)))))))]
|
||||
(let [bfills (types.fills/from-plain [fill])]
|
||||
(let [bfills (types.fill/from-plain [fill])]
|
||||
(and (= (count bfills) 1)
|
||||
(equivalent-fill? (first bfills) fill))))
|
||||
{:num 2000}))
|
||||
|
||||
(t/deftest equality-operation
|
||||
(let [fills1 (types.fills/from-plain [sample-fill-6])
|
||||
fills2 (types.fills/from-plain [sample-fill-6])]
|
||||
(let [fills1 (types.fill/from-plain [sample-fill-6])
|
||||
fills2 (types.fill/from-plain [sample-fill-6])]
|
||||
(t/is (= fills1 fills2))))
|
||||
|
||||
(t/deftest reduce-impl
|
||||
(let [fills1 (types.fills/from-plain [sample-fill-6])
|
||||
(let [fills1 (types.fill/from-plain [sample-fill-6])
|
||||
fills2 (reduce (fn [result fill]
|
||||
(conj result fill))
|
||||
[]
|
||||
fills1)
|
||||
fills3 (types.fills/from-plain fills2)]
|
||||
fills3 (types.fill/from-plain fills2)]
|
||||
(t/is (= fills1 fills3))))
|
||||
|
||||
(t/deftest indexed-access
|
||||
(let [fills1 (types.fills/from-plain [sample-fill-6])
|
||||
(let [fills1 (types.fill/from-plain [sample-fill-6])
|
||||
fill0 (nth fills1 0)
|
||||
fill1 (nth fills1 1)]
|
||||
(t/is (nil? fill1))
|
||||
|
||||
@@ -7,36 +7,26 @@
|
||||
(ns common-tests.types.text-test
|
||||
(:require
|
||||
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.text :as cttx]
|
||||
[clojure.test :as t :include-macros true]))
|
||||
|
||||
(def content-base
|
||||
(-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
|
||||
(get :content)
|
||||
(cttx/change-text "hello world")))
|
||||
(def content-base (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
|
||||
(txt/change-text "hello world")
|
||||
(assoc :position-data nil)
|
||||
:content))
|
||||
|
||||
(def content-changed-text
|
||||
(assoc-in content-base [:children 0 :children 0 :children 0 :text] "changed"))
|
||||
|
||||
(def content-changed-attr
|
||||
(assoc-in content-base [:children 0 :children 0 :children 0 :font-size] "32"))
|
||||
|
||||
(def content-changed-both
|
||||
(-> content-base
|
||||
(assoc-in [:children 0 :children 0 :children 0 :text] "changed")
|
||||
(assoc-in [:children 0 :children 0 :children 0 :font-size] "32")))
|
||||
|
||||
(def line
|
||||
(get-in content-base [:children 0 :children 0 :children 0]))
|
||||
|
||||
(def content-changed-structure
|
||||
(update-in content-base [:children 0 :children 0 :children]
|
||||
#(conj % (assoc line :font-weight "700"))))
|
||||
|
||||
|
||||
(def content-changed-structure-same-attrs
|
||||
(update-in content-base [:children 0 :children 0 :children] #(conj % line)))
|
||||
(def content-changed-text (assoc-in content-base [:children 0 :children 0 :children 0 :text] "changed"))
|
||||
(def content-changed-attr (assoc-in content-base [:children 0 :children 0 :children 0 :font-size] "32"))
|
||||
(def content-changed-both (-> content-base
|
||||
(assoc-in [:children 0 :children 0 :children 0 :text] "changed")
|
||||
(assoc-in [:children 0 :children 0 :children 0 :font-size] "32")))
|
||||
(def line (get-in content-base [:children 0 :children 0 :children 0]))
|
||||
(def content-changed-structure (update-in content-base [:children 0 :children 0 :children]
|
||||
#(conj % (assoc line :font-weight "700"))))
|
||||
(def content-changed-structure-same-attrs (update-in content-base [:children 0 :children 0 :children]
|
||||
#(conj % line)))
|
||||
|
||||
(t/deftest test-get-diff-type
|
||||
(let [diff-text (cttx/get-diff-type content-base content-changed-text)
|
||||
|
||||
@@ -125,7 +125,7 @@ RUN set -ex; \
|
||||
|
||||
COPY --from=build /opt/jre /opt/jre
|
||||
COPY --from=build /opt/node /opt/node
|
||||
COPY --from=penpotapp/imagemagick:7.1.1-47 /opt/imagick /opt/imagick
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
|
||||
COPY --chown=penpot:penpot ./bundle-backend/ /opt/penpot/backend/
|
||||
|
||||
USER penpot:penpot
|
||||
|
||||
@@ -39,6 +39,7 @@ RUN set -ex; \
|
||||
fonts-wqy-zenhei \
|
||||
fonts-tlwg-loma-otf \
|
||||
fonts-freefont-ttf \
|
||||
poppler-utils \
|
||||
\
|
||||
libasound2t64 \
|
||||
libatk-bridge2.0-0t64 \
|
||||
|
||||
@@ -103,6 +103,11 @@ If you are deploying Penpot on OpenShift, we recommend following the specific gu
|
||||
|
||||
Make sure to review the section **OpenShift Requirements** for important security and compatibility considerations.
|
||||
|
||||
### Using Rancher?
|
||||
|
||||
If you are deploying Penpot on Rancher, we recommend following the specific guidelines provided in the official documentation:
|
||||
<a href="https://docs.apps.rancher.io/reference-guides/penpot/" target="_blank">Reference guides / Penpot</a>.
|
||||
|
||||
## Upgrade Penpot
|
||||
|
||||
When a new version of Penpot's chart is released, or when you want to change the
|
||||
|
||||
@@ -205,6 +205,10 @@ title: 10· Design Tokens
|
||||
<h4>Y Position (dimension)</h4>
|
||||
<p>The Y property specifies the position of the element on the Y axis of the canvas.</p>
|
||||
|
||||
<h3 id="design-tokens-font-size">Font Size</h3>
|
||||
<p>Font size tokens allow you to define and standardize font-size values across your design system. These tokens can be applied to the <strong>font-size</strong> property in text layers, ensuring consistent typography throughout your designs.</p>
|
||||
<p class="advice">Font size token values are always computed as <strong>px</strong> (pixels).</p>
|
||||
|
||||
<h3 id="design-tokens-opacity">Opacity</h3>
|
||||
<p>Opacity tokens allow you to define the opacity of a layer, ranging from fully opaque to fully transparent.</p>
|
||||
<p>Opacity tokens can be applied to any design element that supports transparency. You can use any decimal value between 0 and 1 to set varying levels of opacity or you can use any value between 0 and 100 with <strong>`%`</strong> sign at the end of the value. For example, you can use <strong>45%</strong> which would resolve to <strong>.45</strong>.</p>
|
||||
@@ -378,7 +382,15 @@ title: 10· Design Tokens
|
||||
</ol>
|
||||
|
||||
<h3 id="design-tokens-import-options">Import Options</h3>
|
||||
<h4>Single file</h4>
|
||||
|
||||
<h4>ZIP file</h4>
|
||||
<p>You can import tokens from a <strong>.zip</strong> file. This file can either contain a single JSON file or a folder structure with multiple files. The ZIP import option provides flexibility for organizing your tokens before importing them into Penpot.</p>
|
||||
<ul>
|
||||
<li>If the ZIP contains a single JSON file, it will be imported as a single set of tokens.</li>
|
||||
<li>If the ZIP contains a folder structure, each file and folder will be interpreted as separate token sets, following the same rules as the multifile import.</li>
|
||||
</ul>
|
||||
|
||||
<h4>Single JSON file</h4>
|
||||
|
||||
<p>You can import a JSON file comprising all tokens, token sets and token themes.</p>
|
||||
<p>When importing a single file, the first-level keys of the json file will be interpreted as the set name.</p>
|
||||
|
||||
@@ -34,7 +34,13 @@ desc: Master layer basics with Penpot's user guide! Learn to create, manipulate,
|
||||
<p>Layers are displayed from the bottom to the top of the layer stack, with layers above on the stack being shown on top in the image.</p>
|
||||
|
||||
<h2 id="hide-lock">Hide and lock layers</h2>
|
||||
<p>Click on the eye icon to change the visibility of a layer. Click on the lock icon to lock or unlock a layer. A locked layer can not be modified.</p>
|
||||
|
||||
<h3>Hide and show layers</h3>
|
||||
<p>You can control the visibility of any layer by clicking the eye icon next to it in the Layers panel. When a layer is hidden, it will not appear on the canvas, but you can still select it in the Layers panel, move its order, or modify its properties. The eye icon always indicates whether a layer is visible or hidden, making it easy to manage complex designs.</p>
|
||||
|
||||
<h3>Lock and unlock layers</h3>
|
||||
<p>Locking a layer helps prevent accidental changes or movement on the canvas. When a layer is locked, it cannot be moved or edited directly in the canvas area. However, you can still select a locked layer in the Layers panel and adjust its properties, such as color, effects, or name. The lock icon next to the layer’s name shows its locked status, helping you keep your design organized and protected.</p>
|
||||
|
||||
<figure>
|
||||
<video title="Layers hide and lock" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-hide-lock.webp" height="auto">
|
||||
<source src="/img/layers/layers-hide-lock.mp4" type="video/mp4">
|
||||
|
||||
@@ -142,6 +142,11 @@ a design.</p>
|
||||
<source src="/img/objects/text-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<p><strong>Tips for resizing</strong></p>
|
||||
<ul>
|
||||
<li>Double-click on the right side of the bounding box to set the resize setting to auto-width.</li>
|
||||
<li>Double-click on the bottom side of the bounding box to set the resize setting to auto-height.</li>
|
||||
</ul>
|
||||
<h3>Edit and style text content</h3>
|
||||
<p>Press <kbd>Enter</kbd> with a text layer selected to start editing the text content. You can style parts of the text content as rich text.</p>
|
||||
<figure>
|
||||
|
||||
@@ -126,8 +126,8 @@ test("Renders a file with texts with images", async ({ page }) => {
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockFileMediaAsset(
|
||||
[
|
||||
"4f89252d-ebbc-813e-8006-8699e4170e17",
|
||||
"4f89252d-ebbc-813e-8006-8699e4170e18"
|
||||
"6bd7c17d-4f59-815e-8006-5e9765e0fabd",
|
||||
"6bd7c17d-4f59-815e-8006-5e97441071cc"
|
||||
],
|
||||
"render-wasm/assets/pattern.png",
|
||||
);
|
||||
|
||||
|
Before Width: | Height: | Size: 508 KiB After Width: | Height: | Size: 512 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 96 KiB |
BIN
frontend/resources/images/features/2.9-font-size.gif
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
frontend/resources/images/features/2.9-overrides.gif
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
frontend/resources/images/features/2.9-qol.gif
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
frontend/resources/images/features/2.9-slide-0.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
@@ -29,33 +29,33 @@
|
||||
const iterations = parseInt(url.searchParams.get('iterations') ?? 1_000, 10);
|
||||
|
||||
function prepare(Module, canvas) {
|
||||
setup({
|
||||
instance: Module,
|
||||
canvas
|
||||
})
|
||||
init(Module);
|
||||
assignCanvas(canvas);
|
||||
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
|
||||
Module._set_view(1, 0, 0);
|
||||
|
||||
const children = [];
|
||||
for (let shape = 0; shape < shapes; shape++) {
|
||||
const uuid = crypto.randomUUID();
|
||||
children.push(uuid);
|
||||
|
||||
children.push(
|
||||
addShape({
|
||||
parent: "00000000-0000-0000-0000-000000000000",
|
||||
type: "rect",
|
||||
selrect: {
|
||||
x: getRandomInt(0, canvas.width),
|
||||
y: getRandomInt(0, canvas.height),
|
||||
width: getRandomInt(20, 100),
|
||||
height: getRandomInt(20, 100)
|
||||
},
|
||||
fills: [{ type: "solid", color: getRandomColor(), opacity: getRandomFloat(0.1, 1.0) }]
|
||||
})
|
||||
);
|
||||
useShape(uuid);
|
||||
|
||||
Module._set_parent(0, 0, 0, 0);
|
||||
Module._set_shape_type(3);
|
||||
const x1 = getRandomInt(0, canvas.width);
|
||||
const y1 = getRandomInt(0, canvas.height);
|
||||
const width = getRandomInt(20, 100);
|
||||
const height = getRandomInt(20, 100);
|
||||
Module._set_shape_selrect(x1, y1, x1 + width, y1 + height);
|
||||
|
||||
const color = getRandomColor();
|
||||
const argb = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||
addShapeSolidFill(argb)
|
||||
}
|
||||
|
||||
addShape({
|
||||
id: "00000000-0000-0000-0000-000000000000",
|
||||
children
|
||||
})
|
||||
useShape("00000000-0000-0000-0000-000000000000");
|
||||
setShapeChildren(children);
|
||||
}
|
||||
|
||||
function createElement(tag, attribs, children) {
|
||||
|
||||
@@ -27,12 +27,12 @@ export function assignCanvas(canvas) {
|
||||
context.getExtension("WEBGL_debug_renderer_info");
|
||||
|
||||
Module._init(canvas.width, canvas.height);
|
||||
Module._set_render_options(1, 1);
|
||||
Module._set_render_options(0, 1);
|
||||
}
|
||||
|
||||
export function hexToU32ARGB(hex, opacity = 1) {
|
||||
const rgb = parseInt(hex.slice(1), 16);
|
||||
const a = Math.floor(opacity * 0xff);
|
||||
const a = Math.floor(opacity * 0xFF);
|
||||
const argb = (a << 24) | rgb;
|
||||
return argb >>> 0;
|
||||
}
|
||||
@@ -42,9 +42,9 @@ export function getRandomInt(min, max) {
|
||||
}
|
||||
|
||||
export function getRandomColor() {
|
||||
const r = getRandomInt(0, 256).toString(16).padStart(2, "0");
|
||||
const g = getRandomInt(0, 256).toString(16).padStart(2, "0");
|
||||
const b = getRandomInt(0, 256).toString(16).padStart(2, "0");
|
||||
const r = getRandomInt(0, 256).toString(16).padStart(2, '0');
|
||||
const g = getRandomInt(0, 256).toString(16).padStart(2, '0');
|
||||
const b = getRandomInt(0, 256).toString(16).padStart(2, '0');
|
||||
return `#${r}${g}${b}`;
|
||||
}
|
||||
|
||||
@@ -103,12 +103,12 @@ export function addShapeSolidStrokeFill(argb) {
|
||||
|
||||
function serializePathAttrs(svgAttrs) {
|
||||
return Object.entries(svgAttrs).reduce((acc, [key, value]) => {
|
||||
return acc + key + "\0" + value + "\0";
|
||||
}, "");
|
||||
return acc + key + '\0' + value + '\0';
|
||||
}, '');
|
||||
}
|
||||
|
||||
export function drawStar(x, y, width, height) {
|
||||
const len = 11; // 1 MOVE + 9 LINE + 1 CLOSE
|
||||
export function draw_star(x, y, width, height) {
|
||||
const len = 11; // 1 MOVE + 9 LINE + 1 CLOSE
|
||||
const ptr = allocBytes(len * 28);
|
||||
const heap = getHeapU32();
|
||||
const dv = new DataView(heap.buffer);
|
||||
@@ -120,7 +120,7 @@ export function drawStar(x, y, width, height) {
|
||||
|
||||
const star = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const angle = (Math.PI / 5) * i - Math.PI / 2;
|
||||
const angle = Math.PI / 5 * i - Math.PI / 2;
|
||||
const r = i % 2 === 0 ? outerRadius : innerRadius;
|
||||
const px = cx + r * Math.cos(angle);
|
||||
const py = cy + r * Math.sin(angle);
|
||||
@@ -149,7 +149,7 @@ export function drawStar(x, y, width, height) {
|
||||
Module._set_shape_path_content();
|
||||
|
||||
const str = serializePathAttrs({
|
||||
fill: "none",
|
||||
"fill": "none",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
});
|
||||
@@ -158,6 +158,7 @@ export function drawStar(x, y, width, height) {
|
||||
Module.stringToUTF8(str, offset, size);
|
||||
Module._set_shape_path_attrs(3);
|
||||
}
|
||||
|
||||
|
||||
export function setShapeChildren(shapeIds) {
|
||||
const offset = allocBytes(shapeIds.length * 16);
|
||||
@@ -175,7 +176,7 @@ export function useShape(id) {
|
||||
Module._use_shape(...buffer);
|
||||
}
|
||||
|
||||
export function setParent(id) {
|
||||
export function set_parent(id) {
|
||||
const buffer = getU32(id);
|
||||
Module._set_parent(...buffer);
|
||||
}
|
||||
@@ -226,12 +227,8 @@ export function setupInteraction(canvas) {
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener("mouseup", () => {
|
||||
isPanning = false;
|
||||
});
|
||||
canvas.addEventListener("mouseout", () => {
|
||||
isPanning = false;
|
||||
});
|
||||
canvas.addEventListener("mouseup", () => { isPanning = false; });
|
||||
canvas.addEventListener("mouseout", () => { isPanning = false; });
|
||||
}
|
||||
|
||||
export function addTextShape(x, y, fontSize, text) {
|
||||
@@ -315,57 +312,4 @@ export function addTextShape(x, y, fontSize, text) {
|
||||
|
||||
// Call the WebAssembly function
|
||||
Module._set_shape_text_content();
|
||||
}
|
||||
|
||||
export function setup(options) {
|
||||
init(options.instance)
|
||||
assignCanvas(options.canvas)
|
||||
Module._set_canvas_background(hexToU32ARGB(options?.backgroundColor ?? "#FABADA", 1));
|
||||
Module._set_view(options?.zoom ?? 1, options?.x ?? 0, options?.y ?? 0);
|
||||
Module._init_shapes_pool(options.shapes + 1);
|
||||
setupInteraction(options.canvas);
|
||||
}
|
||||
|
||||
function getShapeType(type) {
|
||||
switch (type) {
|
||||
default:
|
||||
case "rect": return 3;
|
||||
}
|
||||
}
|
||||
|
||||
export function addShape(init) {
|
||||
const uuid = init?.id ?? crypto.randomUUID()
|
||||
useShape(uuid);
|
||||
setParent(init?.parent ?? "00000000-0000-0000-0000-000000000000");
|
||||
|
||||
Module._set_shape_type(getShapeType(init?.type));
|
||||
if (init.selrect) {
|
||||
Module._set_shape_selrect(
|
||||
init.selrect.x,
|
||||
init.selrect.y,
|
||||
init.selrect.x + init.selrect.width,
|
||||
init.selrect.y + init.selrect.height,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(init?.fills)) {
|
||||
for (const fill of init.fills) {
|
||||
const argb = hexToU32ARGB(fill.color, fill.opacity);
|
||||
addShapeSolidFill(argb);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(init?.strokes)) {
|
||||
for (const stroke of init.strokes) {
|
||||
Module._add_shape_center_stroke(stroke.width, 0, 0, 0);
|
||||
const argb = hexToU32ARGB(stroke.color, stroke.opacity);
|
||||
addShapeSolidStrokeFill(argb);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(init?.children)) {
|
||||
setShapeChildren(init.children);
|
||||
}
|
||||
|
||||
return uuid
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@
|
||||
<script type="module">
|
||||
import initWasmModule from '/js/render_wasm.js';
|
||||
import {
|
||||
addShapeSolidFill, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, addShape, setShapeChildren, setup
|
||||
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStrokeFill
|
||||
} from './js/lib.js';
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
@@ -37,43 +37,46 @@
|
||||
const shapes = params.get("shapes") || 1000;
|
||||
|
||||
initWasmModule().then(Module => {
|
||||
setup({
|
||||
instance: Module,
|
||||
canvas,
|
||||
shapes
|
||||
})
|
||||
init(Module);
|
||||
assignCanvas(canvas);
|
||||
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
|
||||
Module._set_view(1, 0, 0);
|
||||
Module._init_shapes_pool(shapes + 1);
|
||||
setupInteraction(canvas);
|
||||
|
||||
const children = [];
|
||||
for (let shape = 0; shape < shapes; shape++) {
|
||||
const color = getRandomColor()
|
||||
children.push(
|
||||
addShape({
|
||||
parent: "00000000-0000-0000-0000-000000000000",
|
||||
type: "rect", // rect
|
||||
selrect: {
|
||||
x: getRandomInt(0, canvas.width),
|
||||
y: getRandomInt(0, canvas.height),
|
||||
width: getRandomInt(20, 100),
|
||||
height: getRandomInt(20, 100),
|
||||
},
|
||||
fills: [{ type: "solid", color, opacity: getRandomFloat(0.1, 1.0) }],
|
||||
strokes: [{ width: 10, type: "solid", color, opacity: getRandomFloat(0.1, 1.0) }]
|
||||
})
|
||||
);
|
||||
for (let i = 0; i < shapes; i++) {
|
||||
const uuid = crypto.randomUUID();
|
||||
children.push(uuid);
|
||||
|
||||
useShape(uuid);
|
||||
Module._set_parent(0, 0, 0, 0);
|
||||
Module._set_shape_type(3);
|
||||
const x1 = getRandomInt(0, canvas.width);
|
||||
const y1 = getRandomInt(0, canvas.height);
|
||||
const width = getRandomInt(20, 100);
|
||||
const height = getRandomInt(20, 100);
|
||||
Module._set_shape_selrect(x1, y1, x1 + width, y1 + height);
|
||||
|
||||
const color = getRandomColor();
|
||||
const argb = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||
addShapeSolidFill(argb)
|
||||
|
||||
Module._add_shape_center_stroke(10, 0, 0, 0);
|
||||
const argb2 = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||
addShapeSolidStrokeFill(argb2);
|
||||
}
|
||||
|
||||
addShape({
|
||||
id: "00000000-0000-0000-0000-000000000000",
|
||||
children: children
|
||||
})
|
||||
useShape("00000000-0000-0000-0000-000000000000");
|
||||
setShapeChildren(children);
|
||||
|
||||
performance.mark('render:begin');
|
||||
Module._render(performance.now(), true);
|
||||
Module._render(Date.now());
|
||||
performance.mark('render:end');
|
||||
const { duration } = performance.measure('render', 'render:begin', 'render:end');
|
||||
// alert(`render time: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
check-safari-16? (fn [] (and (check-safari?) (str/includes? user-agent "version/16")))
|
||||
check-safari-17? (fn [] (and (check-safari?) (str/includes? user-agent "version/17")))]
|
||||
(cond
|
||||
^boolean (check-edge?) :edge
|
||||
^boolean (check-chrome?) :chrome
|
||||
^boolean (check-firefox?) :firefox
|
||||
^boolean (check-safari-16?) :safari-16
|
||||
^boolean (check-safari-17?) :safari-17
|
||||
^boolean (check-safari?) :safari
|
||||
(check-edge?) :edge
|
||||
(check-chrome?) :chrome
|
||||
(check-firefox?) :firefox
|
||||
(check-safari-16?) :safari-16
|
||||
(check-safari-17?) :safari-17
|
||||
(check-safari?) :safari
|
||||
:else :other)))
|
||||
|
||||
(defn- parse-platform
|
||||
@@ -52,9 +52,9 @@
|
||||
check-linux? (fn [] (str/includes? user-agent "linux"))
|
||||
check-macos? (fn [] (str/includes? user-agent "mac os"))]
|
||||
(cond
|
||||
^boolean (check-windows?) :windows
|
||||
^boolean (check-linux?) :linux
|
||||
^boolean (check-macos?) :macos
|
||||
(check-windows?) :windows
|
||||
(check-linux?) :linux
|
||||
(check-macos?) :macos
|
||||
:else :other)))
|
||||
|
||||
(defn- parse-target
|
||||
@@ -104,12 +104,6 @@
|
||||
(def plugins-whitelist (into #{} (obj/get global "penpotPluginsWhitelist" [])))
|
||||
(def templates-uri (obj/get global "penpotTemplatesUri" "https://penpot.github.io/penpot-files/"))
|
||||
|
||||
|
||||
;; We set the current parsed flags under common for make
|
||||
;; it available for common code without the need to pass
|
||||
;; the flags all arround on parameters.
|
||||
(set! app.common.flags/*current* flags)
|
||||
|
||||
(defn- normalize-uri
|
||||
[uri-str]
|
||||
(let [uri (u/uri uri-str)]
|
||||
|
||||
@@ -79,14 +79,29 @@
|
||||
(defprotocol Event
|
||||
(-data [_] "Get event data"))
|
||||
|
||||
(defn- coerce-to-string
|
||||
[v]
|
||||
(cond
|
||||
(keyword? v)
|
||||
(name v)
|
||||
(string? v)
|
||||
v
|
||||
(nil? v)
|
||||
nil
|
||||
:else
|
||||
(str v)))
|
||||
|
||||
(def ^:private xf:coerce-to-string
|
||||
(keep coerce-to-string))
|
||||
|
||||
(defn- simplify-props
|
||||
"Removes complex data types from props."
|
||||
[data]
|
||||
(reduce-kv (fn [data k v]
|
||||
(cond
|
||||
(map? v) (assoc data k :placeholder/map)
|
||||
(vector? v) (assoc data k :placeholder/vec)
|
||||
(set? v) (assoc data k :placeholder/set)
|
||||
(vector? v) (assoc data k (into [] xf:coerce-to-string v))
|
||||
(set? v) (assoc data k (into [] xf:coerce-to-string v))
|
||||
(coll? v) (assoc data k :placeholder/coll)
|
||||
(fn? v) (assoc data k :placeholder/fn)
|
||||
(nil? v) (dissoc data k)
|
||||
|
||||
@@ -78,23 +78,6 @@
|
||||
(filter selectable?)
|
||||
selected)))))
|
||||
|
||||
(defn split-text-shapes
|
||||
"Split text shapes from non-text shapes"
|
||||
[objects ids]
|
||||
(loop [ids (seq ids)
|
||||
text-ids []
|
||||
shape-ids []]
|
||||
(if-let [id (first ids)]
|
||||
(let [shape (get objects id)]
|
||||
(if (cfh/text-shape? shape)
|
||||
(recur (rest ids)
|
||||
(conj text-ids id)
|
||||
shape-ids)
|
||||
(recur (rest ids)
|
||||
text-ids
|
||||
(conj shape-ids id))))
|
||||
[text-ids shape-ids])))
|
||||
|
||||
;; DEPRECATED
|
||||
(defn lookup-selected-raw
|
||||
[state]
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
[app.plugins.register :as plugins.register]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.storage :as storage]
|
||||
[app.util.theme :as theme]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
@@ -159,9 +158,6 @@
|
||||
(update-in state [:profile :theme]
|
||||
(fn [current]
|
||||
(let [current (cond
|
||||
(= current "system")
|
||||
(theme/get-system-theme)
|
||||
|
||||
;; NOTE: this is a workaround for
|
||||
;; the old data on the database
|
||||
;; where whe have `default` value
|
||||
@@ -172,7 +168,8 @@
|
||||
current)]
|
||||
(case current
|
||||
"dark" "light"
|
||||
"light" "dark"
|
||||
"light" "system"
|
||||
"system" "dark"
|
||||
; Failsafe for missing data
|
||||
"dark")))))
|
||||
|
||||
|
||||
@@ -148,17 +148,17 @@
|
||||
|
||||
(defn- bind!
|
||||
[shortcuts]
|
||||
(let [msbind (fn [command callback type]
|
||||
(->> shortcuts
|
||||
(remove #(:disabled (second %)))
|
||||
(run! (fn [[key {:keys [command fn type overwrite]}]]
|
||||
(let [callback (wrap-cb key fn)
|
||||
undefined (js* "(void 0)")
|
||||
commands (if (vector? command)
|
||||
(into-array command)
|
||||
#js [command])]
|
||||
(if type
|
||||
(mousetrap/bind command callback type)
|
||||
(mousetrap/bind command callback)))]
|
||||
(->> shortcuts
|
||||
(remove #(:disabled (second %)))
|
||||
(run! (fn [[key {:keys [command fn type]}]]
|
||||
(let [callback (wrap-cb key fn)]
|
||||
(if (vector? command)
|
||||
(run! #(msbind % callback type) command)
|
||||
(msbind command callback type))))))))
|
||||
(mousetrap/bind commands callback type overwrite)
|
||||
(mousetrap/bind commands callback undefined overwrite)))))))
|
||||
|
||||
(defn- reset!
|
||||
([]
|
||||
|
||||
@@ -195,7 +195,6 @@
|
||||
value (.-value sd-token)
|
||||
has-references? (str/includes? (:value origin-token) "{")
|
||||
parsed-token-value (case (:type origin-token)
|
||||
:font-family {:value (-> (js->clj value) (flatten))}
|
||||
:color (parse-sd-token-color-value value)
|
||||
:opacity (parse-sd-token-opacity-value value has-references?)
|
||||
:stroke-width (parse-sd-token-stroke-width-value value has-references?)
|
||||
|
||||
@@ -20,11 +20,9 @@
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.comments :as dcmt]
|
||||
[app.main.data.common :as dcm]
|
||||
@@ -82,7 +80,6 @@
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.walk :as walk]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
@@ -136,30 +133,10 @@
|
||||
(rx/of [k v])))))))
|
||||
(rx/reduce conj {})))
|
||||
|
||||
|
||||
(defn process-fills
|
||||
"A function responsible to analyze the file data or shape for references
|
||||
and apply lookup-index on it."
|
||||
[data]
|
||||
(letfn [(process-map-form [form]
|
||||
(let [fills (get form :fills)]
|
||||
(if (vector? fills)
|
||||
(assoc form :fills (types.fills/from-plain fills))
|
||||
form)))
|
||||
|
||||
(process-form [form]
|
||||
(if (map? form)
|
||||
(process-map-form form)
|
||||
form))]
|
||||
(if (contains? cf/flags :frontend-binary-fills)
|
||||
(walk/postwalk process-form data)
|
||||
data)))
|
||||
|
||||
(defn- resolve-file
|
||||
[file]
|
||||
(->> (fpmap/resolve-file file)
|
||||
(rx/map :data)
|
||||
(rx/map process-fills)
|
||||
(rx/mapcat
|
||||
(fn [{:keys [pages-index] :as data}]
|
||||
(->> (rx/from (seq pages-index))
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
head
|
||||
(cond-> head
|
||||
(and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
(assoc :fills (path/get-default-bool-fills)))
|
||||
(assoc :fills path/default-bool-fills))
|
||||
|
||||
shape
|
||||
{:id shape-id
|
||||
@@ -62,7 +62,7 @@
|
||||
head (if (= type :difference) (first shapes) (last shapes))
|
||||
head (cond-> head
|
||||
(and (contains? head :svg-attrs) (empty? (:fills head)))
|
||||
(assoc :fills (path/get-default-bool-fills)))]
|
||||
(assoc :fills path/default-bool-fills))]
|
||||
(-> group
|
||||
(assoc :type :bool)
|
||||
(assoc :bool-type type)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
[app.common.geom.shapes.grid-layout :as gslg]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.text :as txt]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.container :as ctn]
|
||||
@@ -27,7 +28,6 @@
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.shape.text :as types.text]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.typography :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
@@ -925,7 +925,7 @@
|
||||
(let [paragraphs (->> (str/lines text)
|
||||
(map str/trim)
|
||||
(mapv #(hash-map :type "paragraph"
|
||||
:children [(merge (txt/get-default-text-attrs) {:text %})])))]
|
||||
:children [(merge txt/default-text-attrs {:text %})])))]
|
||||
;; if text is composed only by line breaks paragraphs is an empty list and should be nil
|
||||
(when (d/not-empty? paragraphs)
|
||||
{:type "root"
|
||||
|
||||
@@ -6,18 +6,19 @@
|
||||
|
||||
(ns app.main.data.workspace.colors
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.types.library :as ctl]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.color :as types.color]
|
||||
[app.common.types.fill :as types.fill]
|
||||
[app.common.types.shape :as shp]
|
||||
[app.common.types.shape.shadow :refer [check-shadow]]
|
||||
[app.common.types.text :as txt]
|
||||
[app.config :as cfg]
|
||||
[app.main.broadcast :as mbc]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.modal :as md]
|
||||
[app.main.data.workspace.layout :as layout]
|
||||
@@ -102,7 +103,11 @@
|
||||
|
||||
(defn assoc-shape-fill
|
||||
[shape position fill]
|
||||
(update shape :fills types.fills/assoc position fill))
|
||||
(update shape :fills
|
||||
(fn [fills]
|
||||
(if (nil? fills)
|
||||
[fill]
|
||||
(assoc fills position fill)))))
|
||||
|
||||
(defn transform-fill*
|
||||
"A lower-level companion function for `transform-fill`"
|
||||
@@ -148,7 +153,7 @@
|
||||
(d/without-nils)
|
||||
|
||||
:always
|
||||
(types.fills/check-fill))
|
||||
(types.fill/check-fill))
|
||||
|
||||
transform-attrs
|
||||
#(transform % fill)]
|
||||
@@ -162,28 +167,17 @@
|
||||
(assoc-in [attr index] second)
|
||||
(assoc-in [attr new-index] first))))
|
||||
|
||||
(defn- swap-fills-index
|
||||
[fills index new-index]
|
||||
(let [first (get fills index)
|
||||
second (get fills new-index)]
|
||||
(-> fills
|
||||
(assoc index second)
|
||||
(assoc new-index first))))
|
||||
|
||||
(defn reorder-fills
|
||||
[ids index new-index]
|
||||
(ptk/reify ::reorder-fills
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects
|
||||
(dsh/lookup-page-objects state)
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
|
||||
[text-ids shape-ids]
|
||||
(dsh/split-text-shapes objects ids)
|
||||
|
||||
transform-attrs
|
||||
(fn [object]
|
||||
(update object :fills types.fills/update swap-fills-index index new-index))]
|
||||
is-text? #(= :text (:type (get objects %)))
|
||||
text-ids (filter is-text? ids)
|
||||
shape-ids (remove is-text? ids)
|
||||
transform-attrs #(swap-attrs % :fills index new-index)]
|
||||
|
||||
(rx/concat
|
||||
(rx/from (map #(dwt/update-text-with-function % transform-attrs) text-ids))
|
||||
@@ -196,7 +190,7 @@
|
||||
(assert (every? uuid? ids) "expect a coll of uuids for `ids`")
|
||||
(assert (number? position) "expect a number for position")
|
||||
|
||||
(let [color (clr/check-color color)]
|
||||
(let [color (types.color/check-color color)]
|
||||
(ptk/reify ::change-fill
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
@@ -213,7 +207,7 @@
|
||||
(ptk/reify ::change-fill-and-clear
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [change-fn (fn [shape attrs] (assoc shape :fills (types.fills/create attrs)))
|
||||
(let [change-fn (fn [shape attrs] (assoc shape :fills [attrs]))
|
||||
undo-id (js/Symbol)]
|
||||
(rx/concat
|
||||
(rx/of (dwu/start-undo-transaction undo-id))
|
||||
@@ -225,13 +219,14 @@
|
||||
([ids color options]
|
||||
|
||||
(assert (every? uuid? ids) "expected a valid coll of uuid's")
|
||||
(let [color (clr/check-color color)]
|
||||
(let [color (types.color/check-color color)]
|
||||
(ptk/reify ::add-fill
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [change-fn
|
||||
(fn [shape attrs]
|
||||
(update shape :fills types.fills/prepend attrs))
|
||||
(-> shape
|
||||
(update :fills #(into [attrs] %))))
|
||||
undo-id
|
||||
(js/Symbol)]
|
||||
(rx/concat
|
||||
@@ -252,13 +247,13 @@
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [detach-fn
|
||||
(fn [fills index]
|
||||
(update fills index dissoc :fill-color-ref-id :fill-color-ref-file))
|
||||
(fn [values index]
|
||||
(update values index dissoc :fill-color-ref-id :fill-color-ref-file))
|
||||
|
||||
change-fn
|
||||
;; The `node` can be a shape or a text content node
|
||||
(fn [node]
|
||||
(update node :fills types.fills/update detach-fn position))
|
||||
(update node :fills detach-fn position))
|
||||
|
||||
undo-id
|
||||
(js/Symbol)]
|
||||
@@ -280,17 +275,17 @@
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [remove-fill-by-index
|
||||
(fn [fills index]
|
||||
(fn [values index]
|
||||
(into []
|
||||
(comp
|
||||
(map-indexed (fn [i o] (when (not= i index) o)))
|
||||
(filter some?))
|
||||
fills))
|
||||
values))
|
||||
|
||||
change-fn
|
||||
;; The `node` can be a shape or a text content node
|
||||
(fn [node]
|
||||
(update node :fills types.fills/update remove-fill-by-index position))
|
||||
(update node :fills remove-fill-by-index position))
|
||||
|
||||
undo-id
|
||||
(js/Symbol)]
|
||||
@@ -308,7 +303,7 @@
|
||||
(ptk/reify ::remove-all-fills
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [change-fn (fn [node] (assoc node :fills (types.fills/create)))
|
||||
(let [change-fn (fn [node] (assoc node :fills []))
|
||||
undo-id (js/Symbol)]
|
||||
(rx/concat
|
||||
(rx/of (dwu/start-undo-transaction undo-id))
|
||||
@@ -565,7 +560,7 @@
|
||||
(assoc-in [:workspace-global :picking-color?] true)
|
||||
(assoc ::md/modal {:id (random-uuid)
|
||||
:type :colorpicker
|
||||
:props {:data {:color clr/black
|
||||
:props {:data {:color cc/black
|
||||
:opacity 1}
|
||||
:disable-opacity false
|
||||
:disable-gradient false
|
||||
@@ -581,26 +576,16 @@
|
||||
:fill-color-ref-file (:ref-file color)
|
||||
:fill-color-gradient (:gradient color)}))
|
||||
|
||||
(defn- change-text-color
|
||||
(defn change-text-color
|
||||
[old-color new-color index node]
|
||||
(update node :fills types.fills/update
|
||||
(fn [fills]
|
||||
(let [fills'
|
||||
(map #(dissoc % :fill-color-ref-id :fill-color-ref-file) fills)
|
||||
|
||||
parsed-color
|
||||
(-> (color-att->text old-color)
|
||||
(dissoc :fill-color-ref-id :fill-color-ref-file))
|
||||
|
||||
parsed-new-color
|
||||
(color-att->text new-color)
|
||||
|
||||
has-color?
|
||||
(d/index-of fills' parsed-color)]
|
||||
|
||||
(cond-> fills
|
||||
(some? has-color?)
|
||||
(assoc index parsed-new-color))))))
|
||||
(let [fills (map #(dissoc % :fill-color-ref-id :fill-color-ref-file) (:fills node))
|
||||
parsed-color (-> (color-att->text old-color)
|
||||
(dissoc :fill-color-ref-id :fill-color-ref-file))
|
||||
parsed-new-color (color-att->text new-color)
|
||||
has-color? (d/index-of fills parsed-color)]
|
||||
(cond-> node
|
||||
(some? has-color?)
|
||||
(assoc-in [:fills index] parsed-new-color))))
|
||||
|
||||
(def ^:private schema:change-color-operation
|
||||
[:map
|
||||
@@ -616,9 +601,17 @@
|
||||
|
||||
(defn change-color-in-selected
|
||||
[operations new-color old-color]
|
||||
(assert (check-change-color-operations operations))
|
||||
(assert (clr/check-color new-color))
|
||||
(assert (clr/check-color old-color))
|
||||
|
||||
(assert (check-change-color-operations operations)
|
||||
"expected valid color operations")
|
||||
|
||||
(assert
|
||||
(types.color/check-color new-color)
|
||||
"expected valid color structure")
|
||||
|
||||
(assert
|
||||
(types.color/check-color old-color)
|
||||
"expected valid color structure")
|
||||
|
||||
(ptk/reify ::change-color-in-selected
|
||||
ptk/WatchEvent
|
||||
@@ -639,7 +632,7 @@
|
||||
|
||||
(defn apply-color-from-palette
|
||||
[color stroke?]
|
||||
(let [color (clr/check-color color)]
|
||||
(let [color (types.color/check-color color)]
|
||||
(ptk/reify ::apply-color-from-palette
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
@@ -674,7 +667,7 @@
|
||||
|
||||
(defn apply-color-from-colorpicker
|
||||
[color]
|
||||
(let [color (clr/check-color color)]
|
||||
(let [color (types.color/check-color color)]
|
||||
(ptk/reify ::apply-color-from-colorpicker
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
@@ -715,7 +708,7 @@
|
||||
|
||||
(defn add-recent-color
|
||||
[color]
|
||||
(let [color (clr/check-color color)]
|
||||
(let [color (types.color/check-color color)]
|
||||
(ptk/reify ::add-recent-color
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
@@ -735,11 +728,11 @@
|
||||
|
||||
(defn apply-color-from-assets
|
||||
[file-id color stroke?]
|
||||
(let [color (clr/check-library-color color)]
|
||||
(let [color (types.color/check-library-color color)]
|
||||
(ptk/reify ::apply-color-from-asserts
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [color (clr/library-color->color color file-id)]
|
||||
(let [color (types.color/library-color->color color file-id)]
|
||||
(rx/of (apply-color-from-palette color stroke?)
|
||||
(add-recent-color color)))))))
|
||||
|
||||
@@ -749,9 +742,9 @@
|
||||
|
||||
(defn split-color-components
|
||||
[{:keys [color opacity] :as data}]
|
||||
(let [value (if (clr/valid-hex-color? color) color clr/black)
|
||||
[r g b] (clr/hex->rgb value)
|
||||
[h s v] (clr/hex->hsv value)]
|
||||
(let [value (if (cc/valid-hex-color? color) color cc/black)
|
||||
[r g b] (cc/hex->rgb value)
|
||||
[h s v] (cc/hex->hsv value)]
|
||||
(merge data
|
||||
{:hex (or value "000000")
|
||||
:alpha (or opacity 1)
|
||||
@@ -900,11 +893,11 @@
|
||||
(update state :colorpicker
|
||||
(fn [{:keys [stops editing-stop] :as state}]
|
||||
(let [cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills))
|
||||
can-add-stop? (or (not cap-stops?) (< (count stops) types.fills/MAX-GRADIENT-STOPS))]
|
||||
can-add-stop? (or (not cap-stops?) (< (count stops) types.fill/MAX-GRADIENT-STOPS))]
|
||||
(if can-add-stop?
|
||||
(if (clr/uniform-spread? stops)
|
||||
(if (cc/uniform-spread? stops)
|
||||
;; Add to uniform
|
||||
(let [stops (->> (clr/uniform-spread (first stops) (last stops) (inc (count stops)))
|
||||
(let [stops (->> (cc/uniform-spread (first stops) (last stops) (inc (count stops)))
|
||||
(mapv split-color-components))]
|
||||
(-> state
|
||||
(assoc :current-color (get stops editing-stop))
|
||||
@@ -925,7 +918,7 @@
|
||||
half-point-offset
|
||||
(+ from-offset (/ (- to-offset from-offset) 2))
|
||||
|
||||
new-stop (-> (clr/interpolate-gradient stops half-point-offset)
|
||||
new-stop (-> (cc/interpolate-gradient stops half-point-offset)
|
||||
(split-color-components))
|
||||
|
||||
stops (conj stops new-stop)
|
||||
@@ -945,24 +938,18 @@
|
||||
(update state :colorpicker
|
||||
(fn [state]
|
||||
(let [stops (:stops state)
|
||||
cap-stops?
|
||||
(or (features/active-feature? state "render-wasm/v1")
|
||||
(contains? cfg/flags :frontend-binary-fills))
|
||||
|
||||
can-add-stop?
|
||||
(or (not cap-stops?) (< (count stops) types.fills/MAX-GRADIENT-STOPS))]
|
||||
|
||||
(if can-add-stop?
|
||||
(let [new-stop (-> (clr/interpolate-gradient stops offset)
|
||||
(split-color-components))
|
||||
stops (conj stops new-stop)
|
||||
stops (into [] (sort-by :offset stops))
|
||||
editing-stop (d/index-of-pred stops #(= new-stop %))]
|
||||
(-> state
|
||||
(assoc :editing-stop editing-stop)
|
||||
(assoc :current-color (get stops editing-stop))
|
||||
(assoc :stops stops)))
|
||||
state)))))))
|
||||
cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills))
|
||||
can-add-stop? (or (not cap-stops?) (< (count stops) types.fill/MAX-GRADIENT-STOPS))]
|
||||
(if can-add-stop? (let [new-stop (-> (cc/interpolate-gradient stops offset)
|
||||
(split-color-components))
|
||||
stops (conj stops new-stop)
|
||||
stops (into [] (sort-by :offset stops))
|
||||
editing-stop (d/index-of-pred stops #(= new-stop %))]
|
||||
(-> state
|
||||
(assoc :editing-stop editing-stop)
|
||||
(assoc :current-color (get stops editing-stop))
|
||||
(assoc :stops stops)))
|
||||
state)))))))
|
||||
|
||||
(defn update-colorpicker-stops
|
||||
[stops]
|
||||
@@ -976,7 +963,7 @@
|
||||
(contains? cfg/flags :frontend-binary-fills))
|
||||
stops (mapv split-color-components
|
||||
(if cap-stops?
|
||||
(take types.fills/MAX-GRADIENT-STOPS stops)
|
||||
(take types.fill/MAX-GRADIENT-STOPS stops)
|
||||
stops))]
|
||||
(-> state
|
||||
(assoc :current-color (get stops stop))
|
||||
@@ -1093,7 +1080,11 @@
|
||||
(fn [state]
|
||||
(-> state
|
||||
(assoc :type :color)
|
||||
(dissoc :editing-stop :stops :gradient)))))))
|
||||
(dissoc :editing-stop :stops :gradient)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (update-colorpicker-color {} false)))))
|
||||
|
||||
(defn activate-colorpicker-gradient
|
||||
[type]
|
||||
@@ -1129,114 +1120,35 @@
|
||||
(assoc :type :image)
|
||||
(dissoc :editing-stop :stops :gradient)))))))
|
||||
|
||||
(defn- stroke->color-att
|
||||
[stroke file-id libraries]
|
||||
(let [ref-file (:stroke-color-ref-file stroke)
|
||||
ref-id (:stroke-color-ref-id stroke)
|
||||
|
||||
colors (-> libraries
|
||||
(get ref-file)
|
||||
(get :data)
|
||||
(ctl/get-colors))
|
||||
|
||||
is-shared? (contains? colors ref-id)
|
||||
has-color? (or (:stroke-color stroke)
|
||||
(:stroke-color-gradient stroke))
|
||||
attrs (cond-> (clr/stroke->color stroke)
|
||||
(not (or is-shared? (= ref-file file-id)))
|
||||
(dissoc :ref-id :ref-file))]
|
||||
|
||||
(when has-color?
|
||||
{:attrs attrs
|
||||
:prop :stroke
|
||||
:shape-id (:shape-id stroke)
|
||||
:index (:index stroke)})))
|
||||
|
||||
(defn- shadow->color-att
|
||||
[shadow file-id libraries]
|
||||
(let [color (get shadow :color)
|
||||
ref-file (get color :ref-file)
|
||||
ref-id (get color :ref-id)
|
||||
colors (-> libraries
|
||||
(get ref-id)
|
||||
(get :data)
|
||||
(ctl/get-colors))
|
||||
shared? (contains? colors ref-id)
|
||||
attrs (cond-> (clr/shadow->color shadow)
|
||||
(not (or shared? (= ref-file file-id)))
|
||||
(dissoc :ref-file :ref-id))]
|
||||
{:attrs attrs
|
||||
:prop :shadow
|
||||
:shape-id (:shape-id shadow)
|
||||
:index (:index shadow)}))
|
||||
|
||||
(defn- text->color-att
|
||||
[fill file-id libraries]
|
||||
(let [ref-file (:fill-color-ref-file fill)
|
||||
ref-id (:fill-color-ref-id fill)
|
||||
colors (-> libraries
|
||||
(get ref-id)
|
||||
(get :data)
|
||||
(ctl/get-colors))
|
||||
|
||||
shared? (contains? colors ref-id)
|
||||
attrs (cond-> (types.fills/fill->color fill)
|
||||
(not (or shared? (= ref-file file-id)))
|
||||
(dissoc :ref-file :ref-id))]
|
||||
|
||||
{:attrs attrs
|
||||
:prop :content
|
||||
:shape-id (:shape-id fill)
|
||||
:index (:index fill)}))
|
||||
|
||||
(defn- extract-text-colors
|
||||
[text file-id libraries]
|
||||
(let [treat-node
|
||||
(fn [node shape-id]
|
||||
(map-indexed #(assoc %2 :shape-id shape-id :index %1) node))]
|
||||
(->> (txt/node-seq txt/is-text-node? (:content text))
|
||||
(map :fills)
|
||||
(mapcat #(treat-node % (:id text)))
|
||||
(map #(text->color-att % file-id libraries)))))
|
||||
|
||||
(defn- fill->color-att
|
||||
[fill file-id libraries]
|
||||
(let [ref-file (:fill-color-ref-file fill)
|
||||
ref-id (:fill-color-ref-id fill)
|
||||
|
||||
colors (-> libraries
|
||||
(get ref-id)
|
||||
(get :data)
|
||||
(ctl/get-colors))
|
||||
shared? (contains? colors ref-id)
|
||||
has-color? (or (:fill-color fill)
|
||||
(:fill-color-gradient fill))
|
||||
attrs (cond-> (types.fills/fill->color fill)
|
||||
(not (or shared? (= ref-file file-id)))
|
||||
(dissoc :ref-file :ref-id))]
|
||||
|
||||
(when has-color?
|
||||
{:attrs attrs
|
||||
:prop :fill
|
||||
:shape-id (:shape-id fill)
|
||||
:index (:index fill)})))
|
||||
|
||||
(defn extract-all-colors
|
||||
[shapes file-id libraries]
|
||||
(reduce
|
||||
(fn [result shape]
|
||||
(let [fill-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:fills shape))
|
||||
stroke-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:strokes shape))
|
||||
shadow-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:shadow shape))]
|
||||
(if (= :text (:type shape))
|
||||
(-> result
|
||||
(into (keep #(stroke->color-att % file-id libraries)) stroke-obj)
|
||||
(into (map #(shadow->color-att % file-id libraries)) shadow-obj)
|
||||
(into (extract-text-colors shape file-id libraries)))
|
||||
|
||||
(-> result
|
||||
(into (keep #(fill->color-att % file-id libraries)) fill-obj)
|
||||
(into (keep #(stroke->color-att % file-id libraries)) stroke-obj)
|
||||
(into (map #(shadow->color-att % file-id libraries)) shadow-obj)))))
|
||||
[]
|
||||
shapes))
|
||||
(defn select-color
|
||||
[position add-color]
|
||||
;; FIXME: revisit
|
||||
(ptk/reify ::select-color
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (dsh/lookup-selected state)
|
||||
shapes (dsh/lookup-shapes state selected)
|
||||
shape (first shapes)
|
||||
fills (if (cfh/text-shape? shape)
|
||||
(:fills (dwt/current-text-values
|
||||
{:editor-state (dm/get-in state [:workspace-editor-state (:id shape)])
|
||||
:shape shape
|
||||
:attrs (conj txt/text-fill-attrs :fills)}))
|
||||
(:fills shape))
|
||||
fill (first fills)
|
||||
single? (and (= 1 (count selected))
|
||||
(= 1 (count fills)))
|
||||
data (if single?
|
||||
(d/without-nils {:color (:fill-color fill)
|
||||
:opacity (:fill-opacity fill)
|
||||
:gradient (:fill-color-gradient fill)})
|
||||
{:color "#406280"
|
||||
:opacity 1})]
|
||||
(rx/of (md/show :colorpicker
|
||||
{:x (:x position)
|
||||
:y (:y position)
|
||||
:on-accept add-color
|
||||
:data data
|
||||
:position :right})
|
||||
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
|
||||
:asset-type "color"}))))))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
(ns app.main.data.workspace.fix-deleted-fonts
|
||||
(:require
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.text :as txt]
|
||||
[app.main.data.changes :as dwc]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.fonts :as fonts]
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.library :as ctl]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.typography :as ctt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
@@ -196,7 +195,7 @@
|
||||
(if (str/empty? new-name)
|
||||
(rx/empty)
|
||||
(let [data (dsh/lookup-file-data state)
|
||||
color (-> (ctl/get-color data id)
|
||||
color (-> (ctc/get-color data id)
|
||||
(assoc :name new-name)
|
||||
(d/without-nils)
|
||||
(ctc/check-library-color))]
|
||||
@@ -963,8 +962,8 @@
|
||||
orig-shapes (when keep-touched? (cfh/get-children-with-self objects (:id shape)))
|
||||
|
||||
;; If the target parent is a grid layout we need to pass the target cell
|
||||
target-cell (when (ctsl/grid-layout? parent)
|
||||
(ctsl/get-cell-by-shape-id parent (:id shape)))
|
||||
target-cell (when (ctl/grid-layout? parent)
|
||||
(ctl/get-cell-by-shape-id parent (:id shape)))
|
||||
|
||||
index (find-shape-index objects (:parent-id shape) (:id shape))
|
||||
|
||||
@@ -1096,15 +1095,24 @@
|
||||
(when (seq (:redo-changes changes))
|
||||
(rx/of (dch/commit-changes changes)))
|
||||
(when-not (empty? updated-frames)
|
||||
(rx/merge
|
||||
(rx/of (ptk/data-event :layout/update {:ids (map :id updated-frames) :undo-group undo-group}))
|
||||
(->> (rx/from updated-frames)
|
||||
(rx/mapcat
|
||||
(fn [shape]
|
||||
(rx/of
|
||||
(dwt/clear-thumbnail file-id (:page-id shape) (:id shape) "frame")
|
||||
(when-not (= (:frame-id shape) uuid/zero)
|
||||
(dwt/clear-thumbnail file-id (:page-id shape) (:frame-id shape) "frame"))))))))
|
||||
(let [frames-by-page (->> updated-frames
|
||||
(group-by :page-id))]
|
||||
(rx/merge
|
||||
;; Emit one layout/update event for each page
|
||||
(rx/from
|
||||
(map (fn [[page-id frames]]
|
||||
(ptk/data-event :layout/update
|
||||
{:page-id page-id
|
||||
:ids (map :id frames)
|
||||
:undo-group undo-group}))
|
||||
frames-by-page))
|
||||
(->> (rx/from updated-frames)
|
||||
(rx/mapcat
|
||||
(fn [shape]
|
||||
(rx/of
|
||||
(dwt/clear-thumbnail file-id (:page-id shape) (:id shape) "frame")
|
||||
(when-not (= (:frame-id shape) uuid/zero)
|
||||
(dwt/clear-thumbnail file-id (:page-id shape) (:frame-id shape) "frame")))))))))
|
||||
|
||||
(when (not= file-id library-id)
|
||||
;; When we have just updated the library file, give some time for the
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
[app.common.media :as media]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
@@ -64,22 +63,18 @@
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [name width height id mtype]} image
|
||||
|
||||
fills (types.fills/create
|
||||
{:fill-opacity 1
|
||||
:fill-image {:width width
|
||||
:height height
|
||||
:mtype mtype
|
||||
:id id
|
||||
:keep-aspect-ratio true}})
|
||||
|
||||
shape {:name name
|
||||
:width width
|
||||
:height height
|
||||
:x (mth/round (- x (/ width 2)))
|
||||
:y (mth/round (- y (/ height 2)))
|
||||
:fills fills}]
|
||||
|
||||
:fills [{:fill-opacity 1
|
||||
:fill-image {:name name
|
||||
:width width
|
||||
:height height
|
||||
:mtype mtype
|
||||
:id id
|
||||
:keep-aspect-ratio true}}]}]
|
||||
(rx/of (dwsh/create-and-add-shape :rect x y shape))))))
|
||||
|
||||
(defn svg-uploaded
|
||||
@@ -304,7 +299,7 @@
|
||||
:name (:name root-svg-shape)
|
||||
:frame-id uuid/zero
|
||||
:parent-id uuid/zero
|
||||
:fills (types.fills/create)})
|
||||
:fills []})
|
||||
|
||||
root-svg-shape
|
||||
(-> root-svg-shape
|
||||
@@ -340,21 +335,19 @@
|
||||
:frame-id uuid/zero
|
||||
:parent-id uuid/zero})
|
||||
|
||||
img-fills (types.fills/create
|
||||
{:fill-opacity 1
|
||||
:fill-image {:id id
|
||||
:width width
|
||||
:height height
|
||||
:mtype mtype
|
||||
:keep-aspect-ratio true}})
|
||||
|
||||
img-shape (cts/setup-shape
|
||||
{:type :rect
|
||||
:x (:x pos)
|
||||
:y (:y pos)
|
||||
:width width
|
||||
:height height
|
||||
:fills img-fills
|
||||
:fills [{:fill-opacity 1
|
||||
:fill-image {:name name
|
||||
:id id
|
||||
:width width
|
||||
:height height
|
||||
:mtype mtype
|
||||
:keep-aspect-ratio true}}]
|
||||
:name name
|
||||
:frame-id (:id frame-shape)
|
||||
:parent-id (:id frame-shape)})]
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
(ns app.main.data.workspace.shape-layout
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
@@ -15,7 +16,6 @@
|
||||
[app.common.geom.shapes.flex-layout :as flex]
|
||||
[app.common.geom.shapes.grid-layout :as grid]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
@@ -131,11 +131,12 @@
|
||||
;; they are process together. It will get a better performance.
|
||||
(rx/buffer-time 100)
|
||||
(rx/filter #(d/not-empty? %))
|
||||
(rx/map
|
||||
(rx/mapcat
|
||||
(fn [data]
|
||||
(let [page-id (->> data (keep :page-id) first)
|
||||
ids (reduce #(into %1 (:ids %2)) #{} data)]
|
||||
(update-layout-positions {:page-id page-id :ids ids}))))
|
||||
(->> (group-by :page-id data)
|
||||
(map (fn [[page-id items]]
|
||||
(let [ids (reduce #(into %1 (:ids %2)) #{} items)]
|
||||
(update-layout-positions {:page-id page-id :ids ids})))))))
|
||||
(rx/take-until stopper))))))
|
||||
|
||||
(defn finalize-shape-layout
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.text :as txt]
|
||||
[app.main.data.shortcuts :as ds]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.fill :as types.fill]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
@@ -196,8 +196,8 @@
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [text-state (some->> content ted/import-content)
|
||||
attrs (merge (txt/get-default-text-attrs)
|
||||
(get-in state [:workspace-global :default-font]))
|
||||
attrs (d/merge txt/default-text-attrs
|
||||
(get-in state [:workspace-global :default-font]))
|
||||
editor (cond-> (ted/create-editor-state text-state decorator)
|
||||
(and (nil? content) (some? attrs))
|
||||
(ted/update-editor-current-block-data attrs))]
|
||||
@@ -237,9 +237,7 @@
|
||||
|
||||
(defn- to-new-fills
|
||||
[data]
|
||||
;; FIXME: maybe export this as a specific helper ?
|
||||
(types.fills/create
|
||||
(d/without-nils (select-keys data types.fills/fill-attrs))))
|
||||
[(d/without-nils (select-keys data types.fill/fill-attrs))])
|
||||
|
||||
(defn- shape-current-values
|
||||
[shape pred attrs]
|
||||
@@ -247,21 +245,17 @@
|
||||
nodes (->> (txt/node-seq pred root)
|
||||
(map (fn [node]
|
||||
(if (txt/is-text-node? node)
|
||||
(let [default-text-attrs
|
||||
(txt/get-default-text-attrs)
|
||||
|
||||
fills
|
||||
(let [fills
|
||||
(cond
|
||||
(types.fills/has-valid-fill-attrs? node)
|
||||
(types.fill/has-valid-fill-attrs? node)
|
||||
(to-new-fills node)
|
||||
|
||||
(some? (:fills node))
|
||||
(:fills node)
|
||||
|
||||
:else
|
||||
(:fills default-text-attrs))]
|
||||
|
||||
(-> (merge default-text-attrs node)
|
||||
(:fills txt/default-text-attrs))]
|
||||
(-> (merge txt/default-text-attrs node)
|
||||
(assoc :fills fills)))
|
||||
node))))]
|
||||
(attrs/get-attrs-multi nodes attrs)))
|
||||
@@ -296,9 +290,7 @@
|
||||
[{:keys [editor-state attrs]}]
|
||||
(let [result (-> (ted/get-editor-current-inline-styles editor-state)
|
||||
(select-keys attrs))
|
||||
result (if (empty? result)
|
||||
(txt/get-default-text-attrs)
|
||||
result)]
|
||||
result (if (empty? result) txt/default-text-attrs result)]
|
||||
result))
|
||||
|
||||
(defn current-text-values
|
||||
@@ -474,24 +466,24 @@
|
||||
|
||||
(defn migrate-node
|
||||
[node]
|
||||
(let [color-attrs (not-empty (select-keys node types.fills/fill-attrs))]
|
||||
(let [color-attrs (not-empty (select-keys node types.fill/fill-attrs))]
|
||||
(cond-> node
|
||||
(nil? (:fills node))
|
||||
(assoc :fills (types.fills/create))
|
||||
(assoc :fills [])
|
||||
|
||||
;; Migrate old colors and remove the old fromat
|
||||
color-attrs
|
||||
(-> (dissoc :fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient)
|
||||
(update :fills types.fills/update conj color-attrs))
|
||||
(update :fills conj color-attrs))
|
||||
|
||||
;; We don't have the fills attribute. It's an old text without color
|
||||
;; so need to be black
|
||||
(and (nil? (:fills node)) (empty? color-attrs))
|
||||
(assoc :fills (txt/get-default-text-fills))
|
||||
(assoc :fills (:fills txt/default-text-attrs))
|
||||
|
||||
;; Remove duplicates from the fills
|
||||
:always
|
||||
(update :fills types.fills/update distinct))))
|
||||
(update :fills (comp vec distinct)))))
|
||||
|
||||
(defn migrate-content
|
||||
[content]
|
||||
@@ -913,9 +905,9 @@
|
||||
(ptk/reify ::v2-update-text-editor-styles
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [merged-styles (merge (txt/get-default-text-attrs)
|
||||
(get-in state [:workspace-global :default-font])
|
||||
new-styles)]
|
||||
(let [merged-styles (d/merge txt/default-text-attrs
|
||||
(get-in state [:workspace-global :default-font])
|
||||
new-styles)]
|
||||
(update-in state [:workspace-v2-editor-state id] (fnil merge {}) merged-styles)))))
|
||||
|
||||
(defn v2-update-text-shape-position-data
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.stroke :as cts]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.types.typography :as cty]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
@@ -25,7 +25,6 @@
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.transforms :as dwtr]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.store :as st]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.set :as set]
|
||||
@@ -33,88 +32,6 @@
|
||||
|
||||
(declare token-properties)
|
||||
|
||||
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
|
||||
|
||||
(defn apply-token
|
||||
"Apply `attributes` that match `token` for `shape-ids`.
|
||||
|
||||
Optionally remove attributes from `attributes-to-remove`,
|
||||
this is useful for applying a single attribute from an attributes set
|
||||
while removing other applied tokens from this set."
|
||||
[{:keys [attributes attributes-to-remove token shape-ids on-update-shape]}]
|
||||
(ptk/reify ::apply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
;; We do not allow to apply tokens while text editor is open.
|
||||
(when (empty? (get state :workspace-editor-state))
|
||||
(when-let [tokens (some-> (dsh/lookup-file-data state)
|
||||
(get :tokens-lib)
|
||||
(ctob/get-tokens-in-active-sets))]
|
||||
(->> (sd/resolve-tokens tokens)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [undo-id (js/Symbol)
|
||||
objects (dsh/lookup-page-objects state)
|
||||
|
||||
shape-ids (or (->> (select-keys objects shape-ids)
|
||||
(filter (fn [[_ shape]]
|
||||
(ctt/any-appliable-attr? attributes (:type shape))))
|
||||
(keys))
|
||||
[])
|
||||
|
||||
resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value])
|
||||
tokenized-attributes (cft/attributes-map attributes token)]
|
||||
(rx/of
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "apply-tokens"}))
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dwsh/update-shapes shape-ids (fn [shape]
|
||||
(cond-> shape
|
||||
attributes-to-remove
|
||||
(update :applied-tokens #(apply (partial dissoc %) attributes-to-remove))
|
||||
:always
|
||||
(update :applied-tokens merge tokenized-attributes))))
|
||||
(when on-update-shape
|
||||
(on-update-shape resolved-value shape-ids attributes))
|
||||
(dwu/commit-undo-transaction undo-id)))))))))))
|
||||
|
||||
(defn unapply-token
|
||||
"Removes `attributes` that match `token` for `shape-ids`.
|
||||
|
||||
Doesn't update shape attributes."
|
||||
[{:keys [attributes token shape-ids] :as _props}]
|
||||
(ptk/reify ::unapply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(let [remove-token #(when % (cft/remove-attributes-for-token attributes token %))]
|
||||
(dwsh/update-shapes
|
||||
shape-ids
|
||||
(fn [shape]
|
||||
(update shape :applied-tokens remove-token))))))))
|
||||
|
||||
(defn toggle-token
|
||||
[{:keys [token shapes]}]
|
||||
(ptk/reify ::on-toggle-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [attributes all-attributes on-update-shape]}
|
||||
(get token-properties (:type token))
|
||||
|
||||
unapply-tokens?
|
||||
(cft/shapes-token-applied? token shapes (or all-attributes attributes))
|
||||
|
||||
shape-ids (map :id shapes)]
|
||||
(if unapply-tokens?
|
||||
(rx/of
|
||||
(unapply-token {:attributes (or all-attributes attributes)
|
||||
:token token
|
||||
:shape-ids shape-ids}))
|
||||
(rx/of
|
||||
(apply-token {:attributes attributes
|
||||
:token token
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape on-update-shape})))))))
|
||||
|
||||
;; Events to update the value of attributes with applied tokens ---------------------------------------------------------
|
||||
|
||||
;; (note that dwsh/update-shapes function returns an event)
|
||||
@@ -175,8 +92,10 @@
|
||||
(when (number? value)
|
||||
(dwsh/update-shapes shape-ids
|
||||
(fn [shape]
|
||||
(when (seq (:strokes shape))
|
||||
(assoc-in shape [:strokes 0 :stroke-width] value)))
|
||||
(if (seq (:strokes shape))
|
||||
(assoc-in shape [:strokes 0 :stroke-width] value)
|
||||
(let [stroke (assoc cts/default-stroke :stroke-width value)]
|
||||
(assoc shape :strokes [stroke]))))
|
||||
{:reg-objects? true
|
||||
:ignore-touched true
|
||||
:page-id page-id
|
||||
@@ -334,73 +253,157 @@
|
||||
(dwsl/update-layout-child shape-ids props {:ignore-touched true
|
||||
:page-id page-id}))))))))
|
||||
|
||||
(defn generate-text-shape-update
|
||||
[txt-attrs shape-ids page-id]
|
||||
(let [update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))
|
||||
update-fn (fn [node _]
|
||||
(-> node
|
||||
(d/txt-merge txt-attrs)
|
||||
(cty/remove-typography-from-node)))]
|
||||
(dwsh/update-shapes shape-ids
|
||||
#(txt/update-text-content % update-node? update-fn nil)
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))
|
||||
|
||||
(defn update-line-height
|
||||
([value shape-ids attributes] (update-line-height value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id]
|
||||
(let [update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))
|
||||
update-fn (fn [node _]
|
||||
(-> node
|
||||
(d/txt-merge {:line-height value})
|
||||
(cty/remove-typography-from-node)))]
|
||||
(when (number? value)
|
||||
(dwsh/update-shapes shape-ids
|
||||
#(txt/update-text-content % update-node? update-fn nil)
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))))
|
||||
(when (number? value)
|
||||
(generate-text-shape-update {:line-height value} shape-ids page-id))))
|
||||
|
||||
(defn update-letter-spacing
|
||||
([value shape-ids attributes] (update-letter-spacing value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id]
|
||||
(let [update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))
|
||||
update-fn (fn [node _]
|
||||
(-> node
|
||||
(d/txt-merge {:letter-spacing (str value)})
|
||||
(cty/remove-typography-from-node)))]
|
||||
(when (number? value)
|
||||
(dwsh/update-shapes shape-ids
|
||||
#(txt/update-text-content % update-node? update-fn nil)
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))))
|
||||
|
||||
(defn update-font-family
|
||||
([value shape-ids attributes] (update-font-family value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id]
|
||||
(let [font-family (first value)
|
||||
font (some-> font-family
|
||||
(fonts/find-font-family))
|
||||
text-attrs (if font
|
||||
{:font-id (:id font)
|
||||
:font-family (:family font)}
|
||||
{:font-id (str uuid/zero)
|
||||
:font-family font-family})
|
||||
update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))]
|
||||
(when text-attrs
|
||||
(dwsh/update-shapes shape-ids
|
||||
#(txt/update-text-content % update-node? d/txt-merge text-attrs)
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))))
|
||||
(when (number? value)
|
||||
(generate-text-shape-update {:letter-spacing (str value)} shape-ids page-id))))
|
||||
|
||||
(defn update-font-size
|
||||
([value shape-ids attributes] (update-font-size value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id]
|
||||
(let [update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))
|
||||
update-fn (fn [node _]
|
||||
(-> node
|
||||
(d/txt-merge {:font-size (str value)})
|
||||
(cty/remove-typography-from-node)))]
|
||||
(when (number? value)
|
||||
(dwsh/update-shapes shape-ids
|
||||
#(txt/update-text-content % update-node? update-fn nil)
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))))
|
||||
(when (number? value)
|
||||
(generate-text-shape-update {:font-size (str value)} shape-ids page-id))))
|
||||
|
||||
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
|
||||
|
||||
(defn apply-token
|
||||
"Apply `attributes` that match `token` for `shape-ids`.
|
||||
|
||||
Optionally remove attributes from `attributes-to-remove`,
|
||||
this is useful for applying a single attribute from an attributes set
|
||||
while removing other applied tokens from this set."
|
||||
[{:keys [attributes attributes-to-remove token shape-ids on-update-shape]}]
|
||||
(ptk/reify ::apply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
;; We do not allow to apply tokens while text editor is open.
|
||||
(when (empty? (get state :workspace-editor-state))
|
||||
(when-let [tokens (some-> (dsh/lookup-file-data state)
|
||||
(get :tokens-lib)
|
||||
(ctob/get-tokens-in-active-sets))]
|
||||
(->> (sd/resolve-tokens tokens)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [undo-id (js/Symbol)
|
||||
objects (dsh/lookup-page-objects state)
|
||||
selected-shapes (select-keys objects shape-ids)
|
||||
|
||||
shape-ids (or (->> selected-shapes
|
||||
(filter (fn [[_ shape]]
|
||||
(or
|
||||
(and (ctsl/any-layout-immediate-child? objects shape)
|
||||
(some ctt/spacing-margin-keys attributes))
|
||||
(ctt/any-appliable-attr? attributes (:type shape)))))
|
||||
(keys))
|
||||
[])
|
||||
|
||||
resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value])
|
||||
tokenized-attributes (cft/attributes-map attributes token)
|
||||
type (:type token)]
|
||||
(rx/of
|
||||
(st/emit! (ev/event {::ev/name "apply-tokens"
|
||||
:type type
|
||||
:applyed-to attributes}))
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dwsh/update-shapes shape-ids (fn [shape]
|
||||
(cond-> shape
|
||||
attributes-to-remove
|
||||
(update :applied-tokens #(apply (partial dissoc %) attributes-to-remove))
|
||||
:always
|
||||
(update :applied-tokens merge tokenized-attributes))))
|
||||
(when on-update-shape
|
||||
(on-update-shape resolved-value shape-ids attributes))
|
||||
(dwu/commit-undo-transaction undo-id)))))))))))
|
||||
|
||||
(defn apply-spacing-token
|
||||
"Handles edge-case for spacing token when applying token via toggle button.
|
||||
Splits out `shape-ids` into seperate default actions:
|
||||
- Layouts take the `default` update function
|
||||
- Shapes inside layout will only take margin"
|
||||
[{:keys [token shapes]}]
|
||||
(ptk/reify ::apply-spacing-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
|
||||
{:keys [attributes on-update-shape]}
|
||||
(get token-properties (:type token))
|
||||
|
||||
{:keys [other frame-children]}
|
||||
(group-by #(if (ctsl/any-layout-immediate-child? objects %) :frame-children :other) shapes)]
|
||||
|
||||
(rx/of
|
||||
(apply-token {:attributes attributes
|
||||
:token token
|
||||
:shape-ids (map :id other)
|
||||
:on-update-shape on-update-shape})
|
||||
(apply-token {:attributes ctt/spacing-margin-keys
|
||||
:token token
|
||||
:shape-ids (map :id frame-children)
|
||||
:on-update-shape update-layout-item-margin}))))))
|
||||
|
||||
(defn unapply-token
|
||||
"Removes `attributes` that match `token` for `shape-ids`.
|
||||
|
||||
Doesn't update shape attributes."
|
||||
[{:keys [attributes token shape-ids] :as _props}]
|
||||
(ptk/reify ::unapply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(let [remove-token #(when % (cft/remove-attributes-for-token attributes token %))]
|
||||
(dwsh/update-shapes
|
||||
shape-ids
|
||||
(fn [shape]
|
||||
(update shape :applied-tokens remove-token))))))))
|
||||
|
||||
(defn toggle-token
|
||||
[{:keys [token shapes]}]
|
||||
(ptk/reify ::on-toggle-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [attributes all-attributes on-update-shape]}
|
||||
(get token-properties (:type token))
|
||||
|
||||
unapply-tokens?
|
||||
(cft/shapes-token-applied? token shapes (or all-attributes attributes))
|
||||
|
||||
shape-ids (map :id shapes)]
|
||||
(if unapply-tokens?
|
||||
(rx/of
|
||||
(unapply-token {:attributes (or all-attributes attributes)
|
||||
:token token
|
||||
:shape-ids shape-ids}))
|
||||
(rx/of
|
||||
(case (:type token)
|
||||
:spacing
|
||||
(apply-spacing-token {:token token
|
||||
:shapes shapes})
|
||||
(apply-token {:attributes attributes
|
||||
:token token
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape on-update-shape}))))))))
|
||||
|
||||
;; Map token types to different properties used along the cokde ---------------------------------------------
|
||||
|
||||
@@ -443,14 +446,6 @@
|
||||
:fields [{:label "Letter Spacing"
|
||||
:key :letter-spacing}]}}
|
||||
|
||||
:font-family
|
||||
{:title "Font Family"
|
||||
:attributes ctt/font-family-keys
|
||||
:on-update-shape update-font-family
|
||||
:modal {:key :tokens/font-family
|
||||
:fields [{:label "Font Family"
|
||||
:key :font-family}]}}
|
||||
|
||||
:stroke-width
|
||||
{:title "Stroke Width"
|
||||
:attributes ctt/stroke-width-keys
|
||||
@@ -474,6 +469,7 @@
|
||||
ctt/spacing-keys
|
||||
ctt/sizing-keys
|
||||
ctt/border-radius-keys
|
||||
ctt/axis-keys
|
||||
ctt/stroke-width-keys)
|
||||
:on-update-shape update-shape-dimensions
|
||||
:modal {:key :tokens/dimensions
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#{:line-height} dwta/update-line-height
|
||||
#{:font-size} dwta/update-font-size
|
||||
#{:letter-spacing} dwta/update-letter-spacing
|
||||
#{:font-family} dwta/update-font-family
|
||||
#{:x :y} dwta/update-shape-position
|
||||
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding
|
||||
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
(ns app.main.data.workspace.variants
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.helpers :as cfh]
|
||||
@@ -13,7 +14,6 @@
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logic.variant-properties :as clvp]
|
||||
[app.common.logic.variants :as clv]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.text :as txt]
|
||||
[app.config :as cf]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as globals]
|
||||
@@ -78,15 +78,6 @@
|
||||
data))
|
||||
(vals @fontsdb)))
|
||||
|
||||
(defn find-font-family
|
||||
"Case insensitive lookup of font-family."
|
||||
[family]
|
||||
(let [family' (str/lower family)]
|
||||
(d/seek
|
||||
(fn [{:keys [family]}]
|
||||
(= family' (str/lower family)))
|
||||
(vals @fontsdb))))
|
||||
|
||||
(defn resolve-variants
|
||||
[id]
|
||||
(get-in @fontsdb [id :variants]))
|
||||
@@ -304,7 +295,7 @@
|
||||
(let [current-font
|
||||
(if (some? font-id)
|
||||
(select-keys node [:font-id :font-variant-id])
|
||||
(select-keys txt/default-typography [:font-id :font-variant-id]))]
|
||||
(select-keys txt/default-text-attrs [:font-id :font-variant-id]))]
|
||||
(conj result current-font)))
|
||||
#{})))
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
(:require
|
||||
["react-dom/server" :as rds]
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
@@ -22,7 +23,6 @@
|
||||
[app.common.geom.shapes.bounds :as gsb]
|
||||
[app.common.logging :as l]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
(ns app.main.ui.components.color-input
|
||||
(:require
|
||||
[app.common.colors :as cc]
|
||||
[app.common.data :as d]
|
||||
[app.common.types.color :as cc]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as globals]
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
[app.util.dom.dnd :as dnd]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as ts]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
@@ -196,7 +195,7 @@
|
||||
:on-edit on-edit-open
|
||||
:on-menu-close on-menu-close}]]))
|
||||
|
||||
(mf/defc sidebar-search
|
||||
(mf/defc sidebar-search*
|
||||
[{:keys [search-term team-id] :as props}]
|
||||
(let [search-term (or search-term "")
|
||||
focused? (mf/use-state false)
|
||||
@@ -586,8 +585,8 @@
|
||||
:data-testid "delete-team"}
|
||||
(tr "dashboard.delete-team")])]))
|
||||
|
||||
(mf/defc sidebar-team-switch
|
||||
[{:keys [team profile] :as props}]
|
||||
(mf/defc sidebar-team-switch*
|
||||
[{:keys [team profile]}]
|
||||
(let [teams (mf/deref refs/teams)
|
||||
teams-without-default (into {} (filter (fn [[_ v]] (= false (:is-default v))) teams))
|
||||
team-ids (map #(str "teams-selector-" %) (keys teams-without-default))
|
||||
@@ -606,10 +605,6 @@
|
||||
(when (get-in team [:permissions :is-owner])
|
||||
"teams-options-delete-team")]
|
||||
|
||||
|
||||
;; _ (prn "--------------- sidebar-team-switch")
|
||||
;; _ (app.common.pprint/pprint teams)
|
||||
|
||||
handle-show-team-click
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
@@ -716,8 +711,7 @@
|
||||
:profile profile}]]]))
|
||||
|
||||
(mf/defc sidebar-content*
|
||||
{::mf/private true
|
||||
::mf/props :obj}
|
||||
{::mf/private true}
|
||||
[{:keys [projects profile section team project search-term default-project] :as props}]
|
||||
(let [default-project-id
|
||||
(get default-project :id)
|
||||
@@ -730,6 +724,7 @@
|
||||
drafts? (and (= section :dashboard-files)
|
||||
(= (:id project) default-project-id))
|
||||
container (mf/use-ref nil)
|
||||
|
||||
overflow* (mf/use-state false)
|
||||
overflow? (deref overflow*)
|
||||
|
||||
@@ -740,13 +735,14 @@
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
(fn []
|
||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(when-let [projects-title (dom/get-element "dashboard-projects-title")]
|
||||
(dom/set-attribute! projects-title "tabindex" "0")
|
||||
(dom/focus! projects-title)
|
||||
(dom/set-attribute! projects-title "tabindex" "-1")))))))
|
||||
(st/emit!
|
||||
(dcm/go-to-dashboard-recent :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(when-let [projects-title (dom/get-element "dashboard-projects-title")]
|
||||
(dom/set-attribute! projects-title "tabindex" "0")
|
||||
(dom/focus! projects-title)
|
||||
(dom/set-attribute! projects-title "tabindex" "-1")))))))
|
||||
|
||||
go-fonts
|
||||
(mf/use-fn
|
||||
@@ -756,14 +752,17 @@
|
||||
go-fonts-with-key
|
||||
(mf/use-fn
|
||||
(mf/deps team)
|
||||
#(st/emit! (dcm/go-to-dashboard-fonts :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [font-title (dom/get-element "dashboard-fonts-title")]
|
||||
(when font-title
|
||||
(dom/set-attribute! font-title "tabindex" "0")
|
||||
(dom/focus! font-title)
|
||||
(dom/set-attribute! font-title "tabindex" "-1")))))))
|
||||
(fn []
|
||||
(st/emit!
|
||||
(dcm/go-to-dashboard-fonts :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [font-title (dom/get-element "dashboard-fonts-title")]
|
||||
(when font-title
|
||||
(dom/set-attribute! font-title "tabindex" "0")
|
||||
(dom/focus! font-title)
|
||||
(dom/set-attribute! font-title "tabindex" "-1"))))))))
|
||||
|
||||
go-drafts
|
||||
(mf/use-fn
|
||||
(mf/deps team-id default-project-id)
|
||||
@@ -785,39 +784,43 @@
|
||||
go-libs
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
#(st/emit! (dcm/go-to-dashboard-libraries :team-id team-id)))
|
||||
(fn [] (st/emit! (dcm/go-to-dashboard-libraries :team-id team-id))))
|
||||
|
||||
go-libs-with-key
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
#(st/emit! (dcm/go-to-dashboard-libraries :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [libs-title (dom/get-element "dashboard-libraries-title")]
|
||||
(when libs-title
|
||||
(dom/set-attribute! libs-title "tabindex" "0")
|
||||
(dom/focus! libs-title)
|
||||
(dom/set-attribute! libs-title "tabindex" "-1")))))))
|
||||
pinned-projects
|
||||
(->> projects
|
||||
(remove :is-default)
|
||||
(filter :is-pinned))]
|
||||
(fn []
|
||||
(st/emit!
|
||||
(dcm/go-to-dashboard-libraries :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [libs-title (dom/get-element "dashboard-libraries-title")]
|
||||
(when libs-title
|
||||
(dom/set-attribute! libs-title "tabindex" "0")
|
||||
(dom/focus! libs-title)
|
||||
(dom/set-attribute! libs-title "tabindex" "-1"))))))))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps pinned-projects)
|
||||
(fn []
|
||||
(let [dom (mf/ref-val container)
|
||||
client-height (obj/get dom "clientHeight")
|
||||
scroll-height (obj/get dom "scrollHeight")]
|
||||
(reset! overflow* (> scroll-height client-height)))))
|
||||
pinned-projects
|
||||
(mf/with-memo [projects]
|
||||
(->> projects
|
||||
(remove :is-default)
|
||||
(filter :is-pinned)
|
||||
(sort-by :name)
|
||||
(not-empty)))]
|
||||
|
||||
(mf/with-layout-effect [pinned-projects]
|
||||
(let [node (mf/ref-val container)
|
||||
client-height (.-clientHeight ^js node)
|
||||
scroll-height (.-scrollHeight ^js node)]
|
||||
(reset! overflow* (> scroll-height client-height))))
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css-case :sidebar-content true)
|
||||
:ref container}
|
||||
[:& sidebar-team-switch {:team team :profile profile}]
|
||||
[:> sidebar-team-switch* {:team team :profile profile}]
|
||||
|
||||
[:& sidebar-search {:search-term search-term
|
||||
:team-id (:id team)}]
|
||||
[:> sidebar-search* {:search-term search-term
|
||||
:team-id (:id team)}]
|
||||
|
||||
[:div {:class (stl/css :sidebar-content-section)}
|
||||
[:ul {:class (stl/css :sidebar-nav)}
|
||||
@@ -861,7 +864,7 @@
|
||||
:data-testid "pinned-projects"}
|
||||
[:div {:class (stl/css :sidebar-section-title)}
|
||||
(tr "labels.pinned-projects")]
|
||||
(if (seq pinned-projects)
|
||||
(if (some? pinned-projects)
|
||||
[:ul {:class (stl/css :sidebar-nav :pinned-projects)}
|
||||
(for [item pinned-projects]
|
||||
[:& sidebar-project
|
||||
@@ -876,7 +879,6 @@
|
||||
[:div {:class (stl/css-case :separator true :overflow-separator overflow?)}]]))
|
||||
|
||||
(mf/defc profile-section*
|
||||
{::mf/props :obj}
|
||||
[{:keys [profile team]}]
|
||||
(let [show* (mf/use-state false)
|
||||
show (deref show*)
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
[:> cta-power-up*
|
||||
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
|
||||
:top-description (tr "subscription.dashboard.power-up.unlimited-plan")
|
||||
:bottom-description (tr "subscription.dashboard.power-up.unlimited.bottom", subscription-href)
|
||||
:bottom-description (tr "subscription.dashboard.power-up.unlimited.bottom-text", subscription-href)
|
||||
:has-dropdown true}])
|
||||
|
||||
"enterprise"
|
||||
@@ -125,7 +125,11 @@
|
||||
|
||||
(mf/defc menu-team-icon*
|
||||
[{:keys [subscription-type]}]
|
||||
[:span {:class (stl/css :subscription-icon) :data-testid "subscription-icon"}
|
||||
[:span {:class (stl/css :subscription-icon)
|
||||
:title (if (= subscription-type "unlimited")
|
||||
(tr "subscription.dashboard.power-up.unlimited-plan")
|
||||
(tr "subscription.dashboard.power-up.enterprise-plan"))
|
||||
:data-testid "subscription-icon"}
|
||||
(case subscription-type
|
||||
"unlimited" i/character-u
|
||||
"enterprise" i/character-e)])
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
[:class {:optional true} :string]
|
||||
[:id :string]
|
||||
[:icon {:optional true}
|
||||
[:maybe [:and :string [:fn #(contains? icon-list %)]]]]
|
||||
[:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:has-hint {:optional true} :boolean]
|
||||
[:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]
|
||||
[:type {:optional true} :string]
|
||||
|
||||
@@ -154,12 +154,17 @@
|
||||
the dom with the result."
|
||||
[tooltip placement origin-brect offset]
|
||||
(show-popover tooltip)
|
||||
(let [tooltip-brect (dom/get-bounding-rect tooltip)
|
||||
(let [saved-height (dom/get-data tooltip "height")
|
||||
saved-width (dom/get-data tooltip "width")
|
||||
tooltip-brect (dom/get-bounding-rect tooltip)
|
||||
tooltip-brect (assoc tooltip-brect :height (or saved-height (:height tooltip-brect)) :width (or saved-width (:width tooltip-brect)))
|
||||
window-size (dom/get-window-size)]
|
||||
(when-let [[placement placement-rect] (find-matching-placement placement tooltip-brect origin-brect window-size offset)]
|
||||
(let [height (if (or (= placement "right") (= placement "left"))
|
||||
(- (:height placement-rect) arrow-height)
|
||||
(:height placement-rect))]
|
||||
(dom/set-data! tooltip "height" (:height tooltip-brect))
|
||||
(dom/set-data! tooltip "width" (:width tooltip-brect))
|
||||
(dom/set-css-property! tooltip "block-size" (dm/str height "px"))
|
||||
(dom/set-css-property! tooltip "inset-block-start" (dm/str (:top placement-rect) "px"))
|
||||
(dom/set-css-property! tooltip "inset-inline-start" (dm/str (:left placement-rect) "px")))
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"Assets exportation common components."
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as clr]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.color :as clr]
|
||||
[app.main.data.exports.assets :as de]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
|
||||
@@ -27,13 +27,12 @@
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def type->options
|
||||
{:multiple [:fill :stroke :image :text :shadow :blur :layout-element]
|
||||
{:multiple [:fill :stroke :text :shadow :blur :layout-element]
|
||||
:frame [:visibility :geometry :fill :stroke :shadow :blur :layout :layout-element]
|
||||
:group [:visibility :geometry :svg :layout-element]
|
||||
:rect [:visibility :geometry :fill :stroke :shadow :blur :svg :layout-element]
|
||||
:circle [:visibility :geometry :fill :stroke :shadow :blur :svg :layout-element]
|
||||
:path [:visibility :geometry :fill :stroke :shadow :blur :svg :layout-element]
|
||||
:image [:visibility :image :geometry :fill :stroke :shadow :blur :svg :layout-element]
|
||||
:text [:visibility :geometry :text :shadow :blur :stroke :layout-element]
|
||||
:variant [:variant :geometry :fill :stroke :shadow :blur :layout :layout-element]})
|
||||
|
||||
|
||||