mirror of
https://github.com/penpot/penpot.git
synced 2026-01-24 14:21:26 -05:00
Compare commits
51 Commits
eva-replac
...
nitrate-mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfe80bd94c | ||
|
|
760a4bf6b9 | ||
|
|
ae3bb328e4 | ||
|
|
ea2e16be37 | ||
|
|
8784d57f02 | ||
|
|
e289095f63 | ||
|
|
75e599645a | ||
|
|
d53edea8ae | ||
|
|
b0015216be | ||
|
|
969ebc3ee6 | ||
|
|
c5b8ccc569 | ||
|
|
2f1416ccb7 | ||
|
|
ca77a138b2 | ||
|
|
76cf9f03d6 | ||
|
|
35fbbfeb80 | ||
|
|
71d61c7142 | ||
|
|
a4ee898292 | ||
|
|
387569857f | ||
|
|
c1335961b4 | ||
|
|
b70eb768e0 | ||
|
|
b8c70be9a2 | ||
|
|
525adcfcbe | ||
|
|
7cce4c6532 | ||
|
|
a3fdd8b691 | ||
|
|
b6a9579c98 | ||
|
|
4397ede5c1 | ||
|
|
ff25df0457 | ||
|
|
8c7fd0af4b | ||
|
|
cf46051f56 | ||
|
|
079b3fbfad | ||
|
|
299f628951 | ||
|
|
32d0fe6463 | ||
|
|
6393330ee1 | ||
|
|
8252bc485e | ||
|
|
cecd3d4a90 | ||
|
|
1c2c0987f5 | ||
|
|
0418147e74 | ||
|
|
47775a9e2c | ||
|
|
8191d04114 | ||
|
|
b7c2d9a079 | ||
|
|
aeb34a6f64 | ||
|
|
6fa0c3af0c | ||
|
|
260b9fb040 | ||
|
|
884954f4ff | ||
|
|
88f0f75174 | ||
|
|
1ffa956251 | ||
|
|
31054099ff | ||
|
|
983487d73c | ||
|
|
5c71c57dd9 | ||
|
|
5abc1aafb4 | ||
|
|
935728aa39 |
2
.github/workflows/build-bundle.yml
vendored
2
.github/workflows/build-bundle.yml
vendored
@@ -40,7 +40,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build-bundle:
|
build-bundle:
|
||||||
name: Build and Upload Penpot Bundle
|
name: Build and Upload Penpot Bundle
|
||||||
runs-on: ubuntu-24.04
|
runs-on: penpot-runner-01
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
|||||||
73
.github/workflows/plugins-deploy-api-doc.yml
vendored
Normal file
73
.github/workflows/plugins-deploy-api-doc.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
name: Plugins/api-doc deployer
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- staging
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "plugins/**"
|
||||||
|
- ".github/workflows/deploy-plugin-docs.yml"
|
||||||
|
- "wrangle-penpot-plugins-api-doc.toml"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
gh_ref:
|
||||||
|
description: 'Name of the branch or ref'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
default: 'develop'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: plugins
|
||||||
|
steps:
|
||||||
|
- name: Extract some useful variables
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build docs
|
||||||
|
run: pnpm run build:doc
|
||||||
|
|
||||||
|
- name: Select Worker name
|
||||||
|
run: |
|
||||||
|
REF="${{ steps.vars.outputs.gh_ref }}"
|
||||||
|
case "$REF" in
|
||||||
|
main) echo "WORKER_NAME=penpot-plugins-api-doc-pro" >> $GITHUB_ENV ;;
|
||||||
|
staging) echo "WORKER_NAME=penpot-plugins-api-doc-pre" >> $GITHUB_ENV ;;
|
||||||
|
develop) echo "WORKER_NAME=penpot-plugins-api-doc-hourly" >> $GITHUB_ENV ;;
|
||||||
|
*) echo "Unsupported branch ${REF}" && exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
- name: Deploy to Cloudflare Workers
|
||||||
|
uses: cloudflare/wrangler-action@v3
|
||||||
|
with:
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
command: deploy --config wrangle-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -21,6 +21,7 @@
|
|||||||
.rebel_readline_history
|
.rebel_readline_history
|
||||||
.repl
|
.repl
|
||||||
.shadow-cljs
|
.shadow-cljs
|
||||||
|
.pnpm-store/
|
||||||
/*.jpg
|
/*.jpg
|
||||||
/*.md
|
/*.md
|
||||||
/*.png
|
/*.png
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
/library/target/
|
/library/target/
|
||||||
/library/*.zip
|
/library/*.zip
|
||||||
/external
|
/external
|
||||||
|
/penpot-nitrate
|
||||||
|
|
||||||
clj-profiler/
|
clj-profiler/
|
||||||
node_modules
|
node_modules
|
||||||
|
|||||||
40
.travis.yml
40
.travis.yml
@@ -1,40 +0,0 @@
|
|||||||
dist: xenial
|
|
||||||
|
|
||||||
language: generic
|
|
||||||
sudo: required
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.m2
|
|
||||||
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
|
|
||||||
install:
|
|
||||||
- curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh
|
|
||||||
- chmod +x linux-install-1.10.1.447.sh
|
|
||||||
- sudo ./linux-install-1.10.1.447.sh
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- env | sort
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./manage.sh build-devenv
|
|
||||||
- ./manage.sh run-frontend-tests
|
|
||||||
- ./manage.sh run-backend-tests
|
|
||||||
- ./manage.sh build-images
|
|
||||||
- ./manage.sh run
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
- docker images
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
|
|
||||||
env:
|
|
||||||
- NODE_VERSION=10.16.0
|
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
- Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955)
|
- Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955)
|
||||||
- Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012)
|
- Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012)
|
||||||
- Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
|
- Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
|
||||||
|
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
|
||||||
|
|
||||||
## 2.13.0 (Unreleased)
|
## 2.13.0 (Unreleased)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ export PENPOT_FLAGS="\
|
|||||||
enable-file-validation \
|
enable-file-validation \
|
||||||
enable-file-schema-validation \
|
enable-file-schema-validation \
|
||||||
enable-redis-cache \
|
enable-redis-cache \
|
||||||
enable-subscriptions";
|
enable-subscriptions \
|
||||||
|
enable-nitrate";
|
||||||
|
|
||||||
# Default deletion delay for devenv
|
# Default deletion delay for devenv
|
||||||
export PENPOT_DELETION_DELAY="24h"
|
export PENPOT_DELETION_DELAY="24h"
|
||||||
@@ -55,6 +56,8 @@ export PENPOT_OBJECTS_STORAGE_BACKEND=s3
|
|||||||
export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
|
export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
|
||||||
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
|
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
|
||||||
|
|
||||||
|
export PENPOT_NITRATE_BACKEND_URI=http://localhost:3000/control-center
|
||||||
|
|
||||||
export JAVA_OPTS="\
|
export JAVA_OPTS="\
|
||||||
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
||||||
-Djdk.attach.allowAttachSelf \
|
-Djdk.attach.allowAttachSelf \
|
||||||
|
|||||||
@@ -225,6 +225,8 @@
|
|||||||
[:netty-io-threads {:optional true} ::sm/int]
|
[:netty-io-threads {:optional true} ::sm/int]
|
||||||
[:executor-threads {:optional true} ::sm/int]
|
[:executor-threads {:optional true} ::sm/int]
|
||||||
|
|
||||||
|
[:nitrate-backend-uri {:optional true} ::sm/uri]
|
||||||
|
|
||||||
;; DEPRECATED
|
;; DEPRECATED
|
||||||
[:assets-storage-backend {:optional true} :keyword]
|
[:assets-storage-backend {:optional true} :keyword]
|
||||||
[:storage-assets-fs-directory {:optional true} :string]
|
[:storage-assets-fs-directory {:optional true} :string]
|
||||||
|
|||||||
@@ -323,6 +323,7 @@
|
|||||||
{::http.client/client (ig/ref ::http.client/client)
|
{::http.client/client (ig/ref ::http.client/client)
|
||||||
::db/pool (ig/ref ::db/pool)
|
::db/pool (ig/ref ::db/pool)
|
||||||
::rds/pool (ig/ref ::rds/pool)
|
::rds/pool (ig/ref ::rds/pool)
|
||||||
|
:app.nitrate/client (ig/ref :app.nitrate/client)
|
||||||
::wrk/executor (ig/ref ::wrk/netty-executor)
|
::wrk/executor (ig/ref ::wrk/netty-executor)
|
||||||
::session/manager (ig/ref ::session/manager)
|
::session/manager (ig/ref ::session/manager)
|
||||||
::ldap/provider (ig/ref ::ldap/provider)
|
::ldap/provider (ig/ref ::ldap/provider)
|
||||||
@@ -339,6 +340,9 @@
|
|||||||
::email/blacklist (ig/ref ::email/blacklist)
|
::email/blacklist (ig/ref ::email/blacklist)
|
||||||
::email/whitelist (ig/ref ::email/whitelist)}
|
::email/whitelist (ig/ref ::email/whitelist)}
|
||||||
|
|
||||||
|
:app.nitrate/client
|
||||||
|
{::http.client/client (ig/ref ::http.client/client)}
|
||||||
|
|
||||||
:app.rpc/management-methods
|
:app.rpc/management-methods
|
||||||
{::http.client/client (ig/ref ::http.client/client)
|
{::http.client/client (ig/ref ::http.client/client)
|
||||||
::db/pool (ig/ref ::db/pool)
|
::db/pool (ig/ref ::db/pool)
|
||||||
@@ -348,6 +352,7 @@
|
|||||||
::sto/storage (ig/ref ::sto/storage)
|
::sto/storage (ig/ref ::sto/storage)
|
||||||
::mtx/metrics (ig/ref ::mtx/metrics)
|
::mtx/metrics (ig/ref ::mtx/metrics)
|
||||||
::mbus/msgbus (ig/ref ::mbus/msgbus)
|
::mbus/msgbus (ig/ref ::mbus/msgbus)
|
||||||
|
:app.nitrate/client (ig/ref :app.nitrate/client)
|
||||||
::rds/client (ig/ref ::rds/client)
|
::rds/client (ig/ref ::rds/client)
|
||||||
::setup/props (ig/ref ::setup/props)}
|
::setup/props (ig/ref ::setup/props)}
|
||||||
|
|
||||||
|
|||||||
124
backend/src/app/nitrate.clj
Normal file
124
backend/src/app/nitrate.clj
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
;; 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.nitrate
|
||||||
|
"Module that make calls to the external nitrate aplication"
|
||||||
|
(:require
|
||||||
|
[app.common.logging :as l]
|
||||||
|
[app.common.schema :as sm]
|
||||||
|
[app.config :as cf]
|
||||||
|
[app.http.client :as http]
|
||||||
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.setup :as-alias setup]
|
||||||
|
[app.util.json :as json]
|
||||||
|
[clojure.core :as c]
|
||||||
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; HELPERS
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn- request-builder
|
||||||
|
[cfg method uri management-key profile-id]
|
||||||
|
(fn []
|
||||||
|
(http/req! cfg {:method method
|
||||||
|
:headers {"content-type" "application/json"
|
||||||
|
"accept" "application/json"
|
||||||
|
"x-shared-key" management-key
|
||||||
|
"x-profile-id" (str profile-id)}
|
||||||
|
:uri uri
|
||||||
|
:version :http1.1})))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- with-retries
|
||||||
|
[handler max-retries]
|
||||||
|
(fn []
|
||||||
|
(loop [attempt 1]
|
||||||
|
(let [result (try
|
||||||
|
(handler)
|
||||||
|
(catch Exception e
|
||||||
|
(if (< attempt max-retries)
|
||||||
|
::retry
|
||||||
|
(do
|
||||||
|
;; TODO Error handling
|
||||||
|
(l/error :hint "request fail after multiple retries" :cause e)
|
||||||
|
nil))))]
|
||||||
|
(if (= result ::retry)
|
||||||
|
(recur (inc attempt))
|
||||||
|
result)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- with-validate [handler uri schema]
|
||||||
|
(fn []
|
||||||
|
(let [coercer-http (sm/coercer schema
|
||||||
|
:type :validation
|
||||||
|
:hint (str "invalid data received calling " uri))]
|
||||||
|
(try
|
||||||
|
(coercer-http (-> (handler) :body json/decode))
|
||||||
|
(catch Exception e
|
||||||
|
;; TODO Error handling
|
||||||
|
(l/error :hint "error validating json response" :cause e)
|
||||||
|
nil)))))
|
||||||
|
|
||||||
|
(defn- request-to-nitrate
|
||||||
|
[{:keys [::management-key] :as cfg} method uri schema {:keys [::rpc/profile-id] :as params}]
|
||||||
|
(let [full-http-call (-> (request-builder cfg method uri management-key profile-id)
|
||||||
|
(with-retries 3)
|
||||||
|
(with-validate uri schema))]
|
||||||
|
(full-http-call)))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; API
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn call
|
||||||
|
[cfg method params]
|
||||||
|
(when (contains? cf/flags :nitrate)
|
||||||
|
(let [client (get cfg ::client)
|
||||||
|
method (get client method)]
|
||||||
|
(method params))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(def ^:private schema:organization
|
||||||
|
[:map
|
||||||
|
[:id ::sm/text]
|
||||||
|
[:name ::sm/text]])
|
||||||
|
|
||||||
|
(def ^:private schema:user
|
||||||
|
[:map
|
||||||
|
[:valid ::sm/boolean]])
|
||||||
|
|
||||||
|
(defn- get-team-org
|
||||||
|
[cfg {:keys [team-id] :as params}]
|
||||||
|
(let [baseuri (cf/get :nitrate-backend-uri)]
|
||||||
|
(request-to-nitrate cfg :get (str baseuri "/api/teams/" (str team-id)) schema:organization params)))
|
||||||
|
|
||||||
|
(defn- is-valid-user
|
||||||
|
[cfg {:keys [profile-id] :as params}]
|
||||||
|
(let [baseuri (cf/get :nitrate-backend-uri)]
|
||||||
|
(request-to-nitrate cfg :get (str baseuri "/api/users/" (str profile-id)) schema:user params)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; INITIALIZATION
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defmethod ig/init-key ::client
|
||||||
|
[_ {:keys [::setup/props] :as cfg}]
|
||||||
|
(if (contains? cf/flags :nitrate)
|
||||||
|
(let [management-key (or (cf/get :management-api-key)
|
||||||
|
(get props :management-key))
|
||||||
|
cfg (assoc cfg ::management-key management-key)]
|
||||||
|
{:get-team-org (partial get-team-org cfg)
|
||||||
|
:is-valid-user (partial is-valid-user cfg)})
|
||||||
|
{}))
|
||||||
|
|
||||||
|
(defmethod ig/halt-key! ::client
|
||||||
|
[_ {:keys []}]
|
||||||
|
(do :stuff))
|
||||||
@@ -301,6 +301,7 @@
|
|||||||
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)]
|
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)]
|
||||||
(->> (sv/scan-ns
|
(->> (sv/scan-ns
|
||||||
'app.rpc.management.subscription
|
'app.rpc.management.subscription
|
||||||
|
'app.rpc.management.nitrate
|
||||||
'app.rpc.management.exporter)
|
'app.rpc.management.exporter)
|
||||||
(map (partial process-method cfg "management" wrap-management))
|
(map (partial process-method cfg "management" wrap-management))
|
||||||
(into {}))))
|
(into {}))))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
[app.auth :as auth]
|
[app.auth :as auth]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.logging :as l]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.common.types.plugins :refer [schema:plugin-registry]]
|
[app.common.types.plugins :refer [schema:plugin-registry]]
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
[app.loggers.audit :as audit]
|
[app.loggers.audit :as audit]
|
||||||
[app.main :as-alias main]
|
[app.main :as-alias main]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
|
[app.nitrate :as nitrate]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.climit :as climit]
|
[app.rpc.climit :as climit]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
@@ -88,6 +90,17 @@
|
|||||||
|
|
||||||
;; --- QUERY: Get profile (own)
|
;; --- QUERY: Get profile (own)
|
||||||
|
|
||||||
|
(defn- add-nitrate-licence-to-profile
|
||||||
|
[cfg profile]
|
||||||
|
(try
|
||||||
|
(let [nitrate-licence (nitrate/call cfg :is-valid-user {:profile-id (:id profile)})]
|
||||||
|
(assoc profile :nitrate-licence (:valid nitrate-licence)))
|
||||||
|
(catch Throwable cause
|
||||||
|
(l/error :hint "failed to get nitrate licence"
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:cause cause)
|
||||||
|
profile)))
|
||||||
|
|
||||||
(sv/defmethod ::get-profile
|
(sv/defmethod ::get-profile
|
||||||
{::rpc/auth false
|
{::rpc/auth false
|
||||||
::doc/added "1.18"
|
::doc/added "1.18"
|
||||||
@@ -98,9 +111,13 @@
|
|||||||
;; no profile-id is in session, and when db call raises not found. In all other
|
;; no profile-id is in session, and when db call raises not found. In all other
|
||||||
;; cases we need to reraise the exception.
|
;; cases we need to reraise the exception.
|
||||||
(try
|
(try
|
||||||
(-> (get-profile pool profile-id)
|
(let [profile (-> (get-profile pool profile-id)
|
||||||
(strip-private-attrs)
|
(strip-private-attrs)
|
||||||
(update :props filter-props))
|
(update :props filter-props))]
|
||||||
|
(if (contains? cf/flags :nitrate)
|
||||||
|
(add-nitrate-licence-to-profile cfg profile)
|
||||||
|
profile))
|
||||||
|
|
||||||
(catch Throwable _
|
(catch Throwable _
|
||||||
{:id uuid/zero :fullname "Anonymous User"})))
|
{:id uuid/zero :fullname "Anonymous User"})))
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
[app.main :as-alias main]
|
[app.main :as-alias main]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.msgbus :as mbus]
|
[app.msgbus :as mbus]
|
||||||
|
[app.nitrate :as nitrate]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.commands.profile :as profile]
|
[app.rpc.commands.profile :as profile]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
@@ -172,6 +173,12 @@
|
|||||||
(map decode-row)
|
(map decode-row)
|
||||||
(map process-permissions)))
|
(map process-permissions)))
|
||||||
|
|
||||||
|
(defn- add-org-to-team
|
||||||
|
[cfg team params]
|
||||||
|
(let [params (assoc (or params {}) :team-id (:id team))
|
||||||
|
org (nitrate/call cfg :get-team-org params)]
|
||||||
|
(assoc team :organization-id (:id org) :organization-name (:name org))))
|
||||||
|
|
||||||
(defn get-teams
|
(defn get-teams
|
||||||
[conn profile-id]
|
[conn profile-id]
|
||||||
(let [profile (profile/get-profile conn profile-id)
|
(let [profile (profile/get-profile conn profile-id)
|
||||||
@@ -190,7 +197,9 @@
|
|||||||
::sm/params schema:get-teams}
|
::sm/params schema:get-teams}
|
||||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
||||||
(dm/with-open [conn (db/open pool)]
|
(dm/with-open [conn (db/open pool)]
|
||||||
(get-teams conn profile-id)))
|
(cond->> (get-teams conn profile-id)
|
||||||
|
(contains? cf/flags :nitrate)
|
||||||
|
(map #(add-org-to-team cfg % params)))))
|
||||||
|
|
||||||
(def ^:private sql:get-owned-teams
|
(def ^:private sql:get-owned-teams
|
||||||
"SELECT t.id, t.name,
|
"SELECT t.id, t.name,
|
||||||
|
|||||||
112
backend/src/app/rpc/management/nitrate.clj
Normal file
112
backend/src/app/rpc/management/nitrate.clj
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
;; 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.rpc.management.nitrate
|
||||||
|
"Internal Nitrate HTTP API.
|
||||||
|
Provides authenticated access to organization management and token validation endpoints.
|
||||||
|
All requests must include a valid shared key token in the `x-shared-key` header, and
|
||||||
|
a cookie `auth-token` with the user token.
|
||||||
|
They will return `401 Unauthorized` if the shared key or user token are invalid."
|
||||||
|
(:require
|
||||||
|
[app.common.schema :as sm]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.config :as cf]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.msgbus :as mbus]
|
||||||
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.rpc.commands.files :as files]
|
||||||
|
[app.rpc.commands.profile :as profile]
|
||||||
|
[app.rpc.doc :as doc]
|
||||||
|
[app.util.services :as sv]))
|
||||||
|
|
||||||
|
;; ---- API: authenticate
|
||||||
|
(def ^:private schema:profile
|
||||||
|
[:map
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:name :string]
|
||||||
|
[:email :string]
|
||||||
|
[:photo-url :string]])
|
||||||
|
|
||||||
|
(sv/defmethod ::authenticate
|
||||||
|
"Authenticate an user
|
||||||
|
@api GET /authenticate
|
||||||
|
@returns
|
||||||
|
200 OK: Returns the authenticated user."
|
||||||
|
{::doc/added "2.12"
|
||||||
|
::sm/result schema:profile}
|
||||||
|
[cfg {:keys [::rpc/profile-id] :as params}]
|
||||||
|
(let [profile (profile/get-profile cfg profile-id)]
|
||||||
|
{:id (get profile :id)
|
||||||
|
:name (get profile :fullname)
|
||||||
|
:email (get profile :email)
|
||||||
|
:photo-url (files/resolve-public-uri (get profile :photo-id))}))
|
||||||
|
|
||||||
|
;; ---- API: get-teams
|
||||||
|
|
||||||
|
(def ^:private sql:get-teams
|
||||||
|
"SELECT t.*
|
||||||
|
FROM team AS t
|
||||||
|
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
|
||||||
|
WHERE tpr.profile_id = ?
|
||||||
|
AND tpr.is_owner = 't'
|
||||||
|
AND t.is_default = 'f'
|
||||||
|
AND t.deleted_at is null;")
|
||||||
|
|
||||||
|
(def ^:private schema:team
|
||||||
|
[:map
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:name :string]])
|
||||||
|
|
||||||
|
(def ^:private schema:get-teams-result
|
||||||
|
[:vector schema:team])
|
||||||
|
|
||||||
|
(sv/defmethod ::get-teams
|
||||||
|
"List teams for which current user is owner.
|
||||||
|
@api GET /get-teams
|
||||||
|
@returns
|
||||||
|
200 OK: Returns the list of teams for the user."
|
||||||
|
{::doc/added "2.12"
|
||||||
|
::sm/result schema:get-teams-result}
|
||||||
|
[cfg {:keys [::rpc/profile-id]}]
|
||||||
|
(when (contains? cf/flags :nitrate)
|
||||||
|
(let [current-user-id (-> (profile/get-profile cfg profile-id) :id)]
|
||||||
|
(->> (db/exec! cfg [sql:get-teams current-user-id])
|
||||||
|
(map #(select-keys % [:id :name]))))))
|
||||||
|
|
||||||
|
;; ---- API: notify-team-change
|
||||||
|
|
||||||
|
(def ^:private schema:notify-team-change
|
||||||
|
[:map
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:organization-id ::sm/text]])
|
||||||
|
|
||||||
|
|
||||||
|
(sv/defmethod ::notify-team-change
|
||||||
|
"Notify to Penpot a team change from nitrate
|
||||||
|
@api POST /notify-team-change
|
||||||
|
@returns
|
||||||
|
200 OK"
|
||||||
|
{::doc/added "2.12"
|
||||||
|
::sm/params schema:notify-team-change
|
||||||
|
::rpc/auth false}
|
||||||
|
[cfg {:keys [id organization-id organization-name]}]
|
||||||
|
(when (contains? cf/flags :nitrate)
|
||||||
|
(let [msgbus (::mbus/msgbus cfg)]
|
||||||
|
(mbus/pub! msgbus
|
||||||
|
;;TODO There is a bug on dashboard with teams notifications.
|
||||||
|
;;For now we send it to uuid/zero instead of team-id
|
||||||
|
:topic uuid/zero
|
||||||
|
:message {:type :team-org-change
|
||||||
|
:team-id id
|
||||||
|
:organization-id organization-id
|
||||||
|
:organization-name organization-name}))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -145,7 +145,10 @@
|
|||||||
|
|
||||||
;; A temporal flag, enables backend code use more extensivelly
|
;; A temporal flag, enables backend code use more extensivelly
|
||||||
;; redis for caching data
|
;; redis for caching data
|
||||||
:redis-cache})
|
:redis-cache
|
||||||
|
|
||||||
|
;; Activates the nitrate module
|
||||||
|
:nitrate})
|
||||||
|
|
||||||
(def all-flags
|
(def all-flags
|
||||||
(set/union email login varia))
|
(set/union email login varia))
|
||||||
|
|||||||
@@ -141,8 +141,14 @@ http {
|
|||||||
proxy_pass http://127.0.0.1:5000;
|
proxy_pass http://127.0.0.1:5000;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /nitrate/ {
|
location /control-center {
|
||||||
proxy_pass http://127.0.0.1:3000/;
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /wasm-playground {
|
location /wasm-playground {
|
||||||
|
|||||||
@@ -152,9 +152,9 @@ services:
|
|||||||
|
|
||||||
# AWS_ACCESS_KEY_ID: <KEY_ID>
|
# AWS_ACCESS_KEY_ID: <KEY_ID>
|
||||||
# AWS_SECRET_ACCESS_KEY: <ACCESS_KEY>
|
# AWS_SECRET_ACCESS_KEY: <ACCESS_KEY>
|
||||||
# PENPOT_ASSETS_STORAGE_BACKEND: assets-s3
|
# PENPOT_OBJECTS_STORAGE_BACKEND: s3
|
||||||
# PENPOT_STORAGE_ASSETS_S3_ENDPOINT: <ENDPOINT>
|
# PENPOT_OBJECTS_STORAGE_S3_ENDPOINT: <ENDPOINT>
|
||||||
# PENPOT_STORAGE_ASSETS_S3_BUCKET: <BUKET_NAME>
|
# PENPOT_OBJECTS_STORAGE_S3_BUCKET: <BUKET_NAME>
|
||||||
|
|
||||||
## Telemetry. When enabled, a periodical process will send anonymous data about this
|
## Telemetry. When enabled, a periodical process will send anonymous data about this
|
||||||
## instance. Telemetry data will enable us to learn how the application is used,
|
## instance. Telemetry data will enable us to learn how the application is used,
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ update_flags /var/www/app/js/config.js
|
|||||||
|
|
||||||
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
|
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
|
||||||
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
|
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
|
||||||
|
export PENPOT_NITRATE_URI=${PENPOT_NITRATE_URI:-http://penpot-nitrate:3000}
|
||||||
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE:-367001600} # Default to 350MiB
|
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE:-367001600} # Default to 350MiB
|
||||||
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
|
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
|
||||||
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
|
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
|
||||||
|
|
||||||
PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)"
|
PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)"
|
||||||
|
|||||||
@@ -139,6 +139,14 @@ http {
|
|||||||
proxy_pass $PENPOT_BACKEND_URI/ws/notifications;
|
proxy_pass $PENPOT_BACKEND_URI/ws/notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /control-center {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $http_cf_connecting_ip;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_pass $PENPOT_NITRATE_URI$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
include /etc/nginx/overrides/server.d/*.conf;
|
include /etc/nginx/overrides/server.d/*.conf;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
|||||||
@@ -114,14 +114,7 @@ configuration.
|
|||||||
The callback has the following format:
|
The callback has the following format:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
https://<your_domain>/api/auth/oauth/<oauth_provider>/callback
|
https://<your_domain>/api/auth/oidc/callback
|
||||||
```
|
|
||||||
|
|
||||||
You will need to change <your_domain> and <oauth_provider> according to your setup.
|
|
||||||
This is how it looks with Gitlab provider:
|
|
||||||
|
|
||||||
```html
|
|
||||||
https://<your_domain>/api/auth/oauth/gitlab/callback
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Google
|
#### Google
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
|
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
|
||||||
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
||||||
"watch": "exit 0",
|
"watch": "exit 0",
|
||||||
"watch:app": "yarn run clear:shadow-cache && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
|
"watch:app": "yarn run clear:shadow-cache && yarn run build:wasm && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
|
||||||
"watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
|
"watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -216,4 +216,32 @@ test.describe("Tokens: Sets Tab", () => {
|
|||||||
await expect(tokenSetItems.nth(1)).toHaveAttribute("aria-checked", "false");
|
await expect(tokenSetItems.nth(1)).toHaveAttribute("aria-checked", "false");
|
||||||
await expect(tokenSetItems.nth(2)).toHaveAttribute("aria-checked", "true");
|
await expect(tokenSetItems.nth(2)).toHaveAttribute("aria-checked", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Display active set and verify if is enabled", async ({ page }) => {
|
||||||
|
const { tokenThemesSetsSidebar, tokensSidebar, tokenSetItems } =
|
||||||
|
await setupTokensFile(page);
|
||||||
|
|
||||||
|
// Create set
|
||||||
|
await tokenThemesSetsSidebar
|
||||||
|
.getByRole("button", { name: "Add set" })
|
||||||
|
.click();
|
||||||
|
await changeSetInput(tokenThemesSetsSidebar, "Inactive set");
|
||||||
|
await tokenThemesSetsSidebar
|
||||||
|
.getByRole("button", { name: "Inactive set" })
|
||||||
|
.click();
|
||||||
|
let activeSetTitle = await tokensSidebar.getByTestId(
|
||||||
|
"active-token-set-title",
|
||||||
|
);
|
||||||
|
await expect(activeSetTitle).toHaveText("TOKENS - Inactive set");
|
||||||
|
const inactiveSetInfo = await tokensSidebar.getByTitle(
|
||||||
|
"This set is not active.",
|
||||||
|
);
|
||||||
|
await expect(inactiveSetInfo).toBeVisible();
|
||||||
|
|
||||||
|
// Switch active set
|
||||||
|
|
||||||
|
await tokenThemesSetsSidebar.getByRole("button", { name: "theme" }).click();
|
||||||
|
await expect(activeSetTitle).toHaveText("TOKENS - theme");
|
||||||
|
await expect(inactiveSetInfo).not.toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
--app-background: var(--color-background-primary);
|
--app-background: var(--color-background-primary);
|
||||||
--loader-background: var(--color-background-primary);
|
--loader-background: var(--color-background-primary);
|
||||||
--panel-title-background-color: var(--color-background-secondary);
|
|
||||||
|
|
||||||
// BUTTONS
|
// BUTTONS
|
||||||
--button-foreground-hover: var(--color-accent-primary);
|
--button-foreground-hover: var(--color-accent-primary);
|
||||||
|
|||||||
@@ -17,17 +17,18 @@
|
|||||||
<meta name="twitter:site" content="@penpotapp">
|
<meta name="twitter:site" content="@penpotapp">
|
||||||
<meta name="twitter:creator" content="@penpotapp">
|
<meta name="twitter:creator" content="@penpotapp">
|
||||||
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
|
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
|
||||||
<link id="theme" href="css/main.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
<link id="theme" href="css/main.css?version={{& version_tag}}" rel="stylesheet" type="text/css" />
|
||||||
{{#isDebug}}
|
{{#isDebug}}
|
||||||
<link href="css/debug.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
<link href="css/debug.css?version={{& version_tag}}" rel="stylesheet" type="text/css" />
|
||||||
{{/isDebug}}
|
{{/isDebug}}
|
||||||
|
|
||||||
<link rel="icon" href="images/favicon.png" />
|
<link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
|
||||||
|
|
||||||
<script type="importmap">{{& manifest.importmap }}</script>
|
<script type="importmap">{{& manifest.importmap }}</script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
globalThis.penpotVersion = "{{& version}}";
|
globalThis.penpotVersion = "{{& version}}";
|
||||||
|
globalThis.penpotVersionTag = "{{& version_tag}}";
|
||||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||||
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
|
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Penpot - Rasterizer</title>
|
<title>Penpot - Rasterizer</title>
|
||||||
<link rel="icon" href="images/favicon.png" />
|
<link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
globalThis.penpotVersion = "{{& version}}";
|
globalThis.penpotVersion = "{{& version}}";
|
||||||
|
globalThis.penpotVersionTag = "{{& version_tag}}";
|
||||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||||
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
|
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
<title>Penpot - Render</title>
|
<title>Penpot - Render</title>
|
||||||
<link rel="icon" href="images/favicon.png" />
|
|
||||||
|
<link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
globalThis.penpotVersion = "{{& version}}";
|
globalThis.penpotVersion = "{{& version}}";
|
||||||
|
globalThis.penpotVersionTag = "{{& version_tag}}";
|
||||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ export function startWorker() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isDebug = process.env.NODE_ENV !== "production";
|
export const IS_DEBUG = process.env.NODE_ENV !== "production";
|
||||||
export const CURRENT_VERSION = process.env.CURRENT_VERSION || "develop";
|
export const BUILD_DATE = process.env.BUILD_DATE || (new Date().toString()) ;
|
||||||
export const BUILD_DATE = process.env.BUILD_DATE || "" + new Date();
|
export const BUILD_TS = process.env.BUILD_TS || Date.now();
|
||||||
|
export const VERSION = process.env.VERSION || "develop";
|
||||||
|
export const VERSION_TAG = process.env.VERSION_TAG || VERSION;
|
||||||
|
|
||||||
async function findFiles(basePath, predicate, options = {}) {
|
async function findFiles(basePath, predicate, options = {}) {
|
||||||
predicate =
|
predicate =
|
||||||
@@ -172,6 +174,7 @@ export async function watch(baseDir, predicate, callback) {
|
|||||||
const watcher = new Watcher(baseDir, {
|
const watcher = new Watcher(baseDir, {
|
||||||
persistent: true,
|
persistent: true,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
|
debounce: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
watcher.on("change", (path) => {
|
watcher.on("change", (path) => {
|
||||||
@@ -179,8 +182,19 @@ export async function watch(baseDir, predicate, callback) {
|
|||||||
callback(path);
|
callback(path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
watcher.on("error", (cause) => {
|
||||||
|
console.log("WATCHER ERROR", cause);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function ensureDirectories() {
|
||||||
|
await fs.mkdir("./resources/public/js/worker/", { recursive: true });
|
||||||
|
await fs.mkdir("./resources/public/css/", { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function readManifestFile(resource) {
|
async function readManifestFile(resource) {
|
||||||
const manifestPath = "resources/public/" + resource;
|
const manifestPath = "resources/public/" + resource;
|
||||||
let content = await fs.readFile(manifestPath, { encoding: "utf8" });
|
let content = await fs.readFile(manifestPath, { encoding: "utf8" });
|
||||||
@@ -193,25 +207,25 @@ async function generateManifest() {
|
|||||||
render_main: "./js/render.js",
|
render_main: "./js/render.js",
|
||||||
rasterizer_main: "./js/rasterizer.js",
|
rasterizer_main: "./js/rasterizer.js",
|
||||||
|
|
||||||
config: "./js/config.js?version=" + CURRENT_VERSION,
|
config: "./js/config.js?version=" + VERSION_TAG,
|
||||||
polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION,
|
polyfills: "./js/polyfills.js?version=" + VERSION_TAG,
|
||||||
libs: "./js/libs.js?version=" + CURRENT_VERSION,
|
libs: "./js/libs.js?version=" + VERSION_TAG,
|
||||||
worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION,
|
worker_main: "./js/worker/main.js?version=" + VERSION_TAG,
|
||||||
default_translations: "./js/translation.en.js?version=" + CURRENT_VERSION,
|
default_translations: "./js/translation.en.js?version=" + VERSION_TAG,
|
||||||
|
|
||||||
importmap: JSON.stringify({
|
importmap: JSON.stringify({
|
||||||
"imports": {
|
"imports": {
|
||||||
"./js/shared.js": "./js/shared.js?version=" + CURRENT_VERSION,
|
"./js/shared.js": "./js/shared.js?version=" + VERSION_TAG,
|
||||||
"./js/main.js": "./js/main.js?version=" + CURRENT_VERSION,
|
"./js/main.js": "./js/main.js?version=" + VERSION_TAG,
|
||||||
"./js/render.js": "./js/render.js?version=" + CURRENT_VERSION,
|
"./js/render.js": "./js/render.js?version=" + VERSION_TAG,
|
||||||
"./js/render-wasm.js": "./js/render-wasm.js?version=" + CURRENT_VERSION,
|
"./js/render-wasm.js": "./js/render-wasm.js?version=" + VERSION_TAG,
|
||||||
"./js/rasterizer.js": "./js/rasterizer.js?version=" + CURRENT_VERSION,
|
"./js/rasterizer.js": "./js/rasterizer.js?version=" + VERSION_TAG,
|
||||||
"./js/main-dashboard.js": "./js/main-dashboard.js?version=" + CURRENT_VERSION,
|
"./js/main-dashboard.js": "./js/main-dashboard.js?version=" + VERSION_TAG,
|
||||||
"./js/main-auth.js": "./js/main-auth.js?version=" + CURRENT_VERSION,
|
"./js/main-auth.js": "./js/main-auth.js?version=" + VERSION_TAG,
|
||||||
"./js/main-viewer.js": "./js/main-viewer.js?version=" + CURRENT_VERSION,
|
"./js/main-viewer.js": "./js/main-viewer.js?version=" + VERSION_TAG,
|
||||||
"./js/main-settings.js": "./js/main-settings.js?version=" + CURRENT_VERSION,
|
"./js/main-settings.js": "./js/main-settings.js?version=" + VERSION_TAG,
|
||||||
"./js/main-workspace.js": "./js/main-workspace.js?version=" + CURRENT_VERSION,
|
"./js/main-workspace.js": "./js/main-workspace.js?version=" + VERSION_TAG,
|
||||||
"./js/util-highlight.js": "./js/util-highlight.js?version=" + CURRENT_VERSION
|
"./js/util-highlight.js": "./js/util-highlight.js?version=" + VERSION_TAG
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -222,11 +236,12 @@ async function generateManifest() {
|
|||||||
async function renderTemplate(path, context = {}, partials = {}) {
|
async function renderTemplate(path, context = {}, partials = {}) {
|
||||||
const content = await fs.readFile(path, { encoding: "utf-8" });
|
const content = await fs.readFile(path, { encoding: "utf-8" });
|
||||||
|
|
||||||
const ts = Math.floor(new Date());
|
|
||||||
|
|
||||||
context = Object.assign({}, context, {
|
context = Object.assign({}, context, {
|
||||||
ts: ts,
|
isDebug: IS_DEBUG,
|
||||||
isDebug,
|
version: VERSION,
|
||||||
|
version_tag: VERSION_TAG,
|
||||||
|
build_date: BUILD_DATE,
|
||||||
|
build_ts: BUILD_TS,
|
||||||
});
|
});
|
||||||
|
|
||||||
return mustache.render(content, context, partials);
|
return mustache.render(content, context, partials);
|
||||||
@@ -257,6 +272,9 @@ const markedOptions = {
|
|||||||
marked.use(markedOptions);
|
marked.use(markedOptions);
|
||||||
|
|
||||||
export async function compileTranslations() {
|
export async function compileTranslations() {
|
||||||
|
const outputDir = "resources/public/js/";
|
||||||
|
await fs.mkdir(outputDir, { recursive: true });
|
||||||
|
|
||||||
const langs = [
|
const langs = [
|
||||||
"ar",
|
"ar",
|
||||||
"ca",
|
"ca",
|
||||||
@@ -338,7 +356,6 @@ export async function compileTranslations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const esm = `export default ${JSON.stringify(result, null, 0)};\n`;
|
const esm = `export default ${JSON.stringify(result, null, 0)};\n`;
|
||||||
const outputDir = "resources/public/js/";
|
|
||||||
const outputFile = ph.join(outputDir, "translation." + lang + ".js");
|
const outputFile = ph.join(outputDir, "translation." + lang + ".js");
|
||||||
await fs.writeFile(outputFile, esm);
|
await fs.writeFile(outputFile, esm);
|
||||||
}
|
}
|
||||||
@@ -390,7 +407,6 @@ async function generateSvgSprites() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function generateTemplates() {
|
async function generateTemplates() {
|
||||||
const isDebug = process.env.NODE_ENV !== "production";
|
|
||||||
await fs.mkdir("./resources/public/", { recursive: true });
|
await fs.mkdir("./resources/public/", { recursive: true });
|
||||||
|
|
||||||
const manifest = await generateManifest();
|
const manifest = await generateManifest();
|
||||||
@@ -415,10 +431,7 @@ async function generateTemplates() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
manifest: manifest,
|
manifest: manifest
|
||||||
version: CURRENT_VERSION,
|
|
||||||
build_date: BUILD_DATE,
|
|
||||||
isDebug,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
content = await renderTemplate(
|
content = await renderTemplate(
|
||||||
@@ -487,7 +500,7 @@ export async function compileStyles() {
|
|||||||
await fs.mkdir("./resources/public/css", { recursive: true });
|
await fs.mkdir("./resources/public/css", { recursive: true });
|
||||||
await fs.writeFile("./resources/public/css/main.css", result);
|
await fs.writeFile("./resources/public/css/main.css", result);
|
||||||
|
|
||||||
if (isDebug) {
|
if (IS_DEBUG) {
|
||||||
let debugCSS = await compileSassDebug(worker);
|
let debugCSS = await compileSassDebug(worker);
|
||||||
await fs.writeFile("./resources/public/css/debug.css", debugCSS);
|
await fs.writeFile("./resources/public/css/debug.css", debugCSS);
|
||||||
}
|
}
|
||||||
@@ -500,17 +513,43 @@ export async function compileStyles() {
|
|||||||
export async function compileSvgSprites() {
|
export async function compileSvgSprites() {
|
||||||
const start = process.hrtime();
|
const start = process.hrtime();
|
||||||
log.info("init: compile svgsprite");
|
log.info("init: compile svgsprite");
|
||||||
await generateSvgSprites();
|
let error = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await generateSvgSprites();
|
||||||
|
} catch (cause) {
|
||||||
|
error = cause;
|
||||||
|
}
|
||||||
|
|
||||||
const end = process.hrtime(start);
|
const end = process.hrtime(start);
|
||||||
log.info("done: compile svgsprite", `(${ppt(end)})`);
|
|
||||||
|
if (error) {
|
||||||
|
log.error("error: compile svgsprite", `(${ppt(end)})`);
|
||||||
|
console.error(error);
|
||||||
|
} else {
|
||||||
|
log.info("done: compile svgsprite", `(${ppt(end)})`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compileTemplates() {
|
export async function compileTemplates() {
|
||||||
const start = process.hrtime();
|
const start = process.hrtime();
|
||||||
|
let error = false;
|
||||||
log.info("init: compile templates");
|
log.info("init: compile templates");
|
||||||
await generateTemplates();
|
|
||||||
|
try {
|
||||||
|
await generateTemplates();
|
||||||
|
} catch (cause) {
|
||||||
|
error = cause;
|
||||||
|
}
|
||||||
|
|
||||||
const end = process.hrtime(start);
|
const end = process.hrtime(start);
|
||||||
log.info("done: compile templates", `(${ppt(end)})`);
|
|
||||||
|
if (error) {
|
||||||
|
log.error("error: compile templates", `(${ppt(end)})`);
|
||||||
|
console.error(error);
|
||||||
|
} else {
|
||||||
|
log.info("done: compile templates", `(${ppt(end)})`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compilePolyfills() {
|
export async function compilePolyfills() {
|
||||||
|
|||||||
@@ -28,14 +28,12 @@ async function compileFile(path) {
|
|||||||
],
|
],
|
||||||
sourceMap: false,
|
sourceMap: false,
|
||||||
});
|
});
|
||||||
// console.dir(result);
|
|
||||||
resolve({
|
resolve({
|
||||||
inputPath: path,
|
inputPath: path,
|
||||||
outputPath: dest,
|
outputPath: dest,
|
||||||
css: result.css,
|
css: result.css,
|
||||||
});
|
});
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
console.error(cause);
|
|
||||||
reject(cause);
|
reject(cause);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,26 +2,26 @@
|
|||||||
# NOTE: this script should be called from the parent directory to
|
# NOTE: this script should be called from the parent directory to
|
||||||
# properly work.
|
# properly work.
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no};
|
export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no};
|
||||||
export INCLUDE_WASM=${BUILD_WASM:-yes};
|
export INCLUDE_WASM=${BUILD_WASM:-yes};
|
||||||
export CURRENT_VERSION=$1;
|
|
||||||
export BUILD_DATE=$(date -R);
|
|
||||||
export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)};
|
|
||||||
export EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS;
|
export EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS;
|
||||||
export TS=$(date +%s);
|
|
||||||
|
export BUILD_DATE=$(date -R);
|
||||||
|
export BUILD_TS=$(date +%s);
|
||||||
|
|
||||||
|
export VERSION=${1:-develop};
|
||||||
|
export VERSION_TAG="${VERSION}-${BUILD_TS}";
|
||||||
|
|
||||||
# Some cljs reacts on this environment variable for define more
|
# Some cljs reacts on this environment variable for define more
|
||||||
# performant code on macros (example: rumext)
|
# performant code on macros (example: rumext)
|
||||||
export NODE_ENV=production;
|
export NODE_ENV=production;
|
||||||
|
|
||||||
echo "Current path:"
|
|
||||||
echo $PATH
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install;
|
corepack install;
|
||||||
yarn install || exit 1;
|
yarn install;
|
||||||
|
|
||||||
rm -rf target/dist;
|
rm -rf target/dist;
|
||||||
rm -rf resources/public;
|
rm -rf resources/public;
|
||||||
@@ -37,7 +37,7 @@ yarn run build:app:main $EXTRA_PARAMS;
|
|||||||
yarn run build:app:libs;
|
yarn run build:app:libs;
|
||||||
yarn run build:app:assets;
|
yarn run build:app:assets;
|
||||||
|
|
||||||
sed -i "s/\.\/render.js/.\/render.js?version=$CURRENT_VERSION/g" resources/public/js/worker/main*.js
|
sed -i "s/\.\/render.js/.\/render.js?version=$VERSION_TAG/g" resources/public/js/worker/main*.js
|
||||||
|
|
||||||
rsync -avr resources/public/ target/dist/
|
rsync -avr resources/public/ target/dist/
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as h from "./_helpers.js";
|
import * as h from "./_helpers.js";
|
||||||
|
|
||||||
|
await h.ensureDirectories();
|
||||||
await h.compileStyles();
|
await h.compileStyles();
|
||||||
await h.copyAssets();
|
await h.copyAssets();
|
||||||
await h.copyWasmPlayground();
|
await h.copyWasmPlayground();
|
||||||
|
|||||||
@@ -2,18 +2,16 @@
|
|||||||
# NOTE: this script should be called from the parent directory to
|
# NOTE: this script should be called from the parent directory to
|
||||||
# properly work.
|
# properly work.
|
||||||
|
|
||||||
export CURRENT_VERSION=$1;
|
set -ex
|
||||||
|
|
||||||
|
export BUILD_TS=$(date +%s);
|
||||||
export BUILD_DATE=$(date -R);
|
export BUILD_DATE=$(date -R);
|
||||||
export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)};
|
|
||||||
export TS=$(date +%s);
|
export VERSION=${1:-develop};
|
||||||
|
export VERSION_TAG="${VERSION}-${BUILD_TS}";
|
||||||
|
|
||||||
export NODE_ENV=production;
|
export NODE_ENV=production;
|
||||||
|
|
||||||
echo "Current path:"
|
|
||||||
echo $PATH
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install || exit 1;
|
corepack install || exit 1;
|
||||||
yarn install || exit 1;
|
yarn install || exit 1;
|
||||||
|
|||||||
@@ -12,19 +12,31 @@ let sass = null;
|
|||||||
|
|
||||||
async function compileSassAll() {
|
async function compileSassAll() {
|
||||||
const start = process.hrtime();
|
const start = process.hrtime();
|
||||||
|
let error = false;
|
||||||
|
|
||||||
log.info("init: compile styles");
|
log.info("init: compile styles");
|
||||||
|
|
||||||
sass = await h.compileSassAll(worker);
|
try {
|
||||||
let output = await h.concatSass(sass);
|
sass = await h.compileSassAll(worker);
|
||||||
await fs.writeFile("./resources/public/css/main.css", output);
|
let output = await h.concatSass(sass);
|
||||||
|
await fs.writeFile("./resources/public/css/main.css", output);
|
||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
let debugCSS = await h.compileSassDebug(worker);
|
let debugCSS = await h.compileSassDebug(worker);
|
||||||
await fs.writeFile("./resources/public/css/debug.css", debugCSS);
|
await fs.writeFile("./resources/public/css/debug.css", debugCSS);
|
||||||
|
}
|
||||||
|
} catch (cause) {
|
||||||
|
error = cause;
|
||||||
}
|
}
|
||||||
|
|
||||||
const end = process.hrtime(start);
|
const end = process.hrtime(start);
|
||||||
log.info("done: compile styles", `(${ppt(end)})`);
|
|
||||||
|
if (error) {
|
||||||
|
log.error("error: compile styles", `(${ppt(end)})`);
|
||||||
|
console.error(error);
|
||||||
|
} else {
|
||||||
|
log.info("done: compile styles", `(${ppt(end)})`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compileSass(path) {
|
async function compileSass(path) {
|
||||||
@@ -48,7 +60,7 @@ async function compileSass(path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.mkdir("./resources/public/css/", { recursive: true });
|
await h.ensureDirectories();
|
||||||
await compileSassAll();
|
await compileSassAll();
|
||||||
await h.copyAssets();
|
await h.copyAssets();
|
||||||
await h.copyWasmPlayground();
|
await h.copyWasmPlayground();
|
||||||
|
|||||||
@@ -95,6 +95,7 @@
|
|||||||
(def browser (parse-browser))
|
(def browser (parse-browser))
|
||||||
(def platform (parse-platform))
|
(def platform (parse-platform))
|
||||||
|
|
||||||
|
(def version-tag (obj/get global "penpotVersionTag"))
|
||||||
(def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI"))
|
(def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI"))
|
||||||
(def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI"))
|
(def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI"))
|
||||||
(def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/"))
|
(def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/"))
|
||||||
@@ -190,9 +191,8 @@
|
|||||||
|
|
||||||
(defn resolve-href
|
(defn resolve-href
|
||||||
[resource]
|
[resource]
|
||||||
(let [version (get version :full)
|
(let [href (-> public-uri
|
||||||
href (-> public-uri
|
(u/ensure-path-slash)
|
||||||
(u/ensure-path-slash)
|
(u/join resource)
|
||||||
(u/join resource)
|
(get :path))]
|
||||||
(get :path))]
|
(str href "?version=" version-tag)))
|
||||||
(str href "?version=" version)))
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.common.types.project :refer [valid-project?]]
|
[app.common.types.project :refer [valid-project?]]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
[app.config :as cf]
|
||||||
[app.main.constants :as mconst]
|
[app.main.constants :as mconst]
|
||||||
[app.main.data.common :as dcm]
|
[app.main.data.common :as dcm]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
@@ -683,12 +684,26 @@
|
|||||||
(rx/of (dcm/change-team-role params)
|
(rx/of (dcm/change-team-role params)
|
||||||
(modal/hide)))))
|
(modal/hide)))))
|
||||||
|
|
||||||
|
(defn handle-change-team-org
|
||||||
|
[{:keys [team-id organization-id organization-name]}]
|
||||||
|
(ptk/reify ::handle-change-team-org
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(if (contains? cf/flags :nitrate)
|
||||||
|
(d/update-when-in state [:teams team-id]
|
||||||
|
#(-> %
|
||||||
|
(assoc :organization-id organization-id)
|
||||||
|
(assoc :organization-name organization-name)))
|
||||||
|
state))))
|
||||||
|
|
||||||
|
|
||||||
(defn- process-message
|
(defn- process-message
|
||||||
[{:keys [type] :as msg}]
|
[{:keys [type] :as msg}]
|
||||||
(case type
|
(case type
|
||||||
:notification (dcm/handle-notification msg)
|
:notification (dcm/handle-notification msg)
|
||||||
:team-role-change (handle-change-team-role msg)
|
:team-role-change (handle-change-team-role msg)
|
||||||
:team-membership-change (dcm/team-membership-change msg)
|
:team-membership-change (dcm/team-membership-change msg)
|
||||||
|
:team-org-change (handle-change-team-org msg)
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,10 @@
|
|||||||
[app.main.data.workspace.colors :as wdc]
|
[app.main.data.workspace.colors :as wdc]
|
||||||
[app.main.data.workspace.shape-layout :as dwsl]
|
[app.main.data.workspace.shape-layout :as dwsl]
|
||||||
[app.main.data.workspace.shapes :as dwsh]
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
|
[app.main.data.workspace.texts :as dwt]
|
||||||
[app.main.data.workspace.transforms :as dwtr]
|
[app.main.data.workspace.transforms :as dwtr]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.fonts :as fonts]
|
[app.main.fonts :as fonts]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
@@ -300,11 +302,20 @@
|
|||||||
update-fn (fn [node _]
|
update-fn (fn [node _]
|
||||||
(-> node
|
(-> node
|
||||||
(d/txt-merge txt-attrs)
|
(d/txt-merge txt-attrs)
|
||||||
(cty/remove-typography-from-node)))]
|
(cty/remove-typography-from-node)))
|
||||||
(dwsh/update-shapes shape-ids
|
;; Check if any attribute affects text layout (requires resize)
|
||||||
#(txt/update-text-content % update-node? update-fn nil)
|
affects-layout? (some #(contains? txt-attrs %) [:font-size :font-family :font-weight :letter-spacing :line-height])]
|
||||||
{:ignore-touched true
|
(ptk/reify ::generate-text-shape-update
|
||||||
:page-id page-id})))
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(cond-> (rx/of (dwsh/update-shapes shape-ids
|
||||||
|
#(txt/update-text-content % update-node? update-fn nil)
|
||||||
|
{:ignore-touched true
|
||||||
|
:page-id page-id}))
|
||||||
|
(and affects-layout?
|
||||||
|
(features/active-feature? state "render-wasm/v1"))
|
||||||
|
(rx/merge
|
||||||
|
(rx/of (dwt/resize-wasm-text-all shape-ids))))))))
|
||||||
|
|
||||||
(defn update-line-height
|
(defn update-line-height
|
||||||
([value shape-ids attributes] (update-line-height value shape-ids attributes nil))
|
([value shape-ids attributes] (update-line-height value shape-ids attributes nil))
|
||||||
@@ -353,11 +364,17 @@
|
|||||||
(-> node
|
(-> node
|
||||||
(d/txt-merge txt-attrs)
|
(d/txt-merge txt-attrs)
|
||||||
(cty/remove-typography-from-node))))]
|
(cty/remove-typography-from-node))))]
|
||||||
(dwsh/update-shapes shape-ids
|
(ptk/reify ::generate-font-family-text-shape-update
|
||||||
(fn [shape]
|
ptk/WatchEvent
|
||||||
(txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil))
|
(watch [_ state _]
|
||||||
{:ignore-touched true
|
(cond-> (rx/of (dwsh/update-shapes shape-ids
|
||||||
:page-id page-id})))
|
(fn [shape]
|
||||||
|
(txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil))
|
||||||
|
{:ignore-touched true
|
||||||
|
:page-id page-id}))
|
||||||
|
(features/active-feature? state "render-wasm/v1")
|
||||||
|
(rx/merge
|
||||||
|
(rx/of (dwt/resize-wasm-text-all shape-ids))))))))
|
||||||
|
|
||||||
(defn- create-font-family-text-attrs
|
(defn- create-font-family-text-attrs
|
||||||
[value]
|
[value]
|
||||||
@@ -425,10 +442,16 @@
|
|||||||
(-> node
|
(-> node
|
||||||
(d/txt-merge txt-attrs)
|
(d/txt-merge txt-attrs)
|
||||||
(cty/remove-typography-from-node))))]
|
(cty/remove-typography-from-node))))]
|
||||||
(dwsh/update-shapes shape-ids
|
(ptk/reify ::generate-font-weight-text-shape-update
|
||||||
#(txt/update-text-content % update-node? update-fn nil)
|
ptk/WatchEvent
|
||||||
{:ignore-touched true
|
(watch [_ state _]
|
||||||
:page-id page-id})))
|
(cond-> (rx/of (dwsh/update-shapes shape-ids
|
||||||
|
#(txt/update-text-content % update-node? update-fn nil)
|
||||||
|
{:ignore-touched true
|
||||||
|
:page-id page-id}))
|
||||||
|
(features/active-feature? state "render-wasm/v1")
|
||||||
|
(rx/merge
|
||||||
|
(rx/of (dwt/resize-wasm-text-all shape-ids))))))))
|
||||||
|
|
||||||
(defn update-font-weight
|
(defn update-font-weight
|
||||||
([value shape-ids attributes] (update-font-weight value shape-ids attributes nil))
|
([value shape-ids attributes] (update-font-weight value shape-ids attributes nil))
|
||||||
|
|||||||
@@ -483,6 +483,9 @@
|
|||||||
(def workspace-active-theme-paths
|
(def workspace-active-theme-paths
|
||||||
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))
|
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))
|
||||||
|
|
||||||
|
(def workspace-all-tokens-map
|
||||||
|
(l/derived (d/nilf ctob/get-all-tokens-map) tokens-lib))
|
||||||
|
|
||||||
(defn token-sets-at-path-all-active
|
(defn token-sets-at-path-all-active
|
||||||
[group-path]
|
[group-path]
|
||||||
(l/derived
|
(l/derived
|
||||||
|
|||||||
@@ -193,11 +193,11 @@
|
|||||||
restore-fn
|
restore-fn
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(st/emit! (dd/restore-files-immediately
|
(st/emit! (dd/restore-files-immediately
|
||||||
(with-meta {:team-id (:id current-team)
|
(with-meta {:team-id current-team-id
|
||||||
:ids (into #{} d/xf:map-id files)}
|
:ids (into #{} d/xf:map-id files)}
|
||||||
{:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file)))
|
{:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file)))
|
||||||
(dd/fetch-projects (:id current-team))
|
(dd/fetch-projects current-team-id)
|
||||||
(dd/fetch-deleted-files (:id current-team)))
|
(dd/fetch-deleted-files current-team-id))
|
||||||
:on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))}))))
|
:on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))}))))
|
||||||
|
|
||||||
on-restore-immediately
|
on-restore-immediately
|
||||||
@@ -214,7 +214,7 @@
|
|||||||
on-delete-immediately
|
on-delete-immediately
|
||||||
(fn []
|
(fn []
|
||||||
(let [accept-fn #(st/emit! (dd/delete-files-immediately
|
(let [accept-fn #(st/emit! (dd/delete-files-immediately
|
||||||
{:team-id (:id current-team)
|
{:team-id current-team-id
|
||||||
:ids (into #{} d/xf:map-id files)}))]
|
:ids (into #{} d/xf:map-id files)}))]
|
||||||
(st/emit!
|
(st/emit!
|
||||||
(modal/show {:type :confirm
|
(modal/show {:type :confirm
|
||||||
@@ -244,8 +244,7 @@
|
|||||||
(for [project current-projects]
|
(for [project current-projects]
|
||||||
{:name (get-project-name project)
|
{:name (get-project-name project)
|
||||||
:id (get-project-id project)
|
:id (get-project-id project)
|
||||||
:handler (on-move (:id current-team)
|
:handler (on-move current-team-id (:id project))})
|
||||||
(:id project))})
|
|
||||||
(when (seq other-teams)
|
(when (seq other-teams)
|
||||||
[{:name (tr "dashboard.move-to-other-team")
|
[{:name (tr "dashboard.move-to-other-team")
|
||||||
:id "move-to-other-team"
|
:id "move-to-other-team"
|
||||||
|
|||||||
46
frontend/src/app/main/ui/dashboard/nitrate_form.cljs
Normal file
46
frontend/src/app/main/ui/dashboard/nitrate_form.cljs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
;; 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.main.ui.dashboard.nitrate-form
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
|
||||||
|
[app.main.data.modal :as modal]
|
||||||
|
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||||
|
[app.main.ui.icons :as deprecated-icon]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(mf/defc nitrate-form-modal*
|
||||||
|
{::mf/register modal/components
|
||||||
|
::mf/register-as :nitrate-form}
|
||||||
|
[]
|
||||||
|
(let [on-click
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(dom/open-new-window "/control-center/licenses/start")))]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :modal-overlay)}
|
||||||
|
[:div {:class (stl/css :modal-container)}
|
||||||
|
[:div {:class (stl/css :nitrate-form)}
|
||||||
|
|
||||||
|
[:div {:class (stl/css :modal-header)}
|
||||||
|
[:h2 {:class (stl/css :modal-title)}
|
||||||
|
"BUY NITRATE"]
|
||||||
|
|
||||||
|
[:button {:class (stl/css :modal-close-btn)
|
||||||
|
:on-click modal/hide!} deprecated-icon/close]]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :modal-content)}
|
||||||
|
"Nitrate is so cool! You should buy it!"]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :modal-footer)}
|
||||||
|
[:div {:class (stl/css :action-buttons)}
|
||||||
|
[:> button* {:variant "primary"
|
||||||
|
:on-click on-click}
|
||||||
|
"BUY NOW!"]]]]]]))
|
||||||
|
|
||||||
|
|
||||||
52
frontend/src/app/main/ui/dashboard/nitrate_form.scss
Normal file
52
frontend/src/app/main/ui/dashboard/nitrate_form.scss
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
@extend .modal-overlay-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
@extend .modal-container-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
margin-bottom: deprecated.$s-24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
@include deprecated.uppercaseTitleTipography;
|
||||||
|
color: var(--modal-title-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn {
|
||||||
|
@extend .modal-close-btn-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
margin-bottom: deprecated.$s-24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitrate-form {
|
||||||
|
min-width: deprecated.$s-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
@extend .modal-action-btns;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button {
|
||||||
|
@extend .modal-cancel-btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-btn {
|
||||||
|
@extend .modal-accept-btn;
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
@extend .modal-danger-btn;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
[app.main.ui.components.link :refer [link]]
|
[app.main.ui.components.link :refer [link]]
|
||||||
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
|
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
|
||||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||||
|
[app.main.ui.dashboard.nitrate-form]
|
||||||
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
||||||
[app.main.ui.dashboard.subscription :refer [dashboard-cta*
|
[app.main.ui.dashboard.subscription :refer [dashboard-cta*
|
||||||
get-subscription-type
|
get-subscription-type
|
||||||
@@ -280,8 +281,8 @@
|
|||||||
|
|
||||||
(mf/defc teams-selector-dropdown*
|
(mf/defc teams-selector-dropdown*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [team profile teams] :rest props}]
|
[{:keys [team profile teams show-default-team allow-create-teams allow-create-org] :rest props}]
|
||||||
(let [on-create-click
|
(let [on-create-team-click
|
||||||
(mf/use-fn #(st/emit! (modal/show :team-form {})))
|
(mf/use-fn #(st/emit! (modal/show :team-form {})))
|
||||||
|
|
||||||
on-team-click
|
on-team-click
|
||||||
@@ -290,18 +291,27 @@
|
|||||||
(let [team-id (-> (dom/get-current-target event)
|
(let [team-id (-> (dom/get-current-target event)
|
||||||
(dom/get-data "value")
|
(dom/get-data "value")
|
||||||
(uuid/parse))]
|
(uuid/parse))]
|
||||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))]
|
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))
|
||||||
|
|
||||||
|
on-create-org-click
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(if (:nitrate-licence profile)
|
||||||
|
;; TODO update when org creation route is ready
|
||||||
|
(dom/open-new-window "/control-center/org/create")
|
||||||
|
(st/emit! (modal/show :nitrate-form {})))))]
|
||||||
|
|
||||||
[:> dropdown-menu* props
|
[:> dropdown-menu* props
|
||||||
|
|
||||||
[:> dropdown-menu-item* {:on-click on-team-click
|
(when show-default-team
|
||||||
:data-value (:default-team-id profile)
|
[:> dropdown-menu-item* {:on-click on-team-click
|
||||||
:class (stl/css :team-dropdown-item)}
|
:data-value (:default-team-id profile)
|
||||||
[:span {:class (stl/css :penpot-icon)} deprecated-icon/logo-icon]
|
:class (stl/css :team-dropdown-item)}
|
||||||
|
[:span {:class (stl/css :penpot-icon)} deprecated-icon/logo-icon]
|
||||||
|
|
||||||
[:span {:class (stl/css :team-text)} (tr "dashboard.your-penpot")]
|
[:span {:class (stl/css :team-text)} (tr "dashboard.your-penpot")]
|
||||||
(when (= (:default-team-id profile) (:id team))
|
(when (= (:default-team-id profile) (:id team))
|
||||||
tick-icon)]
|
tick-icon)])
|
||||||
|
|
||||||
(for [team-item (remove :is-default (vals teams))]
|
(for [team-item (remove :is-default (vals teams))]
|
||||||
[:> dropdown-menu-item* {:on-click on-team-click
|
[:> dropdown-menu-item* {:on-click on-team-click
|
||||||
@@ -322,11 +332,19 @@
|
|||||||
(when (= (:id team-item) (:id team))
|
(when (= (:id team-item) (:id team))
|
||||||
tick-icon)])
|
tick-icon)])
|
||||||
|
|
||||||
[:hr {:role "separator" :class (stl/css :team-separator)}]
|
(when allow-create-teams
|
||||||
[:> dropdown-menu-item* {:on-click on-create-click
|
[:hr {:role "separator" :class (stl/css :team-separator)}]
|
||||||
:class (stl/css :team-dropdown-item :action)}
|
[:> dropdown-menu-item* {:on-click on-create-team-click
|
||||||
[:span {:class (stl/css :icon-wrapper)} add-icon]
|
:class (stl/css :team-dropdown-item :action)}
|
||||||
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-team")]]]))
|
[:span {:class (stl/css :icon-wrapper)} add-icon]
|
||||||
|
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-team")]])
|
||||||
|
|
||||||
|
(when allow-create-org
|
||||||
|
[:hr {:role "separator" :class (stl/css :team-separator)}]
|
||||||
|
[:> dropdown-menu-item* {:on-click on-create-org-click
|
||||||
|
:class (stl/css :team-dropdown-item :action)}
|
||||||
|
[:span {:class (stl/css :icon-wrapper)} add-icon]
|
||||||
|
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-org")]])]))
|
||||||
|
|
||||||
(mf/defc team-options-dropdown*
|
(mf/defc team-options-dropdown*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
@@ -476,9 +494,80 @@
|
|||||||
:data-testid "delete-team"}
|
:data-testid "delete-team"}
|
||||||
(tr "dashboard.delete-team")])]))
|
(tr "dashboard.delete-team")])]))
|
||||||
|
|
||||||
|
|
||||||
|
(mf/defc sidebar-org-switch*
|
||||||
|
[{:keys [team profile]}]
|
||||||
|
(let [teams (->> (mf/deref refs/teams)
|
||||||
|
vals
|
||||||
|
(group-by :organization-id)
|
||||||
|
(map (fn [[_group entries]] (first entries)))
|
||||||
|
vec
|
||||||
|
(d/index-by :id))
|
||||||
|
|
||||||
|
teams (update-vals teams
|
||||||
|
(fn [t]
|
||||||
|
(assoc t :name (str "ORG: " (:organization-name t)))))
|
||||||
|
|
||||||
|
team (assoc team :name (str "ORG: " (:organization-name team)))
|
||||||
|
|
||||||
|
show-teams-menu*
|
||||||
|
(mf/use-state false)
|
||||||
|
|
||||||
|
show-teams-menu?
|
||||||
|
(deref show-teams-menu*)
|
||||||
|
|
||||||
|
on-show-teams-click
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(swap! show-teams-menu* not)))
|
||||||
|
|
||||||
|
on-show-teams-keydown
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [event]
|
||||||
|
(when (or (kbd/space? event)
|
||||||
|
(kbd/enter? event))
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(some-> (dom/get-current-target event)
|
||||||
|
(dom/click!)))))
|
||||||
|
close-teams-menu
|
||||||
|
(mf/use-fn #(reset! show-teams-menu* false))]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :sidebar-team-switch)}
|
||||||
|
[:div {:class (stl/css :switch-content)}
|
||||||
|
[:button {:class (stl/css :current-team)
|
||||||
|
:on-click on-show-teams-click
|
||||||
|
:on-key-down on-show-teams-keydown}
|
||||||
|
|
||||||
|
[:div {:class (stl/css :team-name)}
|
||||||
|
[:img {:src (cf/resolve-team-photo-url team)
|
||||||
|
:class (stl/css :team-picture)
|
||||||
|
:alt (:name team)}]
|
||||||
|
[:span {:class (stl/css :team-text) :title (:name team)} (:name team)]]
|
||||||
|
|
||||||
|
arrow-icon]]
|
||||||
|
|
||||||
|
;; Teams Dropdown
|
||||||
|
|
||||||
|
[:> teams-selector-dropdown* {:show show-teams-menu?
|
||||||
|
:on-close close-teams-menu
|
||||||
|
:id "organizations-list"
|
||||||
|
:class (stl/css :dropdown :teams-dropdown)
|
||||||
|
:team team
|
||||||
|
:profile profile
|
||||||
|
:teams teams
|
||||||
|
:show-default-team false
|
||||||
|
:allow-create-teams false
|
||||||
|
:allow-create-org true}]]))
|
||||||
|
|
||||||
(mf/defc sidebar-team-switch*
|
(mf/defc sidebar-team-switch*
|
||||||
[{:keys [team profile]}]
|
[{:keys [team profile]}]
|
||||||
(let [teams (mf/deref refs/teams)
|
(let [nitrate? (contains? cf/flags :nitrate)
|
||||||
|
org-id (when nitrate? (:organization-id team))
|
||||||
|
teams (cond->> (mf/deref refs/teams)
|
||||||
|
nitrate?
|
||||||
|
(filter #(= (-> % val :organization-id) org-id)))
|
||||||
|
|
||||||
subscription
|
subscription
|
||||||
(get team :subscription)
|
(get team :subscription)
|
||||||
@@ -586,7 +675,10 @@
|
|||||||
:class (stl/css :dropdown :teams-dropdown)
|
:class (stl/css :dropdown :teams-dropdown)
|
||||||
:team team
|
:team team
|
||||||
:profile profile
|
:profile profile
|
||||||
:teams teams}]
|
:teams teams
|
||||||
|
:show-default-team true
|
||||||
|
:allow-create-teams true
|
||||||
|
:allow-create-org false}]
|
||||||
|
|
||||||
[:> team-options-dropdown* {:show show-team-options-menu?
|
[:> team-options-dropdown* {:show show-team-options-menu?
|
||||||
:on-close close-team-options-menu
|
:on-close close-team-options-menu
|
||||||
@@ -703,6 +795,8 @@
|
|||||||
[:*
|
[:*
|
||||||
[:div {:class (stl/css-case :sidebar-content true)
|
[:div {:class (stl/css-case :sidebar-content true)
|
||||||
:ref container}
|
:ref container}
|
||||||
|
(when (contains? cf/flags :nitrate)
|
||||||
|
[:> sidebar-org-switch* {:team team :profile profile}])
|
||||||
[:> sidebar-team-switch* {:team team :profile profile}]
|
[:> sidebar-team-switch* {:team team :profile profile}]
|
||||||
|
|
||||||
[:> sidebar-search* {:search-term search-term
|
[:> sidebar-search* {:search-term search-term
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||||
[app.main.ui.ds.product.milestone :refer [milestone*]]
|
[app.main.ui.ds.product.milestone :refer [milestone*]]
|
||||||
[app.main.ui.ds.product.milestone-group :refer [milestone-group*]]
|
[app.main.ui.ds.product.milestone-group :refer [milestone-group*]]
|
||||||
|
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||||
[app.main.ui.ds.storybook :as sb]
|
[app.main.ui.ds.storybook :as sb]
|
||||||
[app.main.ui.ds.tooltip.tooltip :refer [tooltip*]]
|
[app.main.ui.ds.tooltip.tooltip :refer [tooltip*]]
|
||||||
[app.main.ui.ds.utilities.date :refer [date*]]
|
[app.main.ui.ds.utilities.date :refer [date*]]
|
||||||
@@ -81,6 +82,7 @@
|
|||||||
:Milestone milestone*
|
:Milestone milestone*
|
||||||
:MilestoneGroup milestone-group*
|
:MilestoneGroup milestone-group*
|
||||||
:Date date*
|
:Date date*
|
||||||
|
:PanelTitle panel-title*
|
||||||
|
|
||||||
:set-default-translations
|
:set-default-translations
|
||||||
(fn [data]
|
(fn [data]
|
||||||
|
|||||||
34
frontend/src/app/main/ui/ds/product/panel_title.cljs
Normal file
34
frontend/src/app/main/ui/ds/product/panel_title.cljs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
;; 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.main.ui.ds.product.panel-title
|
||||||
|
(:require-macros
|
||||||
|
[app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||||
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
|
[app.util.i18n :refer [tr]]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(def ^:private schema:panel-title
|
||||||
|
[:map
|
||||||
|
[:class {:optional true} :string]
|
||||||
|
[:text :string]
|
||||||
|
[:on-close {:optional true} fn?]])
|
||||||
|
|
||||||
|
(mf/defc panel-title*
|
||||||
|
{::mf/schema schema:panel-title}
|
||||||
|
[{:keys [class text on-close] :rest props}]
|
||||||
|
(let [props
|
||||||
|
(mf/spread-props props {:class [class (stl/css :panel-title)]})]
|
||||||
|
|
||||||
|
[:> :div props
|
||||||
|
[:span {:class (stl/css :panel-title-text)} text]
|
||||||
|
(when on-close
|
||||||
|
[:> icon-button* {:variant "ghost"
|
||||||
|
:aria-label (tr "labels.close")
|
||||||
|
:on-click on-close
|
||||||
|
:icon i/close}])]))
|
||||||
26
frontend/src/app/main/ui/ds/product/panel_title.mdx
Normal file
26
frontend/src/app/main/ui/ds/product/panel_title.mdx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{ /* 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 */ }
|
||||||
|
|
||||||
|
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
|
||||||
|
import * as PanelTitle from "./panel_title.stories";
|
||||||
|
|
||||||
|
<Meta title="Product/PanelTitle" />
|
||||||
|
|
||||||
|
# PanelTitle
|
||||||
|
|
||||||
|
The `panel-title*` is used as a header for some sidebar sections.
|
||||||
|
|
||||||
|
<Canvas of={PanelTitle.Default} />
|
||||||
|
|
||||||
|
## Technical notes
|
||||||
|
|
||||||
|
The only mandatory parameter is `text`. Usually you'll want to pass a function property `on-close` that will be called when the user clicks on the close button on the right.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[:> panel-title* {:class class
|
||||||
|
:text text
|
||||||
|
:on-close on-close}]
|
||||||
|
```
|
||||||
25
frontend/src/app/main/ui/ds/product/panel_title.scss
Normal file
25
frontend/src/app/main/ui/ds/product/panel_title.scss
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/typography.scss" as t;
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
block-size: $sz-32;
|
||||||
|
border-radius: $br-8;
|
||||||
|
background-color: var(--color-background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title-text {
|
||||||
|
@include t.use-typography("headline-small");
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-foreground-primary);
|
||||||
|
}
|
||||||
21
frontend/src/app/main/ui/ds/product/panel_title.stories.jsx
Normal file
21
frontend/src/app/main/ui/ds/product/panel_title.stories.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import Components from "@target/components";
|
||||||
|
|
||||||
|
const { PanelTitle } = Components;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Product/PanelTitle",
|
||||||
|
component: PanelTitle,
|
||||||
|
argTypes: {
|
||||||
|
text: {
|
||||||
|
control: { type: "text" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
text: "Lorem ipsum",
|
||||||
|
onClose: () => null,
|
||||||
|
},
|
||||||
|
render: ({ ...args }) => <PanelTitle {...args} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = {};
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
touched? (and (contains? (:data @form) input-name)
|
touched? (and (contains? (:data @form) input-name)
|
||||||
(get-in @form [:touched input-name]))
|
(get-in @form [:touched input-name]))
|
||||||
|
|
||||||
error (get-in @form [:errors input-name])
|
error (get-in @form [:errors input-name])
|
||||||
|
|
||||||
value (get-in @form [:data input-name] "")
|
value (get-in @form [:data input-name] "")
|
||||||
@@ -52,7 +53,8 @@
|
|||||||
(let [form (mf/use-ctx context)
|
(let [form (mf/use-ctx context)
|
||||||
disabled? (or (and (some? form)
|
disabled? (or (and (some? form)
|
||||||
(or (not (:valid @form))
|
(or (not (:valid @form))
|
||||||
(seq (:external-errors @form))))
|
(seq (:async-errors @form))
|
||||||
|
(seq (:extra-errors @form))))
|
||||||
(true? disabled))
|
(true? disabled))
|
||||||
handle-key-down-save
|
handle-key-down-save
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
[app.main.ui.comments :as cmt]
|
[app.main.ui.comments :as cmt]
|
||||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
|
||||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||||
|
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||||
[app.main.ui.icons :as deprecated-icon]
|
[app.main.ui.icons :as deprecated-icon]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
@@ -121,15 +121,12 @@
|
|||||||
(st/emit! (with-meta (dcmt/open-thread thread) {::ev/origin "viewer"}))
|
(st/emit! (with-meta (dcmt/open-thread thread) {::ev/origin "viewer"}))
|
||||||
(st/emit! (dwcm/navigate-to-comment thread)))))]
|
(st/emit! (dwcm/navigate-to-comment thread)))))]
|
||||||
|
|
||||||
[:div {:class (stl/css-case :comments-section true
|
[:div {:class (stl/css-case :comments-section true
|
||||||
:from-viewer from-viewer)}
|
:from-viewer from-viewer)}
|
||||||
[:div {:class (stl/css-case :comments-section-title true
|
|
||||||
:viewer-title from-viewer)}
|
[:> panel-title* {:class (stl/css :comments-title)
|
||||||
[:span (tr "labels.comments")]
|
:text (tr "labels.comments")
|
||||||
[:> icon-button* {:variant "ghost"
|
:on-close close-section}]
|
||||||
:aria-label (tr "labels.close")
|
|
||||||
:on-click close-section
|
|
||||||
:icon i/close}]]
|
|
||||||
|
|
||||||
[:button {:class (stl/css :mode-dropdown-wrapper)
|
[:button {:class (stl/css :mode-dropdown-wrapper)
|
||||||
:on-click toggle-mode-selector}
|
:on-click toggle-mode-selector}
|
||||||
|
|||||||
@@ -18,25 +18,8 @@
|
|||||||
padding: 0 deprecated.$s-8;
|
padding: 0 deprecated.$s-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments-section-title {
|
.comments-title {
|
||||||
@include deprecated.flexCenter;
|
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
|
||||||
@include deprecated.uppercaseTitleTipography;
|
|
||||||
position: relative;
|
|
||||||
height: deprecated.$s-32;
|
|
||||||
min-height: deprecated.$s-32;
|
|
||||||
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
|
|
||||||
border-radius: deprecated.$br-8;
|
|
||||||
background-color: var(--panel-title-background-color);
|
|
||||||
span {
|
|
||||||
@include deprecated.flexCenter;
|
|
||||||
flex-grow: 1;
|
|
||||||
color: var(--title-foreground-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewer-title {
|
|
||||||
margin: 0;
|
|
||||||
margin-block-start: deprecated.$s-8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-dropdown-wrapper {
|
.mode-dropdown-wrapper {
|
||||||
|
|||||||
@@ -11,12 +11,11 @@
|
|||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
|
||||||
[app.main.ui.icons :as deprecated-icon]
|
[app.main.ui.icons :as deprecated-icon]
|
||||||
[app.util.debug :as dbg]
|
[app.util.debug :as dbg]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(mf/defc debug-panel*
|
(mf/defc debug-panel*
|
||||||
@@ -35,12 +34,9 @@
|
|||||||
(st/emit! (dw/remove-layout-flag :debug-panel))))]
|
(st/emit! (dw/remove-layout-flag :debug-panel))))]
|
||||||
|
|
||||||
[:div {:class (dm/str class " " (stl/css :debug-panel))}
|
[:div {:class (dm/str class " " (stl/css :debug-panel))}
|
||||||
[:div {:class (stl/css :panel-title)}
|
[:> panel-title* {:class (stl/css :debug-panel-title)
|
||||||
[:span "Debugging tools"]
|
:text (tr "workspace.debug.title")
|
||||||
[:> icon-button* {:variant "ghost"
|
:on-close handle-close}]
|
||||||
:aria-label (tr "labels.close")
|
|
||||||
:on-click handle-close
|
|
||||||
:icon i/close}]]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :debug-panel-inner)}
|
[:div {:class (stl/css :debug-panel-inner)}
|
||||||
(for [option (sort-by d/name dbg/options)]
|
(for [option (sort-by d/name dbg/options)]
|
||||||
|
|||||||
@@ -12,21 +12,12 @@
|
|||||||
background-color: var(--panel-background-color);
|
background-color: var(--panel-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-title {
|
.debug-panel-title {
|
||||||
@include deprecated.flexCenter;
|
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
|
||||||
@include deprecated.uppercaseTitleTipography;
|
}
|
||||||
position: relative;
|
|
||||||
height: deprecated.$s-32;
|
|
||||||
min-height: deprecated.$s-32;
|
|
||||||
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
|
|
||||||
border-radius: deprecated.$br-8;
|
|
||||||
background-color: var(--panel-title-background-color);
|
|
||||||
|
|
||||||
span {
|
.debug-panel-inner {
|
||||||
@include deprecated.flexCenter;
|
padding: deprecated.$s-16 deprecated.$s-8;
|
||||||
flex-grow: 1;
|
|
||||||
color: var(--title-foreground-color-hover);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-wrapper {
|
.checkbox-wrapper {
|
||||||
@@ -39,7 +30,3 @@
|
|||||||
@extend .checkbox-icon;
|
@extend .checkbox-icon;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.debug-panel-inner {
|
|
||||||
padding: deprecated.$s-16 deprecated.$s-8;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.icons :as deprecated-icon]
|
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||||
[debug :as dbg]
|
[debug :as dbg]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
@@ -125,11 +125,9 @@
|
|||||||
(map (d/getf objects)))]
|
(map (d/getf objects)))]
|
||||||
|
|
||||||
[:div {:class (stl/css :shape-info)}
|
[:div {:class (stl/css :shape-info)}
|
||||||
[:div {:class (stl/css :shape-info-title)}
|
[:> panel-title* {:class (stl/css :shape-info-title)
|
||||||
[:span "Debug"]
|
:text "Debug"
|
||||||
[:div {:class (stl/css :close-button)
|
:on-close #(dbg/disable! :shape-panel)}]
|
||||||
:on-click #(dbg/disable! :shape-panel)}
|
|
||||||
deprecated-icon/close]]
|
|
||||||
|
|
||||||
(if (empty? selected)
|
(if (empty? selected)
|
||||||
[:div {:class (stl/css :attrs-container)} "No shapes selected"]
|
[:div {:class (stl/css :attrs-container)} "No shapes selected"]
|
||||||
|
|||||||
@@ -16,34 +16,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.shape-info-title {
|
.shape-info-title {
|
||||||
@include deprecated.flexCenter;
|
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
|
||||||
@include deprecated.uppercaseTitleTipography;
|
|
||||||
position: relative;
|
|
||||||
height: deprecated.$s-32;
|
|
||||||
min-height: deprecated.$s-32;
|
|
||||||
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
|
|
||||||
border-radius: deprecated.$br-8;
|
|
||||||
background-color: var(--panel-title-background-color);
|
|
||||||
|
|
||||||
span {
|
|
||||||
@include deprecated.flexCenter;
|
|
||||||
flex-grow: 1;
|
|
||||||
color: var(--title-foreground-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
@extend .button-tertiary;
|
|
||||||
position: absolute;
|
|
||||||
right: deprecated.$s-2;
|
|
||||||
top: deprecated.$s-2;
|
|
||||||
height: deprecated.$s-28;
|
|
||||||
width: deprecated.$s-28;
|
|
||||||
border-radius: deprecated.$br-6;
|
|
||||||
svg {
|
|
||||||
@extend .button-icon;
|
|
||||||
stroke: var(--icon-foreground);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.attrs-container {
|
.attrs-container {
|
||||||
|
|||||||
@@ -13,23 +13,6 @@
|
|||||||
background-color: var(--panel-background-color);
|
background-color: var(--panel-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-toolbox-title {
|
|
||||||
@include deprecated.flexCenter;
|
|
||||||
@include deprecated.uppercaseTitleTipography;
|
|
||||||
position: relative;
|
|
||||||
height: deprecated.$s-32;
|
|
||||||
min-height: deprecated.$s-32;
|
|
||||||
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
|
|
||||||
border-radius: deprecated.$br-8;
|
|
||||||
background-color: var(--panel-title-background-color);
|
|
||||||
|
|
||||||
span {
|
|
||||||
@include deprecated.flexCenter;
|
|
||||||
flex-grow: 1;
|
|
||||||
color: var(--title-foreground-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-entry-empty {
|
.history-entry-empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
[app.main.data.workspace.shortcuts]
|
[app.main.data.workspace.shortcuts]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
|
||||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||||
|
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[app.util.strings :refer [matches-search]]
|
[app.util.strings :refer [matches-search]]
|
||||||
@@ -487,13 +487,9 @@
|
|||||||
(dom/focus! (dom/get-element "shortcut-search")))
|
(dom/focus! (dom/get-element "shortcut-search")))
|
||||||
|
|
||||||
[:div {:class (dm/str class " " (stl/css :shortcuts))}
|
[:div {:class (dm/str class " " (stl/css :shortcuts))}
|
||||||
[:div {:class (stl/css :shortcuts-header)}
|
[:> panel-title* {:class (stl/css :shortcuts-title)
|
||||||
[:div {:class (stl/css :shortcuts-title)} (tr "shortcuts.title")]
|
:text (tr "shortcuts.title")
|
||||||
[:> icon-button* {:variant "ghost"
|
:on-close close-fn}]
|
||||||
:icon i/close
|
|
||||||
:class (stl/css :shortcuts-close-button)
|
|
||||||
:on-click close-fn
|
|
||||||
:aria-label (tr "labels.close")}]]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :search-field)}
|
[:div {:class (stl/css :search-field)}
|
||||||
[:> search-bar* {:on-change on-search-term-change-2
|
[:> search-bar* {:on-change on-search-term-change-2
|
||||||
|
|||||||
@@ -18,27 +18,8 @@
|
|||||||
margin: deprecated.$s-16 deprecated.$s-12 deprecated.$s-4 deprecated.$s-12;
|
margin: deprecated.$s-16 deprecated.$s-12 deprecated.$s-4 deprecated.$s-12;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shortcuts-header {
|
.shortcuts-title {
|
||||||
@include deprecated.flexCenter;
|
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
|
||||||
@include deprecated.uppercaseTitleTipography;
|
|
||||||
position: relative;
|
|
||||||
height: deprecated.$s-32;
|
|
||||||
padding: deprecated.$s-2 deprecated.$s-2 deprecated.$s-2 0;
|
|
||||||
margin: deprecated.$s-4 deprecated.$s-4 0 deprecated.$s-4;
|
|
||||||
border-radius: deprecated.$br-6;
|
|
||||||
background-color: var(--panel-title-background-color);
|
|
||||||
|
|
||||||
.shortcuts-title {
|
|
||||||
@include deprecated.flexCenter;
|
|
||||||
flex-grow: 1;
|
|
||||||
color: var(--title-foreground-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcuts-close-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [tokens-lib selected-token-set-id]}]
|
[{:keys [tokens-lib selected-token-set-id]}]
|
||||||
(let [selected-token-set
|
(let [selected-token-set
|
||||||
(mf/with-memo [tokens-lib]
|
(mf/with-memo [tokens-lib selected-token-set-id]
|
||||||
(when selected-token-set-id
|
(when selected-token-set-id
|
||||||
(some-> tokens-lib (ctob/get-set selected-token-set-id))))
|
(some-> tokens-lib (ctob/get-set selected-token-set-id))))
|
||||||
|
|
||||||
@@ -62,18 +62,20 @@
|
|||||||
[:div {:class (stl/css :sets-header-container)}
|
[:div {:class (stl/css :sets-header-container)}
|
||||||
[:> text* {:as "span"
|
[:> text* {:as "span"
|
||||||
:typography "headline-small"
|
:typography "headline-small"
|
||||||
:class (stl/css :sets-header)}
|
:class (stl/css :sets-header)
|
||||||
|
:data-testid "active-token-set-title"}
|
||||||
(tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
|
(tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
|
||||||
[:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")}
|
(when (and (some? selected-token-set-id)
|
||||||
|
(not (token-set-active? (ctob/get-name selected-token-set))))
|
||||||
|
[:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")}
|
||||||
;; NOTE: when no set in tokens-lib, the selected-token-set-id
|
;; NOTE: when no set in tokens-lib, the selected-token-set-id
|
||||||
;; will be `nil`, so for properly hide the inactive message we
|
;; will be `nil`, so for properly hide the inactive message we
|
||||||
;; check that at least `selected-token-set-id` has a value
|
;; check that at least `selected-token-set-id` has a value
|
||||||
(when (and (some? selected-token-set-id)
|
|
||||||
(not (token-set-active? (ctob/get-name selected-token-set))))
|
|
||||||
[:*
|
[:*
|
||||||
[:> icon* {:class (stl/css :sets-header-status-icon) :icon-id i/eye-off}]
|
[:> icon* {:class (stl/css :sets-header-status-icon) :icon-id i/eye-off}]
|
||||||
[:> text* {:as "span" :typography "body-small" :class (stl/css :sets-header-status-text)}
|
[:> text* {:as "span" :typography "body-small" :class (stl/css :sets-header-status-text)}
|
||||||
(tr "workspace.tokens.inactive-set")]])]]))
|
(tr "workspace.tokens.inactive-set")]]])]))
|
||||||
|
|
||||||
(mf/defc tokens-section*
|
(mf/defc tokens-section*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
@@ -158,7 +160,7 @@
|
|||||||
[:& token-context-menu]
|
[:& token-context-menu]
|
||||||
[:> token-node-context-menu* {:on-delete-node delete-node}]
|
[:> token-node-context-menu* {:on-delete-node delete-node}]
|
||||||
|
|
||||||
[:& selected-set-info* {:tokens-lib tokens-lib
|
[:> selected-set-info* {:tokens-lib tokens-lib
|
||||||
:selected-token-set-id selected-token-set-id}]
|
:selected-token-set-id selected-token-set-id}]
|
||||||
|
|
||||||
(for [type filled-group]
|
(for [type filled-group]
|
||||||
|
|||||||
@@ -140,6 +140,9 @@
|
|||||||
error
|
error
|
||||||
(get-in @form [:errors input-name])
|
(get-in @form [:errors input-name])
|
||||||
|
|
||||||
|
extra-error
|
||||||
|
(get-in @form [:extra-errors input-name])
|
||||||
|
|
||||||
value
|
value
|
||||||
(get-in @form [:data input-name] "")
|
(get-in @form [:data input-name] "")
|
||||||
|
|
||||||
@@ -247,9 +250,14 @@
|
|||||||
:hint-type (:type hint)})
|
:hint-type (:type hint)})
|
||||||
|
|
||||||
props
|
props
|
||||||
(if (and error touched?)
|
(cond
|
||||||
|
(and error touched?)
|
||||||
(mf/spread-props props {:hint-type "error"
|
(mf/spread-props props {:hint-type "error"
|
||||||
:hint-message (:message error)})
|
:hint-message (:message error)})
|
||||||
|
(and extra-error touched?)
|
||||||
|
(mf/spread-props props {:hint-type "error"
|
||||||
|
:hint-message (:message extra-error)})
|
||||||
|
:else
|
||||||
props)]
|
props)]
|
||||||
|
|
||||||
(mf/with-effect [resolve-stream tokens token input-name]
|
(mf/with-effect [resolve-stream tokens token input-name]
|
||||||
|
|||||||
@@ -236,12 +236,14 @@
|
|||||||
(on-composite-input-change form field value false))
|
(on-composite-input-change form field value false))
|
||||||
([form field value trim?]
|
([form field value trim?]
|
||||||
(letfn [(clean-errors [errors]
|
(letfn [(clean-errors [errors]
|
||||||
(-> errors
|
(some-> errors
|
||||||
(dissoc field)
|
(update :value #(when (map? %) (dissoc % field)))
|
||||||
(not-empty)))]
|
(update :value #(when (seq %) %))
|
||||||
|
(not-empty)))]
|
||||||
(swap! form (fn [state]
|
(swap! form (fn [state]
|
||||||
(-> state
|
(-> state
|
||||||
(assoc-in [:data :value field] (if trim? (str/trim value) value))
|
(assoc-in [:data :value field] (if trim? (str/trim value) value))
|
||||||
|
(assoc-in [:touched :value field] true)
|
||||||
(update :errors clean-errors)
|
(update :errors clean-errors)
|
||||||
(update :extra-errors clean-errors)))))))
|
(update :extra-errors clean-errors)))))))
|
||||||
|
|
||||||
@@ -257,6 +259,9 @@
|
|||||||
value
|
value
|
||||||
(get-in @form [:data :value input-name] "")
|
(get-in @form [:data :value input-name] "")
|
||||||
|
|
||||||
|
touched?
|
||||||
|
(get-in @form [:touched :value input-name])
|
||||||
|
|
||||||
resolve-stream
|
resolve-stream
|
||||||
(mf/with-memo [token]
|
(mf/with-memo [token]
|
||||||
(if-let [value (get-in token [:value input-name])]
|
(if-let [value (get-in token [:value input-name])]
|
||||||
@@ -284,7 +289,7 @@
|
|||||||
:hint-message (:message hint)
|
:hint-message (:message hint)
|
||||||
:hint-type (:type hint)})
|
:hint-type (:type hint)})
|
||||||
props
|
props
|
||||||
(if error
|
(if (and touched? error)
|
||||||
(mf/spread-props props {:hint-type "error"
|
(mf/spread-props props {:hint-type "error"
|
||||||
:hint-message (:message error)})
|
:hint-message (:message error)})
|
||||||
props)
|
props)
|
||||||
@@ -332,6 +337,7 @@
|
|||||||
message (tr "workspace.tokens.resolved-value" (or resolved-value value))]
|
message (tr "workspace.tokens.resolved-value" (or resolved-value value))]
|
||||||
(swap! form update :errors dissoc :value)
|
(swap! form update :errors dissoc :value)
|
||||||
(swap! form update :extra-errors dissoc :value)
|
(swap! form update :extra-errors dissoc :value)
|
||||||
|
(swap! form update :async-errors dissoc :reference)
|
||||||
(if (= input-value (str resolved-value))
|
(if (= input-value (str resolved-value))
|
||||||
(reset! hint* {})
|
(reset! hint* {})
|
||||||
(reset! hint* {:message message :type "hint"})))))))]
|
(reset! hint* {:message message :type "hint"})))))))]
|
||||||
|
|||||||
@@ -23,21 +23,19 @@
|
|||||||
(let [token-type
|
(let [token-type
|
||||||
(or (:type token) token-type)
|
(or (:type token) token-type)
|
||||||
|
|
||||||
tokens-in-selected-set
|
|
||||||
(mf/deref refs/workspace-all-tokens-in-selected-set)
|
|
||||||
|
|
||||||
token-path
|
token-path
|
||||||
(mf/with-memo [token]
|
(mf/with-memo [token]
|
||||||
(cft/token-name->path (:name token)))
|
(cft/token-name->path (:name token)))
|
||||||
|
|
||||||
tokens-tree-in-selected-set
|
all-tokens (mf/deref refs/workspace-all-tokens-map)
|
||||||
(mf/with-memo [token-path tokens-in-selected-set]
|
|
||||||
(-> (ctob/tokens-tree tokens-in-selected-set)
|
all-tokens
|
||||||
|
(mf/with-memo [token-path all-tokens]
|
||||||
|
(-> (ctob/tokens-tree all-tokens)
|
||||||
(d/dissoc-in token-path)))
|
(d/dissoc-in token-path)))
|
||||||
props
|
props
|
||||||
(mf/spread-props props {:token-type token-type
|
(mf/spread-props props {:token-type token-type
|
||||||
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
:all-token-tree all-tokens
|
||||||
:tokens-in-selected-set tokens-in-selected-set
|
|
||||||
:token token})
|
:token token})
|
||||||
text-case-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-case-value-enter")})
|
text-case-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-case-value-enter")})
|
||||||
text-decoration-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-decoration-value-enter")})
|
text-decoration-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-decoration-value-enter")})
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
[app.main.data.helpers :as dh]
|
[app.main.data.helpers :as dh]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.workspace.tokens.application :as dwta]
|
[app.main.data.workspace.tokens.application :as dwta]
|
||||||
|
[app.main.data.workspace.tokens.errors :as wte]
|
||||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||||
[app.main.data.workspace.tokens.remapping :as remap]
|
[app.main.data.workspace.tokens.remapping :as remap]
|
||||||
@@ -88,14 +89,13 @@
|
|||||||
action
|
action
|
||||||
is-create
|
is-create
|
||||||
selected-token-set-id
|
selected-token-set-id
|
||||||
tokens-tree-in-selected-set
|
all-token-tree
|
||||||
token-type
|
token-type
|
||||||
make-schema
|
make-schema
|
||||||
input-component
|
input-component
|
||||||
initial
|
initial
|
||||||
type
|
type
|
||||||
value-subfield
|
value-subfield
|
||||||
tokens-in-selected-set
|
|
||||||
input-value-placeholder] :as props}]
|
input-value-placeholder] :as props}]
|
||||||
|
|
||||||
(let [make-schema (or make-schema default-make-schema)
|
(let [make-schema (or make-schema default-make-schema)
|
||||||
@@ -105,13 +105,6 @@
|
|||||||
active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite))
|
active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite))
|
||||||
active-tab (deref active-tab*)
|
active-tab (deref active-tab*)
|
||||||
|
|
||||||
on-toggle-tab
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps)
|
|
||||||
(fn [new-tab]
|
|
||||||
(let [new-tab (keyword new-tab)]
|
|
||||||
(reset! active-tab* new-tab))))
|
|
||||||
|
|
||||||
token
|
token
|
||||||
(mf/with-memo [token]
|
(mf/with-memo [token]
|
||||||
(or token {:type token-type}))
|
(or token {:type token-type}))
|
||||||
@@ -124,6 +117,9 @@
|
|||||||
tokens
|
tokens
|
||||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||||
|
|
||||||
|
tokens-in-selected-set
|
||||||
|
(mf/deref refs/workspace-all-tokens-in-selected-set)
|
||||||
|
|
||||||
tokens
|
tokens
|
||||||
(mf/with-memo [tokens tokens-in-selected-set token]
|
(mf/with-memo [tokens tokens-in-selected-set token]
|
||||||
;; Ensure that the resolved value uses the currently editing token
|
;; Ensure that the resolved value uses the currently editing token
|
||||||
@@ -134,8 +130,8 @@
|
|||||||
(assoc (:name token) token)))
|
(assoc (:name token) token)))
|
||||||
|
|
||||||
schema
|
schema
|
||||||
(mf/with-memo [tokens-tree-in-selected-set active-tab]
|
(mf/with-memo [all-token-tree active-tab]
|
||||||
(make-schema tokens-tree-in-selected-set active-tab))
|
(make-schema all-token-tree active-tab))
|
||||||
|
|
||||||
initial
|
initial
|
||||||
(mf/with-memo [token]
|
(mf/with-memo [token]
|
||||||
@@ -148,6 +144,17 @@
|
|||||||
(fm/use-form :schema schema
|
(fm/use-form :schema schema
|
||||||
:initial initial)
|
:initial initial)
|
||||||
|
|
||||||
|
on-toggle-tab
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps form)
|
||||||
|
(fn [new-tab]
|
||||||
|
(let [new-tab (keyword new-tab)]
|
||||||
|
(if (= new-tab :reference)
|
||||||
|
(swap! form assoc-in [:async-errors :reference]
|
||||||
|
{:message "Need valid reference"})
|
||||||
|
(swap! form update :async-errors dissoc :reference))
|
||||||
|
(reset! active-tab* new-tab))))
|
||||||
|
|
||||||
on-cancel
|
on-cancel
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [e]
|
(fn [e]
|
||||||
@@ -224,7 +231,12 @@
|
|||||||
:description description}))
|
:description description}))
|
||||||
(dwtl/toggle-token-path path)
|
(dwtl/toggle-token-path path)
|
||||||
(dwtp/propagate-workspace-tokens)
|
(dwtp/propagate-workspace-tokens)
|
||||||
(modal/hide!))))))))))]
|
(modal/hide!)))))
|
||||||
|
;; WORKAROUND: display validation errors in the form instead of crashing
|
||||||
|
(fn [{:keys [errors]}]
|
||||||
|
(let [error-messages (wte/humanize-errors errors)
|
||||||
|
error-message (first error-messages)]
|
||||||
|
(swap! form assoc-in [:extra-errors :value] {:message error-message}))))))))]
|
||||||
|
|
||||||
[:> fc/form* {:class (stl/css :form-wrapper)
|
[:> fc/form* {:class (stl/css :form-wrapper)
|
||||||
:form form
|
:form form
|
||||||
|
|||||||
@@ -291,6 +291,7 @@
|
|||||||
[:color {:optional true} [:maybe :string]]
|
[:color {:optional true} [:maybe :string]]
|
||||||
[:color-result {:optional true} ::sm/any]
|
[:color-result {:optional true} ::sm/any]
|
||||||
[:inset {:optional true} [:maybe :boolean]]]]]
|
[:inset {:optional true} [:maybe :boolean]]]]]
|
||||||
|
|
||||||
(if (= active-tab :reference)
|
(if (= active-tab :reference)
|
||||||
[:reference {:optional false} ::sm/text]
|
[:reference {:optional false} ::sm/text]
|
||||||
[:reference {:optional true} [:maybe :string]])]]
|
[:reference {:optional true} [:maybe :string]])]]
|
||||||
|
|||||||
@@ -228,7 +228,7 @@
|
|||||||
:class (stl/css :main-toolbar-options-button)
|
:class (stl/css :main-toolbar-options-button)
|
||||||
:icon i/bug
|
:icon i/bug
|
||||||
:aria-pressed (contains? layout :debug-panel)
|
:aria-pressed (contains? layout :debug-panel)
|
||||||
:aria-label "Debugging tool"
|
:aria-label (tr "workspace.toolbar.debug")
|
||||||
:tooltip-placement "bottom"
|
:tooltip-placement "bottom"
|
||||||
:on-click toggle-debug-panel}]])]]
|
:on-click toggle-debug-panel}]])]]
|
||||||
|
|
||||||
|
|||||||
@@ -1185,7 +1185,6 @@
|
|||||||
{:cmd :export-shapes
|
{:cmd :export-shapes
|
||||||
:profile-id (:profile-id @st/state)
|
:profile-id (:profile-id @st/state)
|
||||||
:wait true
|
:wait true
|
||||||
:skip-children (:skip-children value false)
|
|
||||||
:exports [{:file-id file-id
|
:exports [{:file-id file-id
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:object-id id
|
:object-id id
|
||||||
|
|||||||
@@ -114,7 +114,7 @@
|
|||||||
|
|
||||||
(defn- load
|
(defn- load
|
||||||
[locale]
|
[locale]
|
||||||
(let [path (str "./translation." locale ".js?version=" (:full cf/version))]
|
(let [path (str "./translation." locale ".js?version=" cf/version-tag)]
|
||||||
(->> (mod/import path)
|
(->> (mod/import path)
|
||||||
(p/fmap (fn [result] (unchecked-get result "default")))
|
(p/fmap (fn [result] (unchecked-get result "default")))
|
||||||
(p/fnly (fn [data cause]
|
(p/fnly (fn [data cause]
|
||||||
|
|||||||
@@ -433,6 +433,9 @@ msgstr "(copy)"
|
|||||||
msgid "dashboard.create-new-team"
|
msgid "dashboard.create-new-team"
|
||||||
msgstr "Create new team"
|
msgstr "Create new team"
|
||||||
|
|
||||||
|
msgid "dashboard.create-new-org"
|
||||||
|
msgstr "Create new org"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/main_menu.cljs:661
|
#: src/app/main/ui/workspace/main_menu.cljs:661
|
||||||
msgid "dashboard.create-version-menu"
|
msgid "dashboard.create-version-menu"
|
||||||
msgstr "Pin this version"
|
msgstr "Pin this version"
|
||||||
@@ -5476,6 +5479,10 @@ msgstr "Delete row and shapes"
|
|||||||
msgid "workspace.context-menu.grid-track.row.duplicate"
|
msgid "workspace.context-menu.grid-track.row.duplicate"
|
||||||
msgstr "Duplicate row"
|
msgstr "Duplicate row"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/sidebar/debug.cljs:37
|
||||||
|
msgid "workspace.debug.title"
|
||||||
|
msgstr "Debugging tools"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/layers.cljs:512
|
#: src/app/main/ui/workspace/sidebar/layers.cljs:512
|
||||||
msgid "workspace.focus.focus-mode"
|
msgid "workspace.focus.focus-mode"
|
||||||
msgstr "Focus mode"
|
msgstr "Focus mode"
|
||||||
@@ -8421,6 +8428,10 @@ msgstr "Comments (%s)"
|
|||||||
msgid "workspace.toolbar.curve"
|
msgid "workspace.toolbar.curve"
|
||||||
msgstr "Curve (%s)"
|
msgstr "Curve (%s)"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/top_toolbar.cljs:231
|
||||||
|
msgid "workspace.toolbar.debug"
|
||||||
|
msgstr "Debugging tools"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/top_toolbar.cljs:172
|
#: src/app/main/ui/workspace/top_toolbar.cljs:172
|
||||||
msgid "workspace.toolbar.ellipse"
|
msgid "workspace.toolbar.ellipse"
|
||||||
msgstr "Ellipse (%s)"
|
msgstr "Ellipse (%s)"
|
||||||
|
|||||||
@@ -442,6 +442,9 @@ msgstr "(copia)"
|
|||||||
msgid "dashboard.create-new-team"
|
msgid "dashboard.create-new-team"
|
||||||
msgstr "Crear nuevo equipo"
|
msgstr "Crear nuevo equipo"
|
||||||
|
|
||||||
|
msgid "dashboard.create-new-org"
|
||||||
|
msgstr "Crear nueva organización"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/main_menu.cljs:661
|
#: src/app/main/ui/workspace/main_menu.cljs:661
|
||||||
msgid "dashboard.create-version-menu"
|
msgid "dashboard.create-version-menu"
|
||||||
msgstr "Guardar esta versión"
|
msgstr "Guardar esta versión"
|
||||||
@@ -5461,6 +5464,10 @@ msgstr "Borrar fila con el contenido"
|
|||||||
msgid "workspace.context-menu.grid-track.row.duplicate"
|
msgid "workspace.context-menu.grid-track.row.duplicate"
|
||||||
msgstr "Duplicar fila"
|
msgstr "Duplicar fila"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/sidebar/debug.cljs:37
|
||||||
|
msgid "workspace.debug.title"
|
||||||
|
msgstr "Herramientas de depuración"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/layers.cljs:512
|
#: src/app/main/ui/workspace/sidebar/layers.cljs:512
|
||||||
msgid "workspace.focus.focus-mode"
|
msgid "workspace.focus.focus-mode"
|
||||||
msgstr "Modo foco"
|
msgstr "Modo foco"
|
||||||
@@ -7965,7 +7972,7 @@ msgstr "Line height (multiplicador, px o %) o {alias}"
|
|||||||
|
|
||||||
#: src/app/main/data/workspace/tokens/errors.cljs:57
|
#: src/app/main/data/workspace/tokens/errors.cljs:57
|
||||||
msgid "workspace.tokens.missing-references"
|
msgid "workspace.tokens.missing-references"
|
||||||
msgstr "Referéncias de tokens no encontradas:"
|
msgstr "Referencias de tokens no encontradas: "
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
|
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
|
||||||
msgid "workspace.tokens.more-options"
|
msgid "workspace.tokens.more-options"
|
||||||
@@ -8282,6 +8289,10 @@ msgstr "Comentarios (%s)"
|
|||||||
msgid "workspace.toolbar.curve"
|
msgid "workspace.toolbar.curve"
|
||||||
msgstr "Curva (%s)"
|
msgstr "Curva (%s)"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/top_toolbar.cljs:231
|
||||||
|
msgid "workspace.toolbar.debug"
|
||||||
|
msgstr "Herramientas de depuración"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/top_toolbar.cljs:172
|
#: src/app/main/ui/workspace/top_toolbar.cljs:172
|
||||||
msgid "workspace.toolbar.ellipse"
|
msgid "workspace.toolbar.ellipse"
|
||||||
msgstr "Elipse (%s)"
|
msgstr "Elipse (%s)"
|
||||||
|
|||||||
4
plugins/wrangle-penpot-plugins-api-doc.toml
Normal file
4
plugins/wrangle-penpot-plugins-api-doc.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
name = "penpot-plugins-api-doc"
|
||||||
|
compatibility_date = "2025-01-01"
|
||||||
|
|
||||||
|
assets = { directory = "dist/doc" }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
export CURRENT_VERSION=${CURRENT_VERSION:-develop};
|
export VERSION_TAG=${VERSION:-develop};
|
||||||
|
|
||||||
if [ "$NODE_ENV" = "production" ]; then
|
if [ "$NODE_ENV" = "production" ]; then
|
||||||
export BUILD_MODE="release";
|
export BUILD_MODE="release";
|
||||||
@@ -81,7 +81,7 @@ function copy_artifacts {
|
|||||||
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js;
|
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js;
|
||||||
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm;
|
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm;
|
||||||
|
|
||||||
sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$CURRENT_VERSION/g" $DEST/$BUILD_NAME.js;
|
sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$VERSION_TAG/g" $DEST/$BUILD_NAME.js;
|
||||||
|
|
||||||
yarn esbuild target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js \
|
yarn esbuild target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js \
|
||||||
--log-level=error \
|
--log-level=error \
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ pub extern "C" fn set_view_end() {
|
|||||||
performance::end_measure!("set_view_end::clear_tile_index");
|
performance::end_measure!("set_view_end::clear_tile_index");
|
||||||
performance::end_timed_log!("clear_tile_index", _clear_start);
|
performance::end_timed_log!("clear_tile_index", _clear_start);
|
||||||
}
|
}
|
||||||
|
state.render_state.sync_cached_viewbox();
|
||||||
performance::end_measure!("set_view_end");
|
performance::end_measure!("set_view_end");
|
||||||
performance::end_timed_log!("set_view_end", _end_start);
|
performance::end_timed_log!("set_view_end", _end_start);
|
||||||
#[cfg(feature = "profile-macros")]
|
#[cfg(feature = "profile-macros")]
|
||||||
|
|||||||
@@ -1136,6 +1136,7 @@ impl RenderState {
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let _start = performance::begin_timed_log!("start_render_loop");
|
let _start = performance::begin_timed_log!("start_render_loop");
|
||||||
let scale = self.get_scale();
|
let scale = self.get_scale();
|
||||||
|
|
||||||
self.tile_viewbox.update(self.viewbox, scale);
|
self.tile_viewbox.update(self.viewbox, scale);
|
||||||
|
|
||||||
self.focus_mode.reset();
|
self.focus_mode.reset();
|
||||||
@@ -2292,6 +2293,10 @@ impl RenderState {
|
|||||||
(self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON
|
(self.viewbox.zoom - self.cached_viewbox.zoom).abs() > f32::EPSILON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sync_cached_viewbox(&mut self) {
|
||||||
|
self.cached_viewbox = self.viewbox;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mark_touched(&mut self, uuid: Uuid) {
|
pub fn mark_touched(&mut self, uuid: Uuid) {
|
||||||
self.touched_ids.insert(uuid);
|
self.touched_ids.insert(uuid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1529,6 +1529,7 @@ impl Shape {
|
|||||||
|| !self.transform.is_identity()
|
|| !self.transform.is_identity()
|
||||||
|| !math::is_close_to(self.rotation, 0.0)
|
|| !math::is_close_to(self.rotation, 0.0)
|
||||||
|| matches!(self.shape_type, Type::Group(_) | Type::Frame(_))
|
|| matches!(self.shape_type, Type::Group(_) | Type::Frame(_))
|
||||||
|
|| matches!(self.shape_type, Type::Text(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_visible_inner_strokes(&self) -> usize {
|
pub fn count_visible_inner_strokes(&self) -> usize {
|
||||||
|
|||||||
@@ -100,6 +100,16 @@ impl<'a> State<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
||||||
|
// If zoom changed, we MUST rebuild the tile index before using it.
|
||||||
|
// Otherwise, the index will have tiles from the old zoom level, causing visible
|
||||||
|
// tiles to appear empty. This can happen if start_render_loop() is called before
|
||||||
|
// set_view_end() finishes rebuilding the index, or if set_view_end() hasn't been
|
||||||
|
// called yet.
|
||||||
|
let zoom_changed = self.render_state.zoom_changed();
|
||||||
|
if zoom_changed {
|
||||||
|
self.rebuild_tiles_shallow();
|
||||||
|
}
|
||||||
|
|
||||||
self.render_state
|
self.render_state
|
||||||
.start_render_loop(None, &self.shapes, timestamp, false)?;
|
.start_render_loop(None, &self.shapes, timestamp, false)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user