mirror of
https://github.com/penpot/penpot.git
synced 2026-02-05 20:22:15 -05:00
Compare commits
7 Commits
niwinz-plu
...
luis-13112
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17b1d7d140 | ||
|
|
2ccb33ba89 | ||
|
|
ee88ee63a2 | ||
|
|
f961f9a123 | ||
|
|
dda3377596 | ||
|
|
17935443df | ||
|
|
150d57b1eb |
@@ -16,6 +16,8 @@
|
||||
- Optimize sidebar performance for deeply nested shapes [Taiga #13017](https://tree.taiga.io/project/penpot/task/13017)
|
||||
- Remove tokens path node and bulk remove tokens [Taiga #13007](https://tree.taiga.io/project/penpot/us/13007)
|
||||
- Replace themes management modal radio buttons for switches [Taiga #9215](https://tree.taiga.io/project/penpot/us/9215)
|
||||
- [MCP server] Integrations section [Taiga #13112](https://tree.taiga.io/project/penpot/us/13112)
|
||||
- [Access Tokens] Look & feel refinement [Taiga #13114](https://tree.taiga.io/project/penpot/us/13114)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.logging :as l]
|
||||
[app.db :as db]
|
||||
[app.migrations.clj.migration-0023 :as mg0023]
|
||||
[app.migrations.clj.migration-0145 :as mg0145]
|
||||
[app.util.migrations :as mg]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
@@ -459,7 +460,13 @@
|
||||
:fn (mg/resource "app/migrations/sql/0143-add-http-session-v2-table.sql")}
|
||||
|
||||
{:name "0144-mod-server-error-report-table"
|
||||
:fn (mg/resource "app/migrations/sql/0144-mod-server-error-report-table.sql")}])
|
||||
:fn (mg/resource "app/migrations/sql/0144-mod-server-error-report-table.sql")}
|
||||
|
||||
{:name "0145-fix-plugins-uri-on-profile"
|
||||
:fn mg0145/migrate}
|
||||
|
||||
{:name "0146-mod-access-token-table"
|
||||
:fn (mg/resource "app/migrations/sql/0146-mod-access-token-table.sql")}])
|
||||
|
||||
(defn apply-migrations!
|
||||
[pool name migrations]
|
||||
|
||||
83
backend/src/app/migrations/clj/migration_0145.clj
Normal file
83
backend/src/app/migrations/clj/migration_0145.clj
Normal file
@@ -0,0 +1,83 @@
|
||||
;; 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.migrations.clj.migration-0145
|
||||
"Migrate plugins references on profiles"
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as l]
|
||||
[app.db :as db]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def ^:private replacements
|
||||
{"https://colors-to-tokens-plugin.pages.dev"
|
||||
"https://colors-to-tokens.plugins.penpot.app"
|
||||
|
||||
"https://contrast-penpot-plugin.pages.dev"
|
||||
"https://contrast.plugins.penpot.app"
|
||||
|
||||
"https://create-palette-penpot-plugin.pages.dev"
|
||||
"https://create-palette.plugins.penpot.app"
|
||||
|
||||
"https://icons-penpot-plugin.pages.dev"
|
||||
"https://icons.plugins.penpot.app"
|
||||
|
||||
"https://lorem-ipsum-penpot-plugin.pages.dev"
|
||||
"https://lorem-ipsum.plugins.penpot.app"
|
||||
|
||||
"https://rename-layers-penpot-plugin.pages.dev"
|
||||
"https://rename-layers.plugins.penpot.app"
|
||||
|
||||
"https://table-penpot-plugin.pages.dev"
|
||||
"https://table.plugins.penpot.app"})
|
||||
|
||||
(defn- fix-url
|
||||
[url]
|
||||
(reduce-kv (fn [url prefix replacement]
|
||||
(if (str/starts-with? url prefix)
|
||||
(reduced (str replacement (subs url (count prefix))))
|
||||
url))
|
||||
url
|
||||
replacements))
|
||||
|
||||
|
||||
(defn- fix-manifest
|
||||
[manifest]
|
||||
(-> manifest
|
||||
(d/update-when :url fix-url)
|
||||
(d/update-when :host fix-url)))
|
||||
|
||||
(defn- fix-plugins-data
|
||||
[props]
|
||||
(d/update-in-when props [:plugins :data]
|
||||
(fn [data]
|
||||
(reduce-kv (fn [data id manifest]
|
||||
(let [manifest' (fix-manifest manifest)]
|
||||
(if (= manifest manifest')
|
||||
data
|
||||
(assoc data id manifest'))))
|
||||
data
|
||||
data))))
|
||||
|
||||
(def ^:private sql:get-profiles
|
||||
"SELECT id, props FROM profile
|
||||
WHERE props ?? '~:plugins'
|
||||
ORDER BY created_at
|
||||
FOR UPDATE")
|
||||
|
||||
(defn migrate
|
||||
[conn]
|
||||
(->> (db/plan conn [sql:get-profiles])
|
||||
(run! (fn [{:keys [id props]}]
|
||||
(when-let [props (some-> props db/decode-transit-pgobject)]
|
||||
(let [props' (fix-plugins-data props)]
|
||||
(when (not= props props')
|
||||
(l/inf :hint "fixing plugins data on profile props" :profile-id (str id))
|
||||
(db/update! conn :profile
|
||||
{:props (db/tjson props')}
|
||||
{:id id}
|
||||
{::db/return-keys false}))))))))
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE access_token
|
||||
ADD COLUMN type text NULL;
|
||||
@@ -23,7 +23,7 @@
|
||||
(dissoc row :perms))
|
||||
|
||||
(defn create-access-token
|
||||
[{:keys [::db/conn] :as cfg} profile-id name expiration]
|
||||
[{:keys [::db/conn] :as cfg} profile-id name expiration type]
|
||||
(let [token-id (uuid/next)
|
||||
expires-at (some-> expiration (ct/in-future))
|
||||
created-at (ct/now)
|
||||
@@ -36,6 +36,7 @@
|
||||
{:id token-id
|
||||
:name name
|
||||
:token token
|
||||
:type type
|
||||
:profile-id profile-id
|
||||
:created-at created-at
|
||||
:updated-at created-at
|
||||
@@ -50,17 +51,18 @@
|
||||
(def ^:private schema:create-access-token
|
||||
[:map {:title "create-access-token"}
|
||||
[:name [:string {:max 250 :min 1}]]
|
||||
[:expiration {:optional true} ::ct/duration]])
|
||||
[:expiration {:optional true} ::ct/duration]
|
||||
[:type {:optional true} :string]])
|
||||
|
||||
(sv/defmethod ::create-access-token
|
||||
{::doc/added "1.18"
|
||||
::sm/params schema:create-access-token}
|
||||
[cfg {:keys [::rpc/profile-id name expiration]}]
|
||||
[cfg {:keys [::rpc/profile-id name expiration type]}]
|
||||
|
||||
(quotes/check! cfg {::quotes/id ::quotes/access-tokens-per-profile
|
||||
::quotes/profile-id profile-id})
|
||||
|
||||
(db/tx-run! cfg create-access-token profile-id name expiration))
|
||||
(db/tx-run! cfg create-access-token profile-id name expiration type))
|
||||
|
||||
(def ^:private schema:delete-access-token
|
||||
[:map {:title "delete-access-token"}
|
||||
@@ -83,5 +85,5 @@
|
||||
(->> (db/query pool :access-token
|
||||
{:profile-id profile-id}
|
||||
{:order-by [[:expires-at :asc] [:created-at :asc]]
|
||||
:columns [:id :name :perms :created-at :updated-at :expires-at]})
|
||||
:columns [:id :name :perms :type :created-at :updated-at :expires-at]})
|
||||
(mapv decode-row)))
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
(def schema:props
|
||||
[:map {:title "ProfileProps"}
|
||||
[:plugins {:optional true} schema:plugin-registry]
|
||||
[:mcp-status {:optional true} ::sm/boolean]
|
||||
[:newsletter-updates {:optional true} ::sm/boolean]
|
||||
[:newsletter-news {:optional true} ::sm/boolean]
|
||||
[:onboarding-team-id {:optional true} ::sm/uuid]
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
(t/deftest access-token-authz
|
||||
(let [profile (th/create-profile* 1)
|
||||
token (db/tx-run! th/*system* app.rpc.commands.access-token/create-access-token (:id profile) "test" nil)
|
||||
token (db/tx-run! th/*system* app.rpc.commands.access-token/create-access-token (:id profile) "test" nil nil)
|
||||
handler (#'app.http.access-token/wrap-authz identity th/*system*)]
|
||||
|
||||
(let [response (handler nil)]
|
||||
|
||||
@@ -39,12 +39,12 @@
|
||||
(into {})))
|
||||
|
||||
(defn remove-attributes-for-token
|
||||
"Removes applied tokens with `token-id` for the given `attributes` set from `applied-tokens`."
|
||||
[attributes token applied-tokens]
|
||||
"Removes applied tokens with `token-name` for the given `attributes` set from `applied-tokens`."
|
||||
[attributes token-name applied-tokens]
|
||||
(let [attr? (set attributes)]
|
||||
(->> (remove (fn [[k v]]
|
||||
(and (attr? k)
|
||||
(= v (or (token-identifier token) token))))
|
||||
(= v token-name)))
|
||||
applied-tokens)
|
||||
(into {}))))
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
:token-base-font-size
|
||||
:token-color
|
||||
:token-shadow
|
||||
:token-tokenscript
|
||||
:transit-readable-response
|
||||
:user-feedback
|
||||
;; TODO: remove this flag.
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"devDependencies": {
|
||||
"@penpot/draft-js": "workspace:./packages/draft-js",
|
||||
"@penpot/mousetrap": "workspace:./packages/mousetrap",
|
||||
"@penpot/tokenscript": "workspace:./packages/tokenscript",
|
||||
"@penpot/plugins-runtime": "1.4.2",
|
||||
"@penpot/svgo": "penpot/svgo#v3.2",
|
||||
"@penpot/text-editor": "workspace:./text-editor",
|
||||
|
||||
3
frontend/packages/tokenscript/.gitignore
vendored
Normal file
3
frontend/packages/tokenscript/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
*.sublime-workspace
|
||||
/.yarn
|
||||
2
frontend/packages/tokenscript/index.js
Normal file
2
frontend/packages/tokenscript/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./schemas.js";
|
||||
export * from "@tokens-studio/tokenscript-interpreter";
|
||||
13
frontend/packages/tokenscript/package.json
Normal file
13
frontend/packages/tokenscript/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@penpot/tokenscript",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@tokens-studio/tokenscript-interpreter": "^0.23.1"
|
||||
}
|
||||
}
|
||||
1431
frontend/packages/tokenscript/schemas.js
Normal file
1431
frontend/packages/tokenscript/schemas.js
Normal file
File diff suppressed because one or more lines are too long
@@ -774,7 +774,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await workspace.layers
|
||||
.getByTestId("layer-row")
|
||||
.nth(1)
|
||||
.getByRole("button", { name: "Toggle layer" })
|
||||
.getByTestId("toggle-content")
|
||||
.click();
|
||||
|
||||
await workspace.layers.getByTestId("layer-row").nth(2).click();
|
||||
@@ -831,15 +831,102 @@ test.describe("Tokens: Apply token", () => {
|
||||
});
|
||||
await detachButton.click();
|
||||
await expect(marginPillXL).not.toBeVisible();
|
||||
const horizontalMarginInput = layoutItemSectionSidebar.getByText('Horizontal marginOpen token');
|
||||
const horizontalMarginInput = layoutItemSectionSidebar.getByText(
|
||||
"Horizontal marginOpen token",
|
||||
);
|
||||
await expect(horizontalMarginInput).toBeVisible();
|
||||
|
||||
const tokenDropdown = horizontalMarginInput.getByRole('button', { name: 'Open token list' });
|
||||
const tokenDropdown = horizontalMarginInput.getByRole("button", {
|
||||
name: "Open token list",
|
||||
});
|
||||
await tokenDropdown.click();
|
||||
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
|
||||
await expect(marginPillXL).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Tokens: Detach token", () => {
|
||||
test("User applies border-radius token to a shape from sidebar", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
// Open tokens sections on left sidebar
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
// Unfold border radius tokens
|
||||
await page.getByRole("button", { name: "Border Radius 3" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius" }),
|
||||
).toBeVisible();
|
||||
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Apply border radius token from token panels
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "borderRadius.sm" })
|
||||
.click();
|
||||
|
||||
// Check if border radius sections is visible on right sidebar
|
||||
const borderRadiusSection = page.getByRole("region", {
|
||||
name: "border-radius-section",
|
||||
});
|
||||
await expect(borderRadiusSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const brTokenPillSM = borderRadiusSection.getByRole("button", {
|
||||
name: "borderRadius.sm",
|
||||
});
|
||||
await expect(brTokenPillSM).toBeVisible();
|
||||
await brTokenPillSM.click();
|
||||
|
||||
// Rename token
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "borderRadius.sm" })
|
||||
.click({ button: "right" });
|
||||
await expect(page.getByText("Edit token")).toBeVisible();
|
||||
await page.getByText("Edit token").click();
|
||||
const editModal = page.getByTestId("token-update-create-modal");
|
||||
await expect(editModal).toBeVisible();
|
||||
await expect(
|
||||
editModal.getByRole("textbox", { name: "Name" }),
|
||||
).toBeVisible();
|
||||
await editModal
|
||||
.getByRole("textbox", { name: "Name" })
|
||||
.fill("BorderRadius.smBis");
|
||||
const submitButton = editModal.getByRole("button", { name: "Save" });
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
await expect(page.getByText("Don't remap")).toBeVisible();
|
||||
await page.getByText("Don't remap").click();
|
||||
const brokenPill = borderRadiusSection.getByRole("button", {
|
||||
name: "This token is not in any",
|
||||
});
|
||||
await expect(brokenPill).toBeVisible();
|
||||
|
||||
// Detach broken token
|
||||
const detachButton = borderRadiusSection.getByRole("button", {
|
||||
name: "Detach token",
|
||||
});
|
||||
await detachButton.click();
|
||||
await expect(brokenPill).not.toBeVisible();
|
||||
|
||||
//De-select and select shape again to double check token is detached
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(0).click();
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
await expect(brokenPill).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
123
frontend/pnpm-lock.yaml
generated
123
frontend/pnpm-lock.yaml
generated
@@ -28,6 +28,9 @@ importers:
|
||||
'@penpot/text-editor':
|
||||
specifier: workspace:./text-editor
|
||||
version: link:text-editor
|
||||
'@penpot/tokenscript':
|
||||
specifier: workspace:./packages/tokenscript
|
||||
version: link:packages/tokenscript
|
||||
'@playwright/test':
|
||||
specifier: 1.58.0
|
||||
version: 1.58.0
|
||||
@@ -248,6 +251,12 @@ importers:
|
||||
|
||||
packages/mousetrap: {}
|
||||
|
||||
packages/tokenscript:
|
||||
dependencies:
|
||||
'@tokens-studio/tokenscript-interpreter':
|
||||
specifier: ^0.23.1
|
||||
version: 0.23.1
|
||||
|
||||
text-editor:
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
@@ -299,6 +308,12 @@ packages:
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@ark/schema@0.56.0':
|
||||
resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==}
|
||||
|
||||
'@ark/util@0.56.0':
|
||||
resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==}
|
||||
|
||||
'@asamuzakjp/css-color@4.1.1':
|
||||
resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==}
|
||||
|
||||
@@ -924,36 +939,42 @@ packages:
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.1':
|
||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||
@@ -1035,24 +1056,28 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@resvg/resvg-js-linux-arm64-musl@2.6.2':
|
||||
resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@resvg/resvg-js-linux-x64-gnu@2.6.2':
|
||||
resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@resvg/resvg-js-linux-x64-musl@2.6.2':
|
||||
resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@resvg/resvg-js-win32-arm64-msvc@2.6.2':
|
||||
resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==}
|
||||
@@ -1149,121 +1174,145 @@ packages:
|
||||
resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.56.0':
|
||||
resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.54.0':
|
||||
resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.56.0':
|
||||
resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.56.0':
|
||||
resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.54.0':
|
||||
resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.56.0':
|
||||
resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.56.0':
|
||||
resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.56.0':
|
||||
resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.56.0':
|
||||
resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.56.0':
|
||||
resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.56.0':
|
||||
resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.54.0':
|
||||
resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.56.0':
|
||||
resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.56.0':
|
||||
resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.56.0':
|
||||
resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.54.0':
|
||||
resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.56.0':
|
||||
resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.56.0':
|
||||
resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==}
|
||||
@@ -1436,6 +1485,11 @@ packages:
|
||||
peerDependencies:
|
||||
style-dictionary: '>=4.3.0 < 6'
|
||||
|
||||
'@tokens-studio/tokenscript-interpreter@0.23.1':
|
||||
resolution: {integrity: sha512-aIcJprCkHIyckl0Knn78Sn7ef3U3IXLjNv9MOePdNR0Mz3Z4PleerldtfLmr1DdXUXiroVSyJROyJrO3TfB2Gg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@tokens-studio/types@0.5.2':
|
||||
resolution: {integrity: sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==}
|
||||
|
||||
@@ -1674,6 +1728,12 @@ packages:
|
||||
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
arkregex@0.0.5:
|
||||
resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==}
|
||||
|
||||
arktype@2.1.29:
|
||||
resolution: {integrity: sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ==}
|
||||
|
||||
array-buffer-byte-length@1.0.2:
|
||||
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1782,6 +1842,9 @@ packages:
|
||||
buffer-builder@0.2.0:
|
||||
resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==}
|
||||
|
||||
buffer-crc32@0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
@@ -1952,6 +2015,10 @@ packages:
|
||||
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@14.0.2:
|
||||
resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
commander@7.2.0:
|
||||
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -3360,6 +3427,9 @@ packages:
|
||||
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
|
||||
engines: {node: '>= 14.16'}
|
||||
|
||||
pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -3637,6 +3707,10 @@ packages:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
readline-sync@1.4.10:
|
||||
resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
recast@0.23.11:
|
||||
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -3784,48 +3858,56 @@ packages:
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: glibc
|
||||
|
||||
sass-embedded-linux-arm@1.97.1:
|
||||
resolution: {integrity: sha512-48VxaTUApLyx1NXFdZhKqI/7FYLmz8Ju3Ki2V/p+mhn5raHgAiYeFgn8O1WGxTOh+hBb9y3FdSR5a8MNTbmKMQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: glibc
|
||||
|
||||
sass-embedded-linux-musl-arm64@1.97.1:
|
||||
resolution: {integrity: sha512-kD35WSD9o0279Ptwid3Jnbovo1FYnuG2mayYk9z4ZI4mweXEK6vTu+tlvCE/MdF/zFKSj11qaxaH+uzXe2cO5A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: musl
|
||||
|
||||
sass-embedded-linux-musl-arm@1.97.1:
|
||||
resolution: {integrity: sha512-FUFs466t3PVViVOKY/60JgLLtl61Pf7OW+g5BeEfuqVcSvYUECVHeiYHtX1fT78PEVa0h9tHpM6XpWti+7WYFA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: musl
|
||||
|
||||
sass-embedded-linux-musl-riscv64@1.97.1:
|
||||
resolution: {integrity: sha512-ZgpYps5YHuhA2+KiLkPukRbS5298QObgUhPll/gm5i0LOZleKCwrFELpVPcbhsSBuxqji2uaag5OL+n3JRBVVg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: musl
|
||||
|
||||
sass-embedded-linux-musl-x64@1.97.1:
|
||||
resolution: {integrity: sha512-wcAigOyyvZ6o1zVypWV7QLZqpOEVnlBqJr9MbpnRIm74qFTSbAEmShoh8yMXBymzuVSmEbThxAwW01/TLf62tA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: musl
|
||||
|
||||
sass-embedded-linux-riscv64@1.97.1:
|
||||
resolution: {integrity: sha512-9j1qE1ZrLMuGb+LUmBzw93Z4TNfqlRkkxjPVZy6u5vIggeSfvGbte7eRoYBNWX6SFew/yBCL90KXIirWFSGrlQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: glibc
|
||||
|
||||
sass-embedded-linux-x64@1.97.1:
|
||||
resolution: {integrity: sha512-7nrLFYMH/UgvEgXR5JxQJ6y9N4IJmnFnYoDxN0nw0jUp+CQWQL4EJ4RqAKTGelneueRbccvt2sEyPK+X0KJ9Jg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: glibc
|
||||
|
||||
sass-embedded-unknown-all@1.97.1:
|
||||
resolution: {integrity: sha512-oPSeKc7vS2dx3ZJHiUhHKcyqNq0GWzAiR8zMVpPd/kVMl5ZfVyw+5HTCxxWDBGkX02lNpou27JkeBPCaneYGAQ==}
|
||||
@@ -4156,6 +4238,7 @@ packages:
|
||||
tar@6.2.1:
|
||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
|
||||
|
||||
tdigest@0.1.2:
|
||||
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
|
||||
@@ -4677,6 +4760,10 @@ packages:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yauzl@3.2.0:
|
||||
resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yocto-queue@1.2.2:
|
||||
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
|
||||
engines: {node: '>=12.20'}
|
||||
@@ -4695,6 +4782,12 @@ snapshots:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@ark/schema@0.56.0':
|
||||
dependencies:
|
||||
'@ark/util': 0.56.0
|
||||
|
||||
'@ark/util@0.56.0': {}
|
||||
|
||||
'@asamuzakjp/css-color@4.1.1':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||
@@ -5608,6 +5701,13 @@ snapshots:
|
||||
is-mergeable-object: 1.1.1
|
||||
style-dictionary: 5.0.0-rc.1
|
||||
|
||||
'@tokens-studio/tokenscript-interpreter@0.23.1':
|
||||
dependencies:
|
||||
arktype: 2.1.29
|
||||
commander: 14.0.2
|
||||
readline-sync: 1.4.10
|
||||
yauzl: 3.2.0
|
||||
|
||||
'@tokens-studio/types@0.5.2': {}
|
||||
|
||||
'@trysound/sax@0.2.0': {}
|
||||
@@ -5901,6 +6001,16 @@ snapshots:
|
||||
|
||||
aria-query@5.3.2: {}
|
||||
|
||||
arkregex@0.0.5:
|
||||
dependencies:
|
||||
'@ark/util': 0.56.0
|
||||
|
||||
arktype@2.1.29:
|
||||
dependencies:
|
||||
'@ark/schema': 0.56.0
|
||||
'@ark/util': 0.56.0
|
||||
arkregex: 0.0.5
|
||||
|
||||
array-buffer-byte-length@1.0.2:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -6040,6 +6150,8 @@ snapshots:
|
||||
|
||||
buffer-builder@0.2.0: {}
|
||||
|
||||
buffer-crc32@0.2.13: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
@@ -6212,6 +6324,8 @@ snapshots:
|
||||
|
||||
commander@12.1.0: {}
|
||||
|
||||
commander@14.0.2: {}
|
||||
|
||||
commander@7.2.0: {}
|
||||
|
||||
component-emitter@2.0.0: {}
|
||||
@@ -7735,6 +7849,8 @@ snapshots:
|
||||
|
||||
pathval@2.0.1: {}
|
||||
|
||||
pend@1.2.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
@@ -8029,6 +8145,8 @@ snapshots:
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
readline-sync@1.4.10: {}
|
||||
|
||||
recast@0.23.11:
|
||||
dependencies:
|
||||
ast-types: 0.16.1
|
||||
@@ -9195,6 +9313,11 @@ snapshots:
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yauzl@3.2.0:
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
pend: 1.2.0
|
||||
|
||||
yocto-queue@1.2.2: {}
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
@@ -6,4 +6,5 @@ shamefullyHoist: true
|
||||
packages:
|
||||
- "packages/draft-js"
|
||||
- "packages/mousetrap"
|
||||
- "packages/tokenscript"
|
||||
- "text-editor"
|
||||
|
||||
@@ -498,4 +498,3 @@
|
||||
(->> (rp/cmd! :delete-access-token params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.config :as cf]
|
||||
[app.main.data.tinycolor :as tinycolor]
|
||||
[app.main.data.tokenscript :as ts]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.warnings :as wtw]
|
||||
[beicon.v2.core :as rx]
|
||||
@@ -586,22 +588,25 @@
|
||||
;; FIXME: this with effect with trigger all the time because
|
||||
;; `config` will be always a different instance
|
||||
(mf/with-effect [tokens config]
|
||||
(let [cached (get @cache-atom tokens)]
|
||||
(cond
|
||||
(nil? tokens) nil
|
||||
;; The tokens are already processing somewhere
|
||||
(rx/observable? cached) (rx/sub! cached #(reset! tokens-state %))
|
||||
;; Get the cached entry
|
||||
(some? cached) (reset! tokens-state cached)
|
||||
;; No cached entry, start processing
|
||||
:else (let [resolved-tokens-s (if interactive?
|
||||
(resolve-tokens-interactive tokens)
|
||||
(resolve-tokens tokens))]
|
||||
(swap! cache-atom assoc tokens resolved-tokens-s)
|
||||
(rx/sub! resolved-tokens-s (fn [resolved-tokens]
|
||||
(swap! cache-atom assoc tokens resolved-tokens)
|
||||
(reset! tokens-state resolved-tokens)))))))
|
||||
@tokens-state))
|
||||
(when-not (contains? cf/flags :tokenscript)
|
||||
(let [cached (get @cache-atom tokens)]
|
||||
(cond
|
||||
(nil? tokens) nil
|
||||
;; The tokens are already processing somewhere
|
||||
(rx/observable? cached) (rx/sub! cached #(reset! tokens-state %))
|
||||
;; Get the cached entry
|
||||
(some? cached) (reset! tokens-state cached)
|
||||
;; No cached entry, start processing
|
||||
:else (let [resolved-tokens-s (if interactive?
|
||||
(resolve-tokens-interactive tokens)
|
||||
(resolve-tokens tokens))]
|
||||
(swap! cache-atom assoc tokens resolved-tokens-s)
|
||||
(rx/sub! resolved-tokens-s (fn [resolved-tokens]
|
||||
(swap! cache-atom assoc tokens resolved-tokens)
|
||||
(reset! tokens-state resolved-tokens))))))))
|
||||
(if (contains? cf/flags :tokenscript)
|
||||
(ts/resolve-tokens tokens)
|
||||
@tokens-state)))
|
||||
|
||||
(defn use-resolved-tokens*
|
||||
"This hook will return the unresolved tokens as state until they are
|
||||
@@ -612,16 +617,19 @@
|
||||
[tokens & {:keys [interactive?]}]
|
||||
(let [state* (mf/use-state tokens)]
|
||||
(mf/with-effect [tokens interactive?]
|
||||
(if (seq tokens)
|
||||
(let [tpoint (ct/tpoint-ms)
|
||||
tokens-s (if interactive?
|
||||
(resolve-tokens-interactive tokens)
|
||||
(resolve-tokens tokens))]
|
||||
(when-not (contains? cf/flags :tokenscript)
|
||||
(if (seq tokens)
|
||||
(let [tpoint (ct/tpoint-ms)
|
||||
tokens-s (if interactive?
|
||||
(resolve-tokens-interactive tokens)
|
||||
(resolve-tokens tokens))]
|
||||
|
||||
(-> tokens-s
|
||||
(rx/sub! (fn [resolved-tokens]
|
||||
(let [elapsed (tpoint)]
|
||||
(l/dbg :hint "use-resolved-tokens*" :elapsed elapsed)
|
||||
(reset! state* resolved-tokens))))))
|
||||
(reset! state* tokens)))
|
||||
@state*))
|
||||
(-> tokens-s
|
||||
(rx/sub! (fn [resolved-tokens]
|
||||
(let [elapsed (tpoint)]
|
||||
(l/dbg :hint "use-resolved-tokens*" :elapsed elapsed)
|
||||
(reset! state* resolved-tokens))))))
|
||||
(reset! state* tokens))))
|
||||
(if (contains? cf/flags :tokenscript)
|
||||
(ts/resolve-tokens tokens)
|
||||
@state*)))
|
||||
|
||||
164
frontend/src/app/main/data/tokenscript.cljs
Normal file
164
frontend/src/app/main/data/tokenscript.cljs
Normal file
@@ -0,0 +1,164 @@
|
||||
(ns app.main.data.tokenscript
|
||||
(:require
|
||||
["@penpot/tokenscript" :refer [BaseSymbolType
|
||||
ColorSymbol
|
||||
ListSymbol NumberSymbol
|
||||
NumberWithUnitSymbol
|
||||
ProcessorError
|
||||
processTokens
|
||||
TokenSymbol
|
||||
makeConfig]]
|
||||
[app.common.logging :as l]
|
||||
[app.common.time :as ct]
|
||||
[app.main.data.workspace.tokens.errors :as wte]))
|
||||
|
||||
(l/set-level! :debug)
|
||||
|
||||
;; Config ----------------------------------------------------------------------
|
||||
|
||||
(def config (makeConfig))
|
||||
|
||||
;; Class predicates ------------------------------------------------------------
|
||||
;; Predicates to get information about the tokenscript interpreter symbol type
|
||||
;; Or to determine the error
|
||||
|
||||
(defn tokenscript-symbol? [v]
|
||||
(instance? BaseSymbolType v))
|
||||
|
||||
(defn structured-token? [v]
|
||||
(instance? TokenSymbol v))
|
||||
|
||||
(defn structured-record-token? [^js v]
|
||||
(and (structured-token? v) (instance? js/Map (.-value v))))
|
||||
|
||||
(defn structured-array-token? [^js v]
|
||||
(and (structured-token? v) (instance? js/Array (.-value v))))
|
||||
|
||||
(defn number-with-unit-symbol? [v]
|
||||
(instance? NumberWithUnitSymbol v))
|
||||
|
||||
(defn number-symbol? [v]
|
||||
(instance? NumberSymbol v))
|
||||
|
||||
(defn list-symbol? [v]
|
||||
(instance? ListSymbol v))
|
||||
|
||||
(defn color-symbol? [v]
|
||||
(instance? ColorSymbol v))
|
||||
|
||||
(defn processor-error? [err]
|
||||
(instance? ProcessorError err))
|
||||
|
||||
;; Conversion Tools ------------------------------------------------------------
|
||||
;; Helpers to convert tokenscript symbols to penpot accepted formats
|
||||
|
||||
(defn color-symbol->hex-string [^js v]
|
||||
(when (color-symbol? v)
|
||||
(.toString (.to v "hex"))))
|
||||
|
||||
(defn color-alpha [^js v]
|
||||
(if (.isHex v)
|
||||
1
|
||||
(or (.getAttribute v "alpha") 1)))
|
||||
|
||||
(defn color-symbol->penpot-color [^js v]
|
||||
{:color (color-symbol->hex-string v)
|
||||
:opacity (color-alpha v)})
|
||||
|
||||
(defn rem-number-with-unit? [v]
|
||||
(and (number-with-unit-symbol? v)
|
||||
(= (.-unit v) "rem")))
|
||||
|
||||
(defn rem->px [^js v]
|
||||
(* (.-value v) 16))
|
||||
|
||||
(declare tokenscript-symbols->penpot-unit)
|
||||
|
||||
(defn structured-token->penpot-map
|
||||
"Converts structured token (record or array) to penpot map format.
|
||||
Structured tokens are non-primitive token types like `typography` or `box-shadow`."
|
||||
[^js token-symbol]
|
||||
(if (instance? js/Array (.-value token-symbol))
|
||||
(mapv structured-token->penpot-map (.-value token-symbol))
|
||||
(let [entries (es6-iterator-seq (.entries (.-value token-symbol)))]
|
||||
(into {} (map (fn [[k v :as V]]
|
||||
[(keyword k) (tokenscript-symbols->penpot-unit v)])
|
||||
entries)))))
|
||||
|
||||
(defn tokenscript-symbols->penpot-unit [^js v]
|
||||
(cond
|
||||
(structured-token? v) (structured-token->penpot-map v)
|
||||
(list-symbol? v) (tokenscript-symbols->penpot-unit (.nth 1 v))
|
||||
(color-symbol? v) (.-value (.to v "hex"))
|
||||
(rem-number-with-unit? v) (rem->px v)
|
||||
:else (.-value v)))
|
||||
|
||||
;; Processors ------------------------------------------------------------------
|
||||
;; The processor resolves tokens
|
||||
;; resolved/error tokens get put back into a clojure structure directly during build time
|
||||
;; For updating tokens we use the `TokenResolver` crud methods from the processing result
|
||||
;; The `TokenResolver` has detailed information for each tokens dependency graph
|
||||
|
||||
(defn create-token-builder
|
||||
"Collects resolved tokens during build time into a clojure structure.
|
||||
Returns Tokenscript Symbols in `:resolved-value` key."
|
||||
[tokens]
|
||||
(let [output (volatile! tokens)
|
||||
|
||||
;; When a token is resolved (No parsing / reference errors) we assing `:resolved-value` for the original token
|
||||
on-resolve
|
||||
(fn [^js/String token-name ^js/Symbol resolved-symbol]
|
||||
(vswap! output assoc-in [token-name :resolved-value] resolved-symbol))
|
||||
|
||||
;; When a token contains any errors we assing `:errors` for the original token
|
||||
on-error
|
||||
(fn [^js/String token-name ^js/Error _error ^js/String _original-value]
|
||||
(let [value (get tokens token-name)
|
||||
default-error [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]]
|
||||
(vswap! output assoc-in [token-name :errors] default-error)))
|
||||
|
||||
;; Extract the atom value
|
||||
get-result
|
||||
(fn [] @output)]
|
||||
#js {:onResolve on-resolve
|
||||
:onError on-error
|
||||
:getResult get-result}))
|
||||
|
||||
(defn clj->token->tokenscript-token
|
||||
"Convert penpot token into a format that tokenscript can handle."
|
||||
[{:keys [type value]}]
|
||||
#js {"$type" (name type)
|
||||
"$value" (clj->js value)})
|
||||
|
||||
(defn clj-tokens->tokenscript-tokens
|
||||
"Convert penpot map of tokens into tokenscript map structure.
|
||||
tokenscript accepts a map of [token-name {\"$type\": string, \"$value\": any}]"
|
||||
[tokens]
|
||||
(let [token-map (js/Map.)]
|
||||
(doseq [[k token] tokens]
|
||||
(.set token-map k (clj->token->tokenscript-token token)))
|
||||
token-map))
|
||||
|
||||
(defn process-tokens
|
||||
"Builds tokens using `tokenscript`."
|
||||
[tokens]
|
||||
(let [input (clj-tokens->tokenscript-tokens tokens)
|
||||
result (processTokens input #js {:config config
|
||||
:builder (create-token-builder tokens)})]
|
||||
result))
|
||||
|
||||
(defn update-token
|
||||
[tokens token]
|
||||
(let [result (process-tokens tokens)
|
||||
resolver (.-resolver result)]
|
||||
(.updateToken resolver #js {:tokenPath (:name token)
|
||||
:tokenData (clj->token->tokenscript-token token)})))
|
||||
|
||||
;; Main ------------------------------------------------------------------------
|
||||
|
||||
(defn resolve-tokens [tokens]
|
||||
(let [tpoint (ct/tpoint-ms)
|
||||
result (process-tokens tokens)
|
||||
elapsed (tpoint)]
|
||||
(l/dbg :hint "tokenscript/resolve-tokens" :elapsed elapsed)
|
||||
(.-output result)))
|
||||
@@ -18,11 +18,13 @@
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.types.typography :as cty]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.tinycolor :as tinycolor]
|
||||
[app.main.data.tokenscript :as ts]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.colors :as wdc]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
@@ -627,7 +629,9 @@
|
||||
(when-let [tokens (some-> (dsh/lookup-file-data state)
|
||||
(get :tokens-lib)
|
||||
(ctob/get-tokens-in-active-sets))]
|
||||
(->> (sd/resolve-tokens tokens)
|
||||
(->> (if (contains? cf/flags :tokenscript)
|
||||
(rx/of (ts/resolve-tokens tokens))
|
||||
(sd/resolve-tokens tokens))
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [undo-id (js/Symbol)
|
||||
@@ -645,6 +649,9 @@
|
||||
any-variant? (->> shapes vals (some ctk/is-variant?) boolean)
|
||||
|
||||
resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value])
|
||||
resolved-value (if (contains? cf/flags :tokenscript)
|
||||
(ts/tokenscript-symbols->penpot-unit resolved-value)
|
||||
resolved-value)
|
||||
tokenized-attributes (cft/attributes-map attributes token)
|
||||
type (:type token)]
|
||||
(rx/concat
|
||||
@@ -699,17 +706,18 @@
|
||||
"Removes `attributes` that match `token` for `shape-ids`.
|
||||
|
||||
Doesn't update shape attributes."
|
||||
[{:keys [attributes token shape-ids] :as _props}]
|
||||
[{:keys [attributes token-name shape-ids] :as _props}]
|
||||
(ptk/reify ::unapply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(let [remove-token #(when % (cft/remove-attributes-for-token attributes token %))]
|
||||
(let [remove-token #(when % (cft/remove-attributes-for-token attributes token-name %))]
|
||||
(dwsh/update-shapes
|
||||
shape-ids
|
||||
(fn [shape]
|
||||
(update shape :applied-tokens remove-token))))))))
|
||||
|
||||
|
||||
(defn toggle-token
|
||||
[{:keys [token attrs shape-ids expand-with-children]}]
|
||||
(ptk/reify ::on-toggle-token
|
||||
@@ -739,7 +747,7 @@
|
||||
(if unapply-tokens?
|
||||
(rx/of
|
||||
(unapply-token {:attributes (or attrs all-attributes attributes)
|
||||
:token token
|
||||
:token-name (:name token)
|
||||
:shape-ids shape-ids}))
|
||||
(rx/of
|
||||
(cond
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
(ns app.main.data.workspace.tokens.color
|
||||
(:require
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.main.data.tinycolor :as tinycolor]))
|
||||
[app.config :as cf]
|
||||
[app.main.data.tinycolor :as tinycolor]
|
||||
[app.main.data.tokenscript :as ts]))
|
||||
|
||||
(defn color-bullet-color [token-color-value]
|
||||
(when-let [tc (tinycolor/valid-color token-color-value)]
|
||||
@@ -17,5 +19,8 @@
|
||||
(tinycolor/->hex-string tc))))
|
||||
|
||||
(defn resolved-token-bullet-color [{:keys [resolved-value] :as token}]
|
||||
(when (and resolved-value (cft/color-token? token))
|
||||
(color-bullet-color resolved-value)))
|
||||
(if (contains? cf/flags :tokenscript)
|
||||
(when (and resolved-value (ts/color-symbol? resolved-value))
|
||||
(ts/color-symbol->penpot-color resolved-value))
|
||||
(when (and resolved-value (cft/color-token? token))
|
||||
(color-bullet-color resolved-value))))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns app.main.data.workspace.tokens.format
|
||||
(:require
|
||||
[app.main.data.tokenscript :as ts]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def category-dictionary
|
||||
@@ -23,14 +24,52 @@
|
||||
:color "Color"
|
||||
:inset "Inner Shadow"})
|
||||
|
||||
(declare format-token-value)
|
||||
|
||||
(defn- format-map-entries
|
||||
"Formats a sequence of [k v] entries into a formatted string."
|
||||
[entries]
|
||||
(->> entries
|
||||
(map (fn [[k v]]
|
||||
(str "- " (category-dictionary (keyword k)) ": " (format-token-value v))))
|
||||
(str/join "\n")
|
||||
(str "\n")))
|
||||
|
||||
(defn- format-structured-token
|
||||
"Formats tokenscript Token"
|
||||
[token-symbol]
|
||||
(->> (.-value token-symbol)
|
||||
(.entries)
|
||||
(es6-iterator-seq)
|
||||
(format-map-entries)))
|
||||
|
||||
(defn format-tokenscript-symbol
|
||||
[^js tokenscript-symbol]
|
||||
(cond
|
||||
(ts/rem-number-with-unit? tokenscript-symbol)
|
||||
(str (ts/rem->px tokenscript-symbol) "px")
|
||||
|
||||
(ts/color-symbol? tokenscript-symbol)
|
||||
(ts/color-symbol->hex-string tokenscript-symbol)
|
||||
|
||||
(ts/structured-record-token? tokenscript-symbol)
|
||||
(format-structured-token tokenscript-symbol)
|
||||
|
||||
(ts/structured-array-token? tokenscript-symbol)
|
||||
(str/join "\n" (map format-tokenscript-symbol (.-value tokenscript-symbol)))
|
||||
|
||||
:else
|
||||
(.toString tokenscript-symbol)))
|
||||
|
||||
(defn format-token-value
|
||||
"Converts token value of any shape to a string."
|
||||
[token-value]
|
||||
(cond
|
||||
(ts/tokenscript-symbol? token-value)
|
||||
(format-tokenscript-symbol token-value)
|
||||
|
||||
(map? token-value)
|
||||
(->> (map (fn [[k v]] (str "- " (category-dictionary k) ": " (format-token-value v))) token-value)
|
||||
(str/join "\n")
|
||||
(str "\n"))
|
||||
(format-map-entries token-value)
|
||||
|
||||
(and (sequential? token-value) (every? map? token-value))
|
||||
(str/join "\n" (map format-token-value token-value))
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
:settings-options
|
||||
:settings-feedback
|
||||
:settings-subscription
|
||||
:settings-access-tokens
|
||||
:settings-integrations
|
||||
:settings-notifications)
|
||||
(let [params (get params :query)
|
||||
error-report-id (some-> params :error-report-id uuid/parse*)]
|
||||
|
||||
@@ -9,14 +9,17 @@
|
||||
(:require
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[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.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[goog.events :as events]
|
||||
[rumext.v2 :as mf])
|
||||
(:import goog.events.EventType))
|
||||
(:import
|
||||
goog.events.EventType))
|
||||
|
||||
(mf/defc confirm-dialog
|
||||
{::mf/register modal/components
|
||||
@@ -68,8 +71,11 @@
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h2 {:class (stl/css :modal-title)} title]
|
||||
[:button {:class (stl/css :modal-close-btn)
|
||||
:on-click cancel-fn} deprecated-icon/close]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:class (stl/css :modal-close-btn)
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click cancel-fn
|
||||
:icon i/close}]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
(when (and (string? message) (not= message ""))
|
||||
@@ -87,24 +93,19 @@
|
||||
[:ul {:class (stl/css :component-list)}
|
||||
(for [item items]
|
||||
[:li {:class (stl/css :modal-item-element)}
|
||||
[:span {:class (stl/css :modal-component-icon)}
|
||||
deprecated-icon/component]
|
||||
[:> icon* {:icon-id i/component
|
||||
:class (stl/css :modal-component-icon)
|
||||
:size "s"}]
|
||||
[:span {:class (stl/css :modal-component-name)}
|
||||
(:name item)]])]])]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
(when-not (= cancel-label :omit)
|
||||
[:input
|
||||
{:class (stl/css :cancel-button)
|
||||
:type "button"
|
||||
:value cancel-label
|
||||
:on-click cancel-fn}])
|
||||
|
||||
[:input
|
||||
{:class (stl/css-case :accept-btn true
|
||||
:danger (= accept-style :danger)
|
||||
:primary (= accept-style :primary))
|
||||
:type "button"
|
||||
:value accept-label
|
||||
:on-click accept-fn}]]]]]))
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click cancel-fn}
|
||||
cancel-label])
|
||||
[:> button* {:variant (cond (= accept-style :danger) "destructive"
|
||||
(= accept-style :primary) "primary")
|
||||
:on-click accept-fn}
|
||||
accept-label]]]]]))
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
|
||||
.modal-container {
|
||||
@extend .modal-container-base;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
margin-bottom: deprecated.$s-24;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxl);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
@@ -27,12 +26,13 @@
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
@extend .modal-close-btn-base;
|
||||
position: absolute;
|
||||
top: var(--sp-m);
|
||||
right: var(--sp-m);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
@include deprecated.bodyLargeTypography;
|
||||
margin-bottom: deprecated.$s-24;
|
||||
}
|
||||
|
||||
.modal-item-element {
|
||||
@@ -41,32 +41,18 @@
|
||||
|
||||
.modal-component-icon {
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--color);
|
||||
}
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.modal-component-name {
|
||||
@include deprecated.bodyLargeTypography;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
@extend .modal-action-btns;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@extend .modal-cancel-btn;
|
||||
}
|
||||
|
||||
.accept-btn {
|
||||
@extend .modal-accept-btn;
|
||||
&.danger {
|
||||
@extend .modal-danger-btn;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-scd-msg,
|
||||
.modal-subtitle,
|
||||
.modal-msg {
|
||||
|
||||
@@ -18,6 +18,7 @@ $sz-32: px2rem(32);
|
||||
$sz-36: px2rem(36);
|
||||
$sz-40: px2rem(40);
|
||||
$sz-48: px2rem(48);
|
||||
$sz-64: px2rem(64);
|
||||
$sz-88: px2rem(88);
|
||||
$sz-96: px2rem(96);
|
||||
$sz-120: px2rem(120);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
|
||||
[app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
|
||||
@@ -52,10 +51,11 @@
|
||||
:has-hint has-hint
|
||||
:hint-type hint-type
|
||||
:variant variant})]
|
||||
[:div {:class (dm/str class " " (stl/css-case :input-wrapper true
|
||||
:variant-dense (= variant "dense")
|
||||
:variant-comfortable (= variant "comfortable")
|
||||
:has-hint has-hint))}
|
||||
|
||||
[:div {:class [class (stl/css-case :input-wrapper true
|
||||
:variant-dense (= variant "dense")
|
||||
:variant-comfortable (= variant "comfortable")
|
||||
:has-hint has-hint)]}
|
||||
(when has-label
|
||||
[:> label* {:for id :is-optional is-optional} label])
|
||||
[:> input-field* props]
|
||||
@@ -64,4 +64,3 @@
|
||||
:class hint-class
|
||||
:message hint-message
|
||||
:type hint-type}])]))
|
||||
|
||||
|
||||
@@ -564,18 +564,17 @@
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach tokens disabled token-applied)
|
||||
(fn [event]
|
||||
(let [token (get-token-op tokens token-applied)]
|
||||
(when-not disabled
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(reset! token-applied* nil)
|
||||
(reset! selected-id* nil)
|
||||
(reset! focused-id* nil)
|
||||
(when on-detach
|
||||
(on-detach token))
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(dom/focus! (mf/ref-val ref))))))))
|
||||
(when-not disabled
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(reset! token-applied* nil)
|
||||
(reset! selected-id* nil)
|
||||
(reset! focused-id* nil)
|
||||
(when on-detach
|
||||
(on-detach token-applied))
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(dom/focus! (mf/ref-val ref)))))))
|
||||
|
||||
on-token-key-down
|
||||
(mf/use-fn
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.keyboard :as k]
|
||||
@@ -47,6 +48,23 @@
|
||||
|
||||
[:> input* props]))
|
||||
|
||||
(mf/defc form-select*
|
||||
[{:keys [name] :as props}]
|
||||
(let [select-name name
|
||||
form (mf/use-ctx context)
|
||||
value (get-in @form [:data select-name] "")
|
||||
|
||||
handle-change
|
||||
(fn [event]
|
||||
(let [value (if (string? event) event (dom/get-target-val event))]
|
||||
(fm/on-input-change form select-name value)))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change handle-change
|
||||
:value value})]
|
||||
|
||||
[:> select* props]))
|
||||
|
||||
(mf/defc form-submit*
|
||||
[{:keys [disabled on-submit] :rest props}]
|
||||
|
||||
@@ -80,4 +98,4 @@
|
||||
(when (fn? on-submit)
|
||||
(on-submit form event))))]
|
||||
[:> (mf/provider context) {:value form}
|
||||
[:form {:class class :on-submit on-submit'} children]]))
|
||||
[:form {:class class :on-submit on-submit'} children]]))
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
["/feedback" :settings-feedback]
|
||||
["/options" :settings-options]
|
||||
["/subscriptions" :settings-subscription]
|
||||
["/access-tokens" :settings-access-tokens]
|
||||
["/integrations" :settings-integrations]
|
||||
["/notifications" :settings-notifications]]
|
||||
|
||||
["/frame-preview" :frame-preview]
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.modal :refer [modal-container*]]
|
||||
[app.main.ui.settings.access-tokens :refer [access-tokens-page]]
|
||||
[app.main.ui.settings.change-email]
|
||||
[app.main.ui.settings.delete-account]
|
||||
[app.main.ui.settings.feedback :refer [feedback-page*]]
|
||||
[app.main.ui.settings.integrations :refer [integrations-page*]]
|
||||
[app.main.ui.settings.notifications :refer [notifications-page*]]
|
||||
[app.main.ui.settings.options :refer [options-page]]
|
||||
[app.main.ui.settings.password :refer [password-page]]
|
||||
@@ -73,8 +73,8 @@
|
||||
:settings-subscription
|
||||
[:> subscription-page* {:profile profile}]
|
||||
|
||||
:settings-access-tokens
|
||||
[:& access-tokens-page]
|
||||
:settings-integrations
|
||||
[:> integrations-page*]
|
||||
|
||||
:settings-notifications
|
||||
[:& notifications-page* {:profile profile}])]]]]))
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.settings.access-tokens
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.clipboard :as clipboard]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private clipboard-icon
|
||||
(deprecated-icon/icon-xref :clipboard (stl/css :clipboard-icon)))
|
||||
|
||||
(def ^:private close-icon
|
||||
(deprecated-icon/icon-xref :close (stl/css :close-icon)))
|
||||
|
||||
(def ^:private menu-icon
|
||||
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
|
||||
|
||||
(def tokens-ref
|
||||
(l/derived :access-tokens st/state))
|
||||
|
||||
(def token-created-ref
|
||||
(l/derived :access-token-created st/state))
|
||||
|
||||
(def ^:private schema:form
|
||||
[:map {:title "AccessTokenForm"}
|
||||
[:name [::sm/text {:max 250}]]
|
||||
[:expiration-date [::sm/text {:max 250}]]])
|
||||
|
||||
(def initial-data
|
||||
{:name "" :expiration-date "never"})
|
||||
|
||||
(mf/defc access-token-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :access-token}
|
||||
[]
|
||||
(let [form (fm/use-form
|
||||
:initial initial-data
|
||||
:schema schema:form)
|
||||
|
||||
created (mf/deref token-created-ref)
|
||||
created? (mf/use-state false)
|
||||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
(mf/deps created)
|
||||
(fn [_]
|
||||
(let [message (tr "dashboard.access-tokens.create.success")]
|
||||
(st/emit! (du/fetch-access-tokens)
|
||||
(ntf/success message)
|
||||
(reset! created? true)))))
|
||||
|
||||
on-close
|
||||
(mf/use-fn
|
||||
(mf/deps created)
|
||||
(fn [_]
|
||||
(reset! created? false)
|
||||
(st/emit! (modal/hide))))
|
||||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
(fn [_]
|
||||
(st/emit! (ntf/error (tr "errors.generic"))
|
||||
(modal/hide))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(fn [form]
|
||||
(let [cdata (:clean-data @form)
|
||||
mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}
|
||||
expiration (:expiration-date cdata)
|
||||
params (cond-> {:name (:name cdata)
|
||||
:perms (:perms cdata)}
|
||||
(not= "never" expiration) (assoc :expiration expiration))]
|
||||
(st/emit! (du/create-access-token
|
||||
(with-meta params mdata))))))
|
||||
|
||||
copy-token
|
||||
(mf/use-fn
|
||||
(mf/deps created)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(clipboard/to-clipboard (:token created))
|
||||
(st/emit! (ntf/show {:level :info
|
||||
:type :toast
|
||||
:content (tr "dashboard.access-tokens.copied-success")
|
||||
:timeout 7000}))))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:& fm/form {:form form :on-submit on-submit}
|
||||
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h2 {:class (stl/css :modal-title)} (tr "modals.create-access-token.title")]
|
||||
|
||||
[:button {:class (stl/css :modal-close-btn)
|
||||
:on-click on-close}
|
||||
close-icon]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :fields-row)}
|
||||
[:& fm/input {:type "text"
|
||||
:auto-focus? true
|
||||
:form form
|
||||
:name :name
|
||||
:disabled @created?
|
||||
:label (tr "modals.create-access-token.name.label")
|
||||
:show-success? true
|
||||
:placeholder (tr "modals.create-access-token.name.placeholder")}]]
|
||||
|
||||
[:div {:class (stl/css :fields-row)}
|
||||
[:div {:class (stl/css :select-title)}
|
||||
(tr "modals.create-access-token.expiration-date.label")]
|
||||
[:& fm/select {:options [{:label (tr "dashboard.access-tokens.expiration-never") :value "never" :key "never"}
|
||||
{:label (tr "dashboard.access-tokens.expiration-30-days") :value "720h" :key "720h"}
|
||||
{:label (tr "dashboard.access-tokens.expiration-60-days") :value "1440h" :key "1440h"}
|
||||
{:label (tr "dashboard.access-tokens.expiration-90-days") :value "2160h" :key "2160h"}
|
||||
{:label (tr "dashboard.access-tokens.expiration-180-days") :value "4320h" :key "4320h"}]
|
||||
:default "never"
|
||||
:disabled @created?
|
||||
:name :expiration-date}]
|
||||
(when @created?
|
||||
[:span {:class (stl/css :token-created-info)}
|
||||
(if (:expires-at created)
|
||||
(tr "dashboard.access-tokens.token-will-expire" (ct/format-inst (:expires-at created) "PPP"))
|
||||
(tr "dashboard.access-tokens.token-will-not-expire"))])]
|
||||
|
||||
[:div {:class (stl/css :fields-row)}
|
||||
(when @created?
|
||||
[:div {:class (stl/css :custon-input-wrapper)}
|
||||
[:input {:type "text"
|
||||
:value (:token created "")
|
||||
:class (stl/css :custom-input-token)
|
||||
:read-only true}]
|
||||
[:button {:title (tr "modals.create-access-token.copy-token")
|
||||
:class (stl/css :copy-btn)
|
||||
:on-click copy-token}
|
||||
clipboard-icon]])
|
||||
#_(when @created?
|
||||
[:button {:class (stl/css :copy-btn)
|
||||
:title (tr "modals.create-access-token.copy-token")
|
||||
:on-click copy-token}
|
||||
[:span {:class (stl/css :token-value)} (:token created "")]
|
||||
[:span {:class (stl/css :icon)}
|
||||
i/clipboard]])]]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
|
||||
(if @created?
|
||||
[:input {:class (stl/css :cancel-button)
|
||||
:type "button"
|
||||
:value (tr "labels.close")
|
||||
:on-click modal/hide!}]
|
||||
[:*
|
||||
[:input {:class (stl/css :cancel-button)
|
||||
:type "button"
|
||||
:value (tr "labels.cancel")
|
||||
:on-click modal/hide!}]
|
||||
[:> fm/submit-button*
|
||||
{:large? false :label (tr "modals.create-access-token.submit-label")}]])]]]]]))
|
||||
|
||||
(mf/defc access-tokens-hero
|
||||
[]
|
||||
(let [on-click (mf/use-fn #(st/emit! (modal/show :access-token {})))]
|
||||
[:div {:class (stl/css :access-tokens-hero)}
|
||||
[:h2 {:class (stl/css :hero-title)} (tr "dashboard.access-tokens.personal")]
|
||||
[:p {:class (stl/css :hero-desc)} (tr "dashboard.access-tokens.personal.description")]
|
||||
|
||||
[:button {:class (stl/css :hero-btn)
|
||||
:on-click on-click}
|
||||
(tr "dashboard.access-tokens.create")]]))
|
||||
|
||||
(mf/defc access-token-actions
|
||||
[{:keys [on-delete]}]
|
||||
(let [local (mf/use-state {:menu-open false})
|
||||
show? (:menu-open @local)
|
||||
options (mf/with-memo [on-delete]
|
||||
[{:name (tr "labels.delete")
|
||||
:id "access-token-delete"
|
||||
:handler on-delete}])
|
||||
|
||||
menu-ref (mf/use-ref)
|
||||
|
||||
on-menu-close
|
||||
(mf/use-fn #(swap! local assoc :menu-open false))
|
||||
|
||||
on-menu-click
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(swap! local assoc :menu-open true)))
|
||||
|
||||
on-keydown
|
||||
(mf/use-fn
|
||||
(mf/deps on-menu-click)
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(dom/stop-propagation event)
|
||||
(on-menu-click event))))]
|
||||
|
||||
[:button {:class (stl/css :menu-btn)
|
||||
:tab-index "0"
|
||||
:ref menu-ref
|
||||
:on-click on-menu-click
|
||||
:on-key-down on-keydown}
|
||||
menu-icon
|
||||
[:> context-menu*
|
||||
{:on-close on-menu-close
|
||||
:show show?
|
||||
:fixed true
|
||||
:min-width true
|
||||
:top "auto"
|
||||
:left "auto"
|
||||
:options options}]]))
|
||||
|
||||
(mf/defc access-token-item
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [token] :as props}]
|
||||
(let [expires-at (:expires-at token)
|
||||
expires-txt (some-> expires-at (ct/format-inst "PPP"))
|
||||
expired? (and (some? expires-at) (> (ct/now) expires-at))
|
||||
|
||||
delete-fn
|
||||
(mf/use-fn
|
||||
(mf/deps token)
|
||||
(fn []
|
||||
(let [params {:id (:id token)}
|
||||
mdata {:on-success #(st/emit! (du/fetch-access-tokens))}]
|
||||
(st/emit! (du/delete-access-token (with-meta params mdata))))))
|
||||
|
||||
on-delete
|
||||
(mf/use-fn
|
||||
(mf/deps delete-fn)
|
||||
(fn []
|
||||
(st/emit! (modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-acces-token.title")
|
||||
:message (tr "modals.delete-acces-token.message")
|
||||
:accept-label (tr "modals.delete-acces-token.accept")
|
||||
:on-accept delete-fn}))))]
|
||||
|
||||
[:div {:class (stl/css :table-row)}
|
||||
[:div {:class (stl/css :table-field :field-name)}
|
||||
(str (:name token))]
|
||||
|
||||
[:div {:class (stl/css-case :expiration-date true
|
||||
:expired expired?)}
|
||||
(cond
|
||||
(nil? expires-at) (tr "dashboard.access-tokens.no-expiration")
|
||||
expired? (tr "dashboard.access-tokens.expired-on" expires-txt)
|
||||
:else (tr "dashboard.access-tokens.expires-on" expires-txt))]
|
||||
[:div {:class (stl/css :table-field :actions)}
|
||||
[:& access-token-actions
|
||||
{:on-delete on-delete}]]]))
|
||||
|
||||
(mf/defc access-tokens-page
|
||||
[]
|
||||
(let [tokens (mf/deref tokens-ref)]
|
||||
(mf/with-effect []
|
||||
(dom/set-html-title (tr "title.settings.access-tokens"))
|
||||
(st/emit! (du/fetch-access-tokens)))
|
||||
|
||||
[:div {:class (stl/css :dashboard-access-tokens)}
|
||||
[:& access-tokens-hero]
|
||||
(if (empty? tokens)
|
||||
[:div {:class (stl/css :access-tokens-empty)}
|
||||
[:div (tr "dashboard.access-tokens.empty.no-access-tokens")]
|
||||
[:div (tr "dashboard.access-tokens.empty.add-one")]]
|
||||
[:div {:class (stl/css :dashboard-table)}
|
||||
[:div {:class (stl/css :table-rows)}
|
||||
(for [token tokens]
|
||||
[:& access-token-item {:token token :key (:id token)}])]])]))
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
// ACCESS TOKENS PAGE
|
||||
.dashboard-access-tokens {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
margin: deprecated.$s-80 auto deprecated.$s-120 auto;
|
||||
gap: deprecated.$s-32;
|
||||
width: deprecated.$s-800;
|
||||
}
|
||||
|
||||
// hero
|
||||
.access-tokens-hero {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
gap: deprecated.$s-32;
|
||||
width: deprecated.$s-500;
|
||||
font-size: deprecated.$fs-14;
|
||||
margin: deprecated.$s-16 auto 0 auto;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
@include deprecated.bigTitleTipography;
|
||||
color: var(--title-foreground-color-hover);
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
color: var(--title-foreground-color);
|
||||
margin-bottom: 0;
|
||||
font-size: deprecated.$fs-14;
|
||||
}
|
||||
|
||||
.hero-btn {
|
||||
@extend .button-primary;
|
||||
}
|
||||
|
||||
// table empty
|
||||
.access-tokens-empty {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
align-content: center;
|
||||
height: deprecated.$s-156;
|
||||
max-width: deprecated.$s-1000;
|
||||
width: 100%;
|
||||
padding: deprecated.$s-32;
|
||||
border: deprecated.$s-1 solid var(--panel-border-color);
|
||||
border-radius: deprecated.$br-8;
|
||||
color: var(--dashboard-list-text-foreground-color);
|
||||
}
|
||||
|
||||
// Access tokens table
|
||||
.dashboard-table {
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.table-rows {
|
||||
display: grid;
|
||||
grid-auto-rows: deprecated.$s-64;
|
||||
gap: deprecated.$s-16;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: deprecated.$s-1000;
|
||||
margin-top: deprecated.$s-16;
|
||||
color: var(--title-foreground-color);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 43% 1fr auto;
|
||||
align-items: center;
|
||||
height: deprecated.$s-64;
|
||||
width: 100%;
|
||||
padding: 0 deprecated.$s-16;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--dashboard-list-background-color);
|
||||
color: var(--dashboard-list-foreground-color);
|
||||
}
|
||||
|
||||
.field-name {
|
||||
@include deprecated.textEllipsis;
|
||||
display: grid;
|
||||
width: 43%;
|
||||
min-width: deprecated.$s-300;
|
||||
}
|
||||
|
||||
.expiration-date {
|
||||
@include deprecated.flexCenter;
|
||||
min-width: deprecated.$s-76;
|
||||
width: fit-content;
|
||||
height: deprecated.$s-24;
|
||||
border-radius: deprecated.$br-8;
|
||||
color: var(--dashboard-list-text-foreground-color);
|
||||
}
|
||||
|
||||
.expired {
|
||||
@include deprecated.headlineSmallTypography;
|
||||
padding: 0 deprecated.$s-6;
|
||||
color: var(--pill-foreground-color);
|
||||
background-color: var(--status-widget-background-color-warning);
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: relative;
|
||||
}
|
||||
.menu-icon {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
@include deprecated.buttonStyle;
|
||||
}
|
||||
|
||||
// Create access token modal
|
||||
.modal-overlay {
|
||||
@extend .modal-overlay-base;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
@extend .modal-container-base;
|
||||
min-width: deprecated.$s-408;
|
||||
}
|
||||
|
||||
.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 {
|
||||
@include deprecated.flexColumn;
|
||||
gap: deprecated.$s-24;
|
||||
@include deprecated.bodySmallTypography;
|
||||
margin-bottom: deprecated.$s-24;
|
||||
}
|
||||
|
||||
.select-title {
|
||||
@include deprecated.bodySmallTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.custon-input-wrapper {
|
||||
@include deprecated.flexRow;
|
||||
border-radius: deprecated.$br-8;
|
||||
height: deprecated.$s-32;
|
||||
background-color: var(--input-background-color);
|
||||
}
|
||||
|
||||
.custom-input-token {
|
||||
@extend .input-element;
|
||||
@include deprecated.bodySmallTypography;
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: deprecated.$s-1 solid var(--input-border-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
.token-value {
|
||||
@include deprecated.textEllipsis;
|
||||
@include deprecated.bodySmallTypography;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
@include deprecated.flexCenter;
|
||||
@extend .button-secondary;
|
||||
height: deprecated.$s-28;
|
||||
width: deprecated.$s-28;
|
||||
}
|
||||
|
||||
.clipboard-icon {
|
||||
@extend .button-icon-small;
|
||||
}
|
||||
|
||||
.token-created-info {
|
||||
color: var(--modal-text-foreground-color);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
@extend .modal-action-btns;
|
||||
button {
|
||||
@extend .modal-accept-btn;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@extend .modal-cancel-btn;
|
||||
}
|
||||
490
frontend/src/app/main/ui/settings/integrations.cljs
Normal file
490
frontend/src/app/main/ui/settings/integrations.cljs
Normal file
@@ -0,0 +1,490 @@
|
||||
;; 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.settings.integrations
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.config :as cf]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.switch :refer [switch*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||
[app.main.ui.ds.foundations.typography :as t]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.ds.notifications.shared.notification-pill :refer [notification-pill*]]
|
||||
[app.main.ui.ds.tooltip :refer [tooltip*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.util.clipboard :as clipboard]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def tokens-ref
|
||||
(l/derived :access-tokens st/state))
|
||||
|
||||
(def token-created-ref
|
||||
(l/derived :access-token-created st/state))
|
||||
|
||||
(def mcp-server-url "https://mcp.penpot.dev")
|
||||
(def mcp-server-tech-guide "https://help.penpot.app/technical-guide/")
|
||||
|
||||
(def ^:private schema:form
|
||||
[:map {:title "AccessTokenForm"}
|
||||
[:name [::sm/text {:max 250}]]
|
||||
[:expiration-date [::sm/text {:max 250}]]])
|
||||
|
||||
(def initial-data
|
||||
{:name ""
|
||||
:expiration-date "never"})
|
||||
|
||||
(mf/defc create-token-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :create-token}
|
||||
[{:keys [token-type title-create title-created notification-create remove-token-id]}]
|
||||
(let [form (fm/use-form
|
||||
:initial initial-data
|
||||
:schema schema:form)
|
||||
|
||||
created (mf/deref token-created-ref)
|
||||
created? (mf/use-state false)
|
||||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
(mf/deps created)
|
||||
(fn []
|
||||
(when (some? remove-token-id)
|
||||
(st/emit! (du/delete-access-token {:id remove-token-id})))
|
||||
(st/emit! (du/fetch-access-tokens)
|
||||
(ntf/success (tr "dashboard.integrations.notification.success.created"))
|
||||
(reset! created? true))))
|
||||
|
||||
on-close
|
||||
(mf/use-fn
|
||||
(mf/deps created)
|
||||
(fn []
|
||||
(reset! created? false)
|
||||
(st/emit! (modal/hide))))
|
||||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(st/emit! (ntf/error (tr "errors.generic"))
|
||||
(modal/hide))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(fn [form]
|
||||
(let [cdata (:clean-data @form)
|
||||
mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}
|
||||
expiration (:expiration-date cdata)
|
||||
params (cond-> {:name (:name cdata)
|
||||
:perms (:perms cdata)}
|
||||
(not= "never" expiration) (assoc :expiration expiration)
|
||||
(some? token-type) (assoc :type token-type))]
|
||||
(st/emit! (du/create-access-token (with-meta params mdata))))))
|
||||
|
||||
on-copy-to-clipboard
|
||||
(mf/use-fn
|
||||
(mf/deps created)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(clipboard/to-clipboard (:token created))
|
||||
(st/emit! (ntf/show {:level :info
|
||||
:type :toast
|
||||
:content (tr "dashboard.integrations.notification.success.copied")
|
||||
:timeout 7000}))))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:class (stl/css :modal-close-button)
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click on-close
|
||||
:icon i/close}]
|
||||
|
||||
(if @created?
|
||||
[:div {:class (stl/css :modal-form)}
|
||||
[:> text* {:as "h2"
|
||||
:typography t/headline-large
|
||||
:class (stl/css :color-primary)}
|
||||
title-created]
|
||||
|
||||
[:> notification-pill* {:level :info
|
||||
:type :context}
|
||||
(tr "modals.integrations.create-token.info.non-recuperable")]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-token)}
|
||||
[:> input* {:type "text"
|
||||
:default-value (:token created "")
|
||||
:read-only true}]
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:class (stl/css :modal-token-button)
|
||||
:aria-label (tr "modals.integrations.create-token.copy-token")
|
||||
:on-click on-copy-to-clipboard
|
||||
:icon i/clipboard}]]
|
||||
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-small
|
||||
:class (stl/css :color-secondary)}
|
||||
(if (:expires-at created)
|
||||
(tr "modals.integrations.create-token.token-will-expire" (ct/format-inst (:expires-at created) "PPP"))
|
||||
(tr "modals.integrations.create-token.token-will-not-expire"))]]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click modal/hide!}
|
||||
(tr "labels.close")]]]
|
||||
|
||||
[:> fc/form* {:form form
|
||||
:class (stl/css :modal-form)
|
||||
:on-submit on-submit}
|
||||
|
||||
[:> text* {:as "h2"
|
||||
:typography t/headline-large
|
||||
:class (stl/css :color-primary)}
|
||||
title-create]
|
||||
|
||||
(when (some? notification-create)
|
||||
[:> notification-pill* {:level :info
|
||||
:type :context}
|
||||
notification-create])
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:> fc/form-input* {:type "text"
|
||||
:auto-focus? true
|
||||
:form form
|
||||
:name :name
|
||||
:label (tr "modals.integrations.create-token.name.label")
|
||||
:placeholder (tr "modals.integrations.create-token.name.placeholder")}]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:> text* {:as "label"
|
||||
:typography t/body-small
|
||||
:for :expiration-date
|
||||
:class (stl/css :color-primary)}
|
||||
(tr "modals.integrations.create-token.expiration-date.label")]
|
||||
[:> fc/form-select* {:options [{:label (tr "modals.integrations.create-token.expiration-never") :value "never" :id "never"}
|
||||
{:label (tr "modals.integrations.create-token.expiration-30-days") :value "720h" :id "720h"}
|
||||
{:label (tr "modals.integrations.create-token.expiration-60-days") :value "1440h" :id "1440h"}
|
||||
{:label (tr "modals.integrations.create-token.expiration-90-days") :value "2160h" :id "2160h"}
|
||||
{:label (tr "modals.integrations.create-token.expiration-180-days") :value "4320h" :id "4320h"}]
|
||||
:default-selected "never"
|
||||
:name :expiration-date}]]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click modal/hide!}
|
||||
(tr "labels.cancel")]
|
||||
[:> fc/form-submit* {:variant "primary"}
|
||||
title-create]]])]]))
|
||||
|
||||
(mf/defc token-item*
|
||||
{::mf/private true
|
||||
::mf/wrap [mf/memo]}
|
||||
[{:keys [name expires-at on-delete]}]
|
||||
(let [expires-txt (some-> expires-at (ct/format-inst "PPP"))
|
||||
expired? (and (some? expires-at) (> (ct/now) expires-at))
|
||||
|
||||
menu-open* (mf/use-state false)
|
||||
menu-open? (deref menu-open*)
|
||||
|
||||
handle-menu-close
|
||||
(mf/use-fn
|
||||
#(reset! menu-open* false))
|
||||
|
||||
handle-menu-click
|
||||
(mf/use-fn
|
||||
#(reset! menu-open* (not menu-open?)))
|
||||
|
||||
handle-open-confirm-modal
|
||||
(mf/use-fn
|
||||
(mf/deps on-delete)
|
||||
(fn []
|
||||
(st/emit! (modal/show {:type :confirm
|
||||
:title (tr "modals.integrations.delete-token.title")
|
||||
:message (tr "modals.integrations.delete-token.message")
|
||||
:accept-label (tr "modals.integrations.delete-token.accept")
|
||||
:on-accept on-delete}))))
|
||||
|
||||
options
|
||||
(mf/with-memo [on-delete]
|
||||
[{:name (tr "labels.delete")
|
||||
:id "token-delete"
|
||||
:handler handle-open-confirm-modal}])]
|
||||
|
||||
[:div {:class (stl/css :item)}
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:title name
|
||||
:class (stl/css :item-title)}
|
||||
name]
|
||||
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-small
|
||||
:class (stl/css-case :item-subtitle true
|
||||
:warning expired?)}
|
||||
(cond
|
||||
(nil? expires-at) (tr "modals.integrations.create-token.no-expiration")
|
||||
expired? (tr "modals.integrations.create-token.expired-on" expires-txt)
|
||||
:else (tr "modals.integrations.create-token.expires-on" expires-txt))]
|
||||
|
||||
[:div {:class (stl/css :item-actions)}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:class (stl/css :item-button)
|
||||
:aria-pressed menu-open?
|
||||
:aria-label (tr "labels.options")
|
||||
:on-click handle-menu-click
|
||||
:icon i/menu}]
|
||||
[:> context-menu* {:on-close handle-menu-close
|
||||
:show menu-open?
|
||||
:fixed true
|
||||
:min-width true
|
||||
:top "auto"
|
||||
:left "auto"
|
||||
:options options}]]]))
|
||||
|
||||
(mf/defc mcp-server-section*
|
||||
{::mf/private true}
|
||||
[]
|
||||
(let [tokens (mf/deref tokens-ref)
|
||||
profile (mf/deref refs/profile)
|
||||
|
||||
mcp-token (some #(when (= (:type %) "mcp") %) tokens)
|
||||
mcp-active? (d/nilv (-> profile :props :mcp-status) false)
|
||||
|
||||
expires-at (:expires-at mcp-token)
|
||||
expired? (and (some? expires-at) (> (ct/now) expires-at))
|
||||
|
||||
tooltip-id
|
||||
(mf/use-id)
|
||||
|
||||
handle-mcp-status-change
|
||||
(mf/use-fn
|
||||
(mf/deps tokens)
|
||||
(fn [mcp-status]
|
||||
(st/emit! (du/update-profile-props {:mcp-status mcp-status}))
|
||||
(if (true? mcp-status)
|
||||
(if (nil? mcp-token)
|
||||
(st/emit! (modal/show {:type :create-token
|
||||
:token-type "mcp"
|
||||
:title-create (tr "modals.integrations.create-token.title")
|
||||
:title-created (tr "modals.integrations.create-token.title.created")}))
|
||||
(st/emit! (ntf/show {:level :info
|
||||
:type :toast
|
||||
:content "MCP server succesfully enabled"
|
||||
:timeout 7000})))
|
||||
|
||||
(st/emit! (ntf/show {:level :info
|
||||
:type :toast
|
||||
:content "MCP server succesfully disabled"
|
||||
:timeout 7000})))))
|
||||
|
||||
handle-delete
|
||||
(mf/use-fn
|
||||
(mf/deps mcp-token)
|
||||
(fn []
|
||||
(let [params {:id (:id mcp-token)}
|
||||
mdata {:on-success #(st/emit! (du/fetch-access-tokens))}]
|
||||
(st/emit! (du/delete-access-token (with-meta params mdata)))
|
||||
(st/emit! (du/update-profile-props {:mcp-status false})))))
|
||||
|
||||
handle-regenerate-mcp-token
|
||||
(mf/use-fn
|
||||
(mf/deps mcp-token)
|
||||
(fn []
|
||||
(st/emit! (modal/show {:type :create-token
|
||||
:token-type "mcp"
|
||||
:title-create (tr "modals.integrations.create-token.title")
|
||||
:title-created (tr "modals.integrations.create-token.title.created")
|
||||
:notification-create (tr "modals.integrations.create-token.info.regenerate")
|
||||
:remove-token-id (:id mcp-token)}))))
|
||||
|
||||
on-copy-to-clipboard
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(clipboard/to-clipboard mcp-server-url)
|
||||
(st/emit! (ntf/show {:level :info
|
||||
:type :toast
|
||||
:content "Link succesfully copied"
|
||||
:timeout 7000}))))]
|
||||
|
||||
[:section {:class (stl/css :mcp-server-section)}
|
||||
[:div
|
||||
[:div {:class (stl/css :title)}
|
||||
[:> heading* {:level 2
|
||||
:typography t/title-medium
|
||||
:class (stl/css :color-primary :mcp-server-title)}
|
||||
"MCP Server"]
|
||||
[:> text* {:as "span"
|
||||
:typography t/body-small
|
||||
:class (stl/css :beta)}
|
||||
"Beta"]]
|
||||
|
||||
[:> text* {:as "p"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :color-secondary)}
|
||||
"The Penpot MCP Server enables MCP clients to interact directly with Penpot design files."]]
|
||||
|
||||
[:div
|
||||
[:> text* {:as "h3"
|
||||
:typography t/headline-small
|
||||
:class (stl/css :color-primary)}
|
||||
"Status"]
|
||||
|
||||
[:div {:class (stl/css :mcp-server-block)}
|
||||
(when expired?
|
||||
[:> notification-pill* {:level :error
|
||||
:type :context}
|
||||
[:div {:class (stl/css :mcp-server-notification)}
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :color-primary)}
|
||||
"The MCP key used to connect to the MCP server has expired. As a result, the connection cannot be established."]
|
||||
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :color-primary)}
|
||||
"Please regenerate the MCP key and update your client configuration with the new key."]]])
|
||||
|
||||
[:> switch* {:label (if mcp-active? "Enabled" "Disabled")
|
||||
:default-checked mcp-active?
|
||||
:on-change handle-mcp-status-change}]]]
|
||||
|
||||
(when (some? mcp-token)
|
||||
[:div {:class (stl/css :mcp-server-block)}
|
||||
[:> text* {:as "h3"
|
||||
:typography t/headline-small
|
||||
:class (stl/css :color-primary)}
|
||||
"MCP keys"]
|
||||
|
||||
[:div {:class (stl/css :mcp-server-regenerate)}
|
||||
[:> button* {:variant "primary"
|
||||
:class (stl/css :fit-content)
|
||||
:on-click handle-regenerate-mcp-token}
|
||||
"Regenerate MCP key"]
|
||||
[:> tooltip* {:content "The MCP key is needed for the MCP client set up"
|
||||
:id tooltip-id}
|
||||
[:> icon* {:icon-id i/info
|
||||
:class (stl/css :color-secondary)}]]]
|
||||
|
||||
[:div {:class (stl/css :list)}
|
||||
[:> token-item* {:key (:id mcp-token)
|
||||
:name (:name mcp-token)
|
||||
:expires-at (:expires-at mcp-token)
|
||||
:on-delete handle-delete}]]])
|
||||
|
||||
[:> notification-pill* {:level :default
|
||||
:type :context}
|
||||
[:div {:class (stl/css :mcp-server-notification)}
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :color-secondary)}
|
||||
"This is the server url you'll need to configure your MCP client in order to connect it to the Penpot MCP server."]
|
||||
[:div {:class (stl/css :mcp-server-notification-line)}
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :color-primary)}
|
||||
mcp-server-url]
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:on-click on-copy-to-clipboard
|
||||
:class (stl/css :mcp-server-notification-link)}
|
||||
[:> icon* {:icon-id i/clipboard}] "Copy link"]]
|
||||
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :color-secondary)}
|
||||
[:a {:href mcp-server-tech-guide
|
||||
:class (stl/css :mcp-server-notification-link)}
|
||||
"How to configure MCP clients" [:> icon* {:icon-id i/open-link}]]]]]]))
|
||||
|
||||
(mf/defc access-tokens-section*
|
||||
{::mf/private true}
|
||||
[]
|
||||
(let [tokens (mf/deref tokens-ref)
|
||||
|
||||
handle-click
|
||||
(mf/use-fn
|
||||
#(st/emit! (modal/show {:type :create-token
|
||||
:title-create (tr "modals.integrations.create-token.title")
|
||||
:title-created (tr "modals.integrations.create-token.title.created")})))
|
||||
|
||||
handle-delete
|
||||
(mf/use-fn
|
||||
(fn [token-id]
|
||||
(let [params {:id token-id}
|
||||
mdata {:on-success #(st/emit! (du/fetch-access-tokens))}]
|
||||
(st/emit! (du/delete-access-token (with-meta params mdata))))))]
|
||||
|
||||
[:section {:class (stl/css :access-tokens-section)}
|
||||
[:> heading* {:level 2
|
||||
:typography t/title-medium
|
||||
:class (stl/css :color-primary)}
|
||||
(tr "dashboard.integrations.access-tokens.personal")]
|
||||
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :color-secondary)}
|
||||
(tr "dashboard.integrations.access-tokens.personal.description")]
|
||||
|
||||
[:> button* {:variant "primary"
|
||||
:class (stl/css :fit-content)
|
||||
:on-click handle-click}
|
||||
(tr "dashboard.integrations.access-tokens.create")]
|
||||
|
||||
(if (empty? tokens)
|
||||
[:div {:class (stl/css :frame)}
|
||||
[:> text* {:as "div"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :color-secondary :text-center)}
|
||||
[:div (tr "dashboard.integrations.access-tokens.empty.no-access-tokens")]
|
||||
[:div (tr "dashboard.integrations.access-tokens.empty.add-one")]]]
|
||||
|
||||
[:div {:class (stl/css :list)}
|
||||
(for [token tokens]
|
||||
(when (nil? (:type token))
|
||||
[:> token-item* {:key (:id token)
|
||||
:name (:name token)
|
||||
:expires-at (:expires-at token)
|
||||
:on-delete (partial handle-delete (:id token))}]))])]))
|
||||
|
||||
(mf/defc integrations-page*
|
||||
[]
|
||||
(mf/with-effect []
|
||||
(dom/set-html-title (tr "title.settings.integrations"))
|
||||
(st/emit! (du/fetch-access-tokens)))
|
||||
|
||||
[:div {:class (stl/css :integrations)}
|
||||
[:> heading* {:level 1
|
||||
:typography t/title-large
|
||||
:class (stl/css :color-primary)}
|
||||
(tr "dashboard.integrations.title")]
|
||||
|
||||
(when (contains? cf/flags :mcp-server)
|
||||
[:> mcp-server-section*])
|
||||
|
||||
(when (and (contains? cf/flags :mcp-server)
|
||||
(contains? cf/flags :access-tokens))
|
||||
[:hr {:class (stl/css :separator)}])
|
||||
|
||||
(when (contains? cf/flags :access-tokens)
|
||||
[:> access-tokens-section*])])
|
||||
200
frontend/src/app/main/ui/settings/integrations.scss
Normal file
200
frontend/src/app/main/ui/settings/integrations.scss
Normal file
@@ -0,0 +1,200 @@
|
||||
// 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;
|
||||
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/spacing.scss" as *;
|
||||
@use "ds/mixins.scss" as *;
|
||||
|
||||
.color-primary {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.color-secondary {
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fit-content {
|
||||
inline-size: fit-content;
|
||||
}
|
||||
|
||||
.beta {
|
||||
color: var(--color-accent-primary);
|
||||
border: 1px solid var(--color-accent-primary);
|
||||
inline-size: fit-content;
|
||||
padding: var(--sp-xxs) var(--sp-s);
|
||||
border-radius: $br-4;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
@extend .modal-overlay-base;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
@extend .modal-container-base;
|
||||
inline-size: $sz-400;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxxl);
|
||||
}
|
||||
|
||||
.modal-close-button {
|
||||
position: absolute;
|
||||
top: var(--sp-s);
|
||||
right: var(--sp-s);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.modal-token {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-token-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
}
|
||||
|
||||
.integrations {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
margin: $sz-88 auto $sz-120 auto;
|
||||
gap: $sz-32;
|
||||
inline-size: $sz-500;
|
||||
}
|
||||
|
||||
.access-tokens-section {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.mcp-server-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.mcp-server-notification {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.mcp-server-notification-line {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--sp-m);
|
||||
}
|
||||
|
||||
.mcp-server-notification-link {
|
||||
color: var(--color-accent-primary);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.mcp-server-title {
|
||||
margin: var(--sp-s) 0;
|
||||
}
|
||||
|
||||
.mcp-server-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.mcp-server-regenerate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.separator {
|
||||
border: 1px solid var(--color-background-quaternary);
|
||||
margin: var(--sp-s) 0;
|
||||
}
|
||||
|
||||
.frame {
|
||||
border: 1px solid var(--color-background-quaternary);
|
||||
padding: var(--sp-m);
|
||||
border-radius: $br-8;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: grid;
|
||||
grid-auto-rows: $sz-64;
|
||||
gap: var(--sp-m);
|
||||
}
|
||||
|
||||
.item {
|
||||
display: grid;
|
||||
grid-template-columns: 40% 1fr auto;
|
||||
align-items: center;
|
||||
background-color: var(--color-background-tertiary);
|
||||
border-radius: $br-8;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
@include textEllipsis;
|
||||
align-content: center;
|
||||
block-size: $sz-64;
|
||||
padding: 0 var(--sp-l);
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.item-subtitle {
|
||||
align-content: center;
|
||||
block-size: $sz-64;
|
||||
padding: 0 var(--sp-l);
|
||||
color: var(--color-foreground-secondary);
|
||||
|
||||
&.warning {
|
||||
padding: 0 var(--sp-m);
|
||||
block-size: $sz-32;
|
||||
inline-size: fit-content;
|
||||
color: var(--color-foreground-primary);
|
||||
background-color: var(--color-background-warning);
|
||||
border: 1px solid var(--color-accent-warning);
|
||||
border-radius: $br-8;
|
||||
}
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
block-size: $sz-64;
|
||||
inline-size: $sz-48;
|
||||
border-radius: 0 var(--sp-s) var(--sp-s) 0;
|
||||
}
|
||||
@@ -43,8 +43,8 @@
|
||||
(def ^:private go-settings-subscription
|
||||
#(st/emit! (rt/nav :settings-subscription)))
|
||||
|
||||
(def ^:private go-settings-access-tokens
|
||||
#(st/emit! (rt/nav :settings-access-tokens)))
|
||||
(def ^:private go-settings-integrations
|
||||
#(st/emit! (rt/nav :settings-integrations)))
|
||||
|
||||
(def ^:private go-settings-notifications
|
||||
#(st/emit! (rt/nav :settings-notifications)))
|
||||
@@ -66,7 +66,7 @@
|
||||
options? (= section :settings-options)
|
||||
feedback? (= section :settings-feedback)
|
||||
subscription? (= section :settings-subscription)
|
||||
access-tokens? (= section :settings-access-tokens)
|
||||
integrations? (= section :settings-integrations)
|
||||
notifications? (= section :settings-notifications)
|
||||
team-id (or (dtm/get-last-team-id)
|
||||
(:default-team-id profile))
|
||||
@@ -115,12 +115,13 @@
|
||||
:data-testid "settings-subscription"}
|
||||
[:span {:class (stl/css :element-title)} (tr "subscription.labels")]])
|
||||
|
||||
(when (contains? cf/flags :access-tokens)
|
||||
[:li {:class (stl/css-case :current access-tokens?
|
||||
(when (or (contains? cf/flags :access-tokens)
|
||||
(contains? cf/flags :mcp-server))
|
||||
[:li {:class (stl/css-case :current integrations?
|
||||
:settings-item true)
|
||||
:on-click go-settings-access-tokens
|
||||
:data-testid "settings-access-tokens"}
|
||||
[:span {:class (stl/css :element-title)} (tr "labels.access-tokens")]])
|
||||
:on-click go-settings-integrations
|
||||
:data-testid "settings-integrations"}
|
||||
[:span {:class (stl/css :element-title)} (tr "labels.integrations")]])
|
||||
|
||||
[:hr {:class (stl/css :sidebar-separator)}]
|
||||
|
||||
|
||||
@@ -9,10 +9,15 @@
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.constants :refer [right-sidebar-default-width right-sidebar-default-max-width left-sidebar-default-max-width left-sidebar-default-width]]
|
||||
[app.config :as cf]
|
||||
[app.main.constants :refer [left-sidebar-default-max-width
|
||||
left-sidebar-default-width
|
||||
right-sidebar-default-max-width
|
||||
right-sidebar-default-width]]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.tokenscript :as ts]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
@@ -369,6 +374,12 @@
|
||||
(ctob/get-tokens-in-active-sets tokens-lib)
|
||||
{}))
|
||||
|
||||
tokenscript? (contains? cf/flags :tokenscript)
|
||||
|
||||
tokenscript-resolved-active-tokens
|
||||
(mf/with-memo [tokens-lib tokenscript?]
|
||||
(when tokenscript? (ts/resolve-tokens active-tokens)))
|
||||
|
||||
resolved-active-tokens
|
||||
(sd/use-resolved-tokens* active-tokens)]
|
||||
|
||||
@@ -380,7 +391,9 @@
|
||||
:page-id page-id
|
||||
:tokens-lib tokens-lib
|
||||
:active-tokens active-tokens
|
||||
:resolved-active-tokens resolved-active-tokens}])
|
||||
:resolved-active-tokens (if (contains? cf/flags :tokenscript)
|
||||
tokenscript-resolved-active-tokens
|
||||
resolved-active-tokens)}])
|
||||
[:> right-sidebar* {:section section
|
||||
:selected selected
|
||||
:drawing-tool drawing-tool
|
||||
|
||||
@@ -60,8 +60,8 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(fn [token-name attr]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
|
||||
@@ -152,9 +152,9 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps token-colors groups)
|
||||
(fn [token]
|
||||
(fn [token-name]
|
||||
(let [prev-colors (mf/ref-val prev-colors-ref)
|
||||
token-color (some #(when (= (:token-name %) (:name token)) %) token-colors)
|
||||
token-color (some #(when (= (:token-name %) token-name) %) token-colors)
|
||||
|
||||
[color-operations _] (retrieve-color-operations groups token-color prev-colors)]
|
||||
(doseq [op color-operations]
|
||||
@@ -166,8 +166,8 @@
|
||||
(d/without-nils))]
|
||||
(mf/set-ref-val! prev-colors-ref
|
||||
(conj prev-colors color))
|
||||
(st/emit! (dwta/unapply-token {:attributes attr
|
||||
:token token
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes attr
|
||||
:shape-ids [(:shape-id op)]})))))))
|
||||
|
||||
select-only
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
|
||||
render-wasm? (feat/use-feature "render-wasm/v1")
|
||||
|
||||
|
||||
^boolean
|
||||
multiple? (= :multiple fills)
|
||||
|
||||
@@ -183,9 +182,9 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token]
|
||||
(st/emit! (dwta/unapply-token {:attributes #{:fill}
|
||||
:token token
|
||||
(fn [token-name]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes #{:fill}
|
||||
:shape-ids ids}))))]
|
||||
|
||||
(mf/with-layout-effect [hide-on-export]
|
||||
@@ -215,7 +214,8 @@
|
||||
(when open?
|
||||
[:div {:class (stl/css :fill-content)}
|
||||
(cond
|
||||
(= :multiple fills)
|
||||
(or (= :multiple fills)
|
||||
(= :multiple fill-token-applied))
|
||||
[:div {:class (stl/css :fill-multiple)}
|
||||
[:div {:class (stl/css :fill-multiple-label)}
|
||||
(tr "settings.multiple")]
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(fn [token-name attr]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
@@ -229,13 +229,13 @@
|
||||
:property (tr "workspace.options.opacity")
|
||||
:applied-token (get applied-tokens :opacity)
|
||||
:placeholder (if (or (= :multiple (get applied-tokens :opacity))
|
||||
(= :multiple (or (get values name) 1)))
|
||||
(= :multiple (or (get values :opacity) 1)))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:align :right
|
||||
:class (stl/css :numeric-input-wrapper)
|
||||
:value (* 100
|
||||
(or (get values name) 1))}]
|
||||
(or (get values :opacity) 1))}]
|
||||
|
||||
[:div {:class (stl/css :input)
|
||||
:title (tr "workspace.options.opacity")}
|
||||
@@ -248,7 +248,6 @@
|
||||
:max 100
|
||||
:className (stl/css :numeric-input)}]])
|
||||
|
||||
|
||||
[:div {:class (stl/css :actions)}
|
||||
(cond
|
||||
(or (= :multiple hidden?) (not hidden?))
|
||||
|
||||
@@ -339,8 +339,8 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(fn [token-name attr]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
@@ -475,7 +475,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(st/emit! (dwta/unapply-token {:token-name token
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
@@ -722,7 +722,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(st/emit! (dwta/unapply-token {:token-name token
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
|
||||
@@ -97,8 +97,8 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(fn [token-name attr]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
@@ -220,8 +220,8 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(fn [token-name attr]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
@@ -550,8 +550,8 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(fn [token-name attr]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
|
||||
@@ -319,8 +319,8 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
(fn [token-name attr]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
|
||||
@@ -171,9 +171,9 @@
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attrs]
|
||||
(st/emit! (dwta/unapply-token {:attributes attrs
|
||||
:token token
|
||||
(fn [token-name attrs]
|
||||
(st/emit! (dwta/unapply-token {:token-name token-name
|
||||
:attributes attrs
|
||||
:shape-ids ids}))))]
|
||||
|
||||
[:section {:class (stl/css :stroke-section)
|
||||
|
||||
@@ -85,14 +85,14 @@
|
||||
(mf/use-fn
|
||||
(mf/deps detach-token token applied-token-name)
|
||||
(fn []
|
||||
(let [token (or token applied-token-name)]
|
||||
(detach-token token))))
|
||||
(let [token-name (or (:name token) applied-token-name)]
|
||||
(detach-token token-name))))
|
||||
|
||||
has-errors (some? (:errors token))
|
||||
token-name (:name token)
|
||||
resolved (:resolved-value token)
|
||||
not-active (and (empty? active-tokens)
|
||||
(nil? token))
|
||||
not-active (or (empty? active-tokens)
|
||||
(nil? token))
|
||||
id (dm/str (:id token) "-name")
|
||||
swatch-tooltip-content (cond
|
||||
not-active
|
||||
@@ -344,7 +344,6 @@
|
||||
(mf/with-effect [color prev-color disable-picker]
|
||||
(when (and (not disable-picker) (not= prev-color color))
|
||||
(modal/update-props! :colorpicker {:data (parse-color color)})))
|
||||
|
||||
[:div {:class [class row-class]}
|
||||
;; Drag handler
|
||||
(when (some? on-reorder)
|
||||
|
||||
@@ -64,14 +64,17 @@
|
||||
(let [selected? (selected-pred attribute)
|
||||
props {:attributes #{attribute}
|
||||
:token token
|
||||
:shape-ids shape-ids}]
|
||||
:shape-ids shape-ids}
|
||||
unnaply-props {:token-name (:name token)
|
||||
:attributes #{attribute}
|
||||
:shape-ids shape-ids}]
|
||||
|
||||
{:title title
|
||||
:hint hint
|
||||
:selected? selected?
|
||||
:action (fn []
|
||||
(if selected?
|
||||
(st/emit! (dwta/unapply-token props))
|
||||
(st/emit! (dwta/unapply-token unnaply-props))
|
||||
(st/emit! (dwta/apply-token (assoc props :on-update-shape on-update-shape-fn)))))}))
|
||||
allowed-attributes)))
|
||||
|
||||
@@ -82,12 +85,15 @@
|
||||
{:keys [all-selected? selected-pred shape-ids]} (attribute-actions token selected-shapes attributes)
|
||||
all-action (let [props {:attributes attributes
|
||||
:token token
|
||||
:shape-ids shape-ids}]
|
||||
:shape-ids shape-ids}
|
||||
unnaply-props {:token-name (:name token)
|
||||
:attributes attributes
|
||||
:shape-ids shape-ids}]
|
||||
{:title (tr "labels.all")
|
||||
:selected? all-selected?
|
||||
:hint hint
|
||||
:action #(if all-selected?
|
||||
(st/emit! (dwta/unapply-token props))
|
||||
(st/emit! (dwta/unapply-token unnaply-props))
|
||||
(st/emit! (dwta/apply-token (assoc props :on-update-shape (or on-update-shape-all on-update-shape)))))})
|
||||
single-actions (map (fn [[attr title]]
|
||||
(let [selected? (selected-pred attr)]
|
||||
@@ -96,10 +102,13 @@
|
||||
:action #(let [props {:attributes #{attr}
|
||||
:token token
|
||||
:shape-ids shape-ids}
|
||||
unnaply-props {:token-name (:name token)
|
||||
:attributes #{attr}
|
||||
:shape-ids shape-ids}
|
||||
event (cond
|
||||
all-selected? (-> (assoc props :attributes-to-remove attributes)
|
||||
(dwta/apply-token))
|
||||
selected? (dwta/unapply-token props)
|
||||
selected? (dwta/unapply-token unnaply-props)
|
||||
:else (-> (assoc props :on-update-shape on-update-shape)
|
||||
(dwta/apply-token)))]
|
||||
(st/emit! event))}))
|
||||
@@ -123,9 +132,12 @@
|
||||
:action (fn []
|
||||
(let [props {:attributes attrs
|
||||
:token token
|
||||
:shape-ids shape-ids}]
|
||||
:shape-ids shape-ids}
|
||||
unnaply-props {:token-name (:name token)
|
||||
:attributes attrs
|
||||
:shape-ids shape-ids}]
|
||||
(if all-selected?
|
||||
(st/emit! (dwta/unapply-token props))
|
||||
(st/emit! (dwta/unapply-token unnaply-props))
|
||||
(st/emit! (dwta/apply-token (assoc props :on-update-shape on-update-shape))))))}
|
||||
{:title "Horizontal"
|
||||
:selected? horizontal-selected?
|
||||
@@ -165,10 +177,13 @@
|
||||
:action #(let [props {:attributes #{attr}
|
||||
:token token
|
||||
:shape-ids shape-ids}
|
||||
unnaply-props {:token-name (:name token)
|
||||
:attributes #{attr}
|
||||
:shape-ids shape-ids}
|
||||
event (cond
|
||||
all-selected? (-> (assoc props :attributes-to-remove attrs)
|
||||
(dwta/apply-token))
|
||||
selected? (dwta/unapply-token props)
|
||||
selected? (dwta/unapply-token unnaply-props)
|
||||
:else (-> (assoc props :on-update-shape on-update-shape)
|
||||
(dwta/apply-token)))]
|
||||
(st/emit! event))}))
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.config :as cf]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.tokenscript :as ts]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.ui.ds.controls.input :as ds]
|
||||
[app.main.ui.forms :as fc]
|
||||
@@ -139,6 +142,18 @@
|
||||
;; -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
(defn- resolve-value-tokenscript
|
||||
[tokens prev-token value]
|
||||
(let [result (ts/update-token tokens (assoc prev-token :value value))
|
||||
token-result (.-resolved result)]
|
||||
(rx/of
|
||||
(cond
|
||||
(ts/processor-error? token-result) {:error (wte/error-with-value :error.style-dictionary/missing-reference (some->> (.-dependencyChain token-result)
|
||||
(seq)
|
||||
(rest)))}
|
||||
(instance? js/Error token-result) {:error (wte/error-with-value :error.style-dictionary/invalid-token-value value)}
|
||||
:else {:value token-result}))))
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token token-name value]
|
||||
(let [valid-token-name?
|
||||
@@ -216,7 +231,10 @@
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name token-name]
|
||||
|
||||
(let [subs (->> resolve-stream
|
||||
(let [resolve-value (if (contains? cf/flags :tokenscript)
|
||||
resolve-value-tokenscript
|
||||
resolve-value)
|
||||
subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token token-name))
|
||||
(rx/map (fn [result]
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
||||
[app.main.ui.workspace.tokens.management.forms.validators :refer [default-validate-token]]
|
||||
@@ -143,10 +142,6 @@
|
||||
(fm/use-form :schema schema
|
||||
:initial initial)
|
||||
|
||||
warning-name-change?
|
||||
(not= (get-in @form [:data :name])
|
||||
(:name initial))
|
||||
|
||||
on-toggle-tab
|
||||
(mf/use-fn
|
||||
(mf/deps form)
|
||||
@@ -276,12 +271,7 @@
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:trim true
|
||||
:auto-focus true}]
|
||||
|
||||
(when (and warning-name-change? (= action "edit"))
|
||||
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
|
||||
[:> context-notification*
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
:auto-focus true}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
(case type
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.path-names :as cpn]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.config :as cf]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.color :as dwtc]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
@@ -177,6 +178,8 @@
|
||||
[{:keys [on-click token on-context-menu selected-shapes is-selected-inside-layout active-theme-tokens]}]
|
||||
(let [{:keys [name value errors type]} token
|
||||
|
||||
resolved-token (get active-theme-tokens (:name token))
|
||||
|
||||
has-selected? (pos? (count selected-shapes))
|
||||
is-reference? (cft/is-reference? token)
|
||||
contains-path? (str/includes? name ".")
|
||||
@@ -209,8 +212,10 @@
|
||||
is-viewer? (not can-edit?)
|
||||
|
||||
ref-not-in-active-set
|
||||
(and is-reference?
|
||||
(not (contains-reference-value? value active-theme-tokens)))
|
||||
(if (contains? cf/flags :tokenscript)
|
||||
(seq (:errors resolved-token))
|
||||
(and is-reference?
|
||||
(not (contains-reference-value? value active-theme-tokens))))
|
||||
|
||||
no-valid-value (seq errors)
|
||||
|
||||
@@ -220,9 +225,8 @@
|
||||
|
||||
color
|
||||
(when (cft/color-token? token)
|
||||
(let [theme-token (get active-theme-tokens name)]
|
||||
(or (dwtc/resolved-token-bullet-color theme-token)
|
||||
(dwtc/resolved-token-bullet-color token))))
|
||||
(or (dwtc/resolved-token-bullet-color resolved-token)
|
||||
(dwtc/resolved-token-bullet-color token)))
|
||||
|
||||
status-icon? (contains? token-types-with-status-icon type)
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
;; ==== Action
|
||||
events [(dwta/unapply-token {:shape-ids [(cthi/id :frame1)]
|
||||
:attributes #{:r1 :r2 :r3 :r4}
|
||||
:token (toht/get-token file "test-token-1")})]
|
||||
:token-name "test-token-1"})]
|
||||
|
||||
step2 (fn [_]
|
||||
(let [events2 [(dwl/sync-file (:id file) (:id file))]]
|
||||
@@ -289,7 +289,7 @@
|
||||
;; ==== Action
|
||||
events [(dwta/unapply-token {:shape-ids [(cthi/id :c-frame1)]
|
||||
:attributes #{:r1 :r2 :r3 :r4}
|
||||
:token (toht/get-token file "test-token-1")})
|
||||
:token-name "test-token-1"})
|
||||
(dwta/apply-token {:shape-ids [(cthi/id :frame1)]
|
||||
:attributes #{:r1 :r2 :r3 :r4}
|
||||
:token (toht/get-token file "test-token-3")
|
||||
|
||||
@@ -338,77 +338,6 @@ msgstr "You're going to restore %s."
|
||||
msgid "dashboard-restore-file-confirmation.title"
|
||||
msgstr "Restore file"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:103
|
||||
msgid "dashboard.access-tokens.copied-success"
|
||||
msgstr "Copied token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:189
|
||||
msgid "dashboard.access-tokens.create"
|
||||
msgstr "Generate new token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:64
|
||||
msgid "dashboard.access-tokens.create.success"
|
||||
msgstr "Access token created successfully."
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:286
|
||||
msgid "dashboard.access-tokens.empty.add-one"
|
||||
msgstr "Press the button \"Generate new token\" to generate one."
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:285
|
||||
msgid "dashboard.access-tokens.empty.no-access-tokens"
|
||||
msgstr "You have no tokens so far."
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:135
|
||||
msgid "dashboard.access-tokens.expiration-180-days"
|
||||
msgstr "180 days"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:132
|
||||
msgid "dashboard.access-tokens.expiration-30-days"
|
||||
msgstr "30 days"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:133
|
||||
msgid "dashboard.access-tokens.expiration-60-days"
|
||||
msgstr "60 days"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:134
|
||||
msgid "dashboard.access-tokens.expiration-90-days"
|
||||
msgstr "90 days"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:131
|
||||
msgid "dashboard.access-tokens.expiration-never"
|
||||
msgstr "Never"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:268
|
||||
msgid "dashboard.access-tokens.expired-on"
|
||||
msgstr "Expired on %s"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:269
|
||||
msgid "dashboard.access-tokens.expires-on"
|
||||
msgstr "Expires on %s"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:267
|
||||
msgid "dashboard.access-tokens.no-expiration"
|
||||
msgstr "No expiration date"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:184
|
||||
msgid "dashboard.access-tokens.personal"
|
||||
msgstr "Personal access tokens"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:185
|
||||
msgid "dashboard.access-tokens.personal.description"
|
||||
msgstr ""
|
||||
"Personal access tokens function like an alternative to our login/password "
|
||||
"authentication system and can be used to allow an application to access the "
|
||||
"internal Penpot API"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:142
|
||||
msgid "dashboard.access-tokens.token-will-expire"
|
||||
msgstr "The token will expire on %s"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:143
|
||||
msgid "dashboard.access-tokens.token-will-not-expire"
|
||||
msgstr "The token has no expiration date"
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs:41
|
||||
msgid "dashboard.add-file"
|
||||
msgstr "Add file"
|
||||
@@ -804,6 +733,41 @@ msgstr "Uploading data to server (%s/%s)"
|
||||
msgid "dashboard.import.progress.upload-media"
|
||||
msgstr "Uploading file: %s"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:480
|
||||
msgid "dashboard.integrations.title"
|
||||
msgstr "Integrations"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:189
|
||||
msgid "dashboard.integrations.access-tokens.create"
|
||||
msgstr "Create new access token"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:286
|
||||
msgid "dashboard.integrations.access-tokens.empty.add-one"
|
||||
msgstr "Press the button \"Create new access token\" to generate one."
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:285
|
||||
msgid "dashboard.integrations.access-tokens.empty.no-access-tokens"
|
||||
msgstr "You have no tokens so far."
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:184
|
||||
msgid "dashboard.integrations.access-tokens.personal"
|
||||
msgstr "Personal access tokens"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:185
|
||||
msgid "dashboard.integrations.access-tokens.personal.description"
|
||||
msgstr ""
|
||||
"Personal access tokens function like an alternative to our login/password "
|
||||
"authentication system and can be used to allow an application to access the "
|
||||
"internal Penpot API"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:103
|
||||
msgid "dashboard.integrations.notification.success.copied"
|
||||
msgstr "Copied token"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:64
|
||||
msgid "dashboard.integrations.notification.success.created"
|
||||
msgstr "Access token created successfully."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs:765
|
||||
msgid "dashboard.invitation-modal.delete"
|
||||
msgstr "You're going to delete the invitations to:"
|
||||
@@ -2474,6 +2438,10 @@ msgstr "Info"
|
||||
msgid "labels.installed-fonts"
|
||||
msgstr "Installed fonts"
|
||||
|
||||
#: src/app/main/ui/settings/sidebar.cljs:123
|
||||
msgid "labels.integrations"
|
||||
msgstr "Integrations"
|
||||
|
||||
#: src/app/main/ui/static.cljs:396
|
||||
msgid "labels.internal-error.desc-message-first"
|
||||
msgstr "Something bad happened."
|
||||
@@ -3134,30 +3102,6 @@ msgstr "Change email"
|
||||
msgid "modals.change-email.title"
|
||||
msgstr "Change your email"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:152, src/app/main/ui/settings/access_tokens.cljs:158
|
||||
msgid "modals.create-access-token.copy-token"
|
||||
msgstr "Copy token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:130
|
||||
msgid "modals.create-access-token.expiration-date.label"
|
||||
msgstr "Expiration date"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:124
|
||||
msgid "modals.create-access-token.name.label"
|
||||
msgstr "Name"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:126
|
||||
msgid "modals.create-access-token.name.placeholder"
|
||||
msgstr "The name can help to know what's the token for"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:178
|
||||
msgid "modals.create-access-token.submit-label"
|
||||
msgstr "Create token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:111
|
||||
msgid "modals.create-access-token.title"
|
||||
msgstr "Generate access token"
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs:1127
|
||||
msgid "modals.create-webhook.submit-label"
|
||||
msgstr "Create webhook"
|
||||
@@ -3174,18 +3118,6 @@ msgstr "Payload URL"
|
||||
msgid "modals.create-webhook.url.placeholder"
|
||||
msgstr "https://example.com/postreceive"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:257
|
||||
msgid "modals.delete-acces-token.accept"
|
||||
msgstr "Delete token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:256
|
||||
msgid "modals.delete-acces-token.message"
|
||||
msgstr "Are you sure you want to delete this token?"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:255
|
||||
msgid "modals.delete-acces-token.title"
|
||||
msgstr "Delete token"
|
||||
|
||||
#: src/app/main/ui/settings/delete_account.cljs:56
|
||||
msgid "modals.delete-account.cancel"
|
||||
msgstr "Cancel and keep my account"
|
||||
@@ -3374,6 +3306,90 @@ msgstr "Edit webhook"
|
||||
msgid "modals.edit-webhook.title"
|
||||
msgstr "Edit webhook"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:152, src/app/main/ui/settings/integrations.cljs:158
|
||||
msgid "modals.integrations.create-token.copy-token"
|
||||
msgstr "Copy token"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:135
|
||||
msgid "modals.integrations.create-token.expiration-180-days"
|
||||
msgstr "180 days"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:132
|
||||
msgid "modals.integrations.create-token.expiration-30-days"
|
||||
msgstr "30 days"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:133
|
||||
msgid "modals.integrations.create-token.expiration-60-days"
|
||||
msgstr "60 days"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:134
|
||||
msgid "modals.integrations.create-token.expiration-90-days"
|
||||
msgstr "90 days"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:131
|
||||
msgid "modals.integrations.create-token.expiration-never"
|
||||
msgstr "Never"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:268
|
||||
msgid "modals.integrations.create-token.expired-on"
|
||||
msgstr "Expired on %s"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:269
|
||||
msgid "modals.integrations.create-token.expires-on"
|
||||
msgstr "Expires on %s"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:267
|
||||
msgid "modals.integrations.create-token.no-expiration"
|
||||
msgstr "No expiration date"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:130
|
||||
msgid "modals.integrations.create-token.expiration-date.label"
|
||||
msgstr "Expiration date"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:131
|
||||
msgid "modals.integrations.create-token.info.non-recuperable"
|
||||
msgstr "This unique token is non-recuperable. If you lose it, you will need to create a new one."
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:317
|
||||
msgid "modals.integrations.create-token.info.regenerate"
|
||||
msgstr "Regenerating the key will immediately revoke the current one. Any application using it will stop working."
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:124
|
||||
msgid "modals.integrations.create-token.name.label"
|
||||
msgstr "Name"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:126
|
||||
msgid "modals.integrations.create-token.name.placeholder"
|
||||
msgstr "The name can help to know what's the token for"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:111
|
||||
msgid "modals.integrations.create-token.title"
|
||||
msgstr "Create access token"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:111
|
||||
msgid "modals.integrations.create-token.title.created"
|
||||
msgstr "Access token created"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:142
|
||||
msgid "modals.integrations.create-token.token-will-expire"
|
||||
msgstr "The token will expire on %s"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:143
|
||||
msgid "modals.integrations.create-token.token-will-not-expire"
|
||||
msgstr "The token has no expiration date"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:257
|
||||
msgid "modals.integrations.delete-token.accept"
|
||||
msgstr "Delete token"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:256
|
||||
msgid "modals.integrations.delete-token.message"
|
||||
msgstr "Are you sure you want to delete this token?"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:255
|
||||
msgid "modals.integrations.delete-token.title"
|
||||
msgstr "Delete token"
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs:249
|
||||
msgid "modals.invite-member-confirm.accept"
|
||||
msgstr "Send invitation"
|
||||
@@ -5079,14 +5095,14 @@ msgstr "Shared Libraries - %s - Penpot"
|
||||
msgid "title.default"
|
||||
msgstr "Penpot - Design Freedom for Teams"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:278
|
||||
msgid "title.settings.access-tokens"
|
||||
msgstr "Profile - Access tokens"
|
||||
|
||||
#: src/app/main/ui/settings/feedback.cljs:161
|
||||
msgid "title.settings.feedback"
|
||||
msgstr "Give feedback - Penpot"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:278
|
||||
msgid "title.settings.integrations"
|
||||
msgstr "Integrations - Penpot"
|
||||
|
||||
#: src/app/main/ui/settings/notifications.cljs:45
|
||||
msgid "title.settings.notifications"
|
||||
msgstr "Notifications - Penpot"
|
||||
|
||||
@@ -347,77 +347,6 @@ msgstr "Vas a restaurar %s."
|
||||
msgid "dashboard-restore-file-confirmation.title"
|
||||
msgstr "Restaurar archivo"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:103
|
||||
msgid "dashboard.access-tokens.copied-success"
|
||||
msgstr "Token copiado"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:189
|
||||
msgid "dashboard.access-tokens.create"
|
||||
msgstr "Generar nuevo token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:64
|
||||
msgid "dashboard.access-tokens.create.success"
|
||||
msgstr "Access token creado con éxito."
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:286
|
||||
msgid "dashboard.access-tokens.empty.add-one"
|
||||
msgstr "Pulsa el botón \"Generar nuevo token\" para generar uno."
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:285
|
||||
msgid "dashboard.access-tokens.empty.no-access-tokens"
|
||||
msgstr "Todavía no tienes ningún token."
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:135
|
||||
msgid "dashboard.access-tokens.expiration-180-days"
|
||||
msgstr "180 días"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:132
|
||||
msgid "dashboard.access-tokens.expiration-30-days"
|
||||
msgstr "30 días"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:133
|
||||
msgid "dashboard.access-tokens.expiration-60-days"
|
||||
msgstr "60 días"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:134
|
||||
msgid "dashboard.access-tokens.expiration-90-days"
|
||||
msgstr "90 días"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:131
|
||||
msgid "dashboard.access-tokens.expiration-never"
|
||||
msgstr "Nunca"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:268
|
||||
msgid "dashboard.access-tokens.expired-on"
|
||||
msgstr "Expiró el %s"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:269
|
||||
msgid "dashboard.access-tokens.expires-on"
|
||||
msgstr "Expira el %s"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:267
|
||||
msgid "dashboard.access-tokens.no-expiration"
|
||||
msgstr "Sin fecha de expiración"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:184
|
||||
msgid "dashboard.access-tokens.personal"
|
||||
msgstr "Access tokens personales"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:185
|
||||
msgid "dashboard.access-tokens.personal.description"
|
||||
msgstr ""
|
||||
"Los access tokens personales funcionan como una alternativa a nuestro "
|
||||
"sistema de autenticación usuario/password y se pueden usar para permitir a "
|
||||
"otras aplicaciones acceso a la API interna de Penpot"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:142
|
||||
msgid "dashboard.access-tokens.token-will-expire"
|
||||
msgstr "El token expirará el %s"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:143
|
||||
msgid "dashboard.access-tokens.token-will-not-expire"
|
||||
msgstr "El token no tiene fecha de expiración"
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs:41
|
||||
msgid "dashboard.add-file"
|
||||
msgstr "Añadir archivo"
|
||||
@@ -808,6 +737,41 @@ msgstr "Enviando datos al servidor (%s/%s)"
|
||||
msgid "dashboard.import.progress.upload-media"
|
||||
msgstr "Enviando fichero: %s"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:480
|
||||
msgid "dashboard.integrations.title"
|
||||
msgstr "Integraciones"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:189
|
||||
msgid "dashboard.integrations.access-tokens.create"
|
||||
msgstr "Crear nuevo token de acceso"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:286
|
||||
msgid "dashboard.integrations.access-tokens.empty.add-one"
|
||||
msgstr "Pulsa el botón \"Crear nuevo token de accesso\" para generar uno."
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:285
|
||||
msgid "dashboard.integrations.access-tokens.empty.no-access-tokens"
|
||||
msgstr "Todavía no tienes ningún token."
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:184
|
||||
msgid "dashboard.integrations.access-tokens.personal"
|
||||
msgstr "Access tokens personales"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:185
|
||||
msgid "dashboard.integrations.access-tokens.personal.description"
|
||||
msgstr ""
|
||||
"Los access tokens personales funcionan como una alternativa a nuestro "
|
||||
"sistema de autenticación usuario/password y se pueden usar para permitir a "
|
||||
"otras aplicaciones acceso a la API interna de Penpot"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:103
|
||||
msgid "dashboard.integrations.notification.success.copied"
|
||||
msgstr "Token copiado"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:64
|
||||
msgid "dashboard.integrations.notification.success.created"
|
||||
msgstr "Access token creado con éxito."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs:765
|
||||
msgid "dashboard.invitation-modal.delete"
|
||||
msgstr "Vas a eliminar las invitaciones para:"
|
||||
@@ -2445,6 +2409,10 @@ msgstr "Información"
|
||||
msgid "labels.installed-fonts"
|
||||
msgstr "Fuentes instaladas"
|
||||
|
||||
#: src/app/main/ui/settings/sidebar.cljs:123
|
||||
msgid "labels.integrations"
|
||||
msgstr "Integraciones"
|
||||
|
||||
#: src/app/main/ui/static.cljs:396
|
||||
msgid "labels.internal-error.desc-message-first"
|
||||
msgstr "Ha ocurrido algo extraño."
|
||||
@@ -3101,30 +3069,6 @@ msgstr "Cambiar correo"
|
||||
msgid "modals.change-email.title"
|
||||
msgstr "Cambiar tu correo"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:152, src/app/main/ui/settings/access_tokens.cljs:158
|
||||
msgid "modals.create-access-token.copy-token"
|
||||
msgstr "Copiar token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:130
|
||||
msgid "modals.create-access-token.expiration-date.label"
|
||||
msgstr "Fecha de expiración"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:124
|
||||
msgid "modals.create-access-token.name.label"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:126
|
||||
msgid "modals.create-access-token.name.placeholder"
|
||||
msgstr "El nombre te pude ayudar a saber para qué se utiliza el token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:178
|
||||
msgid "modals.create-access-token.submit-label"
|
||||
msgstr "Crear token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:111
|
||||
msgid "modals.create-access-token.title"
|
||||
msgstr "Generar access token"
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs:1127
|
||||
msgid "modals.create-webhook.submit-label"
|
||||
msgstr "Crear webhook"
|
||||
@@ -3141,18 +3085,6 @@ msgstr "Payload URL"
|
||||
msgid "modals.create-webhook.url.placeholder"
|
||||
msgstr "https://example.com/postreceive"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:257
|
||||
msgid "modals.delete-acces-token.accept"
|
||||
msgstr "Borrar token"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:256
|
||||
msgid "modals.delete-acces-token.message"
|
||||
msgstr "¿Seguro que deseas borrar este token?"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:255
|
||||
msgid "modals.delete-acces-token.title"
|
||||
msgstr "Borrar token"
|
||||
|
||||
#: src/app/main/ui/settings/delete_account.cljs:56
|
||||
msgid "modals.delete-account.cancel"
|
||||
msgstr "Cancelar y mantener mi cuenta"
|
||||
@@ -3341,6 +3273,90 @@ msgstr "Modificar webhook"
|
||||
msgid "modals.edit-webhook.title"
|
||||
msgstr "Modificar webhook"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:152, src/app/main/ui/settings/integrations.cljs:158
|
||||
msgid "modals.integrations.create-token.copy-token"
|
||||
msgstr "Copiar token"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:135
|
||||
msgid "modals.integrations.create-token.expiration-180-days"
|
||||
msgstr "180 días"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:132
|
||||
msgid "modals.integrations.create-token.expiration-30-days"
|
||||
msgstr "30 días"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:133
|
||||
msgid "modals.integrations.create-token.expiration-60-days"
|
||||
msgstr "60 días"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:134
|
||||
msgid "modals.integrations.create-token.expiration-90-days"
|
||||
msgstr "90 días"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:131
|
||||
msgid "modals.integrations.create-token.expiration-never"
|
||||
msgstr "Nunca"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:268
|
||||
msgid "modals.integrations.create-token.expired-on"
|
||||
msgstr "Expiró el %s"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:269
|
||||
msgid "modals.integrations.create-token.expires-on"
|
||||
msgstr "Expira el %s"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:267
|
||||
msgid "modals.integrations.create-token.no-expiration"
|
||||
msgstr "Sin fecha de expiración"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:130
|
||||
msgid "modals.integrations.create-token.expiration-date.label"
|
||||
msgstr "Fecha de expiración"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:131
|
||||
msgid "modals.integrations.create-token.info.non-recuperable"
|
||||
msgstr "This unique token is non-recuperable. If you lose it, you will need to create a new one."
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:317
|
||||
msgid "modals.integrations.create-token.info.regenerate"
|
||||
msgstr "Regenerating the key will immediately revoke the current one. Any application using it will stop working."
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:124
|
||||
msgid "modals.integrations.create-token.name.label"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:126
|
||||
msgid "modals.integrations.create-token.name.placeholder"
|
||||
msgstr "El nombre te pude ayudar a saber para qué se utiliza el token"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:111
|
||||
msgid "modals.integrations.create-token.title"
|
||||
msgstr "Crear token de accesso"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:111
|
||||
msgid "modals.integrations.create-token.title.created"
|
||||
msgstr "Token de acceso creado"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:142
|
||||
msgid "modals.integrations.create-token.token-will-expire"
|
||||
msgstr "El token expirará el %s"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:143
|
||||
msgid "modals.integrations.create-token.token-will-not-expire"
|
||||
msgstr "El token no tiene fecha de expiración"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:257
|
||||
msgid "modals.integrations.delete-token.accept"
|
||||
msgstr "Borrar token"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:256
|
||||
msgid "modals.integrations.delete-token.message"
|
||||
msgstr "¿Seguro que deseas borrar este token?"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:255
|
||||
msgid "modals.integrations.delete-token.title"
|
||||
msgstr "Borrar token"
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs:249
|
||||
msgid "modals.invite-member-confirm.accept"
|
||||
msgstr "Enviar invitacion"
|
||||
@@ -5062,14 +5078,14 @@ msgstr "Bibliotecas Compartidas - %s - Penpot"
|
||||
msgid "title.default"
|
||||
msgstr "Penpot - Diseño Libre para Equipos"
|
||||
|
||||
#: src/app/main/ui/settings/access_tokens.cljs:278
|
||||
msgid "title.settings.access-tokens"
|
||||
msgstr "Perfil - Access tokens"
|
||||
|
||||
#: src/app/main/ui/settings/feedback.cljs:161
|
||||
msgid "title.settings.feedback"
|
||||
msgstr "Danos tu opinión - Penpot"
|
||||
|
||||
#: src/app/main/ui/settings/integrations.cljs:278
|
||||
msgid "title.settings.integrations"
|
||||
msgstr "Integraciones - Penpot"
|
||||
|
||||
#: src/app/main/ui/settings/notifications.cljs:45
|
||||
msgid "title.settings.notifications"
|
||||
msgstr "Notificaciones - Penpot"
|
||||
|
||||
Reference in New Issue
Block a user