Proper RTL support (#1154)

* Add RTL styles. Most of the work is done by the browser 💖

* Refactor getLanguage

* Additional fixes

* Mirror the mouse pointer icon

* Move the vertical scrollbar to the left on RTL

* Revert "Mirror the mouse pointer icon"

This reverts commit f69b132538038d231b1b1acc0d6f4a28c91130bb.
This commit is contained in:
Jed Fox 2020-04-02 12:21:19 -04:00 committed by GitHub
parent 45e4949da0
commit 663526129a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 157 additions and 94 deletions

View File

@ -183,7 +183,7 @@
width="40"
height="40"
viewBox="0 0 250 250"
style="position: absolute; top: 0; right: 0;"
class="github-corner rtl-mirror"
>
<a
href="https://github.com/excalidraw/excalidraw"

View File

@ -139,7 +139,7 @@ export function ZoomActions({
{renderAction("zoomIn")}
{renderAction("zoomOut")}
{renderAction("resetZoom")}
<div style={{ marginLeft: 4 }}>{(zoom * 100).toFixed(0)}%</div>
<div style={{ marginInlineStart: 4 }}>{(zoom * 100).toFixed(0)}%</div>
</Stack.Row>
</Stack.Col>
);

View File

@ -78,7 +78,7 @@ import { actions } from "../actions/register";
import { ActionResult } from "../actions/types";
import { getDefaultAppState } from "../appState";
import { t, getLanguage } from "../i18n";
import { t } from "../i18n";
import {
copyToAppClipboard,
@ -197,7 +197,6 @@ export class App extends React.Component<any, AppState> {
return !element.isDeleted;
})}
setElements={this.setElements}
language={getLanguage()}
onRoomCreate={this.createRoom}
onRoomDestroy={this.destroyRoom}
onLockToggle={this.toggleLock}

View File

@ -4,7 +4,12 @@
box-shadow: rgba(0, 0, 0, 0.25) 0px 1px 4px;
border-radius: 4px;
position: absolute;
left: -5.5px;
:root[dir="ltr"] & {
left: -5.5px;
}
:root[dir="rtl"] & {
right: -5.5px;
}
}
.color-picker-control-container {
@ -21,7 +26,12 @@
border-color: transparent transparent rgb(255, 255, 255);
position: absolute;
top: -10px;
left: 12px;
:root[dir="ltr"] & {
left: 12px;
}
:root[dir="rtl"] & {
right: 12px;
}
}
.color-picker-triangle-shadow {
@ -30,7 +40,7 @@
}
.color-picker-content {
padding: 0.5rem 0.5rem;
padding: 0.5rem;
display: grid;
grid-template-columns: repeat(5, auto);
grid-gap: 0.5rem;
@ -76,7 +86,12 @@
background: #dee2e6;
height: 1.875rem;
width: 1.875rem;
border-radius: 4px 0px 0px 4px;
:root[dir="ltr"] & {
border-radius: 4px 0px 0px 4px;
}
:root[dir="rtl"] & {
border-radius: 0px 4px 4px 0px;
}
color: #495057;
display: flex;
align-items: center;
@ -97,11 +112,21 @@
}
.color-input-container:focus-within .color-picker-hash::before {
background: #dee2e6;
right: -1px;
:root[dir="ltr"] & {
right: -1px;
}
:root[dir="rtl"] & {
left: -1px;
}
}
.color-input-container:focus-within .color-picker-hash::after {
background: #fff;
right: -2px;
:root[dir="ltr"] & {
right: -2px;
}
:root[dir="rtl"] & {
left: -2px;
}
}
.color-input-container {
@ -117,17 +142,22 @@
outline: none;
height: 1.75em;
box-shadow: #dee2e6 0px 0px 0px 1px inset;
border-radius: 0px 4px 4px 0px;
:root[dir="ltr"] & {
border-radius: 0px 4px 4px 0px;
}
:root[dir="rtl"] & {
border-radius: 4px 0px 0px 4px;
}
float: left;
padding: 1px;
padding-left: 0.5em;
padding-inline-start: 0.5em;
appearance: none;
}
.color-picker-label-swatch {
height: 1.875rem;
width: 1.875rem;
margin-right: 0.25rem;
margin-inline-end: 0.25rem;
border: 1px solid #dee2e6;
position: relative;
overflow: hidden;
@ -147,7 +177,12 @@
.color-picker-keybinding {
position: absolute;
bottom: 2px;
right: 2px;
:root[dir="ltr"] & {
right: 2px;
}
:root[dir="rtl"] & {
left: 2px;
}
font-size: 0.7em;
color: #ccc;
}

View File

@ -1,9 +1,9 @@
import React from "react";
import { Popover } from "./Popover";
import "./ColorPicker.css";
import "./ColorPicker.scss";
import { KEYS } from "../keys";
import { t } from "../i18n";
import { t, getLanguage } from "../i18n";
import { isWritableElement } from "../utils";
import colors from "../colors";
@ -69,6 +69,7 @@ const Picker = function ({
event.key === KEYS.ARROW_DOWN
) {
const { activeElement } = document;
const isRTL = getLanguage().rtl;
const index = Array.prototype.indexOf.call(
gallery!.current!.children,
activeElement,
@ -76,9 +77,9 @@ const Picker = function ({
if (index !== -1) {
const length = gallery!.current!.children.length - (showInput ? 1 : 0);
const nextIndex =
event.key === KEYS.ARROW_RIGHT
event.key === (isRTL ? KEYS.ARROW_LEFT : KEYS.ARROW_RIGHT)
? (index + 1) % length
: event.key === KEYS.ARROW_LEFT
: event.key === (isRTL ? KEYS.ARROW_RIGHT : KEYS.ARROW_LEFT)
? (length + index - 1) % length
: event.key === KEYS.ARROW_DOWN
? (index + 5) % length

View File

@ -17,7 +17,7 @@
min-width: 9.5rem;
margin: 0;
padding: 0.25rem 1rem 0.25rem 1.25rem;
text-align: left;
text-align: start;
border-radius: 0;
background-color: #f2f2f2;
border: none;

View File

@ -20,7 +20,7 @@
.Dialog {
--metric: calc(var(--space-factor) * 4);
--inset-left: #{"max(var(--metric), var(--sal))"};
--inset-right: #{"max(var(--metric), env(safe-area-inset-right))"};
--inset-right: #{"max(var(--metric), var(--sar))"};
}
.Dialog__title {
grid-template-columns: calc(var(--space-factor) * 7) 1fr calc(

View File

@ -15,6 +15,7 @@
z-index: 2;
}
/* TODO: if these are used, make sure to implement RTL support
.FixedSideContainer_side_left {
left: var(--margin);
top: var(--margin);
@ -28,3 +29,4 @@
bottom: var(--margin);
z-index: 3;
}
*/

View File

@ -4,7 +4,7 @@ import * as i18n from "../i18n";
export function LanguageList({
onChange,
languages = i18n.languages,
currentLanguage = i18n.getLanguage(),
currentLanguage = i18n.getLanguage().lng,
floating,
}: {
languages?: { lng: string; label: string }[];

View File

@ -30,7 +30,6 @@ interface LayerUIProps {
canvas: HTMLCanvasElement | null;
setAppState: any;
elements: readonly ExcalidrawElement[];
language: string;
setElements: (elements: readonly ExcalidrawElement[]) => void;
onRoomCreate: () => void;
onRoomDestroy: () => void;
@ -44,7 +43,6 @@ export const LayerUI = React.memo(
setAppState,
canvas,
elements,
language,
setElements,
onRoomCreate,
onRoomDestroy,
@ -192,7 +190,6 @@ export const LayerUI = React.memo(
setAppState({});
}}
languages={languages}
currentLanguage={language}
floating
/>
{appState.scrolledOutside && (
@ -229,7 +226,6 @@ export const LayerUI = React.memo(
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
return (
prev.language === next.language &&
prev.elements === next.elements &&
keys.every((key) => prevAppState[key] === nextAppState[key])
);

View File

@ -33,7 +33,7 @@ const ICONS = {
height="1792"
viewBox="0 0 1792 1792"
xmlns="http://www.w3.org/2000/svg"
style={{ left: 2 }}
className="unlocked-icon rtl-mirror"
>
<path d="M1728 576v256q0 26-19 45t-45 19h-64q-26 0-45-19t-19-45v-256q0-106-75-181t-181-75-181 75-75 181v192h96q40 0 68 28t28 68v576q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-576q0-40 28-68t68-28h672v-192q0-185 131.5-316.5t316.5-131.5 316.5 131.5 131.5 316.5z" />
</svg>

View File

@ -149,3 +149,12 @@
}
}
}
.unlocked-icon {
:root[dir="ltr"] & {
left: 2px;
}
:root[dir="rtl"] & {
right: 2px;
}
}

View File

@ -6,62 +6,50 @@
import React from "react";
const ACTIVE_ELEMENT_COLOR = "#ffa94d"; // OC ORANGE 4
type Opts = { width?: number; height?: number; mirror?: true } & React.SVGProps<
SVGSVGElement
>;
const createIcon = (
d: string | React.ReactNode,
width = 512,
height = width,
style?: React.CSSProperties,
) => (
<svg
aria-hidden="true"
focusable="false"
role="img"
viewBox={`0 0 ${width} ${height}`}
style={style}
>
{typeof d === "string" ? <path fill="currentColor" d={d} /> : d}
</svg>
);
const createIcon = (d: string | React.ReactNode, opts: number | Opts = 512) => {
const { width = 512, height = width, mirror, style } =
typeof opts === "number" ? ({ width: opts } as Opts) : opts;
return (
<svg
aria-hidden="true"
focusable="false"
role="img"
viewBox={`0 0 ${width} ${height}`}
className={mirror && "rtl-mirror"}
style={style}
>
{typeof d === "string" ? <path fill="currentColor" d={d} /> : d}
</svg>
);
};
export const link = createIcon(
"M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z",
{ mirror: true },
);
export const save = createIcon(
"M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM224 416c-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64zm96-304.52V212c0 6.627-5.373 12-12 12H76c-6.627 0-12-5.373-12-12V108c0-6.627 5.373-12 12-12h228.52c3.183 0 6.235 1.264 8.485 3.515l3.48 3.48A11.996 11.996 0 0 1 320 111.48z",
448,
512,
{ width: 448, height: 512 },
);
export const load = createIcon(
"M572.694 292.093L500.27 416.248A63.997 63.997 0 0 1 444.989 448H45.025c-18.523 0-30.064-20.093-20.731-36.093l72.424-124.155A64 64 0 0 1 152 256h399.964c18.523 0 30.064 20.093 20.73 36.093zM152 224h328v-48c0-26.51-21.49-48-48-48H272l-64-64H48C21.49 64 0 85.49 0 112v278.046l69.077-118.418C86.214 242.25 117.989 224 152 224z",
576,
512,
);
export const image = createIcon(
"M384 121.941V128H256V0h6.059a24 24 0 0 1 16.97 7.029l97.941 97.941a24.002 24.002 0 0 1 7.03 16.971zM248 160c-13.2 0-24-10.8-24-24V0H24C10.745 0 0 10.745 0 24v464c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V160H248zm-135.455 16c26.51 0 48 21.49 48 48s-21.49 48-48 48-48-21.49-48-48 21.491-48 48-48zm208 240h-256l.485-48.485L104.545 328c4.686-4.686 11.799-4.201 16.485.485L160.545 368 264.06 264.485c4.686-4.686 12.284-4.686 16.971 0L320.545 304v112z",
384,
512,
{ width: 576, height: 512, mirror: true },
);
export const clipboard = createIcon(
"M384 112v352c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h80c0-35.29 28.71-64 64-64s64 28.71 64 64h80c26.51 0 48 21.49 48 48zM192 40c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24m96 114v-20a6 6 0 0 0-6-6H102a6 6 0 0 0-6 6v20a6 6 0 0 0 6 6h180a6 6 0 0 0 6-6z",
384,
512,
);
export const broadcast = createIcon(
"M150.94 192h33.73c11.01 0 18.61-10.83 14.86-21.18-4.93-13.58-7.55-27.98-7.55-42.82s2.62-29.24 7.55-42.82C203.29 74.83 195.68 64 184.67 64h-33.73c-7.01 0-13.46 4.49-15.41 11.23C130.64 92.21 128 109.88 128 128c0 18.12 2.64 35.79 7.54 52.76 1.94 6.74 8.39 11.24 15.4 11.24zM89.92 23.34C95.56 12.72 87.97 0 75.96 0H40.63c-6.27 0-12.14 3.59-14.74 9.31C9.4 45.54 0 85.65 0 128c0 24.75 3.12 68.33 26.69 118.86 2.62 5.63 8.42 9.14 14.61 9.14h34.84c12.02 0 19.61-12.74 13.95-23.37-49.78-93.32-16.71-178.15-.17-209.29zM614.06 9.29C611.46 3.58 605.6 0 599.33 0h-35.42c-11.98 0-19.66 12.66-14.02 23.25 18.27 34.29 48.42 119.42.28 209.23-5.72 10.68 1.8 23.52 13.91 23.52h35.23c6.27 0 12.13-3.58 14.73-9.29C630.57 210.48 640 170.36 640 128s-9.42-82.48-25.94-118.71zM489.06 64h-33.73c-11.01 0-18.61 10.83-14.86 21.18 4.93 13.58 7.55 27.98 7.55 42.82s-2.62 29.24-7.55 42.82c-3.76 10.35 3.85 21.18 14.86 21.18h33.73c7.02 0 13.46-4.49 15.41-11.24 4.9-16.97 7.53-34.64 7.53-52.76 0-18.12-2.64-35.79-7.54-52.76-1.94-6.75-8.39-11.24-15.4-11.24zm-116.3 100.12c7.05-10.29 11.2-22.71 11.2-36.12 0-35.35-28.63-64-63.96-64-35.32 0-63.96 28.65-63.96 64 0 13.41 4.15 25.83 11.2 36.12l-130.5 313.41c-3.4 8.15.46 17.52 8.61 20.92l29.51 12.31c8.15 3.4 17.52-.46 20.91-8.61L244.96 384h150.07l49.2 118.15c3.4 8.16 12.76 12.01 20.91 8.61l29.51-12.31c8.15-3.4 12-12.77 8.61-20.92l-130.5-313.41zM271.62 320L320 203.81 368.38 320h-96.76z",
640,
512,
{ width: 384, height: 512 },
);
export const trash = createIcon(
"M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z",
448,
512,
{ width: 448, height: 512 },
);
export const palette = createIcon(
@ -70,24 +58,22 @@ export const palette = createIcon(
export const exportFile = createIcon(
"M384 121.9c0-6.3-2.5-12.4-7-16.9L279.1 7c-4.5-4.5-10.6-7-17-7H256v128h128zM571 308l-95.7-96.4c-10.1-10.1-27.4-3-27.4 11.3V288h-64v64h64v65.2c0 14.3 17.3 21.4 27.4 11.3L571 332c6.6-6.6 6.6-17.4 0-24zm-379 28v-32c0-8.8 7.2-16 16-16h176V160H248c-13.2 0-24-10.8-24-24V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V352H208c-8.8 0-16-7.2-16-16z",
576,
512,
{ width: 576, height: 512, mirror: true },
);
export const zoomIn = createIcon(
"M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z",
448,
512,
{ width: 448, height: 512 },
);
export const zoomOut = createIcon(
"M416 208H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h384c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z",
448,
512,
{ width: 448, height: 512 },
);
export const done = createIcon(
"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z",
{ mirror: true },
);
export const menu = createIcon(
@ -96,10 +82,12 @@ export const menu = createIcon(
export const undo = createIcon(
"M255.545 8c-66.269.119-126.438 26.233-170.86 68.685L48.971 40.971C33.851 25.851 8 36.559 8 57.941V192c0 13.255 10.745 24 24 24h134.059c21.382 0 32.09-25.851 16.971-40.971l-41.75-41.75c30.864-28.899 70.801-44.907 113.23-45.273 92.398-.798 170.283 73.977 169.484 169.442C423.236 348.009 349.816 424 256 424c-41.127 0-79.997-14.678-110.63-41.556-4.743-4.161-11.906-3.908-16.368.553L89.34 422.659c-4.872 4.872-4.631 12.815.482 17.433C133.798 479.813 192.074 504 256 504c136.966 0 247.999-111.033 248-247.998C504.001 119.193 392.354 7.755 255.545 8z",
{ mirror: true },
);
export const redo = createIcon(
"M256.455 8c66.269.119 126.437 26.233 170.859 68.685l35.715-35.715C478.149 25.851 504 36.559 504 57.941V192c0 13.255-10.745 24-24 24H345.941c-21.382 0-32.09-25.851-16.971-40.971l41.75-41.75c-30.864-28.899-70.801-44.907-113.23-45.273-92.398-.798-170.283 73.977-169.484 169.442C88.764 348.009 162.184 424 256 424c41.127 0 79.997-14.678 110.629-41.556 4.743-4.161 11.906-3.908 16.368.553l39.662 39.662c4.872 4.872 4.631 12.815-.482 17.433C378.202 479.813 319.926 504 256 504 119.034 504 8.001 392.967 8 256.002 7.999 119.193 119.646 7.755 256.455 8z",
{ mirror: true },
);
// Icon imported form Storybook
@ -111,7 +99,7 @@ export const resetZoom = createIcon(
fill="currentColor"
d="M148 560a318 318 0 0 0 522 110 316 316 0 0 0 0-450 316 316 0 0 0-450 0c-11 11-21 22-30 34v4h47c25 0 46 21 46 46s-21 45-46 45H90c-13 0-25-6-33-14-9-9-14-20-14-33V156c0-25 20-45 45-45s45 20 45 45v32l1 1a401 401 0 0 1 623 509l212 212a42 42 0 0 1-59 59L698 757A401 401 0 0 1 65 570a42 42 0 0 1 83-10z"
/>,
1024,
{ width: 1024 },
);
export const bringForward = createIcon(
@ -128,7 +116,7 @@ export const bringForward = createIcon(
strokeWidth="2"
/>
</>,
24,
{ width: 24 },
);
export const sendBackward = createIcon(
@ -145,7 +133,7 @@ export const sendBackward = createIcon(
strokeWidth="2"
/>
</>,
24,
{ width: 24 },
);
export const bringToFront = createIcon(
@ -162,7 +150,7 @@ export const bringToFront = createIcon(
strokeWidth="2"
/>
</>,
24,
{ width: 24 },
);
export const sendToBack = createIcon(
@ -181,13 +169,12 @@ export const sendToBack = createIcon(
strokeWidth="2"
/>
</>,
24,
{ width: 24 },
);
export const users = createIcon(
"M192 256c61.9 0 112-50.1 112-112S253.9 32 192 32 80 82.1 80 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C51.6 288 0 339.6 0 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zM480 256c53 0 96-43 96-96s-43-96-96-96-96 43-96 96 43 96 96 96zm48 32h-3.8c-13.9 4.8-28.6 8-44.2 8s-30.3-3.2-44.2-8H432c-20.4 0-39.2 5.9-55.7 15.4 24.4 26.3 39.7 61.2 39.7 99.8v38.4c0 2.2-.5 4.3-.6 6.4H592c26.5 0 48-21.5 48-48 0-61.9-50.1-112-112-112z",
640,
512,
{ width: 640, height: 512, mirror: true },
);
export const start = createIcon(
@ -200,19 +187,15 @@ export const stop = createIcon(
export const close = createIcon(
"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z",
352,
512,
{ width: 352, height: 512 },
);
export const back = createIcon(
"M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z",
320,
512,
{ marginLeft: "-0.2rem" },
{ width: 320, height: 512, style: { marginLeft: "-0.2rem" }, mirror: true },
);
export const clone = createIcon(
"M464 0c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48H176c-26.51 0-48-21.49-48-48V48c0-26.51 21.49-48 48-48h288M176 416c-44.112 0-80-35.888-80-80V128H48c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h288c26.51 0 48-21.49 48-48v-48H176z",
512,
512,
{ mirror: true },
);

View File

@ -43,6 +43,7 @@ export function textWysiwyg({
} catch {
editable.contentEditable = "true";
}
editable.dir = "auto";
editable.tabIndex = 0;
editable.innerText = initText;
editable.dataset.type = "wysiwyg";

View File

@ -21,7 +21,12 @@ export const languages = [
{ lng: "ko-KR", label: "한국어", data: require("./locales/ko-KR.json") },
{ lng: "zh-TW", label: "繁體中文", data: require("./locales/zh-TW.json") },
{ lng: "zh-CN", label: "简体中文", data: require("./locales/zh-CN.json") },
{ lng: "ar-SA", label: "عربي", data: require("./locales/ar-SA.json") },
{
lng: "ar-SA",
label: "عربي",
data: require("./locales/ar-SA.json"),
rtl: true,
},
];
let currentLanguage = languages[0];
@ -31,11 +36,13 @@ export function setLanguage(newLng: string | undefined) {
currentLanguage =
languages.find((language) => language.lng === newLng) || fallbackLanguage;
document.documentElement.dir = currentLanguage.rtl ? "rtl" : "ltr";
languageDetector.cacheUserLanguage(currentLanguage.lng);
}
export function getLanguage() {
return currentLanguage.lng;
return currentLanguage;
}
function findPartsForData(data: any, parts: string[]) {

View File

@ -3,6 +3,7 @@ import { getCommonBounds } from "../element";
import { FlooredNumber } from "../types";
import { ScrollBars } from "./types";
import { getGlobalCSSVariable } from "../utils";
import { getLanguage } from "../i18n";
export const SCROLLBAR_MARGIN = 4;
export const SCROLLBAR_WIDTH = 6;
@ -44,6 +45,8 @@ export function getScrollBars(
right: parseInt(getGlobalCSSVariable("sar")),
};
const isRTL = getLanguage().rtl;
// The viewport is the rectangle currently visible for the user
const viewportMinX = -scrollX + viewportWidthDiff / 2 + safeArea.left;
const viewportMinY = -scrollY + viewportHeightDiff / 2 + safeArea.top;
@ -81,10 +84,11 @@ export function getScrollBars(
viewportMinY === sceneMinY && viewportMaxY === sceneMaxY
? null
: {
x:
viewportWidth -
SCROLLBAR_WIDTH -
Math.max(safeArea.right, SCROLLBAR_MARGIN),
x: isRTL
? Math.max(safeArea.left, SCROLLBAR_MARGIN)
: viewportWidth -
SCROLLBAR_WIDTH -
Math.max(safeArea.right, SCROLLBAR_MARGIN),
y:
((viewportMinY - sceneMinY) / (sceneMaxY - sceneMinY)) *
viewportHeight +

View File

@ -5,7 +5,7 @@ export const SHAPES = [
{
icon: (
// fa-mouse-pointer
<svg viewBox="0 0 320 512">
<svg viewBox="0 0 320 512" className="">
<path d="M302.189 329.126H196.105l55.831 135.993c3.889 9.428-.555 19.999-9.444 23.999l-49.165 21.427c-9.165 4-19.443-.571-23.332-9.714l-53.053-129.136-86.664 89.138C18.729 472.71 0 463.554 0 447.977V18.299C0 1.899 19.921-6.096 30.277 5.443l284.412 292.542c11.472 11.179 3.007 31.141-12.5 31.141z" />
</svg>
),
@ -41,7 +41,7 @@ export const SHAPES = [
{
icon: (
// fa-long-arrow-alt-right
<svg viewBox="0 0 448 512">
<svg viewBox="0 0 448 512" className="rtl-mirror">
<path d="M313.941 216H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12h301.941v46.059c0 21.382 25.851 32.09 40.971 16.971l86.059-86.059c9.373-9.373 9.373-24.569 0-33.941l-86.059-86.059c-15.119-15.119-40.971-4.411-40.971 16.971V216z" />
</svg>
),

View File

@ -319,7 +319,9 @@ button,
.dropdown-select {
height: 1.5rem;
padding: 0 1.5rem 0 0.5rem;
padding: 0;
padding-inline-start: 0.5rem;
padding-inline-end: 1.5rem;
background-color: #e9ecef;
border-radius: var(--space-factor);
border: 1px solid #ced4da;
@ -329,6 +331,9 @@ button,
background-image: url("https://free-use.s3-us-west-2.amazonaws.com/up-sort.svg");
background-repeat: no-repeat;
background-position: right 0.7rem top 50%, 0 0;
:root[dir="rtl"] & {
background-position: left 0.7rem top 50%, 0 0;
}
background-size: 0.65em auto, 100%;
&:focus {
@ -344,14 +349,18 @@ button,
}
&.dropdown-select--floating {
position: absolute;
margin-bottom: 0.5em;
margin-right: 0.5em;
margin: 0.5em;
}
}
.dropdown-select__language.dropdown-select--floating {
right: 0;
bottom: 0;
:root[dir="ltr"] & {
right: 0;
}
:root[dir="rtl"] & {
left: 0;
}
}
.visually-hidden {
@ -395,3 +404,20 @@ button,
z-index: -1;
}
}
.rtl-mirror {
:root[dir="rtl"] & {
transform: scaleX(-1);
}
}
.github-corner {
position: absolute;
top: 0;
:root[dir="ltr"] & {
right: 0;
}
:root[dir="rtl"] & {
left: 0;
}
}