✨ Add Japanese layout controls and opt-in settings

Enable Japanese layout per file through undoable changes, or for all
files through a saved profile preference. Show the controls when the
WASM renderer and either setting are active.

Add writing mode, orientation, ruby, emphasis, warichu and line fitting
controls with icons and English and Japanese translations. Cover the
file change, profile preference and control behavior.
This commit is contained in:
alonso.torres 2026-10-07 17:12:14 +02:00
parent 2e189de8d9
commit 70727ca4d7
31 changed files with 1754 additions and 113 deletions

View File

@ -62,6 +62,7 @@
[:map {:title "ProfileProps" :closed true}
[:plugins {:optional true} ctp/schema:plugin-registry]
[:renderer {:optional true} [::sm/one-of #{:svg :wasm}]]
[:japanese-layout-all-files {:optional true} ::sm/boolean]
[:mcp-enabled {:optional true} ::sm/boolean]
[:newsletter-updates {:optional true} ::sm/boolean]
[:newsletter-news {:optional true} ::sm/boolean]

View File

@ -164,6 +164,21 @@
(t/is (nil? (:error out)))
(t/is (nil? (:photo-id (:result out))))))))
(t/deftest update-profile-japanese-layout-setting
(let [profile (th/create-profile* 1)
params {::th/type :update-profile-props
::rpc/profile-id (:id profile)
:props {:japanese-layout-all-files true}}
out (th/command! params)]
(t/is (nil? (:error out)))
(t/is (true? (get-in out [:result :japanese-layout-all-files])))
(let [params {::th/type :get-profile
::rpc/profile-id (:id profile)}
out (th/command! params)]
(t/is (nil? (:error out)))
(t/is (true? (get-in out [:result :props :japanese-layout-all-files]))))))
(t/deftest profile-deletion-1
(let [prof (th/create-profile* 1)
file (th/create-file* 1 {:profile-id (:id prof)

View File

@ -438,7 +438,12 @@
[:map {:title "SetTokensSource"}
[:type [:= :set-tokens-source]]
[:file-id ::sm/uuid]
[:library-id [:maybe ::sm/uuid]]]]])
[:library-id [:maybe ::sm/uuid]]]]
[:set-japanese-layout
[:map {:title "SetJapaneseLayout"}
[:type [:= :set-japanese-layout]]
[:enabled ::sm/boolean]]]])
(def schema:changes
[:sequential {:gen/max 5 :gen/min 1} schema:change])
@ -1092,6 +1097,10 @@
[data {:keys [library-id]}]
(cfo/set-tokens-source data library-id))
(defmethod process-change :set-japanese-layout
[data {:keys [enabled]}]
(ctf/set-japanese-layout-enabled data enabled))
;; === Operations
(def decode-shape-attrs

View File

@ -1150,6 +1150,18 @@
:library-id prev-val})
(apply-changes-local))))
(defn set-japanese-layout
[changes enabled?]
(assert-file-data! changes)
(let [file-data (::file-data (meta changes))
previous-enabled (ctf/japanese-layout-enabled? file-data)]
(-> changes
(update :redo-changes conj {:type :set-japanese-layout
:enabled enabled?})
(update :undo-changes conj {:type :set-japanese-layout
:enabled previous-enabled})
(apply-changes-local))))
;; Misc changes
(defn reorder-children

View File

@ -77,7 +77,8 @@
(def schema:options
[:map {:title "FileOptions"}
[:components-v2 {:optional true} ::sm/boolean]
[:base-font-size {:optional true} :string]])
[:base-font-size {:optional true} :string]
[:japanese-layout {:optional true} ::sm/boolean]])
(def schema:data
[:map {:title "FileData"}
@ -1255,6 +1256,19 @@
(assoc-in file-data [:options :base-font-size] base-font-size))
;; Japanese text layout
(defn japanese-layout-enabled?
[file-data]
(true? (get-in file-data [:options :japanese-layout])))
(defn set-japanese-layout-enabled
[file-data enabled?]
(if enabled?
(assoc-in file-data [:options :japanese-layout] true)
(d/update-in-when file-data [:options] dissoc :japanese-layout)))
;; Ref Chains
(defn get-ref-chain-until-target-ref
"Returns a vector with the shape ref chain until target-ref, including itself"

View File

@ -251,6 +251,26 @@
"The parent must use the updated bounds of both children")
(assert-idempotent data changes))))))
(t/deftest set-japanese-layout-file-option
(let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)
data (make-file-data file-id page-id)
enabled (ch/process-changes data [{:type :set-japanese-layout
:enabled true}])
disabled (ch/process-changes enabled [{:type :set-japanese-layout
:enabled false}])
changes (-> (pcb/empty-changes)
(pcb/with-file-data data)
(pcb/set-japanese-layout true))]
(t/is (false? (ctf/japanese-layout-enabled? data)))
(t/is (true? (ctf/japanese-layout-enabled? enabled)))
(t/is (false? (ctf/japanese-layout-enabled? disabled)))
(t/is (not (contains? (:options disabled) :japanese-layout)))
(t/is (= [{:type :set-japanese-layout :enabled true}]
(:redo-changes changes)))
(t/is (= '({:type :set-japanese-layout :enabled false})
(:undo-changes changes)))))
(t/deftest add-obj
(let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M2.5 3.5v-2h11v2M2.5 12.5v2h11v-2M5 6.25h1.5v3.5M9 6.25h1.5v3.5H9v-1.75h1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 205 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 4V2h12v2M2 12v2h12v-2M3.5 6H5v4M7.25 6h1.5v4h-1.5V8h1.5M11 6h1.5v4H11m0-2h1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 210 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 2.5h6M5 5.5h6M5 8.5h6M5 11.5h6M8 1v13.5"/>
</svg>

After

Width:  |  Height:  |  Size: 172 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 1.5h6v5H5zM4 9.5l7 2.5-7 2.5M8 10.9v2.2"/>
</svg>

After

Width:  |  Height:  |  Size: 174 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 1.5h6v5H5zM5.5 14l2.5-6 2.5 6M6.5 11.5h3"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 2.5h6v4H5zM5 9.5h6v4H5z"/>
</svg>

After

Width:  |  Height:  |  Size: 156 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 2v12M14 2v12M4.5 3.5h2v3h-2zM9.5 3.5h2v3h-2zM4.5 9.5h2v3h-2zM9.5 9.5h2v3h-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 209 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M1.5 2.5h8m-8 3h8m-8 3h6M13 2v11m-2-2 2 2 2-2"/>
</svg>

After

Width:  |  Height:  |  Size: 177 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M13.5 1.5v8m-3-8v8m-3-8v6M14 13H3m2-2-2 2 2 2"/>
</svg>

After

Width:  |  Height:  |  Size: 177 B

View File

@ -679,6 +679,17 @@
(dm/export layout/toggle-layout-flag)
(dm/export layout/remove-layout-flag)
(defn set-japanese-layout
[enabled?]
(ptk/reify ::set-japanese-layout
ptk/WatchEvent
(watch [it state _]
(let [file-data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(pcb/with-file-data file-data)
(pcb/set-japanese-layout enabled?))]
(rx/of (dch/commit-changes changes))))))
;; --- Profile
(defn update-nudge

View File

@ -11,6 +11,7 @@
[app.common.data.macros :as dm]
[app.common.files.helpers :as cph]
[app.common.files.tokens :as cfo]
[app.common.types.file :as ctf]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
[app.common.types.tokens-lib :as ctob]
@ -289,6 +290,14 @@
"Currently working file data on workspace"
(l/derived dsh/lookup-file-data st/state))
(def japanese-layout-config
"Japanese layout switches: `:file?` for the workspace file and
`:all-files?` from the profile."
(l/derived (fn [state]
{:file? (ctf/japanese-layout-enabled? (dsh/lookup-file-data state))
:all-files? (true? (dm/get-in state [:profile :props :japanese-layout-all-files]))})
st/state =))
(def workspace-file-colors
(l/derived (fn [{:keys [id] :as data}]
(some-> (:colors data) (update-vals #(assoc % :file-id id))))

View File

@ -276,6 +276,9 @@
(def ^:icon-id text-auto-height "text-auto-height")
(def ^:icon-id text-auto-width "text-auto-width")
(def ^:icon-id text-bottom "text-bottom")
(def ^:icon-id text-combine-upright-all "text-combine-upright-all")
(def ^:icon-id text-combine-upright-digits "text-combine-upright-digits")
(def ^:icon-id text-combine-upright-none "text-combine-upright-none")
(def ^:icon-id text-fixed "text-fixed")
(def ^:icon-id text-font-family "text-font-family")
(def ^:icon-id text-font-size "text-font-size")
@ -287,6 +290,8 @@
(def ^:icon-id text-ltr "text-ltr")
(def ^:icon-id text-middle "text-middle")
(def ^:icon-id text-mixed "text-mixed")
(def ^:icon-id text-orientation-mixed "text-orientation-mixed")
(def ^:icon-id text-orientation-upright "text-orientation-upright")
(def ^:icon-id text-palette "text-palette")
(def ^:icon-id text-paragraph "text-paragraph")
(def ^:icon-id text-rtl "text-rtl")
@ -309,7 +314,11 @@
(def ^:icon-id vertical-align-items-start "vertical-align-items-start")
(def ^:icon-id view-as-icons "view-as-icons")
(def ^:icon-id view-as-list "view-as-list")
(def ^:icon-id warichu "warichu")
(def ^:icon-id warichu-none "warichu-none")
(def ^:icon-id wrap "wrap")
(def ^:icon-id writing-mode-horizontal "writing-mode-horizontal")
(def ^:icon-id writing-mode-vertical "writing-mode-vertical")
(def icon-list
"A collection of all icons"

View File

@ -247,6 +247,9 @@
(def ^:icon text-auto-height (icon-xref :text-auto-height))
(def ^:icon text-auto-width (icon-xref :text-auto-width))
(def ^:icon text-bottom (icon-xref :text-bottom))
(def ^:icon text-combine-upright-all (icon-xref :text-combine-upright-all))
(def ^:icon text-combine-upright-digits (icon-xref :text-combine-upright-digits))
(def ^:icon text-combine-upright-none (icon-xref :text-combine-upright-none))
(def ^:icon text-fixed (icon-xref :text-fixed))
(def ^:icon text-font-family (icon-xref :text-font-family))
(def ^:icon text-font-size (icon-xref :text-font-size))
@ -258,6 +261,8 @@
(def ^:icon text-ltr (icon-xref :text-ltr))
(def ^:icon text-middle (icon-xref :text-middle))
(def ^:icon text-mixed (icon-xref :text-mixed))
(def ^:icon text-orientation-mixed (icon-xref :text-orientation-mixed))
(def ^:icon text-orientation-upright (icon-xref :text-orientation-upright))
(def ^:icon text-palette (icon-xref :text-palette))
(def ^:icon text-paragraph (icon-xref :text-paragraph))
(def ^:icon text-rtl (icon-xref :text-rtl))
@ -284,7 +289,11 @@
(def ^:icon vertical-align-items-start (icon-xref :vertical-align-items-start))
(def ^:icon view-as-icons (icon-xref :view-as-icons))
(def ^:icon view-as-list (icon-xref :view-as-list))
(def ^:icon warichu (icon-xref :warichu))
(def ^:icon warichu-none (icon-xref :warichu-none))
(def ^:icon wrap (icon-xref :wrap))
(def ^:icon writing-mode-horizontal (icon-xref :writing-mode-horizontal))
(def ^:icon writing-mode-vertical (icon-xref :writing-mode-vertical))
(def default
"A collection of all icons"

View File

@ -115,10 +115,35 @@
:on-change handle-render-change}]]
[:> text* {:typography t/body-medium :class (stl/css :feedback)} [:a {:href "#" :on-click go-settings-feedback :class (stl/css :link)} (tr "dashboard.webgl-switch.feedback") [:> icon* {:icon-id "arrow-up-right" :size "s"}]]]]))
(mf/defc japanese-layout-settings*
[{:keys [enabled]}]
(let [handle-change
(mf/use-fn
(fn [enabled?]
(st/emit! (ev/event {::ev/name (if enabled?
"enable-japanese-layout-all-files"
"disable-japanese-layout-all-files")
::ev/origin "settings"})
(du/update-profile-props
{:japanese-layout-all-files enabled?})
(ntf/success (tr "notifications.profile-saved")))))]
[:section {:class (stl/css :japanese-layout-container)}
[:> heading* {:class (stl/css :title)
:level 2
:typography t/title-large}
(tr "dashboard.japanese-layout.title")]
[:> text* {:class (stl/css :description) :typography t/body-medium}
(tr "dashboard.japanese-layout.description")]
[:form {:class (stl/css :japanese-layout-form)}
[:> switch* {:label (tr "dashboard.japanese-layout.enable-all-files")
:default-checked enabled
:on-change handle-change}]]]))
(mf/defc options-page*
[]
(let [profile (mf/deref refs/profile)
renderer (or (-> profile :props :renderer) :svg)]
renderer (or (-> profile :props :renderer) :svg)
japanese-layout-enabled (:all-files? (mf/deref refs/japanese-layout-config))]
(mf/use-effect
#(dom/set-html-title (tr "title.settings.options")))
@ -132,5 +157,6 @@
:id "options-section-title"}
(tr "labels.settings")]
[:> options-form*]]
[:> japanese-layout-settings* {:enabled japanese-layout-enabled}]
(when (contains? cf/flags :render-switch)
[:> webgl-settings* {:renderer renderer}])]]))

View File

@ -21,9 +21,9 @@
}
}
/* Copied from profile.scss .form-container, but without included nested
rules, since we want custom styles for it */
.webgl-container {
/* Settings sections with standalone controls. */
.webgl-container,
.japanese-layout-container {
display: grid;
grid-auto-rows: auto;
gap: var(--sp-s);
@ -34,6 +34,10 @@
color: var(--color-foreground-primary);
}
.japanese-layout-form {
padding-block-start: var(--sp-xs);
}
.webgl-header {
display: flex;
flex-direction: row;

View File

@ -44,6 +44,7 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.text.writing-mode :as wm]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
@ -232,8 +233,10 @@
(mf/defc preferences-menu*
{::mf/private true
::mf/wrap [mf/memo]}
[{:keys [layout profile toggle-flag on-close toggle-theme toggle-render]}]
(let [renderer (or (-> profile :props :renderer) :svg)
[{:keys [japanese-layout-enabled japanese-layout-all-files layout profile toggle-flag
on-close toggle-theme toggle-render toggle-japanese-layout]}]
(let [renderer (or (-> profile :props :renderer) :svg)
read-only? (mf/use-ctx ctx/workspace-read-only?)
show-nudge-options
(mf/use-fn
@ -316,6 +319,21 @@
:id "file-menu-nudge"}
[:span {:class (stl/css :item-name)} (tr "modals.nudge-title")]]
(when (and (not read-only?)
(not japanese-layout-all-files)
(wm/vertical-layout-active?))
[:> dropdown-menu-item* {:on-click toggle-japanese-layout
:class (stl/css :base-menu-item :submenu-item)
:on-key-down (fn [event]
(when (kbd/enter? event)
(toggle-japanese-layout event)))
:data-testid "japanese-layout"
:id "file-menu-japanese-layout"}
[:span {:class (stl/css :item-name)}
(if japanese-layout-enabled
(tr "workspace.header.menu.disable-japanese-layout")
(tr "workspace.header.menu.enable-japanese-layout"))]])
[:> dropdown-menu-item* {:on-click toggle-theme
:class (stl/css :base-menu-item :submenu-item)
:on-key-down (fn [event]
@ -885,6 +903,8 @@
(mf/defc menu*
[{:keys [layout file]}]
(let [profile (mf/deref refs/profile)
japanese-layout (mf/deref refs/japanese-layout-config)
japanese-layout-enabled (:file? japanese-layout)
show-menu* (mf/use-state false)
show-menu? (deref show-menu*)
@ -946,6 +966,15 @@
(dom/stop-propagation event)
(st/emit! (du/toggle-theme))))
toggle-japanese-layout
(mf/use-fn
(mf/deps japanese-layout-enabled)
(fn [event]
(dom/stop-propagation event)
(st/emit! (dw/set-japanese-layout (not japanese-layout-enabled)))
(reset! show-menu* false)
(reset! selected-sub-menu* nil)))
toggle-render
(mf/use-fn
(mf/deps profile)
@ -1145,10 +1174,13 @@
:preferences
[:> preferences-menu* {:layout layout
:profile profile
:japanese-layout-enabled japanese-layout-enabled
:japanese-layout-all-files (:all-files? japanese-layout)
:toggle-flag toggle-flag
:toggle-theme toggle-theme
:toggle-render toggle-render
:show-shortcuts show-shortcuts
:toggle-japanese-layout toggle-japanese-layout
:on-close close-sub-menu}]
:plugins

View File

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.common
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.store :as st]
[app.util.dom :as dom]
@ -26,6 +27,20 @@
:ref ref}
children])))
(defn radio-selected
"Value to select in a radio group for an attribute value: its name, or
`default` when unset. Mixed (`:multiple`) values select nothing."
([value]
(radio-selected value ""))
([value default]
(cond
(= value :multiple) ""
(or (nil? value)
(and (string? value) (empty? value))) default
(keyword? value) (d/name value)
(string? value) value
:else (str value))))
(defn emit-value-or-token [value emit-value-fn ids attrs]
(if (or (string? value)
(number? value)

View File

@ -30,6 +30,8 @@
[app.main.ui.ds.controls.shared.searchable-options-dropdown :refer [searchable-options-dropdown*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.common :refer [radio-selected]]
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
[app.main.ui.workspace.sidebar.options.menus.token-typography-row :refer [token-typography-row*]]
[app.main.ui.workspace.sidebar.options.menus.typography :refer [text-options* typography-entry*]]
[app.main.ui.workspace.tokens.management.forms.controls.utils :as csu]
@ -52,10 +54,6 @@
Evaluated once at module load time; cf/flags is immutable after startup."
(contains? cf/flags :token-typography-row))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Sub-components
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc text-align-options*
[{:keys [values on-change on-blur]}]
(let [options
@ -85,14 +83,14 @@
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :align-options)}
[:> radio-buttons* {:selected (:text-align values)
[:> radio-buttons* {:selected (radio-selected (:text-align values))
:on-change handle-change
:name "align-text-options"
:options options}]]))
(mf/defc text-direction-options*
[{:keys [values on-change on-blur]}]
(let [direction (:text-direction values)
(let [direction (radio-selected (:text-direction values))
options
(mf/with-memo []
[{:value "ltr"
@ -119,7 +117,7 @@
(mf/defc vertical-align*
[{:keys [values on-change on-blur]}]
(let [vertical-align (or (:vertical-align values) "top")
(let [vertical-align (radio-selected (:vertical-align values) "top")
options
(mf/with-memo []
[{:value "top"
@ -151,6 +149,7 @@
(mf/defc grow-options*
[{:keys [ids values on-blur]}]
(let [grow-type (:grow-type values)
selected (radio-selected grow-type)
editor-instance (mf/deref refs/workspace-editor)
options
(mf/with-memo []
@ -191,14 +190,14 @@
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :grow-options)}
[:> radio-buttons* {:selected (d/name grow-type)
[:> radio-buttons* {:selected selected
:on-change handle-change
:name "grow-text-options"
:options options}]]))
(mf/defc text-decoration-options*
[{:keys [values on-change on-blur token-applied]}]
(let [text-decoration (some-> (:text-decoration values) d/name)
(let [text-decoration (radio-selected (:text-decoration values))
options
(mf/with-memo [token-applied]
[{:value "underline"
@ -222,8 +221,8 @@
[:div {:class (stl/css :text-decoration-options)}
[:> radio-buttons* {:selected (if (= text-decoration "none")
nil
text-decoration)
""
(radio-selected text-decoration))
:on-change handle-change
:name "text-decoration-options"
:disabled (and token-typography-row-enabled? (some? token-applied))
@ -246,8 +245,10 @@
(d/seek #(= (:id %) (uuid/uuid id)))))
(defn- check-props [n-props o-props]
(let [o-values (unchecked-get o-props "values")
n-values (unchecked-get n-props "values")]
(let [o-values (unchecked-get o-props "values")
n-values (unchecked-get n-props "values")
o-ruby-values (unchecked-get o-props "rubyValues")
n-ruby-values (unchecked-get n-props "rubyValues")]
(and (identical? (unchecked-get n-props "ids")
(unchecked-get o-props "ids"))
(identical? (unchecked-get n-props "type")
@ -258,6 +259,9 @@
(unchecked-get o-props "fileId"))
(identical? (unchecked-get n-props "typographies")
(unchecked-get o-props "typographies"))
(identical? (unchecked-get n-props "textSelectionActive")
(unchecked-get o-props "textSelectionActive"))
(= n-ruby-values o-ruby-values)
(identical? (get o-values :fills)
(get n-values :fills))
(identical? (get o-values :font-family)
@ -284,6 +288,32 @@
(get n-values :text-decoration))
(identical? (get o-values :text-direction)
(get n-values :text-direction))
(identical? (get o-values :writing-mode)
(get n-values :writing-mode))
(identical? (get o-values :text-orientation)
(get n-values :text-orientation))
(identical? (get o-values :text-combine-upright)
(get n-values :text-combine-upright))
(identical? (get o-values :warichu)
(get n-values :warichu))
(identical? (get o-values :font-features)
(get n-values :font-features))
(identical? (get o-values :annotation-clearance)
(get n-values :annotation-clearance))
(identical? (get o-values :text-emphasis)
(get n-values :text-emphasis))
(identical? (get o-values :ruby)
(get n-values :ruby))
(identical? (get o-values :ruby-hidden)
(get n-values :ruby-hidden))
(identical? (get o-values :ruby-size)
(get n-values :ruby-size))
(identical? (get o-values :ruby-align)
(get n-values :ruby-align))
(identical? (get o-values :ruby-overhang)
(get n-values :ruby-overhang))
(identical? (get o-values :ruby-side)
(get n-values :ruby-side))
(identical? (get o-values :text-transform)
(get n-values :text-transform))
(identical? (get o-values :typography-ref-file)
@ -291,7 +321,9 @@
(identical? (get o-values :typography-ref-id)
(get n-values :typography-ref-id))
(identical? (get o-values :vertical-align)
(get n-values :vertical-align)))))
(get n-values :vertical-align))
(identical? (get o-values :line-adjustment)
(get n-values :line-adjustment)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Main component
@ -299,14 +331,20 @@
(mf/defc text-menu*
{::mf/wrap [#(mf/memo' % check-props)]}
[{:keys [ids type values applied-tokens libraries file-id typographies]}]
[{:keys [ids type values ruby-values text-selection-active
applied-tokens libraries file-id typographies]}]
(let [;; --- UI state
menu-state* (mf/use-state {:main-menu true
:more-options false})
:more-options false
:japanese-layout true})
menu-state (deref menu-state*)
main-menu-open? (:main-menu menu-state)
more-options-open? (:more-options menu-state)
japanese-layout-open? (:japanese-layout menu-state)
japanese-layout-config-enabled?
(tjl/japanese-layout-config-enabled? (mf/deref refs/japanese-layout-config))
font-id (or (:font-id values) (:font-id txt/default-typography))
@ -397,6 +435,10 @@
(mf/use-fn
#(swap! menu-state* update :more-options not))
toggle-japanese-layout
(mf/use-fn
#(swap! menu-state* update :japanese-layout not))
toggle-token-dropdown
(mf/use-fn
#(swap! token-dropdown-open* not))
@ -427,6 +469,14 @@
(fn [attrs]
(emit-update! ids attrs)))
on-ruby-presentation-change
(mf/use-fn
(mf/deps ids on-change text-selection-active)
(fn [attrs]
(if text-selection-active
(on-change attrs)
(st/emit! (dwt/update-all-ruby-presentation ids attrs)))))
on-convert-to-typography
(mf/use-fn
(mf/deps values ids file-id emit-update!)
@ -485,7 +535,17 @@
:values values
:on-change on-change
:show-recent true
:on-blur on-text-blur})]
:on-blur on-text-blur})
japanese-layout-props
(mf/props
{:ids ids
:values values
:ruby-values ruby-values
:text-selection-active text-selection-active
:on-change on-change
:on-ruby-presentation-change on-ruby-presentation-change
:on-blur on-text-blur})]
(hooks/use-stream
expand-stream
@ -503,97 +563,114 @@
(when token-dropdown-open?
(ts/schedule 0 #(some-> (mf/ref-val dropdown-ref) dom/focus!))))
[:section {:class (stl/css :element-set)
;; Focusing these controls must not exit the v3 text editor (see `keep-editing-on-blur?`).
:data-keep-editing-on-blur true
:aria-label (tr "workspace.options.text-options.text-section")}
[:div {:class (stl/css :element-title)}
[:> title-bar* {:collapsable true
:collapsed (not main-menu-open?)
:on-collapsed toggle-main-menu
:title label
:class (stl/css :title-spacing-text)}
[:*
(when (and token-typography-row-enabled? (some? (resolve-delay typography-tokens)) (not typography))
[:> icon-button* {:variant "ghost"
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
:on-click toggle-token-dropdown
:tooltip-placement "top-left"
:icon i/tokens}])
(when (and (not typography) (not multiple?) (not applied-token-name))
[:> icon-button* {:variant "ghost"
:aria-label (if (not font)
(tr "workspace.options.font-not-available" (:font-family values))
(tr "workspace.options.convert-to-typography"))
:on-click on-convert-to-typography
:tooltip-placement "top-left"
:disabled (not font)
:icon i/add}])]]
(when (and token-typography-row-enabled? token-dropdown-open?)
[:> searchable-options-dropdown* {:on-click on-option-click
:id listbox-id
:options (resolve-delay dropdown-options)
:selected selected-token-id
:align "right"
:placeholder (tr "workspace.tokens.search-by-token")
:ref set-option-ref}])]
(when main-menu-open?
[:div {:class (stl/css :element-content)}
(cond
(and token-typography-row-enabled? (= :multiple current-token-name) (= typography-id :multiple))
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-tokens-and-assets")]]
(and token-typography-row-enabled? (= :multiple current-token-name))
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-tokens")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.libraries.text.multiple-token-tooltip")
[:*
[:section {:class (stl/css :element-set)
;; Focusing these controls must not exit the v3 text editor (see `keep-editing-on-blur?`).
:data-keep-editing-on-blur true
:aria-label (tr "workspace.options.text-options.text-section")}
[:div {:class (stl/css :element-title)}
[:> title-bar* {:collapsable true
:collapsed (not main-menu-open?)
:on-collapsed toggle-main-menu
:title label
:class (stl/css :title-spacing-text)}
[:*
(when (and token-typography-row-enabled? (some? (resolve-delay typography-tokens)) (not typography))
[:> icon-button* {:variant "ghost"
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
:on-click toggle-token-dropdown
:tooltip-placement "top-left"
:on-click handle-detach-all-tokens
:icon i/detach}]]
(and token-typography-row-enabled? current-token-name)
[:> token-typography-row* {:token-name current-token-name
:detach-token detach-token
:active-tokens (resolve-delay typography-tokens)}]
(= typography-id :multiple)
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-typography")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.libraries.text.multiple-assets-tooltip")
:on-click handle-detach-typography
:icon i/tokens}])
(when (and (not typography) (not multiple?) (not applied-token-name))
[:> icon-button* {:variant "ghost"
:aria-label (if (not font)
(tr "workspace.options.font-not-available" (:font-family values))
(tr "workspace.options.convert-to-typography"))
:on-click on-convert-to-typography
:tooltip-placement "top-left"
:icon i/detach}]]
:disabled (not font)
:icon i/add}])]]
(when (and token-typography-row-enabled? token-dropdown-open?)
[:> searchable-options-dropdown* {:on-click on-option-click
:id listbox-id
:options (resolve-delay dropdown-options)
:selected selected-token-id
:align "right"
:placeholder (tr "workspace.tokens.search-by-token")
:ref set-option-ref}])]
typography
[:> typography-entry* {:file-id typography-file-id
:typography typography
:is-local (= typography-file-id file-id)
:on-detach handle-detach-typography
:on-change handle-change-typography}]
(when main-menu-open?
[:div {:class (stl/css :element-content)}
(cond
(and token-typography-row-enabled? (= :multiple current-token-name) (= typography-id :multiple))
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-tokens-and-assets")]]
(and token-typography-row-enabled? (= :multiple current-token-name))
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-tokens")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.libraries.text.multiple-token-tooltip")
:tooltip-placement "top-left"
:on-click handle-detach-all-tokens
:icon i/detach}]]
(and token-typography-row-enabled? current-token-name)
[:> token-typography-row* {:token-name current-token-name
:detach-token detach-token
:active-tokens (resolve-delay typography-tokens)}]
:else
[:> text-options* common-props])
(= typography-id :multiple)
[:div {:class (stl/css :multiple-typography)}
[:span {:class (stl/css :multiple-text)}
(tr "workspace.libraries.text.mixed-typography")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.libraries.text.multiple-assets-tooltip")
:on-click handle-detach-typography
:tooltip-placement "top-left"
:icon i/detach}]]
[:div {:class (stl/css :text-align-options)}
[:> text-align-options* common-props]
[:> grow-options* common-props]
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.options")
:data-testid "text-align-options-button"
:on-click toggle-more-options
:icon i/menu}]]
typography
[:> typography-entry* {:file-id typography-file-id
:typography typography
:is-local (= typography-file-id file-id)
:on-detach handle-detach-typography
:on-change handle-change-typography}]
(when more-options-open?
[:div {:class (stl/css :text-decoration-options)}
[:> vertical-align* common-props]
[:> text-decoration-options* (mf/spread-props common-props {:token-applied current-token-name})]
[:> text-direction-options* common-props]])])]))
:else
[:> text-options* common-props])
[:div {:class (stl/css :text-align-options)}
[:> text-align-options* common-props]
[:> grow-options* common-props]
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.options")
:data-testid "text-align-options-button"
:on-click toggle-more-options
:icon i/menu}]]
(when japanese-layout-config-enabled?
[:> tjl/line-adjustment-option* common-props])
(when more-options-open?
[:div {:class (stl/css :text-decoration-options)}
[:> vertical-align* common-props]
[:> text-decoration-options* (mf/spread-props common-props {:token-applied current-token-name})]
[:> text-direction-options* common-props]])])]
(when japanese-layout-config-enabled?
[:section {:class (stl/css :element-set)
;; Focusing these controls must not exit the v3 text editor (see `keep-editing-on-blur?`).
:data-keep-editing-on-blur true
:aria-label (tr "workspace.options.text-options.japanese-layout")}
[:div {:class (stl/css :element-title)}
[:> title-bar* {:collapsable true
:collapsed (not japanese-layout-open?)
:on-collapsed toggle-japanese-layout
:title (tr "workspace.options.text-options.japanese-layout")
:class (stl/css :title-spacing-text)}]]
(when japanese-layout-open?
[:div {:class (stl/css :element-content)}
[:> tjl/japanese-layout-options* japanese-layout-props]])])]))

View File

@ -0,0 +1,523 @@
;; 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.workspace.sidebar.options.menus.text-japanese-layout
(:require-macros [app.main.style :as stl])
(:require
[app.common.types.text.japanese-layout :as jl]
[app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
[app.main.ui.ds.controls.select :refer [select*]]
[app.main.ui.ds.controls.switch :refer [switch*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options* radio-selected]]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.text.writing-mode :as wm]
[rumext.v2 :as mf]))
(def ^:private mixed-span-values
#{:mixed :multiple "mixed" "multiple"})
(defn- mixed-span-value?
[value]
(contains? mixed-span-values value))
(defn- span-input-value
[value]
(if (mixed-span-value? value)
"mixed"
(radio-selected value)))
(defn span-select-value
[value default]
(if (mixed-span-value? value)
"mixed"
(radio-selected value default)))
(defn with-mixed-span-option
[options value]
(cond-> options
(mixed-span-value? value)
(conj {:id "mixed"
:label (tr "labels.mixed-values")
:disabled true
:dimmed true})))
(defn japanese-layout-config-enabled?
"True under the WASM renderer when Japanese layout is enabled for the file
or in the user's profile. `config` is a `refs/japanese-layout-config`
value."
[{:keys [file? all-files?]}]
(and ^boolean (wm/vertical-layout-active?)
(boolean (or file? all-files?))))
(defn vertical-japanese-layout?
"True when the Japanese layout controls are editing vertical text."
[values]
(= "vertical-rl" (:writing-mode values)))
(defn proportional-metrics-feature
"Proportional metrics feature for the writing mode: `vpal` or `palt`."
[writing-mode]
(if (= writing-mode "vertical-rl") "vpal" "palt"))
(defn text-combine-upright-options
[text-selection-active translate]
(cond-> [{:value "none"
:id "none-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-none")
:icon i/text-combine-upright-none}]
text-selection-active
(conj {:value "all"
:id "all-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-all")
:icon i/text-combine-upright-all})
true
(conj {:value "digits"
:id "digits-text-combine-upright"
:label (translate "workspace.options.text-options.text-combine-upright-digits")
:icon i/text-combine-upright-digits})))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Sub-components
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc attr-radio-options*
"Radio group bound to one text attribute."
[{:keys [values on-change on-blur attr default-value name class options]}]
(let [selected (radio-selected (get values attr) default-value)
handle-change
(mf/use-fn
(mf/deps attr on-change on-blur)
(fn [value]
(on-change {attr value})
(when (some? on-blur) (on-blur))))]
[:div {:class class}
[:> radio-buttons* {:selected selected
:on-change handle-change
:name name
:options options}]]))
(defn writing-mode-options
[translate]
[{:value "horizontal-tb"
:id "horizontal-tb-writing-mode"
:label (translate "workspace.options.text-options.writing-mode-horizontal")
:icon i/writing-mode-horizontal}
{:value "vertical-rl"
:id "vertical-rl-writing-mode"
:label (translate "workspace.options.text-options.writing-mode-vertical")
:icon i/writing-mode-vertical}])
(defn text-orientation-options
[translate]
[{:value "mixed"
:id "mixed-text-orientation"
:label (translate "workspace.options.text-options.text-orientation-mixed")
:icon i/text-orientation-mixed}
{:value "upright"
:id "upright-text-orientation"
:label (translate "workspace.options.text-options.text-orientation-upright")
:icon i/text-orientation-upright}])
;; Warichu (割注): renders the selection as two half-size lines in one inline
;; position (top/bottom horizontally, right/left vertically).
(defn warichu-options
[translate]
[{:value "none"
:id "none-warichu"
:label (translate "workspace.options.text-options.warichu-none")
:icon i/warichu-none}
{:value "warichu"
:id "warichu-warichu"
:label (translate "workspace.options.text-options.warichu")
:icon i/warichu}])
;; select* matches options by :id, so each id is the stored value.
(defn text-combine-upright-count-options
[translate]
[{:id "digits2"
:label (translate "workspace.options.text-options.text-combine-upright-digits-2")}
{:id "digits3"
:label (translate "workspace.options.text-options.text-combine-upright-digits-3")}
{:id "digits"
:label (translate "workspace.options.text-options.text-combine-upright-digits-4")}])
(defn ruby-size-options
[translate]
[{:id "half" :label (translate "workspace.options.text-options.ruby-size-half")}
{:id "third" :label (translate "workspace.options.text-options.ruby-size-third")}
{:id "quarter" :label (translate "workspace.options.text-options.ruby-size-quarter")}])
(defn ruby-align-options
[translate]
[{:id "space-around" :label (translate "workspace.options.text-options.ruby-align-space-around")}
{:id "center" :label (translate "workspace.options.text-options.ruby-align-center")}
{:id "start" :label (translate "workspace.options.text-options.ruby-align-start")}
{:id "space-between" :label (translate "workspace.options.text-options.ruby-align-space-between")}])
(defn ruby-overhang-options
[translate]
[{:id "auto" :label (translate "workspace.options.text-options.ruby-overhang-auto")}
{:id "none" :label (translate "workspace.options.text-options.ruby-overhang-none")}])
(defn ruby-side-options
[translate]
[{:id "over" :label (translate "workspace.options.text-options.ruby-side-over")}
{:id "under" :label (translate "workspace.options.text-options.ruby-side-under")}])
(mf/defc text-combine-upright-options*
;; Digit TCY finds eligible runs itself, so it applies to a whole shape;
;; `all` is offered only for a text selection.
[{:keys [values on-change on-blur text-selection-active]}]
(let [text-combine-upright (radio-selected (:text-combine-upright values)
(jl/enum-default :text-combine-upright))
selected (if (jl/digit-combine? text-combine-upright) "digits" text-combine-upright)
options
(mf/with-memo [text-selection-active]
(text-combine-upright-options text-selection-active tr))
handle-change
(mf/use-fn
(mf/deps on-change on-blur)
(fn [value]
(on-change {:text-combine-upright value})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :text-combine-upright-options)}
[:> radio-buttons* {:selected selected
:on-change handle-change
:name "text-combine-upright-options"
:options options}]]))
(mf/defc text-combine-upright-count-options*
[{:keys [values on-change on-blur]}]
(let [text-combine-upright (radio-selected (:text-combine-upright values)
(jl/enum-default :text-combine-upright))
digits? (jl/digit-combine? text-combine-upright)
options (mf/with-memo [] (text-combine-upright-count-options tr))
handle-change
(mf/use-fn
(mf/deps on-change on-blur)
(fn [value]
(on-change {:text-combine-upright value})
(when (some? on-blur) (on-blur))))]
(when digits?
[:div {:class (stl/css :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)}
(tr "workspace.options.text-options.text-combine-upright-digits-count")]
[:> select* {:default-selected text-combine-upright
:aria-label (tr "workspace.options.text-options.text-combine-upright-digits-count")
:options options
:on-change handle-change}]])))
(mf/defc ruby-options*
[{:keys [values on-change on-blur advanced-open on-toggle-advanced]}]
(let [ruby (span-input-value (:ruby values))
ruby* (mf/use-state ruby)
dirty* (mf/use-state false)
value (deref ruby*)
dirty? (deref dirty*)
commit!
(mf/use-fn
(mf/deps dirty? value on-change on-blur)
(fn []
(when dirty?
(on-change {:ruby value})
(reset! dirty* false))
(when (some? on-blur) (on-blur))))
handle-change
(mf/use-fn
(fn [event]
(reset! dirty* true)
(reset! ruby* (dom/get-target-val event))))
handle-key-down
(mf/use-fn
(mf/deps commit!)
(fn [event]
(when (= "Enter" (.-key event))
(dom/blur! (dom/get-target event)))))]
(mf/with-effect [ruby]
(reset! ruby* ruby)
(reset! dirty* false))
[:div {:class (stl/css :ruby-options)}
[:> input* {:class (stl/css :ruby-input)
:label (tr "workspace.options.text-options.ruby")
:placeholder (tr "workspace.options.text-options.ruby-placeholder")
:value value
:on-change handle-change
:on-blur commit!
:on-key-down handle-key-down}]
[:> icon-button* {:variant "ghost"
:selected advanced-open
:aria-label (tr "workspace.options.text-options.ruby-advanced-options")
:aria-expanded advanced-open
:data-testid "ruby-advanced-options-toggle"
:on-click on-toggle-advanced
:icon i/menu}]]))
(mf/defc span-select-option*
"Select bound to one span attribute, with a disabled entry for mixed values."
[{:keys [values on-change on-blur attr default-value label options]}]
(let [value (get values attr)
selected (span-select-value value default-value)
options (with-mixed-span-option options value)
handle-change
(mf/use-fn
(mf/deps attr on-change on-blur)
(fn [value]
(on-change {attr value})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)} label]
[:> select* {:default-selected selected
:aria-label label
:options options
:on-change handle-change}]]))
(defn- ruby-common-props
[values on-change on-blur]
(mf/props
{:values values
:on-change on-change
:on-blur on-blur}))
(mf/defc ruby-hidden-option*
[{:keys [values on-change on-blur]}]
(let [value (:ruby-hidden values)
enabled? (cond
(mixed-span-value? value) nil
(true? value) true
:else false)
label (tr "workspace.options.text-options.ruby-hidden")
handle-change
(mf/use-fn
(mf/deps on-change on-blur)
(fn [checked?]
(on-change {:ruby-hidden checked?})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :ruby-hidden-option :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)} label]
[:> switch* {:default-checked enabled?
:aria-label label
:on-change handle-change}]]))
(mf/defc ruby-customization-options*
[{:keys [values on-change on-blur]}]
(let [common-props (ruby-common-props values on-change on-blur)
options (mf/with-memo []
{:ruby-size (ruby-size-options tr)
:ruby-align (ruby-align-options tr)
:ruby-overhang (ruby-overhang-options tr)
:ruby-side (ruby-side-options tr)})]
[:div {:class (stl/css :japanese-layout-controls)}
[:> ruby-hidden-option* common-props]
[:> span-select-option* (mf/spread-props common-props
{:attr :ruby-size
:default-value (jl/enum-default :ruby-size)
:label (tr "workspace.options.text-options.ruby-size")
:options (:ruby-size options)})]
[:> span-select-option* (mf/spread-props common-props
{:attr :ruby-align
:default-value (jl/enum-default :ruby-align)
:label (tr "workspace.options.text-options.ruby-align")
:options (:ruby-align options)})]
[:> span-select-option* (mf/spread-props common-props
{:attr :ruby-overhang
:default-value (jl/enum-default :ruby-overhang)
:label (tr "workspace.options.text-options.ruby-overhang")
:options (:ruby-overhang options)})]
[:> span-select-option* (mf/spread-props common-props
{:attr :ruby-side
:default-value (jl/enum-default :ruby-side)
:label (tr "workspace.options.text-options.ruby-side")
:options (:ruby-side options)})]]))
(mf/defc ruby-advanced-options*
[{:keys [values on-change on-blur]}]
(let [open* (mf/use-state false)
open? (deref open*)
toggle-open (mf/use-fn #(swap! open* not))
common-props (ruby-common-props values on-change on-blur)]
[:div {:class (stl/css :ruby-advanced-options)}
[:> ruby-options* (mf/spread-props common-props
{:advanced-open open?
:on-toggle-advanced toggle-open})]
[:> advanced-options* {:class (stl/css :ruby-advanced-content)
:is-visible open?}
[:> ruby-customization-options* common-props]]]))
(mf/defc ruby-presentation-options*
[{:keys [values on-change on-blur]}]
(let [open* (mf/use-state false)
open? (deref open*)
toggle-open (mf/use-fn #(swap! open* not))
common-props (ruby-common-props values on-change on-blur)]
[:div {:class (stl/css :ruby-advanced-options)}
[:> title-bar* {:collapsable true
:collapsed (not open?)
:title (tr "workspace.options.text-options.ruby-advanced-options")
:on-collapsed toggle-open
:aria-expanded open?
:data-testid "ruby-presentation-options-toggle"}]
[:> advanced-options* {:class (stl/css :ruby-advanced-content)
:is-visible open?}
[:> ruby-customization-options* common-props]]]))
(mf/defc font-features-options*
;; Japanese proportional metric alternates, per span: `palt` for
;; horizontal text, `vpal` for vertical.
[{:keys [values on-change on-blur]}]
(let [writing-mode (:writing-mode values)
feature (proportional-metrics-feature writing-mode)
value (:font-features values)
enabled? (cond
(mixed-span-value? value) nil
(= feature value) true
:else false)
handle-change
(mf/use-fn
(mf/deps feature on-change on-blur)
(fn [checked?]
(on-change {:font-features (if checked? feature "none")})
(when (some? on-blur) (on-blur))))]
[:div {:class (stl/css :japanese-select-option)}
[:span {:class (stl/css :japanese-option-label)}
(tr "workspace.options.text-options.font-features")]
[:> switch* {:default-checked enabled?
:aria-label (tr "workspace.options.text-options.font-features")
:on-change handle-change}]]))
(defn text-emphasis-options
([]
(text-emphasis-options tr))
([translate]
[{:id "none"
:label (translate "workspace.options.text-options.text-emphasis-none")}
{:id "filled-dot"
:label (translate "workspace.options.text-options.text-emphasis-filled-dot")}
{:id "open-dot"
:label (translate "workspace.options.text-options.text-emphasis-open-dot")}
{:id "filled-circle"
:label (translate "workspace.options.text-options.text-emphasis-filled-circle")}
{:id "open-circle"
:label (translate "workspace.options.text-options.text-emphasis-open-circle")}
{:id "filled-sesame"
:label (translate "workspace.options.text-options.text-emphasis-filled-sesame")}
{:id "open-sesame"
:label (translate "workspace.options.text-options.text-emphasis-open-sesame")}]))
(defn annotation-clearance-options
([]
(annotation-clearance-options tr))
([translate]
[{:id "none"
:label (translate "workspace.options.text-options.annotation-clearance-none")}
{:id "auto"
:label (translate "workspace.options.text-options.annotation-clearance-auto")}]))
(defn line-adjustment-options
([]
(line-adjustment-options tr))
([translate]
[{:id "push-in-first"
:label (translate "workspace.options.text-options.line-adjustment-push-in-first")}
{:id "push-out-first"
:label (translate "workspace.options.text-options.line-adjustment-push-out-first")}
{:id "push-out-only"
:label (translate "workspace.options.text-options.line-adjustment-push-out-only")}]))
(mf/defc line-adjustment-option*
"Whole-shape setting: how lines slightly too long are fitted."
[{:keys [values on-change on-blur]}]
(let [options (mf/with-memo [] (line-adjustment-options))]
[:> span-select-option* {:values values
:on-change on-change
:on-blur on-blur
:attr :line-adjustment
:default-value (jl/enum-default :line-adjustment)
:label (tr "workspace.options.text-options.line-adjustment")
:options options}]))
(mf/defc japanese-layout-options*
[{:keys [values ruby-values text-selection-active
on-change on-ruby-presentation-change on-blur]}]
(let [vertical? (vertical-japanese-layout? values)
common-props (mf/props
{:values values
:on-change on-change
:on-blur on-blur})
ruby-presentation-props (mf/props
{:values ruby-values
:on-change on-ruby-presentation-change
:on-blur on-blur})
options (mf/with-memo []
{:writing-mode (writing-mode-options tr)
:text-orientation (text-orientation-options tr)
:warichu (warichu-options tr)
:text-emphasis (text-emphasis-options)
:annotation-clearance (annotation-clearance-options)})]
[:div {:class (stl/css :japanese-layout-options)}
[:div {:class (stl/css :japanese-layout-controls)}
[:div {:class (stl/css :japanese-icon-options)}
[:> attr-radio-options* (mf/spread-props common-props
{:attr :writing-mode
:default-value (jl/enum-default :writing-mode)
:name "writing-mode-options"
:class (stl/css :writing-mode-options)
:options (:writing-mode options)})]
(when ^boolean vertical?
[:*
;; An unset orientation reads as nil, or "" in the v2 editor; both
;; select "mixed".
[:> attr-radio-options* (mf/spread-props common-props
{:attr :text-orientation
:default-value (jl/enum-default :text-orientation)
:name "text-orientation-options"
:class (stl/css :text-orientation-options)
:options (:text-orientation options)})]
[:> text-combine-upright-options*
(mf/spread-props common-props
{:text-selection-active text-selection-active})]])
(when ^boolean text-selection-active
[:> attr-radio-options* (mf/spread-props common-props
{:attr :warichu
:default-value (jl/enum-default :warichu)
:name "warichu-options"
:class (stl/css :warichu-options)
:options (:warichu options)})])]
(when ^boolean vertical?
[:> text-combine-upright-count-options* common-props])
[:> font-features-options* common-props]
;; Emphasis marks (圏点 / bouten) apply to the selected characters.
(when ^boolean text-selection-active
[:> span-select-option* (mf/spread-props common-props
{:attr :text-emphasis
:default-value (jl/enum-default :text-emphasis)
:label (tr "workspace.options.text-options.text-emphasis")
:options (:text-emphasis options)})])
[:> span-select-option* (mf/spread-props common-props
{:attr :annotation-clearance
:default-value (jl/enum-default :annotation-clearance)
:label (tr "workspace.options.text-options.annotation-clearance")
:options (:annotation-clearance options)})]
(if text-selection-active
[:> ruby-advanced-options* common-props]
[:> ruby-presentation-options* ruby-presentation-props])]]))

View File

@ -0,0 +1,78 @@
// 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/_sizes.scss" as *;
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
.writing-mode-options,
.text-orientation-options,
.text-combine-upright-options,
.warichu-options {
block-size: $sz-32;
}
.ruby-advanced-options {
display: flex;
flex-direction: column;
overflow: visible;
}
.ruby-options {
display: flex;
align-items: flex-end;
gap: var(--sp-xs);
}
.ruby-input {
flex: 1;
}
.ruby-advanced-content {
display: flex;
flex-direction: column;
gap: var(--sp-xs);
padding: var(--sp-xs);
border: $b-1 solid var(--color-background-quaternary);
border-radius: $br-8;
}
.japanese-layout-options,
.japanese-layout-controls {
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.japanese-layout-options {
padding-block: var(--sp-xs);
}
.ruby-hidden-option {
min-block-size: $sz-32;
}
.japanese-icon-options {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--sp-xs);
}
.japanese-select-option {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
align-items: center;
gap: var(--sp-s);
inline-size: 100%;
}
.japanese-option-label {
@include t.use-typography("body-small");
color: var(--color-foreground-secondary);
}

View File

@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctl]
[app.common.types.text :as txt]
[app.common.types.text.japanese-layout :as jl]
[app.main.data.workspace.texts :as dwt]
[app.main.features :as features]
[app.main.refs :as refs]
@ -27,8 +28,30 @@
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu*]]
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu*]]
[app.main.ui.workspace.sidebar.options.menus.text :refer [text-menu*]]
[app.render-wasm.api :as wasm.api]
[rumext.v2 :as mf]))
(defn- dom-text-range-selected?
[editor]
(when editor
(let [selection (.getSelection js/window)
element (.-element editor)
anchor (some-> selection .-anchorNode)
focus (some-> selection .-focusNode)]
(and selection
(not (.-isCollapsed selection))
element
anchor
focus
(.contains element anchor)
(.contains element focus)))))
(defn- draft-text-range-selected?
[editor-state]
(when editor-state
(let [selection (.getSelection editor-state)]
(and selection (not (.isCollapsed selection))))))
(mf/defc options*
[{:keys [shape libraries file-id page-id]}]
(let [id (dm/get-prop shape :id)
@ -128,6 +151,19 @@
(when text-editor-v2?
editor)
text-selection-active
(boolean
(cond
text-editor-wasm?
(and (= id (wasm.api/text-editor-get-active-shape-id))
(wasm.api/text-editor-has-selection?))
text-editor-v2?
(dom-text-range-selected? editor-instance)
:else
(draft-text-range-selected? editor-state)))
fill-values
(dwt/current-text-values
{:editor-styles editor-styles
@ -154,7 +190,14 @@
:editor-state editor-state
:editor-instance editor-instance
:shape shape
:attrs txt/text-node-attrs}))]
:attrs txt/text-node-attrs}))
ruby-values
(if text-selection-active
(select-keys text-values jl/ruby-presentation-attrs)
(dwt/current-ruby-values
{:shape shape
:attrs jl/ruby-presentation-attrs}))]
[:*
[:> layer-menu* {:ids ids
@ -202,6 +245,8 @@
:type type
:applied-tokens applied-tokens
:values text-values
:ruby-values ruby-values
:text-selection-active text-selection-active
:libraries libraries
:file-id file-id
:typographies typographies}]
@ -237,4 +282,3 @@
:values (select-keys shape exports-attrs)
:page-id page-id
:file-id file-id}]]))

View File

@ -140,6 +140,7 @@
[frontend-tests.ui.sidebar-scroll-test]
[frontend-tests.ui.stroke-menu-test]
[frontend-tests.ui.text-attrs-multiple-test]
[frontend-tests.ui.text-options-test]
[frontend-tests.ui.workspace-libraries-test]
[frontend-tests.util-clipboard-test]
[frontend-tests.util-object-test]
@ -302,6 +303,7 @@
'frontend-tests.ui.sidebar-scroll-test
'frontend-tests.ui.stroke-menu-test
'frontend-tests.ui.text-attrs-multiple-test
'frontend-tests.ui.text-options-test
'frontend-tests.ui.workspace-libraries-test
'frontend-tests.util-clipboard-test
'frontend-tests.util-object-test

View File

@ -0,0 +1,248 @@
;; 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 frontend-tests.ui.text-options-test
(:require
["react-dom/server" :as rds]
[app.common.types.text.japanese-layout :as jl]
[app.main.data.workspace.texts :as dwt]
[app.main.ui.ds.controls.select :as select]
[app.main.ui.shapes.text.html-text :as html-text]
[app.main.ui.shapes.text.styles :as text-styles]
[app.main.ui.workspace.sidebar.options.common :refer [radio-selected]]
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
[app.util.text.writing-mode :as wm]
[cljs.test :as t :include-macros true]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(def ^:private japanese-span-attrs
[:text-combine-upright
:text-emphasis
:ruby
:ruby-hidden
:ruby-size
:ruby-align
:ruby-overhang
:ruby-side
:warichu
:font-features
:annotation-clearance])
(t/deftest japanese-layout-controls-follow-file-or-profile-configuration
(with-redefs [wm/vertical-layout-active? (constantly true)]
(t/is (false? (tjl/japanese-layout-config-enabled? {})))
(t/is (false? (tjl/japanese-layout-config-enabled? {:file? false :all-files? false})))
(t/is (true? (tjl/japanese-layout-config-enabled? {:file? true :all-files? false})))
(t/is (true? (tjl/japanese-layout-config-enabled? {:file? false :all-files? true}))))
(with-redefs [wm/vertical-layout-active? (constantly false)]
(t/is (false? (tjl/japanese-layout-config-enabled? {:file? true :all-files? true})))))
(t/deftest text-emphasis-select-reports-and-restores-canonical-values
(let [options (tjl/text-emphasis-options identity)
reported-id (:id (select/get-option options "filled-dot"))]
(t/is (= "filled-dot" reported-id))
(t/is (= "filled-dot"
(:id (select/get-option options reported-id))))))
(t/deftest annotation-clearance-select-reports-canonical-values
(let [options (tjl/annotation-clearance-options identity)]
(t/is (= ["none" "auto"] (mapv :id options)))
(t/is (= "auto" (:id (select/get-option options "auto"))))))
(t/deftest unrestricted-tcy-is-only-offered-for-a-text-selection
(t/is (= ["none" "digits"]
(mapv :value (tjl/text-combine-upright-options false identity))))
(t/is (= ["none" "all" "digits"]
(mapv :value (tjl/text-combine-upright-options true identity)))))
(t/deftest ruby-container-styles-preserve-customization
(let [style (text-styles/generate-ruby-container-styles
{:ruby-align "space-between"
:ruby-overhang "none"
:ruby-side "under"})]
(t/is (= "space-between" (.-rubyAlign style)))
(t/is (= "none" (.-rubyOverhang style)))
(t/is (= "under" (.-rubyPosition style)))))
(t/deftest advanced-furigana-options-are-collapsed-by-default
(let [markup (rds/renderToStaticMarkup
(mf/element tjl/ruby-presentation-options*
#js {:values {}
:on-change identity
:on-blur identity}))]
(t/is (str/includes? markup "data-testid=\"ruby-presentation-options-toggle\""))
(t/is (str/includes? markup "aria-expanded=\"false\""))
(t/is (str/includes? markup "workspace.options.text-options.ruby-advanced-options"))))
(t/deftest advanced-furigana-options-include-hide-toggle
(let [markup (rds/renderToStaticMarkup
(mf/element tjl/ruby-hidden-option*
#js {:values {:ruby-hidden true}
:on-change identity
:on-blur identity}))]
(t/is (str/includes? markup "workspace.options.text-options.ruby-hidden"))
(t/is (str/includes? markup "role=\"switch\""))
(t/is (str/includes? markup "aria-checked=\"true\""))))
(t/deftest hidden-furigana-renders-base-text-without-ruby-layout
(let [markup (rds/renderToStaticMarkup
(mf/element html-text/render-text*
#js {:node {:text "日"
:ruby "にち"
:ruby-hidden true}
:parent {}
:shape {}}))]
(t/is (str/includes? markup ">日</span>"))
(t/is (not (str/includes? markup "<ruby")))))
(t/deftest shape-level-furigana-presentation-only-updates-ruby-spans
(let [shape {:content
{:type "root"
:children
[{:type "paragraph-set"
:children
[{:type "paragraph"
:children
[{:text "日"
:ruby "にち"
:ruby-hidden false
:ruby-size "half"}
{:text "本"
:ruby ""
:ruby-hidden false
:ruby-size "quarter"}]}]}]}}
values (dwt/current-ruby-values {:shape shape
:attrs jl/ruby-presentation-attrs})
updated (dwt/update-ruby-presentation-attrs
shape {:ruby-hidden true :ruby-size "third"})
spans (get-in updated [:content :children 0 :children 0 :children])]
(t/is (= "half" (:ruby-size values)))
(t/is (false? (:ruby-hidden values)))
(t/is (= "third" (:ruby-size (first spans))))
(t/is (true? (:ruby-hidden (first spans))))
(t/is (= "quarter" (:ruby-size (second spans))))
(t/is (false? (:ruby-hidden (second spans))))))
(t/deftest mixed-span-values-select-a-disabled-mixed-option
(let [options (tjl/with-mixed-span-option (tjl/text-emphasis-options identity) :multiple)]
(t/is (= "mixed" (tjl/span-select-value :multiple "none")))
(t/is (= "none" (tjl/span-select-value nil "none")))
(t/is (= "mixed" (:id (last options))))
(t/is (true? (:disabled (last options))))
(t/is (= options (tjl/with-mixed-span-option options "none"))
"an unmixed value adds no entry")))
(t/deftest mixed-radio-values-select-nothing
(t/is (= "" (radio-selected :multiple "none")))
(t/is (= "none" (radio-selected nil "none")))
(t/is (= "vertical-rl" (radio-selected :vertical-rl))))
(t/deftest whole-text-selection-reports-every-differing-span-value-as-mixed
(let [shape {:content
{:type "root"
:children
[{:type "paragraph-set"
:children
[{:type "paragraph"
:children
[{:text "日"
:text-combine-upright "digits2"
:text-emphasis "filled-dot"
:ruby "にち"
:ruby-hidden true
:ruby-size "third"
:ruby-align "center"
:ruby-overhang "none"
:ruby-side "under"
:warichu "warichu"
:font-features "palt"
:annotation-clearance "auto"}
{:text "本"
:text-combine-upright "none"
:text-emphasis "none"
:ruby ""
:ruby-hidden false
:ruby-size "half"
:ruby-align "space-around"
:ruby-overhang "auto"
:ruby-side "over"
:warichu "none"
:font-features "none"
:annotation-clearance "none"}]}]}]}}
values (dwt/current-text-values {:shape shape
:attrs japanese-span-attrs})]
(t/is (= (zipmap japanese-span-attrs (repeat :multiple)) values))))
(t/deftest wasm-editor-span-styles-preserve-persisted-vertical-paragraph-values
(let [shape {:content
{:type "root"
:children
[{:type "paragraph-set"
:children
[{:type "paragraph"
:writing-mode "vertical-rl"
:text-orientation "upright"
:children [{:text "12" :text-combine-upright "none"}]}]}]}}
editor-styles {:text-combine-upright "digits2"}
paragraph-values (dwt/current-paragraph-values
{:editor-styles editor-styles
:shape shape
:attrs [:writing-mode :text-orientation]})
text-values (dwt/current-text-values
{:editor-styles editor-styles
:shape shape
:attrs [:text-combine-upright]})]
(t/is (= "vertical-rl" (:writing-mode paragraph-values)))
(t/is (= "upright" (:text-orientation paragraph-values)))
(t/is (= "digits2" (:text-combine-upright text-values)))))
(t/deftest japanese-controls-distinguish-horizontal-and-vertical-modes
(t/is (false? (tjl/vertical-japanese-layout? {})))
(t/is (false? (tjl/vertical-japanese-layout?
{:writing-mode "horizontal-tb"})))
(t/is (true? (tjl/vertical-japanese-layout?
{:writing-mode "vertical-rl"})))
(t/is (= "palt"
(tjl/proportional-metrics-feature "horizontal-tb")))
(t/is (= "palt"
(tjl/proportional-metrics-feature nil)))
(t/is (= "vpal"
(tjl/proportional-metrics-feature "vertical-rl"))))
(t/deftest sidebar-options-offer-the-japanese-enum-values
(let [ids #(mapv :id %)
values #(mapv :value %)]
(t/is (= (:writing-mode jl/enum-values) (values (tjl/writing-mode-options identity))))
(t/is (= (:text-orientation jl/enum-values) (values (tjl/text-orientation-options identity))))
(t/is (= (:warichu jl/enum-values) (values (tjl/warichu-options identity))))
(t/is (= (:text-emphasis jl/enum-values) (ids (tjl/text-emphasis-options identity))))
(t/is (= (:annotation-clearance jl/enum-values) (ids (tjl/annotation-clearance-options identity))))
(t/is (= (:ruby-size jl/enum-values) (ids (tjl/ruby-size-options identity))))
(t/is (= (:ruby-align jl/enum-values) (ids (tjl/ruby-align-options identity))))
(t/is (= (:ruby-overhang jl/enum-values) (ids (tjl/ruby-overhang-options identity))))
(t/is (= (:ruby-side jl/enum-values) (ids (tjl/ruby-side-options identity))))
(t/is (= (:line-adjustment jl/enum-values) (ids (tjl/line-adjustment-options identity))))
(t/is (every? #(jl/valid-enum-value? :text-combine-upright %)
(values (tjl/text-combine-upright-options true identity))))
(t/is (every? jl/digit-combine? (ids (tjl/text-combine-upright-count-options identity))))))
(t/deftest html-text-drops-vertical-writing-under-a-horizontal-renderer
(let [shape {:id (random-uuid) :width 100 :height 100
:content {:type "root"
:children
[{:type "paragraph-set"
:children
[{:type "paragraph"
:writing-mode "vertical-rl"
:text-orientation "upright"
:children [{:text "縦"}]}]}]}}
markup #(rds/renderToStaticMarkup (mf/element html-text/text-shape* #js {:shape shape}))]
(with-redefs [wm/vertical-layout-active? (constantly false)]
(t/is (not (str/includes? (markup) "vertical-rl"))))
(with-redefs [wm/vertical-layout-active? (constantly true)]
(t/is (str/includes? (markup) "writing-mode:vertical-rl")))))

View File

@ -783,6 +783,18 @@ msgstr "Invite people"
msgid "dashboard.invite-profile-disabled"
msgstr "You don't have permission to invite people to this team"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.description"
msgstr "Makes Japanese text layout controls available in every file."
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.enable-all-files"
msgstr "Enable Japanese text layout for all files"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.title"
msgstr "Japanese text layout"
#: src/app/main/ui/dashboard/organization_team_switch_menus.cljs:235, src/app/main/ui/dashboard/organization_team_switch_menus.cljs:266
msgid "dashboard.leave-organization"
msgstr "Leave org"
@ -6468,6 +6480,10 @@ msgstr "Radial gradient"
msgid "workspace.header.menu.disable-dynamic-alignment"
msgstr "Disable dynamic alignment"
#: src/app/main/ui/workspace/main_menu.cljs
msgid "workspace.header.menu.disable-japanese-layout"
msgstr "Disable Japanese text layout"
#: src/app/main/ui/workspace/main_menu.cljs:254
msgid "workspace.header.menu.disable-scale-content"
msgstr "Disable proportional scale"
@ -6492,6 +6508,10 @@ msgstr "Disable WebGL rendering (beta)"
msgid "workspace.header.menu.enable-dynamic-alignment"
msgstr "Enable dynamic alignment"
#: src/app/main/ui/workspace/main_menu.cljs
msgid "workspace.header.menu.enable-japanese-layout"
msgstr "Enable Japanese text layout"
#: src/app/main/ui/workspace/main_menu.cljs:255
msgid "workspace.header.menu.enable-scale-content"
msgstr "Enable proportional scale"
@ -8036,6 +8056,34 @@ msgstr "Align middle"
msgid "workspace.options.text-options.align-top"
msgstr "Align top"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.annotation-clearance"
msgstr "Ann. clearance"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.annotation-clearance-auto"
msgstr "Automatic"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.line-adjustment"
msgstr "Line adjustment"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.line-adjustment-push-in-first"
msgstr "Push in first"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.line-adjustment-push-out-first"
msgstr "Push out first"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.line-adjustment-push-out-only"
msgstr "Push out only"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.annotation-clearance-none"
msgstr "Use line height"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs:100
msgid "workspace.options.text-options.direction-ltr"
msgstr "LTR"
@ -8044,6 +8092,22 @@ msgstr "LTR"
msgid "workspace.options.text-options.direction-rtl"
msgstr "RTL"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.font-features"
msgstr "Prop. metrics"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.font-features-none"
msgstr "None"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.font-features-palt"
msgstr "Horizontal (palt)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.font-features-vpal"
msgstr "Vertical (vpal)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs:167
msgid "workspace.options.text-options.grow-auto-height"
msgstr "Auto height"
@ -8056,6 +8120,10 @@ msgstr "Auto width"
msgid "workspace.options.text-options.grow-fixed"
msgstr "Fixed"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.japanese-layout"
msgstr "Japanese text layout"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:688
msgid "workspace.options.text-options.letter-spacing"
msgstr "Letter Spacing"
@ -8064,6 +8132,82 @@ msgstr "Letter Spacing"
msgid "workspace.options.text-options.line-height"
msgstr "Line height"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby"
msgstr "Ruby"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-advanced-options"
msgstr "Advanced furigana options"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align"
msgstr "Ruby alignment"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align-center"
msgstr "Center"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align-space-around"
msgstr "Space around"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align-space-between"
msgstr "Space between"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align-start"
msgstr "Start"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.ruby-hidden"
msgstr "Hide furigana"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-overhang"
msgstr "Ruby overhang"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-overhang-auto"
msgstr "Auto"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-overhang-none"
msgstr "None"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-placeholder"
msgstr "Furigana"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-side"
msgstr "Ruby side"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-side-over"
msgstr "Over / right"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-side-under"
msgstr "Under / left"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size"
msgstr "Ruby size"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size-half"
msgstr "Half"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size-quarter"
msgstr "Quarter"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size-third"
msgstr "Third"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs:212
msgid "workspace.options.text-options.strikethrough"
msgstr "Strikethrough (%s)"
@ -8084,6 +8228,74 @@ msgstr "Align left"
msgid "workspace.options.text-options.text-align-right"
msgstr "Align right"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-all"
msgstr "Tate-chu-yoko"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits"
msgstr "Digits"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits-2"
msgstr "Up to 2 digits"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits-3"
msgstr "Up to 3 digits"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits-4"
msgstr "Up to 4 digits"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits-count"
msgstr "Digit count"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-none"
msgstr "Normal"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis"
msgstr "Emphasis marks"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-filled-circle"
msgstr "Filled circle"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-filled-dot"
msgstr "Filled dot"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-filled-sesame"
msgstr "Filled sesame"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-none"
msgstr "None"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-open-circle"
msgstr "Open circle"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-open-dot"
msgstr "Open dot"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-open-sesame"
msgstr "Open sesame"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-orientation-mixed"
msgstr "Mixed"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-orientation-upright"
msgstr "Upright"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs:509
msgid "workspace.options.text-options.text-section"
msgstr "Text section"
@ -8104,6 +8316,22 @@ msgstr "Selection text"
msgid "workspace.options.text-options.underline"
msgstr "Underline (%s)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.warichu"
msgstr "Warichu"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.warichu-none"
msgstr "Normal"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.writing-mode-horizontal"
msgstr "Horizontal"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.writing-mode-vertical"
msgstr "Vertical (right to left)"
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:788
msgid "workspace.options.use-play-button"
msgstr "Use the play button at the header to run the prototype view."

View File

@ -777,6 +777,18 @@ msgstr "メンバーを招待"
msgid "dashboard.invite-profile-disabled"
msgstr "このチームにユーザーを招待する権限がありません"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.description"
msgstr "すべてのファイルで日本語組版コントロールを利用できるようにします。"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.enable-all-files"
msgstr "すべてのファイルで日本語組版を有効にする"
#: src/app/main/ui/settings/options.cljs
msgid "dashboard.japanese-layout.title"
msgstr "日本語組版"
#: src/app/main/ui/dashboard/organization_team_switch_menus.cljs:235, src/app/main/ui/dashboard/organization_team_switch_menus.cljs:266
msgid "dashboard.leave-organization"
msgstr "組織から退出"
@ -6168,6 +6180,10 @@ msgstr "放射状グラデーション"
msgid "workspace.header.menu.disable-dynamic-alignment"
msgstr "動的整列を無効化"
#: src/app/main/ui/workspace/main_menu.cljs
msgid "workspace.header.menu.disable-japanese-layout"
msgstr "日本語組版を無効にする"
#: src/app/main/ui/workspace/main_menu.cljs:254
msgid "workspace.header.menu.disable-scale-content"
msgstr "比率を保った拡大縮小を無効化"
@ -6192,6 +6208,10 @@ msgstr "WebGL レンダリングを無効化(ベータ)"
msgid "workspace.header.menu.enable-dynamic-alignment"
msgstr "動的整列を有効化"
#: src/app/main/ui/workspace/main_menu.cljs
msgid "workspace.header.menu.enable-japanese-layout"
msgstr "日本語組版を有効にする"
#: src/app/main/ui/workspace/main_menu.cljs:255
msgid "workspace.header.menu.enable-scale-content"
msgstr "比率を保った拡大縮小を有効化"
@ -7688,6 +7708,34 @@ msgstr "上下中央揃え"
msgid "workspace.options.text-options.align-top"
msgstr "上揃え"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.annotation-clearance"
msgstr "注釈の間隔"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.annotation-clearance-auto"
msgstr "自動"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.line-adjustment"
msgstr "禁則調整方式"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.line-adjustment-push-in-first"
msgstr "追い込み優先"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.line-adjustment-push-out-first"
msgstr "追い出し優先"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.line-adjustment-push-out-only"
msgstr "追い出しのみ"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.annotation-clearance-none"
msgstr "行送りを使用"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs:100
msgid "workspace.options.text-options.direction-ltr"
msgstr "LTR"
@ -7696,6 +7744,22 @@ msgstr "LTR"
msgid "workspace.options.text-options.direction-rtl"
msgstr "RTL"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.font-features"
msgstr "プロポーショナルメトリクス"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.font-features-none"
msgstr "なし"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.font-features-palt"
msgstr "横組み(palt)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.font-features-vpal"
msgstr "縦組み(vpal)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs:167
msgid "workspace.options.text-options.grow-auto-height"
msgstr "高さの自動調整"
@ -7708,6 +7772,10 @@ msgstr "幅の自動調整"
msgid "workspace.options.text-options.grow-fixed"
msgstr "固定"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.japanese-layout"
msgstr "日本語組版"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:688
msgid "workspace.options.text-options.letter-spacing"
msgstr "文字間隔"
@ -7716,6 +7784,82 @@ msgstr "文字間隔"
msgid "workspace.options.text-options.line-height"
msgstr "行の高さ"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby"
msgstr "ルビ"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-advanced-options"
msgstr "ふりがなの詳細設定"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align"
msgstr "ルビの配置"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align-center"
msgstr "中央"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align-space-around"
msgstr "均等割り"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align-space-between"
msgstr "両端揃え"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-align-start"
msgstr "先頭"
#: src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
msgid "workspace.options.text-options.ruby-hidden"
msgstr "ふりがなを非表示"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-overhang"
msgstr "ルビのはみ出し"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-overhang-auto"
msgstr "自動"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-overhang-none"
msgstr "なし"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-placeholder"
msgstr "ふりがな"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-side"
msgstr "ルビの側"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-side-over"
msgstr "上/右"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-side-under"
msgstr "下/左"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size"
msgstr "ルビの大きさ"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size-half"
msgstr "二分"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size-quarter"
msgstr "四分"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.ruby-size-third"
msgstr "三分"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs:212
msgid "workspace.options.text-options.strikethrough"
msgstr "取り消し線(%s)"
@ -7736,6 +7880,74 @@ msgstr "左揃え"
msgid "workspace.options.text-options.text-align-right"
msgstr "右揃え"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-all"
msgstr "縦中横"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits"
msgstr "数字のみ"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits-2"
msgstr "2桁まで"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits-3"
msgstr "3桁まで"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits-4"
msgstr "4桁まで"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-digits-count"
msgstr "桁数"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-combine-upright-none"
msgstr "通常"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis"
msgstr "圏点"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-filled-circle"
msgstr "黒丸(大)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-filled-dot"
msgstr "黒丸"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-filled-sesame"
msgstr "ゴマ点(黒)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-none"
msgstr "なし"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-open-circle"
msgstr "白丸(大)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-open-dot"
msgstr "白丸"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-emphasis-open-sesame"
msgstr "ゴマ点(白)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-orientation-mixed"
msgstr "混在"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.text-orientation-upright"
msgstr "正立"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs:509
msgid "workspace.options.text-options.text-section"
msgstr "テキストセクション"
@ -7756,6 +7968,22 @@ msgstr "選択範囲のテキスト"
msgid "workspace.options.text-options.underline"
msgstr "下線(%s)"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.warichu"
msgstr "割注"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.warichu-none"
msgstr "通常"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.writing-mode-horizontal"
msgstr "横書き"
#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs
msgid "workspace.options.text-options.writing-mode-vertical"
msgstr "縦書き(右から左)"
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:788
msgid "workspace.options.use-play-button"
msgstr "ヘッダーの再生ボタンを使ってプロトタイプを表示してください。"