mirror of
https://github.com/penpot/penpot.git
synced 2026-08-08 13:58:35 +00:00
🎉 Add diff modal
This commit is contained in:
parent
14931c1bac
commit
9294b30524
@ -0,0 +1,162 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.main.ui.settings.import-shortcuts-diff-modal
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.i18n :refer [tr]]
|
||||
[app.main.data.dashboard.shortcuts :as dsc]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.shortcuts :as ds]
|
||||
[app.main.data.viewer.shortcuts :as vsc]
|
||||
[app.main.data.workspace.path.shortcuts :as psc]
|
||||
[app.main.data.workspace.shortcuts :as wsc]
|
||||
[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]
|
||||
[app.main.ui.shortcuts :as ss]
|
||||
[app.util.dom :as dom]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private context->defaults
|
||||
{:workspace (d/deep-merge psc/shortcuts wsc/shortcuts)
|
||||
:dashboard dsc/shortcuts
|
||||
:viewer vsc/shortcuts})
|
||||
|
||||
(defn- compute-diff
|
||||
[imported-shortcuts custom-shortcuts]
|
||||
(let [contexts (filter #(contains? imported-shortcuts %) [:workspace :dashboard :viewer])]
|
||||
(mapcat
|
||||
(fn [ctx]
|
||||
(let [imported-ctx (get imported-shortcuts ctx)
|
||||
current-ctx (get custom-shortcuts ctx {})
|
||||
defaults-ctx (get context->defaults ctx)]
|
||||
(keep
|
||||
(fn [[shortcut-key imported-binding]]
|
||||
(let [current-binding (get current-ctx shortcut-key)
|
||||
default-cmd (:command (get defaults-ctx shortcut-key))
|
||||
effective-binding (or current-binding default-cmd)
|
||||
customized? (and (some? current-binding)
|
||||
(seq current-binding)
|
||||
(not= current-binding default-cmd))]
|
||||
(when (and (some? imported-binding)
|
||||
(not= effective-binding imported-binding))
|
||||
{:context ctx
|
||||
:key shortcut-key
|
||||
:current effective-binding
|
||||
:imported imported-binding
|
||||
:customized? customized?})))
|
||||
imported-ctx)))
|
||||
contexts)))
|
||||
|
||||
(def ^:private context-order {:workspace 0 :dashboard 1 :viewer 2})
|
||||
|
||||
(def ^:private context-name
|
||||
{:workspace "Workspace"
|
||||
:dashboard "Dashboard"
|
||||
:viewer "Viewer"})
|
||||
|
||||
(mf/defc import-shortcuts-diff-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :import-shortcuts-diff-modal}
|
||||
[{:keys [imported-shortcuts custom-shortcuts all-shortcuts-raw]}]
|
||||
(let [diff-entries
|
||||
(mf/with-memo [imported-shortcuts custom-shortcuts]
|
||||
(->> (compute-diff imported-shortcuts custom-shortcuts)
|
||||
(sort-by (fn [e] [(get context-order (:context e) 99)
|
||||
(name (:key e))]))))
|
||||
|
||||
handle-close-dialog
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/hide))))
|
||||
|
||||
handle-apply
|
||||
(mf/use-fn
|
||||
(mf/deps imported-shortcuts all-shortcuts-raw)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (ss/import-custom-shortcuts imported-shortcuts all-shortcuts-raw))
|
||||
(st/emit! (modal/hide))))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-dialog)}
|
||||
[:> icon-button* {:class (stl/css :close-btn)
|
||||
:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click handle-close-dialog
|
||||
:tooltip-class (stl/css :close-btn-tooltip)
|
||||
:icon i/close}]
|
||||
[:div {:class (stl/css :modal-title)}
|
||||
(tr "import-shortcuts.diff-modal-title")]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-content-text)}
|
||||
(tr "import-shortcuts.diff-modal-text")]
|
||||
|
||||
(if (seq diff-entries)
|
||||
[:table {:class (stl/css :shortcuts-table)}
|
||||
[:thead
|
||||
[:tr {:class (stl/css :shortcuts-list-header)}
|
||||
[:th {:class (stl/css :shortcut-header-name)}
|
||||
(tr "restore-shortcuts.acction")]
|
||||
[:th {:class (stl/css :shortcut-header-command)}
|
||||
(tr "labels.current")]
|
||||
[:th {:class (stl/css :shortcut-header-command)}
|
||||
(tr "labels.import")]]]
|
||||
[:tbody {:class (stl/css :shortcuts-list-body)}
|
||||
(let [last-ctx* (volatile! nil)]
|
||||
(for [entry diff-entries]
|
||||
(let [{:keys [context key current imported customized?]} entry
|
||||
show-context-label? (not= @last-ctx* context)]
|
||||
(vreset! last-ctx* context)
|
||||
[:* {:key (dm/str (name context) "-" (name key) "-group")}
|
||||
(when show-context-label?
|
||||
[:tr {:key (dm/str "ctx-" (name context))
|
||||
:class (stl/css :context-separator)}
|
||||
[:td {:colSpan 3
|
||||
:class (stl/css :context-label)}
|
||||
(get context-name context)]])
|
||||
[:tr {:key (dm/str (name context) "-" (name key))
|
||||
:class (stl/css :shortcuts-list-item)}
|
||||
[:td {:class (stl/css :shortcut-name)}
|
||||
(ss/translation-keyname :sc key)]
|
||||
[:td {:class (stl/css :shortcut-command)}
|
||||
(if (str/blank? current)
|
||||
[:span {:class (stl/css :shortcut-empty)} "-"]
|
||||
[:> ss/shortcuts-keys* {:content current
|
||||
:command key
|
||||
:is-customized customized?
|
||||
:light-shortcut true
|
||||
:has-conflict? false}])]
|
||||
[:td {:class (stl/css :shortcut-command)}
|
||||
(if (str/blank? imported)
|
||||
[:span {:class (stl/css :shortcut-empty)} "-"]
|
||||
[:> ss/shortcuts-keys* {:content imported
|
||||
:command key
|
||||
:light-shortcut true
|
||||
:is-customized true
|
||||
:has-conflict? false}])]]])))]]
|
||||
[:div {:class (stl/css :no-changes)}
|
||||
(tr "import-shortcuts.no-changes")])]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
[:> button* {:class (stl/css :cancel-button)
|
||||
:variant "secondary"
|
||||
:type "button"
|
||||
:on-click handle-close-dialog}
|
||||
(tr "labels.cancel")]
|
||||
[:> button* {:class (stl/css :cancel-button)
|
||||
:variant "primary"
|
||||
:type "button"
|
||||
:on-click handle-apply}
|
||||
(tr "import-shortcuts.apply")]]]]]))
|
||||
@ -0,0 +1,166 @@
|
||||
// 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 Sucursal en España SL
|
||||
|
||||
@use "ds/typography.scss" as t;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/spacing.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.modal-overlay {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
inset-inline-start: 0;
|
||||
inset-block-start: 0;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
z-index: var(--z-index-set);
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
position: relative;
|
||||
padding: var(--sp-xxxl);
|
||||
border-radius: $br-8;
|
||||
background-color: var(--color-background-primary);
|
||||
border: $b-2 solid var(--color-background-quaternary);
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: var(--sp-xxxl);
|
||||
min-inline-size: $sz-364;
|
||||
min-block-size: $sz-192;
|
||||
max-block-size: $sz-520;
|
||||
inline-size: px2rem(584);
|
||||
max-inline-size: $sz-712;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
@include t.use-typography("headline-medium");
|
||||
|
||||
color: var(--color-foreground-primary);
|
||||
block-size: px2rem(26);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
inset-block-start: px2rem(8);
|
||||
inset-inline-end: px2rem(6);
|
||||
block-size: $sz-32;
|
||||
inline-size: $sz-28;
|
||||
}
|
||||
|
||||
.close-btn-tooltip {
|
||||
position: absolute;
|
||||
inset-block-start: px2rem(8);
|
||||
inset-inline-end: px2rem(6);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-l);
|
||||
block-size: fit-content;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-content-text {
|
||||
@include t.use-typography("body-medium");
|
||||
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.shortcuts-table {
|
||||
inline-size: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.shortcuts-list-item,
|
||||
.shortcuts-list-header {
|
||||
@include t.use-typography("body-medium");
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
display: grid;
|
||||
grid-template-columns: 35% 30% 35%;
|
||||
}
|
||||
|
||||
.shortcuts-list-header {
|
||||
border-block-end: $b-1 solid var(--color-foreground-secondary);
|
||||
block-size: $sz-32;
|
||||
text-align: start;
|
||||
padding-block-end: var(--sp-s);
|
||||
}
|
||||
|
||||
.shortcuts-list-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.shortcuts-list-item {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.shortcuts-list-item:first-child {
|
||||
padding-block-start: var(--sp-s);
|
||||
}
|
||||
|
||||
.shortcut-header-name,
|
||||
.shortcut-header-command {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.shortcut-name {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.shortcut-command {
|
||||
display: flex;
|
||||
gap: var(--sp-s);
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
text-align: start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.shortcut-empty {
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.context-separator {
|
||||
border-block-end: none;
|
||||
}
|
||||
|
||||
.context-label {
|
||||
@include t.use-typography("title-small");
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
text-align: start;
|
||||
padding-block: var(--sp-m) var(--sp-xxs);
|
||||
}
|
||||
|
||||
.no-changes {
|
||||
@include t.use-typography("body-medium");
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
text-align: center;
|
||||
padding-block: var(--sp-xl);
|
||||
}
|
||||
@ -46,7 +46,9 @@
|
||||
(drop-last default-chars-list))
|
||||
|
||||
ctx-customs (get custom-shortcuts context {})
|
||||
current-command (or (get ctx-customs shortcut-key) default-command)
|
||||
custom-val (get ctx-customs shortcut-key)
|
||||
current-command (or custom-val default-command)
|
||||
customized? (and (some? custom-val) (seq custom-val) (not= custom-val default-command))
|
||||
|
||||
current-managed-list (if (coll? current-command)
|
||||
current-command
|
||||
@ -59,7 +61,7 @@
|
||||
current-short-char-list (if (= 1 (count current-chars-list))
|
||||
current-chars-list
|
||||
(drop-last current-chars-list))]
|
||||
[default-last-element default-short-char-list current-last-element current-command current-short-char-list]))
|
||||
[default-last-element default-short-char-list current-last-element current-command current-short-char-list customized?]))
|
||||
|
||||
(mf/defc restore-all-modal
|
||||
{::mf/register modal/components
|
||||
@ -103,36 +105,38 @@
|
||||
[:tbody {:class (stl/css :shortcuts-list-body)}
|
||||
(for [context [:workspace :dashboard :viewer]
|
||||
shortcut-key (keys (get custom-shortcuts context {}))]
|
||||
(let [[default-last-element
|
||||
default-short-char-list
|
||||
current-last-element
|
||||
current-command
|
||||
current-short-char-list] (extract-shortcut-keys shortcut-key custom-shortcuts context)
|
||||
current-penultimate (last current-short-char-list)
|
||||
default-penultimate (last default-short-char-list)]
|
||||
(let [[default-last-element
|
||||
default-short-char-list
|
||||
current-last-element
|
||||
current-command
|
||||
current-short-char-list
|
||||
customized?] (extract-shortcut-keys shortcut-key custom-shortcuts context)
|
||||
current-penultimate (last current-short-char-list)
|
||||
default-penultimate (last default-short-char-list)
|
||||
command-class (if customized? (stl/css :customized-command) (stl/css :default-command))]
|
||||
[:tr {:key (dm/str (name context) "-" (name shortcut-key))
|
||||
:class (stl/css :shortcuts-list-item)}
|
||||
[:td {:class (stl/css :shortcut-name)}
|
||||
(ss/translation-keyname :sc shortcut-key)]
|
||||
[:td {:class (stl/css :shortcut-command)}
|
||||
(if (str/blank? current-command)
|
||||
[:span {:class (stl/css :shortcut-empty)} "-"]
|
||||
(for [chars current-short-char-list]
|
||||
[:* {:key (str/join chars)}
|
||||
(for [char chars]
|
||||
[:> ss/converted-chars* {:key (dm/str char "-" (name shortcut-key))
|
||||
:char char
|
||||
:class (stl/css :default-command)
|
||||
:command shortcut-key}])
|
||||
(when (not= chars current-penultimate) [:span {:class (stl/css :space)} ","])]))
|
||||
(when (not= current-last-element current-penultimate)
|
||||
[:*
|
||||
[:span {:class (stl/css :space)} (tr "shortcuts.or")]
|
||||
(for [char current-last-element]
|
||||
[:> ss/converted-chars* {:key (dm/str char "-" (name shortcut-key))
|
||||
:char char
|
||||
:class (stl/css :default-command)
|
||||
:command shortcut-key}])])]
|
||||
[:td {:class (stl/css :shortcut-name)}
|
||||
(ss/translation-keyname :sc shortcut-key)]
|
||||
[:td {:class (stl/css :shortcut-command)}
|
||||
(if (str/blank? current-command)
|
||||
[:span {:class (stl/css :shortcut-empty)} "-"]
|
||||
(for [chars current-short-char-list]
|
||||
[:* {:key (str/join chars)}
|
||||
(for [char chars]
|
||||
[:> ss/converted-chars* {:key (dm/str char "-" (name shortcut-key))
|
||||
:char char
|
||||
:class command-class
|
||||
:command shortcut-key}])
|
||||
(when (not= chars current-penultimate) [:span {:class (stl/css :space)} ","])]))
|
||||
(when (not= current-last-element current-penultimate)
|
||||
[:*
|
||||
[:span {:class (stl/css :space)} (tr "shortcuts.or")]
|
||||
(for [char current-last-element]
|
||||
[:> ss/converted-chars* {:key (dm/str char "-" (name shortcut-key))
|
||||
:char char
|
||||
:class command-class
|
||||
:command shortcut-key}])])]
|
||||
|
||||
[:td {:class (stl/css :shortcut-command)}
|
||||
(for [chars default-short-char-list]
|
||||
|
||||
@ -142,6 +142,12 @@
|
||||
border-radius: $br-6;
|
||||
}
|
||||
|
||||
.customized-command {
|
||||
background-color: var(--color-background-info);
|
||||
color: var(--color-foreground-primary);
|
||||
border-radius: $br-6;
|
||||
}
|
||||
|
||||
.shortcut-empty {
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.main.ui.settings.import-shortcuts-diff-modal]
|
||||
[app.main.ui.settings.restore-shortcuts-modal]
|
||||
[app.main.ui.shortcuts :as ss]
|
||||
[app.util.dom :as dom]
|
||||
@ -357,7 +358,7 @@
|
||||
|
||||
on-file-selected
|
||||
(mf/use-fn
|
||||
(mf/deps all-shortcuts-raw)
|
||||
(mf/deps all-shortcuts-raw custom-shortcuts)
|
||||
(fn [event]
|
||||
(let [file (-> (dom/get-target event)
|
||||
(dom/get-files)
|
||||
@ -370,7 +371,10 @@
|
||||
:keywordize-keys true)
|
||||
validation (validate-imported-shortcuts shortcuts)]
|
||||
(if (:valid? validation)
|
||||
(st/emit! (ss/import-custom-shortcuts shortcuts all-shortcuts-raw))
|
||||
(st/emit! (modal/show {:type :import-shortcuts-diff-modal
|
||||
:imported-shortcuts shortcuts
|
||||
:custom-shortcuts custom-shortcuts
|
||||
:all-shortcuts-raw all-shortcuts-raw}))
|
||||
(st/emit! (ntf/error (tr "errors.invalid-data")))))
|
||||
(catch :default _
|
||||
(st/emit! (ntf/error (tr "errors.invalid-data"))))))))
|
||||
|
||||
@ -305,7 +305,7 @@
|
||||
:key unique-key} char]))
|
||||
|
||||
(mf/defc shortcuts-keys*
|
||||
[{:keys [content command is-customized has-conflict?]}]
|
||||
[{:keys [content command is-customized has-conflict? light-shortcut]}]
|
||||
(let [managed-list (if (coll? content)
|
||||
content
|
||||
(conj () content))
|
||||
@ -324,7 +324,8 @@
|
||||
:command command
|
||||
:class (cond
|
||||
has-conflict? (stl/css :conflict-key)
|
||||
is-customized (stl/css :customized-key))}])
|
||||
is-customized (stl/css :customized-key)
|
||||
light-shortcut (stl/css :light-key))}])
|
||||
|
||||
(when (not= chars penultimate) [:span {:class (stl/css :space)} ","])])
|
||||
(when (not= last-element penultimate)
|
||||
@ -336,7 +337,8 @@
|
||||
:command command
|
||||
:class (cond
|
||||
has-conflict? (stl/css :conflict-key)
|
||||
is-customized (stl/css :customized-key))}])])]))
|
||||
is-customized (stl/css :customized-key)
|
||||
light-shortcut (stl/css :light-key))}])])]))
|
||||
|
||||
(mf/defc shortcut-row-editable*
|
||||
[{:keys [elements custom-shortcuts command-translate conflicts section-key]}]
|
||||
|
||||
@ -187,6 +187,11 @@
|
||||
color: var(--color-foreground-error);
|
||||
}
|
||||
|
||||
.light-key {
|
||||
background-color: var(--color-background-tertiary);
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.space {
|
||||
margin: 0 var(--sp-xxs);
|
||||
color: var(--color-foreground-secondary);
|
||||
|
||||
@ -5850,6 +5850,22 @@ msgstr "Acction"
|
||||
msgid "restore-shortcuts.restore"
|
||||
msgstr "Restore"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs
|
||||
msgid "import-shortcuts.diff-modal-title"
|
||||
msgstr "Import shortcuts"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs
|
||||
msgid "import-shortcuts.diff-modal-text"
|
||||
msgstr "The following shortcuts will be changed by the imported configuration."
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs
|
||||
msgid "import-shortcuts.no-changes"
|
||||
msgstr "No differences found between the current and imported shortcuts."
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs
|
||||
msgid "import-shortcuts.apply"
|
||||
msgstr "Apply"
|
||||
|
||||
#: src/app/main/ui/dashboard/subscription.cljs:191
|
||||
msgid "subscription.banner.create-org-info"
|
||||
msgstr ""
|
||||
|
||||
@ -5705,6 +5705,22 @@ msgstr "Acción"
|
||||
msgid "restore-shortcuts.restore"
|
||||
msgstr "Restaurar"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs
|
||||
msgid "import-shortcuts.diff-modal-title"
|
||||
msgstr "Importar atajos"
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs
|
||||
msgid "import-shortcuts.diff-modal-text"
|
||||
msgstr "Los siguientes atajos se modificarán con la configuración importada."
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs
|
||||
msgid "import-shortcuts.no-changes"
|
||||
msgstr "No se encontraron diferencias entre los atajos actuales y los importados."
|
||||
|
||||
#: src/app/main/ui/settings/import_shortcuts_diff_modal.cljs
|
||||
msgid "import-shortcuts.apply"
|
||||
msgstr "Aplicar"
|
||||
|
||||
#: src/app/main/ui/dashboard/subscription.cljs:191
|
||||
msgid "subscription.banner.create-org-info"
|
||||
msgstr ""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user