Merge remote-tracking branch 'origin/master' into aakansha-create-text-containers-programmatically

This commit is contained in:
Aakansha Doshi 2023-05-22 13:51:36 +05:30
commit dcf9e27e51
135 changed files with 5873 additions and 57355 deletions

1
.npmrc
View file

@ -1 +1,2 @@
save-exact=true save-exact=true
legacy-peer-deps=true

View file

@ -16,7 +16,6 @@ function App() {
className="custom-footer" className="custom-footer"
onClick={() => alert("This is dummy footer")} onClick={() => alert("This is dummy footer")}
> >
{" "}
custom footer custom footer
</button> </button>
</Footer> </Footer>

View file

@ -14,8 +14,7 @@ function App() {
Item1 Item1
</MainMenu.Item> </MainMenu.Item>
<MainMenu.Item onSelect={() => window.alert("Item2")}> <MainMenu.Item onSelect={() => window.alert("Item2")}>
{" "} Item 2
Item 2{" "}
</MainMenu.Item> </MainMenu.Item>
</MainMenu> </MainMenu>
</Excalidraw> </Excalidraw>
@ -93,7 +92,6 @@ function App() {
style={{ height: "2rem" }} style={{ height: "2rem" }}
onClick={() => window.alert("custom menu item")} onClick={() => window.alert("custom menu item")}
> >
{" "}
custom item custom item
</button> </button>
</MainMenu.ItemCustom> </MainMenu.ItemCustom>

View file

@ -3,7 +3,7 @@
## renderTopRightUI ## renderTopRightUI
<pre> <pre>
(isMobile: boolean, appState:{" "} (isMobile: boolean, appState:
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95"> <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
AppState AppState
</a> </a>
@ -29,8 +29,7 @@ function App() {
}} }}
onClick={() => window.alert("This is dummy top right UI")} onClick={() => window.alert("This is dummy top right UI")}
> >
{" "} Click me
Click me{" "}
</button> </button>
); );
}} }}
@ -55,8 +54,7 @@ function App() {
<Excalidraw <Excalidraw
renderCustomStats={() => ( renderCustomStats={() => (
<p style={{ color: "#70b1ec", fontWeight: "bold" }}> <p style={{ color: "#70b1ec", fontWeight: "bold" }}>
{" "} Dummy stats will be shown here
Dummy stats will be shown here{" "}
</p> </p>
)} )}
/> />
@ -105,8 +103,7 @@ function App() {
return ( return (
<div style={{ height: "500px" }}> <div style={{ height: "500px" }}>
<button className="custom-button" onClick={() => excalidrawAPI.toggleMenu("customSidebar")}> <button className="custom-button" onClick={() => excalidrawAPI.toggleMenu("customSidebar")}>
{" "} Toggle Custom Sidebar
Toggle Custom Sidebar{" "}
</button> </button>
<Excalidraw <Excalidraw
UIOptions={{ dockedSidebarBreakpoint: 100 }} UIOptions={{ dockedSidebarBreakpoint: 100 }}

View file

@ -19,6 +19,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-popover": "1.0.3",
"@radix-ui/react-tabs": "1.0.2", "@radix-ui/react-tabs": "1.0.2",
"@sentry/browser": "6.2.5", "@sentry/browser": "6.2.5",
"@sentry/integrations": "6.2.5", "@sentry/integrations": "6.2.5",

View file

@ -1,4 +1,4 @@
import { ColorPicker } from "../components/ColorPicker"; import { ColorPicker } from "../components/ColorPicker/ColorPicker";
import { ZoomInIcon, ZoomOutIcon } from "../components/icons"; import { ZoomInIcon, ZoomOutIcon } from "../components/icons";
import { ToolButton } from "../components/ToolButton"; import { ToolButton } from "../components/ToolButton";
import { CURSOR_TYPE, MIN_ZOOM, THEME, ZOOM_STEP } from "../constants"; import { CURSOR_TYPE, MIN_ZOOM, THEME, ZOOM_STEP } from "../constants";
@ -19,6 +19,7 @@ import {
isEraserActive, isEraserActive,
isHandToolActive, isHandToolActive,
} from "../appState"; } from "../appState";
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
export const actionChangeViewBackgroundColor = register({ export const actionChangeViewBackgroundColor = register({
name: "changeViewBackgroundColor", name: "changeViewBackgroundColor",
@ -35,24 +36,21 @@ export const actionChangeViewBackgroundColor = register({
commitToHistory: !!value.viewBackgroundColor, commitToHistory: !!value.viewBackgroundColor,
}; };
}, },
PanelComponent: ({ elements, appState, updateData }) => { PanelComponent: ({ elements, appState, updateData, appProps }) => {
// FIXME move me to src/components/mainMenu/DefaultItems.tsx // FIXME move me to src/components/mainMenu/DefaultItems.tsx
return ( return (
<div style={{ position: "relative" }}>
<ColorPicker <ColorPicker
palette={null}
topPicks={DEFAULT_CANVAS_BACKGROUND_PICKS}
label={t("labels.canvasBackground")} label={t("labels.canvasBackground")}
type="canvasBackground" type="canvasBackground"
color={appState.viewBackgroundColor} color={appState.viewBackgroundColor}
onChange={(color) => updateData({ viewBackgroundColor: color })} onChange={(color) => updateData({ viewBackgroundColor: color })}
isActive={appState.openPopup === "canvasColorPicker"}
setActive={(active) =>
updateData({ openPopup: active ? "canvasColorPicker" : null })
}
data-testid="canvas-background-picker" data-testid="canvas-background-picker"
elements={elements} elements={elements}
appState={appState} appState={appState}
updateData={updateData}
/> />
</div>
); );
}, },
}); });

View file

@ -0,0 +1,68 @@
import { Excalidraw } from "../packages/excalidraw/index";
import { queryByTestId, fireEvent } from "@testing-library/react";
import { render } from "../tests/test-utils";
import { Pointer, UI } from "../tests/helpers/ui";
import { API } from "../tests/helpers/api";
const { h } = window;
const mouse = new Pointer("mouse");
describe("element locking", () => {
it("should not show unlockAllElements action in contextMenu if no elements locked", async () => {
await render(<Excalidraw />);
mouse.rightClickAt(0, 0);
const item = queryByTestId(UI.queryContextMenu()!, "unlockAllElements");
expect(item).toBe(null);
});
it("should unlock all elements and select them when using unlockAllElements action in contextMenu", async () => {
await render(
<Excalidraw
initialData={{
elements: [
API.createElement({
x: 100,
y: 100,
width: 100,
height: 100,
locked: true,
}),
API.createElement({
x: 100,
y: 100,
width: 100,
height: 100,
locked: true,
}),
API.createElement({
x: 100,
y: 100,
width: 100,
height: 100,
locked: false,
}),
],
}}
/>,
);
mouse.rightClickAt(0, 0);
expect(Object.keys(h.state.selectedElementIds).length).toBe(0);
expect(h.elements.map((el) => el.locked)).toEqual([true, true, false]);
const item = queryByTestId(UI.queryContextMenu()!, "unlockAllElements");
expect(item).not.toBe(null);
fireEvent.click(item!.querySelector("button")!);
expect(h.elements.map((el) => el.locked)).toEqual([false, false, false]);
// should select the unlocked elements
expect(h.state.selectedElementIds).toEqual({
[h.elements[0].id]: true,
[h.elements[1].id]: true,
});
});
});

View file

@ -5,8 +5,11 @@ import { getSelectedElements } from "../scene";
import { arrayToMap } from "../utils"; import { arrayToMap } from "../utils";
import { register } from "./register"; import { register } from "./register";
export const actionToggleLock = register({ const shouldLock = (elements: readonly ExcalidrawElement[]) =>
name: "toggleLock", elements.every((el) => !el.locked);
export const actionToggleElementLock = register({
name: "toggleElementLock",
trackEvent: { category: "element" }, trackEvent: { category: "element" },
perform: (elements, appState) => { perform: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState, true); const selectedElements = getSelectedElements(elements, appState, true);
@ -15,20 +18,21 @@ export const actionToggleLock = register({
return false; return false;
} }
const operation = getOperation(selectedElements); const nextLockState = shouldLock(selectedElements);
const selectedElementsMap = arrayToMap(selectedElements); const selectedElementsMap = arrayToMap(selectedElements);
const lock = operation === "lock";
return { return {
elements: elements.map((element) => { elements: elements.map((element) => {
if (!selectedElementsMap.has(element.id)) { if (!selectedElementsMap.has(element.id)) {
return element; return element;
} }
return newElementWith(element, { locked: lock }); return newElementWith(element, { locked: nextLockState });
}), }),
appState: { appState: {
...appState, ...appState,
selectedLinearElement: lock ? null : appState.selectedLinearElement, selectedLinearElement: nextLockState
? null
: appState.selectedLinearElement,
}, },
commitToHistory: true, commitToHistory: true,
}; };
@ -41,7 +45,7 @@ export const actionToggleLock = register({
: "labels.elementLock.lock"; : "labels.elementLock.lock";
} }
return getOperation(selected) === "lock" return shouldLock(selected)
? "labels.elementLock.lockAll" ? "labels.elementLock.lockAll"
: "labels.elementLock.unlockAll"; : "labels.elementLock.unlockAll";
}, },
@ -55,6 +59,31 @@ export const actionToggleLock = register({
}, },
}); });
const getOperation = ( export const actionUnlockAllElements = register({
elements: readonly ExcalidrawElement[], name: "unlockAllElements",
): "lock" | "unlock" => (elements.some((el) => !el.locked) ? "lock" : "unlock"); trackEvent: { category: "canvas" },
viewMode: false,
predicate: (elements) => {
return elements.some((element) => element.locked);
},
perform: (elements, appState) => {
const lockedElements = elements.filter((el) => el.locked);
return {
elements: elements.map((element) => {
if (element.locked) {
return newElementWith(element, { locked: false });
}
return element;
}),
appState: {
...appState,
selectedElementIds: Object.fromEntries(
lockedElements.map((el) => [el.id, true]),
),
},
commitToHistory: true,
};
},
contextItemLabel: "labels.elementLock.unlockAll",
});

View file

@ -14,7 +14,7 @@ import {
} from "../element/bounds"; } from "../element/bounds";
import { isLinearElement } from "../element/typeChecks"; import { isLinearElement } from "../element/typeChecks";
import { LinearElementEditor } from "../element/linearElementEditor"; import { LinearElementEditor } from "../element/linearElementEditor";
import { KEYS } from "../keys"; import { CODES, KEYS } from "../keys";
const enableActionFlipHorizontal = ( const enableActionFlipHorizontal = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
@ -48,7 +48,7 @@ export const actionFlipHorizontal = register({
commitToHistory: true, commitToHistory: true,
}; };
}, },
keyTest: (event) => event.shiftKey && event.code === "KeyH", keyTest: (event) => event.shiftKey && event.code === CODES.H,
contextItemLabel: "labels.flipHorizontal", contextItemLabel: "labels.flipHorizontal",
predicate: (elements, appState) => predicate: (elements, appState) =>
enableActionFlipHorizontal(elements, appState), enableActionFlipHorizontal(elements, appState),
@ -65,7 +65,7 @@ export const actionFlipVertical = register({
}; };
}, },
keyTest: (event) => keyTest: (event) =>
event.shiftKey && event.code === "KeyV" && !event[KEYS.CTRL_OR_CMD], event.shiftKey && event.code === CODES.V && !event[KEYS.CTRL_OR_CMD],
contextItemLabel: "labels.flipVertical", contextItemLabel: "labels.flipVertical",
predicate: (elements, appState) => predicate: (elements, appState) =>
enableActionFlipVertical(elements, appState), enableActionFlipVertical(elements, appState),

View file

@ -1,7 +1,13 @@
import { AppState } from "../../src/types"; import { AppState } from "../../src/types";
import {
DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
DEFAULT_ELEMENT_BACKGROUND_PICKS,
DEFAULT_ELEMENT_STROKE_COLOR_PALETTE,
DEFAULT_ELEMENT_STROKE_PICKS,
} from "../colors";
import { trackEvent } from "../analytics"; import { trackEvent } from "../analytics";
import { ButtonIconSelect } from "../components/ButtonIconSelect"; import { ButtonIconSelect } from "../components/ButtonIconSelect";
import { ColorPicker } from "../components/ColorPicker"; import { ColorPicker } from "../components/ColorPicker/ColorPicker";
import { IconPicker } from "../components/IconPicker"; import { IconPicker } from "../components/IconPicker";
// TODO barnabasmolnar/editor-redesign // TODO barnabasmolnar/editor-redesign
// TextAlignTopIcon, TextAlignBottomIcon,TextAlignMiddleIcon, // TextAlignTopIcon, TextAlignBottomIcon,TextAlignMiddleIcon,
@ -226,10 +232,12 @@ export const actionChangeStrokeColor = register({
commitToHistory: !!value.currentItemStrokeColor, commitToHistory: !!value.currentItemStrokeColor,
}; };
}, },
PanelComponent: ({ elements, appState, updateData }) => ( PanelComponent: ({ elements, appState, updateData, appProps }) => (
<> <>
<h3 aria-hidden="true">{t("labels.stroke")}</h3> <h3 aria-hidden="true">{t("labels.stroke")}</h3>
<ColorPicker <ColorPicker
topPicks={DEFAULT_ELEMENT_STROKE_PICKS}
palette={DEFAULT_ELEMENT_STROKE_COLOR_PALETTE}
type="elementStroke" type="elementStroke"
label={t("labels.stroke")} label={t("labels.stroke")}
color={getFormValue( color={getFormValue(
@ -239,12 +247,9 @@ export const actionChangeStrokeColor = register({
appState.currentItemStrokeColor, appState.currentItemStrokeColor,
)} )}
onChange={(color) => updateData({ currentItemStrokeColor: color })} onChange={(color) => updateData({ currentItemStrokeColor: color })}
isActive={appState.openPopup === "strokeColorPicker"}
setActive={(active) =>
updateData({ openPopup: active ? "strokeColorPicker" : null })
}
elements={elements} elements={elements}
appState={appState} appState={appState}
updateData={updateData}
/> />
</> </>
), ),
@ -269,10 +274,12 @@ export const actionChangeBackgroundColor = register({
commitToHistory: !!value.currentItemBackgroundColor, commitToHistory: !!value.currentItemBackgroundColor,
}; };
}, },
PanelComponent: ({ elements, appState, updateData }) => ( PanelComponent: ({ elements, appState, updateData, appProps }) => (
<> <>
<h3 aria-hidden="true">{t("labels.background")}</h3> <h3 aria-hidden="true">{t("labels.background")}</h3>
<ColorPicker <ColorPicker
topPicks={DEFAULT_ELEMENT_BACKGROUND_PICKS}
palette={DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE}
type="elementBackground" type="elementBackground"
label={t("labels.background")} label={t("labels.background")}
color={getFormValue( color={getFormValue(
@ -282,12 +289,9 @@ export const actionChangeBackgroundColor = register({
appState.currentItemBackgroundColor, appState.currentItemBackgroundColor,
)} )}
onChange={(color) => updateData({ currentItemBackgroundColor: color })} onChange={(color) => updateData({ currentItemBackgroundColor: color })}
isActive={appState.openPopup === "backgroundColorPicker"}
setActive={(active) =>
updateData({ openPopup: active ? "backgroundColorPicker" : null })
}
elements={elements} elements={elements}
appState={appState} appState={appState}
updateData={updateData}
/> />
</> </>
), ),

View file

@ -1,9 +1,14 @@
import ExcalidrawApp from "../excalidraw-app"; import ExcalidrawApp from "../excalidraw-app";
import { t } from "../i18n";
import { CODES } from "../keys"; import { CODES } from "../keys";
import { API } from "../tests/helpers/api"; import { API } from "../tests/helpers/api";
import { Keyboard, Pointer, UI } from "../tests/helpers/ui"; import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
import { fireEvent, render, screen } from "../tests/test-utils"; import {
act,
fireEvent,
render,
screen,
togglePopover,
} from "../tests/test-utils";
import { copiedStyles } from "./actionStyles"; import { copiedStyles } from "./actionStyles";
const { h } = window; const { h } = window;
@ -14,7 +19,14 @@ describe("actionStyles", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<ExcalidrawApp />);
}); });
it("should copy & paste styles via keyboard", () => {
afterEach(async () => {
// https://github.com/floating-ui/floating-ui/issues/1908#issuecomment-1301553793
// affects node v16+
await act(async () => {});
});
it("should copy & paste styles via keyboard", async () => {
UI.clickTool("rectangle"); UI.clickTool("rectangle");
mouse.down(10, 10); mouse.down(10, 10);
mouse.up(20, 20); mouse.up(20, 20);
@ -24,10 +36,10 @@ describe("actionStyles", () => {
mouse.up(20, 20); mouse.up(20, 20);
// Change some styles of second rectangle // Change some styles of second rectangle
UI.clickLabeledElement("Stroke"); togglePopover("Stroke");
UI.clickLabeledElement(t("colors.c92a2a")); UI.clickOnTestId("color-red");
UI.clickLabeledElement("Background"); togglePopover("Background");
UI.clickLabeledElement(t("colors.e64980")); UI.clickOnTestId("color-blue");
// Fill style // Fill style
fireEvent.click(screen.getByTitle("Cross-hatch")); fireEvent.click(screen.getByTitle("Cross-hatch"));
// Stroke width // Stroke width
@ -60,8 +72,8 @@ describe("actionStyles", () => {
const firstRect = API.getSelectedElement(); const firstRect = API.getSelectedElement();
expect(firstRect.id).toBe(h.elements[0].id); expect(firstRect.id).toBe(h.elements[0].id);
expect(firstRect.strokeColor).toBe("#c92a2a"); expect(firstRect.strokeColor).toBe("#e03131");
expect(firstRect.backgroundColor).toBe("#e64980"); expect(firstRect.backgroundColor).toBe("#a5d8ff");
expect(firstRect.fillStyle).toBe("cross-hatch"); expect(firstRect.fillStyle).toBe("cross-hatch");
expect(firstRect.strokeWidth).toBe(2); // Bold: 2 expect(firstRect.strokeWidth).toBe(2); // Bold: 2
expect(firstRect.strokeStyle).toBe("dotted"); expect(firstRect.strokeStyle).toBe("dotted");

View file

@ -84,5 +84,5 @@ export { actionToggleZenMode } from "./actionToggleZenMode";
export { actionToggleStats } from "./actionToggleStats"; export { actionToggleStats } from "./actionToggleStats";
export { actionUnbindText, actionBindText } from "./actionBoundText"; export { actionUnbindText, actionBindText } from "./actionBoundText";
export { actionLink } from "../element/Hyperlink"; export { actionLink } from "../element/Hyperlink";
export { actionToggleLock } from "./actionToggleLock"; export { actionToggleElementLock } from "./actionElementLock";
export { actionToggleLinearEditor } from "./actionLinearEditor"; export { actionToggleLinearEditor } from "./actionLinearEditor";

View file

@ -34,7 +34,7 @@ export type ShortcutName =
| "flipHorizontal" | "flipHorizontal"
| "flipVertical" | "flipVertical"
| "hyperlink" | "hyperlink"
| "toggleLock" | "toggleElementLock"
> >
| "saveScene" | "saveScene"
| "imageExport"; | "imageExport";
@ -80,7 +80,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
flipVertical: [getShortcutKey("Shift+V")], flipVertical: [getShortcutKey("Shift+V")],
viewMode: [getShortcutKey("Alt+R")], viewMode: [getShortcutKey("Alt+R")],
hyperlink: [getShortcutKey("CtrlOrCmd+K")], hyperlink: [getShortcutKey("CtrlOrCmd+K")],
toggleLock: [getShortcutKey("CtrlOrCmd+Shift+L")], toggleElementLock: [getShortcutKey("CtrlOrCmd+Shift+L")],
}; };
export const getShortcutFromShortcutName = (name: ShortcutName) => { export const getShortcutFromShortcutName = (name: ShortcutName) => {

View file

@ -111,7 +111,8 @@ export type ActionName =
| "unbindText" | "unbindText"
| "hyperlink" | "hyperlink"
| "bindText" | "bindText"
| "toggleLock" | "unlockAllElements"
| "toggleElementLock"
| "toggleLinearEditor" | "toggleLinearEditor"
| "toggleEraserTool" | "toggleEraserTool"
| "toggleHandTool" | "toggleHandTool"

View file

@ -1,4 +1,4 @@
import oc from "open-color"; import { COLOR_PALETTE } from "./colors";
import { import {
DEFAULT_ELEMENT_PROPS, DEFAULT_ELEMENT_PROPS,
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
@ -84,7 +84,7 @@ export const getDefaultAppState = (): Omit<
startBoundElement: null, startBoundElement: null,
suggestedBindings: [], suggestedBindings: [],
toast: null, toast: null,
viewBackgroundColor: oc.white, viewBackgroundColor: COLOR_PALETTE.white,
zenModeEnabled: false, zenModeEnabled: false,
zoom: { zoom: {
value: 1 as NormalizedZoomValue, value: 1 as NormalizedZoomValue,

View file

@ -1,5 +1,14 @@
import colors from "./colors"; import {
import { DEFAULT_FONT_SIZE, ENV } from "./constants"; COLOR_PALETTE,
DEFAULT_CHART_COLOR_INDEX,
getAllColorsSpecificShade,
} from "./colors";
import {
DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE,
ENV,
VERTICAL_ALIGN,
} from "./constants";
import { newElement, newLinearElement, newTextElement } from "./element"; import { newElement, newLinearElement, newTextElement } from "./element";
import { NonDeletedExcalidrawElement } from "./element/types"; import { NonDeletedExcalidrawElement } from "./element/types";
import { randomId } from "./random"; import { randomId } from "./random";
@ -153,15 +162,22 @@ export const tryParseSpreadsheet = (text: string): ParseSpreadsheetResult => {
return result; return result;
}; };
const bgColors = colors.elementBackground.slice( const bgColors = getAllColorsSpecificShade(DEFAULT_CHART_COLOR_INDEX);
2,
colors.elementBackground.length,
);
// Put all the common properties here so when the whole chart is selected // Put all the common properties here so when the whole chart is selected
// the properties dialog shows the correct selected values // the properties dialog shows the correct selected values
const commonProps = { const commonProps = {
strokeColor: colors.elementStroke[0], fillStyle: "hachure",
fontFamily: DEFAULT_FONT_FAMILY,
fontSize: DEFAULT_FONT_SIZE,
opacity: 100,
roughness: 1,
strokeColor: COLOR_PALETTE.black,
roundness: null,
strokeStyle: "solid",
strokeWidth: 1,
verticalAlign: VERTICAL_ALIGN.MIDDLE,
locked: false,
} as const; } as const;
const getChartDimentions = (spreadsheet: Spreadsheet) => { const getChartDimentions = (spreadsheet: Spreadsheet) => {
@ -322,7 +338,7 @@ const chartBaseElements = (
y: y - chartHeight, y: y - chartHeight,
width: chartWidth, width: chartWidth,
height: chartHeight, height: chartHeight,
strokeColor: colors.elementStroke[0], strokeColor: COLOR_PALETTE.black,
fillStyle: "solid", fillStyle: "solid",
opacity: 6, opacity: 6,
}) })

View file

@ -1,6 +1,17 @@
import colors from "./colors"; import {
DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX,
DEFAULT_ELEMENT_STROKE_COLOR_INDEX,
getAllColorsSpecificShade,
} from "./colors";
import { AppState } from "./types"; import { AppState } from "./types";
const BG_COLORS = getAllColorsSpecificShade(
DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX,
);
const STROKE_COLORS = getAllColorsSpecificShade(
DEFAULT_ELEMENT_STROKE_COLOR_INDEX,
);
export const getClientColors = (clientId: string, appState: AppState) => { export const getClientColors = (clientId: string, appState: AppState) => {
if (appState?.collaborators) { if (appState?.collaborators) {
const currentUser = appState.collaborators.get(clientId); const currentUser = appState.collaborators.get(clientId);
@ -11,18 +22,19 @@ export const getClientColors = (clientId: string, appState: AppState) => {
// Naive way of getting an integer out of the clientId // Naive way of getting an integer out of the clientId
const sum = clientId.split("").reduce((a, str) => a + str.charCodeAt(0), 0); const sum = clientId.split("").reduce((a, str) => a + str.charCodeAt(0), 0);
// Skip transparent & gray colors
const backgrounds = colors.elementBackground.slice(3);
const strokes = colors.elementStroke.slice(3);
return { return {
background: backgrounds[sum % backgrounds.length], background: BG_COLORS[sum % BG_COLORS.length],
stroke: strokes[sum % strokes.length], stroke: STROKE_COLORS[sum % STROKE_COLORS.length],
}; };
}; };
export const getClientInitials = (userName?: string | null) => { /**
if (!userName?.trim()) { * returns first char, capitalized
return "?"; */
} export const getNameInitial = (name?: string | null) => {
return userName.trim()[0].toUpperCase(); // first char can be a surrogate pair, hence using codePointAt
const firstCodePoint = name?.trim()?.codePointAt(0);
return (
firstCodePoint ? String.fromCodePoint(firstCodePoint) : "?"
).toUpperCase();
}; };

View file

@ -1,22 +1,167 @@
import oc from "open-color"; import oc from "open-color";
import { Merge } from "./utility-types";
const shades = (index: number) => [ // FIXME can't put to utils.ts rn because of circular dependency
oc.red[index], const pick = <R extends Record<string, any>, K extends readonly (keyof R)[]>(
oc.pink[index], source: R,
oc.grape[index], keys: K,
oc.violet[index], ) => {
oc.indigo[index], return keys.reduce((acc, key: K[number]) => {
oc.blue[index], if (key in source) {
oc.cyan[index], acc[key] = source[key];
oc.teal[index], }
oc.green[index], return acc;
oc.lime[index], }, {} as Pick<R, K[number]>) as Pick<R, K[number]>;
oc.yellow[index],
oc.orange[index],
];
export default {
canvasBackground: [oc.white, oc.gray[0], oc.gray[1], ...shades(0)],
elementBackground: ["transparent", oc.gray[4], oc.gray[6], ...shades(6)],
elementStroke: [oc.black, oc.gray[8], oc.gray[7], ...shades(9)],
}; };
export type ColorPickerColor =
| Exclude<keyof oc, "indigo" | "lime">
| "transparent"
| "bronze";
export type ColorTuple = readonly [string, string, string, string, string];
export type ColorPalette = Merge<
Record<ColorPickerColor, ColorTuple>,
{ black: string; white: string; transparent: string }
>;
// used general type instead of specific type (ColorPalette) to support custom colors
export type ColorPaletteCustom = { [key: string]: ColorTuple | string };
export type ColorShadesIndexes = [number, number, number, number, number];
export const MAX_CUSTOM_COLORS_USED_IN_CANVAS = 5;
export const COLORS_PER_ROW = 5;
export const DEFAULT_CHART_COLOR_INDEX = 4;
export const DEFAULT_ELEMENT_STROKE_COLOR_INDEX = 4;
export const DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX = 1;
export const ELEMENTS_PALETTE_SHADE_INDEXES = [0, 2, 4, 6, 8] as const;
export const CANVAS_PALETTE_SHADE_INDEXES = [0, 1, 2, 3, 4] as const;
export const getSpecificColorShades = (
color: Exclude<
ColorPickerColor,
"transparent" | "white" | "black" | "bronze"
>,
indexArr: Readonly<ColorShadesIndexes>,
) => {
return indexArr.map((index) => oc[color][index]) as any as ColorTuple;
};
export const COLOR_PALETTE = {
transparent: "transparent",
black: "#1e1e1e",
white: "#ffffff",
// open-colors
gray: getSpecificColorShades("gray", ELEMENTS_PALETTE_SHADE_INDEXES),
red: getSpecificColorShades("red", ELEMENTS_PALETTE_SHADE_INDEXES),
pink: getSpecificColorShades("pink", ELEMENTS_PALETTE_SHADE_INDEXES),
grape: getSpecificColorShades("grape", ELEMENTS_PALETTE_SHADE_INDEXES),
violet: getSpecificColorShades("violet", ELEMENTS_PALETTE_SHADE_INDEXES),
blue: getSpecificColorShades("blue", ELEMENTS_PALETTE_SHADE_INDEXES),
cyan: getSpecificColorShades("cyan", ELEMENTS_PALETTE_SHADE_INDEXES),
teal: getSpecificColorShades("teal", ELEMENTS_PALETTE_SHADE_INDEXES),
green: getSpecificColorShades("green", ELEMENTS_PALETTE_SHADE_INDEXES),
yellow: getSpecificColorShades("yellow", ELEMENTS_PALETTE_SHADE_INDEXES),
orange: getSpecificColorShades("orange", ELEMENTS_PALETTE_SHADE_INDEXES),
// radix bronze shades 3,5,7,9,11
bronze: ["#f8f1ee", "#eaddd7", "#d2bab0", "#a18072", "#846358"],
} as ColorPalette;
const COMMON_ELEMENT_SHADES = pick(COLOR_PALETTE, [
"cyan",
"blue",
"violet",
"grape",
"pink",
"green",
"teal",
"yellow",
"orange",
"red",
]);
// -----------------------------------------------------------------------------
// quick picks defaults
// -----------------------------------------------------------------------------
// ORDER matters for positioning in quick picker
export const DEFAULT_ELEMENT_STROKE_PICKS = [
COLOR_PALETTE.black,
COLOR_PALETTE.red[DEFAULT_ELEMENT_STROKE_COLOR_INDEX],
COLOR_PALETTE.green[DEFAULT_ELEMENT_STROKE_COLOR_INDEX],
COLOR_PALETTE.blue[DEFAULT_ELEMENT_STROKE_COLOR_INDEX],
COLOR_PALETTE.yellow[DEFAULT_ELEMENT_STROKE_COLOR_INDEX],
] as ColorTuple;
// ORDER matters for positioning in quick picker
export const DEFAULT_ELEMENT_BACKGROUND_PICKS = [
COLOR_PALETTE.transparent,
COLOR_PALETTE.red[DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX],
COLOR_PALETTE.green[DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX],
COLOR_PALETTE.blue[DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX],
COLOR_PALETTE.yellow[DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX],
] as ColorTuple;
// ORDER matters for positioning in quick picker
export const DEFAULT_CANVAS_BACKGROUND_PICKS = [
COLOR_PALETTE.white,
// radix slate2
"#f8f9fa",
// radix blue2
"#f5faff",
// radix yellow2
"#fffce8",
// radix bronze2
"#fdf8f6",
] as ColorTuple;
// -----------------------------------------------------------------------------
// palette defaults
// -----------------------------------------------------------------------------
export const DEFAULT_ELEMENT_STROKE_COLOR_PALETTE = {
// 1st row
transparent: COLOR_PALETTE.transparent,
white: COLOR_PALETTE.white,
gray: COLOR_PALETTE.gray,
black: COLOR_PALETTE.black,
bronze: COLOR_PALETTE.bronze,
// rest
...COMMON_ELEMENT_SHADES,
} as const;
// ORDER matters for positioning in pallete (5x3 grid)s
export const DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE = {
transparent: COLOR_PALETTE.transparent,
white: COLOR_PALETTE.white,
gray: COLOR_PALETTE.gray,
black: COLOR_PALETTE.black,
bronze: COLOR_PALETTE.bronze,
...COMMON_ELEMENT_SHADES,
} as const;
// -----------------------------------------------------------------------------
// helpers
// -----------------------------------------------------------------------------
// !!!MUST BE WITHOUT GRAY, TRANSPARENT AND BLACK!!!
export const getAllColorsSpecificShade = (index: 0 | 1 | 2 | 3 | 4) =>
[
// 2nd row
COLOR_PALETTE.cyan[index],
COLOR_PALETTE.blue[index],
COLOR_PALETTE.violet[index],
COLOR_PALETTE.grape[index],
COLOR_PALETTE.pink[index],
// 3rd row
COLOR_PALETTE.green[index],
COLOR_PALETTE.teal[index],
COLOR_PALETTE.yellow[index],
COLOR_PALETTE.orange[index],
COLOR_PALETTE.red[index],
] as const;
// -----------------------------------------------------------------------------

View file

@ -33,7 +33,7 @@ import {
actionBindText, actionBindText,
actionUngroup, actionUngroup,
actionLink, actionLink,
actionToggleLock, actionToggleElementLock,
actionToggleLinearEditor, actionToggleLinearEditor,
} from "../actions"; } from "../actions";
import { createRedoAction, createUndoAction } from "../actions/actionHistory"; import { createRedoAction, createUndoAction } from "../actions/actionHistory";
@ -59,6 +59,7 @@ import {
ELEMENT_TRANSLATE_AMOUNT, ELEMENT_TRANSLATE_AMOUNT,
ENV, ENV,
EVENT, EVENT,
EXPORT_IMAGE_TYPES,
GRID_SIZE, GRID_SIZE,
IMAGE_MIME_TYPES, IMAGE_MIME_TYPES,
IMAGE_RENDER_TIMEOUT, IMAGE_RENDER_TIMEOUT,
@ -82,7 +83,7 @@ import {
VERTICAL_ALIGN, VERTICAL_ALIGN,
ZOOM_STEP, ZOOM_STEP,
} from "../constants"; } from "../constants";
import { loadFromBlob } from "../data"; import { exportCanvas, loadFromBlob } from "../data";
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library"; import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
import { restore, restoreElements } from "../data/restore"; import { restore, restoreElements } from "../data/restore";
import { import {
@ -238,6 +239,7 @@ import {
getShortcutKey, getShortcutKey,
isTransparent, isTransparent,
easeToValuesRAF, easeToValuesRAF,
muteFSAbortError,
} from "../utils"; } from "../utils";
import { import {
ContextMenu, ContextMenu,
@ -252,6 +254,7 @@ import {
generateIdFromFile, generateIdFromFile,
getDataURL, getDataURL,
getFileFromEvent, getFileFromEvent,
isImageFileHandle,
isSupportedImageFile, isSupportedImageFile,
loadSceneOrLibraryFromBlob, loadSceneOrLibraryFromBlob,
normalizeFile, normalizeFile,
@ -291,6 +294,7 @@ import {
isLocalLink, isLocalLink,
} from "../element/Hyperlink"; } from "../element/Hyperlink";
import { shouldShowBoundingBox } from "../element/transformHandles"; import { shouldShowBoundingBox } from "../element/transformHandles";
import { actionUnlockAllElements } from "../actions/actionElementLock";
import { Fonts } from "../scene/Fonts"; import { Fonts } from "../scene/Fonts";
import { actionPaste } from "../actions/actionClipboard"; import { actionPaste } from "../actions/actionClipboard";
import { import {
@ -310,6 +314,7 @@ const deviceContextInitialValue = {
isMobile: false, isMobile: false,
isTouchScreen: false, isTouchScreen: false,
canDeviceFitSidebar: false, canDeviceFitSidebar: false,
isLandscape: false,
}; };
const DeviceContext = React.createContext<Device>(deviceContextInitialValue); const DeviceContext = React.createContext<Device>(deviceContextInitialValue);
DeviceContext.displayName = "DeviceContext"; DeviceContext.displayName = "DeviceContext";
@ -618,6 +623,7 @@ class App extends React.Component<AppProps, AppState> {
} }
UIOptions={this.props.UIOptions} UIOptions={this.props.UIOptions}
onImageAction={this.onImageAction} onImageAction={this.onImageAction}
onExportImage={this.onExportImage}
renderWelcomeScreen={ renderWelcomeScreen={
!this.state.isLoading && !this.state.isLoading &&
this.state.showWelcomeScreen && this.state.showWelcomeScreen &&
@ -657,7 +663,7 @@ class App extends React.Component<AppProps, AppState> {
)} )}
<main>{this.renderCanvas()}</main> <main>{this.renderCanvas()}</main>
</ExcalidrawActionManagerContext.Provider> </ExcalidrawActionManagerContext.Provider>
</ExcalidrawElementsContext.Provider>{" "} </ExcalidrawElementsContext.Provider>
</ExcalidrawAppStateContext.Provider> </ExcalidrawAppStateContext.Provider>
</ExcalidrawSetAppStateContext.Provider> </ExcalidrawSetAppStateContext.Provider>
</DeviceContext.Provider> </DeviceContext.Provider>
@ -688,6 +694,37 @@ class App extends React.Component<AppProps, AppState> {
}); });
}; };
public onExportImage = async (
type: keyof typeof EXPORT_IMAGE_TYPES,
elements: readonly NonDeletedExcalidrawElement[],
) => {
trackEvent("export", type, "ui");
const fileHandle = await exportCanvas(
type,
elements,
this.state,
this.files,
{
exportBackground: this.state.exportBackground,
name: this.state.name,
viewBackgroundColor: this.state.viewBackgroundColor,
},
)
.catch(muteFSAbortError)
.catch((error) => {
console.error(error);
this.setState({ errorMessage: error.message });
});
if (
this.state.exportEmbedScene &&
fileHandle &&
isImageFileHandle(fileHandle)
) {
this.setState({ fileHandle });
}
};
private syncActionResult = withBatchedUpdates( private syncActionResult = withBatchedUpdates(
(actionResult: ActionResult) => { (actionResult: ActionResult) => {
if (this.unmounted || actionResult === false) { if (this.unmounted || actionResult === false) {
@ -912,6 +949,7 @@ class App extends React.Component<AppProps, AppState> {
? this.props.UIOptions.dockedSidebarBreakpoint ? this.props.UIOptions.dockedSidebarBreakpoint
: MQ_RIGHT_SIDEBAR_MIN_WIDTH; : MQ_RIGHT_SIDEBAR_MIN_WIDTH;
this.device = updateObject(this.device, { this.device = updateObject(this.device, {
isLandscape: width > height,
isSmScreen: width < MQ_SM_MAX_WIDTH, isSmScreen: width < MQ_SM_MAX_WIDTH,
isMobile: isMobile:
width < MQ_MAX_WIDTH_PORTRAIT || width < MQ_MAX_WIDTH_PORTRAIT ||
@ -2289,11 +2327,11 @@ class App extends React.Component<AppProps, AppState> {
(hasBackground(this.state.activeTool.type) || (hasBackground(this.state.activeTool.type) ||
selectedElements.some((element) => hasBackground(element.type))) selectedElements.some((element) => hasBackground(element.type)))
) { ) {
this.setState({ openPopup: "backgroundColorPicker" }); this.setState({ openPopup: "elementBackground" });
event.stopPropagation(); event.stopPropagation();
} }
if (event.key === KEYS.S) { if (event.key === KEYS.S) {
this.setState({ openPopup: "strokeColorPicker" }); this.setState({ openPopup: "elementStroke" });
event.stopPropagation(); event.stopPropagation();
} }
} }
@ -6349,6 +6387,7 @@ class App extends React.Component<AppProps, AppState> {
copyText, copyText,
CONTEXT_MENU_SEPARATOR, CONTEXT_MENU_SEPARATOR,
actionSelectAll, actionSelectAll,
actionUnlockAllElements,
CONTEXT_MENU_SEPARATOR, CONTEXT_MENU_SEPARATOR,
actionToggleGridMode, actionToggleGridMode,
actionToggleZenMode, actionToggleZenMode,
@ -6395,7 +6434,7 @@ class App extends React.Component<AppProps, AppState> {
actionToggleLinearEditor, actionToggleLinearEditor,
actionLink, actionLink,
actionDuplicateSelection, actionDuplicateSelection,
actionToggleLock, actionToggleElementLock,
CONTEXT_MENU_SEPARATOR, CONTEXT_MENU_SEPARATOR,
actionDeleteSelected, actionDeleteSelected,
]; ];

View file

@ -1,7 +1,7 @@
import "./Avatar.scss"; import "./Avatar.scss";
import React, { useState } from "react"; import React, { useState } from "react";
import { getClientInitials } from "../clients"; import { getNameInitial } from "../clients";
type AvatarProps = { type AvatarProps = {
onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void; onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
@ -12,7 +12,7 @@ type AvatarProps = {
}; };
export const Avatar = ({ color, onClick, name, src }: AvatarProps) => { export const Avatar = ({ color, onClick, name, src }: AvatarProps) => {
const shortName = getClientInitials(name); const shortName = getNameInitial(name);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const loadImg = !error && src; const loadImg = !error && src;
const style = loadImg ? undefined : { background: color }; const style = loadImg ? undefined : { background: color };

View file

@ -1,430 +0,0 @@
import React from "react";
import { Popover } from "./Popover";
import { isTransparent } from "../utils";
import "./ColorPicker.scss";
import { isArrowKey, KEYS } from "../keys";
import { t, getLanguage } from "../i18n";
import { isWritableElement } from "../utils";
import colors from "../colors";
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
const MAX_CUSTOM_COLORS = 5;
const MAX_DEFAULT_COLORS = 15;
export const getCustomColors = (
elements: readonly ExcalidrawElement[],
type: "elementBackground" | "elementStroke",
) => {
const customColors: string[] = [];
const updatedElements = elements
.filter((element) => !element.isDeleted)
.sort((ele1, ele2) => ele2.updated - ele1.updated);
let index = 0;
const elementColorTypeMap = {
elementBackground: "backgroundColor",
elementStroke: "strokeColor",
};
const colorType = elementColorTypeMap[type] as
| "backgroundColor"
| "strokeColor";
while (
index < updatedElements.length &&
customColors.length < MAX_CUSTOM_COLORS
) {
const element = updatedElements[index];
if (
customColors.length < MAX_CUSTOM_COLORS &&
isCustomColor(element[colorType], type) &&
!customColors.includes(element[colorType])
) {
customColors.push(element[colorType]);
}
index++;
}
return customColors;
};
const isCustomColor = (
color: string,
type: "elementBackground" | "elementStroke",
) => {
return !colors[type].includes(color);
};
const isValidColor = (color: string) => {
const style = new Option().style;
style.color = color;
return !!style.color;
};
const getColor = (color: string): string | null => {
if (isTransparent(color)) {
return color;
}
// testing for `#` first fixes a bug on Electron (more specfically, an
// Obsidian popout window), where a hex color without `#` is (incorrectly)
// considered valid
return isValidColor(`#${color}`)
? `#${color}`
: isValidColor(color)
? color
: null;
};
// This is a narrow reimplementation of the awesome react-color Twitter component
// https://github.com/casesandberg/react-color/blob/master/src/components/twitter/Twitter.js
// Unfortunately, we can't detect keyboard layout in the browser. So this will
// only work well for QWERTY but not AZERTY or others...
const keyBindings = [
["1", "2", "3", "4", "5"],
["q", "w", "e", "r", "t"],
["a", "s", "d", "f", "g"],
["z", "x", "c", "v", "b"],
].flat();
const Picker = ({
colors,
color,
onChange,
onClose,
label,
showInput = true,
type,
elements,
}: {
colors: string[];
color: string | null;
onChange: (color: string) => void;
onClose: () => void;
label: string;
showInput: boolean;
type: "canvasBackground" | "elementBackground" | "elementStroke";
elements: readonly ExcalidrawElement[];
}) => {
const firstItem = React.useRef<HTMLButtonElement>();
const activeItem = React.useRef<HTMLButtonElement>();
const gallery = React.useRef<HTMLDivElement>();
const colorInput = React.useRef<HTMLInputElement>();
const [customColors] = React.useState(() => {
if (type === "canvasBackground") {
return [];
}
return getCustomColors(elements, type);
});
React.useEffect(() => {
// After the component is first mounted focus on first input
if (activeItem.current) {
activeItem.current.focus();
} else if (colorInput.current) {
colorInput.current.focus();
} else if (gallery.current) {
gallery.current.focus();
}
}, []);
const handleKeyDown = (event: React.KeyboardEvent) => {
let handled = false;
if (isArrowKey(event.key)) {
handled = true;
const { activeElement } = document;
const isRTL = getLanguage().rtl;
let isCustom = false;
let index = Array.prototype.indexOf.call(
gallery.current!.querySelector(".color-picker-content--default")
?.children,
activeElement,
);
if (index === -1) {
index = Array.prototype.indexOf.call(
gallery.current!.querySelector(".color-picker-content--canvas-colors")
?.children,
activeElement,
);
if (index !== -1) {
isCustom = true;
}
}
const parentElement = isCustom
? gallery.current?.querySelector(".color-picker-content--canvas-colors")
: gallery.current?.querySelector(".color-picker-content--default");
if (parentElement && index !== -1) {
const length = parentElement.children.length - (showInput ? 1 : 0);
const nextIndex =
event.key === (isRTL ? KEYS.ARROW_LEFT : KEYS.ARROW_RIGHT)
? (index + 1) % length
: event.key === (isRTL ? KEYS.ARROW_RIGHT : KEYS.ARROW_LEFT)
? (length + index - 1) % length
: !isCustom && event.key === KEYS.ARROW_DOWN
? (index + 5) % length
: !isCustom && event.key === KEYS.ARROW_UP
? (length + index - 5) % length
: index;
(parentElement.children[nextIndex] as HTMLElement | undefined)?.focus();
}
event.preventDefault();
} else if (
keyBindings.includes(event.key.toLowerCase()) &&
!event[KEYS.CTRL_OR_CMD] &&
!event.altKey &&
!isWritableElement(event.target)
) {
handled = true;
const index = keyBindings.indexOf(event.key.toLowerCase());
const isCustom = index >= MAX_DEFAULT_COLORS;
const parentElement = isCustom
? gallery?.current?.querySelector(
".color-picker-content--canvas-colors",
)
: gallery?.current?.querySelector(".color-picker-content--default");
const actualIndex = isCustom ? index - MAX_DEFAULT_COLORS : index;
(
parentElement?.children[actualIndex] as HTMLElement | undefined
)?.focus();
event.preventDefault();
} else if (event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) {
handled = true;
event.preventDefault();
onClose();
}
if (handled) {
event.nativeEvent.stopImmediatePropagation();
event.stopPropagation();
}
};
const renderColors = (colors: Array<string>, custom: boolean = false) => {
return colors.map((_color, i) => {
const _colorWithoutHash = _color.replace("#", "");
const keyBinding = custom
? keyBindings[i + MAX_DEFAULT_COLORS]
: keyBindings[i];
const label = custom
? _colorWithoutHash
: t(`colors.${_colorWithoutHash}`);
return (
<button
className="color-picker-swatch"
onClick={(event) => {
(event.currentTarget as HTMLButtonElement).focus();
onChange(_color);
}}
title={`${label}${
!isTransparent(_color) ? ` (${_color})` : ""
} ${keyBinding.toUpperCase()}`}
aria-label={label}
aria-keyshortcuts={keyBindings[i]}
style={{ color: _color }}
key={_color}
ref={(el) => {
if (!custom && el && i === 0) {
firstItem.current = el;
}
if (el && _color === color) {
activeItem.current = el;
}
}}
onFocus={() => {
onChange(_color);
}}
>
{isTransparent(_color) ? (
<div className="color-picker-transparent"></div>
) : undefined}
<span className="color-picker-keybinding">{keyBinding}</span>
</button>
);
});
};
return (
<div
className={`color-picker color-picker-type-${type}`}
role="dialog"
aria-modal="true"
aria-label={t("labels.colorPicker")}
onKeyDown={handleKeyDown}
>
<div className="color-picker-triangle color-picker-triangle-shadow"></div>
<div className="color-picker-triangle"></div>
<div
className="color-picker-content"
ref={(el) => {
if (el) {
gallery.current = el;
}
}}
// to allow focusing by clicking but not by tabbing
tabIndex={-1}
>
<div className="color-picker-content--default">
{renderColors(colors)}
</div>
{!!customColors.length && (
<div className="color-picker-content--canvas">
<span className="color-picker-content--canvas-title">
{t("labels.canvasColors")}
</span>
<div className="color-picker-content--canvas-colors">
{renderColors(customColors, true)}
</div>
</div>
)}
{showInput && (
<ColorInput
color={color}
label={label}
onChange={(color) => {
onChange(color);
}}
ref={colorInput}
/>
)}
</div>
</div>
);
};
const ColorInput = React.forwardRef(
(
{
color,
onChange,
label,
}: {
color: string | null;
onChange: (color: string) => void;
label: string;
},
ref,
) => {
const [innerValue, setInnerValue] = React.useState(color);
const inputRef = React.useRef(null);
React.useEffect(() => {
setInnerValue(color);
}, [color]);
React.useImperativeHandle(ref, () => inputRef.current);
const changeColor = React.useCallback(
(inputValue: string) => {
const value = inputValue.toLowerCase();
const color = getColor(value);
if (color) {
onChange(color);
}
setInnerValue(value);
},
[onChange],
);
return (
<label className="color-input-container">
<div className="color-picker-hash">#</div>
<input
spellCheck={false}
className="color-picker-input"
aria-label={label}
onChange={(event) => changeColor(event.target.value)}
value={(innerValue || "").replace(/^#/, "")}
onBlur={() => setInnerValue(color)}
ref={inputRef}
/>
</label>
);
},
);
ColorInput.displayName = "ColorInput";
export const ColorPicker = ({
type,
color,
onChange,
label,
isActive,
setActive,
elements,
appState,
}: {
type: "canvasBackground" | "elementBackground" | "elementStroke";
color: string | null;
onChange: (color: string) => void;
label: string;
isActive: boolean;
setActive: (active: boolean) => void;
elements: readonly ExcalidrawElement[];
appState: AppState;
}) => {
const pickerButton = React.useRef<HTMLButtonElement>(null);
const coords = pickerButton.current?.getBoundingClientRect();
return (
<div>
<div className="color-picker-control-container">
<div className="color-picker-label-swatch-container">
<button
className="color-picker-label-swatch"
aria-label={label}
style={color ? { "--swatch-color": color } : undefined}
onClick={() => setActive(!isActive)}
ref={pickerButton}
/>
</div>
<ColorInput
color={color}
label={label}
onChange={(color) => {
onChange(color);
}}
/>
</div>
<React.Suspense fallback="">
{isActive ? (
<div
className="color-picker-popover-container"
style={{
position: "fixed",
top: coords?.top,
left: coords?.right,
zIndex: 1,
}}
>
<Popover
onCloseRequest={(event) =>
event.target !== pickerButton.current && setActive(false)
}
>
<Picker
colors={colors[type]}
color={color || null}
onChange={(changedColor) => {
onChange(changedColor);
}}
onClose={() => {
setActive(false);
pickerButton.current?.focus();
}}
label={label}
showInput={false}
type={type}
elements={elements}
/>
</Popover>
</div>
) : null}
</React.Suspense>
</div>
);
};

View file

@ -0,0 +1,75 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { getColor } from "./ColorPicker";
import { useAtom } from "jotai";
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
import { KEYS } from "../../keys";
interface ColorInputProps {
color: string | null;
onChange: (color: string) => void;
label: string;
}
export const ColorInput = ({ color, onChange, label }: ColorInputProps) => {
const [innerValue, setInnerValue] = useState(color);
const [activeSection, setActiveColorPickerSection] = useAtom(
activeColorPickerSectionAtom,
);
useEffect(() => {
setInnerValue(color);
}, [color]);
const changeColor = useCallback(
(inputValue: string) => {
const value = inputValue.toLowerCase();
const color = getColor(value);
if (color) {
onChange(color);
}
setInnerValue(value);
},
[onChange],
);
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, [activeSection]);
return (
<label className="color-picker__input-label">
<div className="color-picker__input-hash">#</div>
<input
ref={activeSection === "hex" ? inputRef : undefined}
style={{ border: 0, padding: 0 }}
spellCheck={false}
className="color-picker-input"
aria-label={label}
onChange={(event) => {
changeColor(event.target.value);
}}
value={(innerValue || "").replace(/^#/, "")}
onBlur={() => {
setInnerValue(color);
}}
tabIndex={-1}
onFocus={() => setActiveColorPickerSection("hex")}
onKeyDown={(e) => {
if (e.key === KEYS.TAB) {
return;
}
if (e.key === KEYS.ESCAPE) {
divRef.current?.focus();
}
e.stopPropagation();
}}
/>
</label>
);
};

View file

@ -1,6 +1,134 @@
@import "../css/variables.module"; @import "../../css/variables.module";
.excalidraw { .excalidraw {
.focus-visible-none {
&:focus-visible {
outline: none !important;
}
}
.color-picker__heading {
padding: 0 0.5rem;
font-size: 0.75rem;
text-align: left;
}
.color-picker-container {
display: grid;
grid-template-columns: 1fr 20px 1.625rem;
padding: 0.25rem 0px;
align-items: center;
@include isMobile {
max-width: 175px;
}
}
.color-picker__top-picks {
display: flex;
justify-content: space-between;
}
.color-picker__button {
--radius: 0.25rem;
padding: 0;
margin: 0;
width: 1.35rem;
height: 1.35rem;
border: 1px solid var(--color-gray-30);
border-radius: var(--radius);
filter: var(--theme-filter);
background-color: var(--swatch-color);
background-position: left center;
position: relative;
font-family: inherit;
box-sizing: border-box;
&:hover {
&::after {
content: "";
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
box-shadow: 0 0 0 1px var(--color-gray-30);
border-radius: calc(var(--radius) + 1px);
filter: var(--theme-filter);
}
}
&.active {
.color-picker__button-outline {
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
box-shadow: 0 0 0 1px var(--color-primary-darkest);
z-index: 1; // due hover state so this has preference
border-radius: calc(var(--radius) + 1px);
filter: var(--theme-filter);
}
}
&:focus-visible {
outline: none;
&::after {
content: "";
position: absolute;
top: -4px;
right: -4px;
bottom: -4px;
left: -4px;
border: 3px solid var(--focus-highlight-color);
border-radius: calc(var(--radius) + 1px);
}
&.active {
.color-picker__button-outline {
display: none;
}
}
}
&--large {
--radius: 0.5rem;
width: 1.875rem;
height: 1.875rem;
}
&.is-transparent {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==");
}
&--no-focus-visible {
border: 0;
&::after {
display: none;
}
&:focus-visible {
outline: none !important;
}
}
&.active-color {
border-radius: calc(var(--radius) + 1px);
width: 1.625rem;
height: 1.625rem;
}
}
.color-picker__button__hotkey-label {
position: absolute;
right: 4px;
bottom: 4px;
filter: none;
font-size: 11px;
}
.color-picker { .color-picker {
background: var(--popup-bg-color); background: var(--popup-bg-color);
border: 0 solid transparentize($oc-white, 0.75); border: 0 solid transparentize($oc-white, 0.75);
@ -72,11 +200,17 @@
} }
} }
.color-picker-content {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.color-picker-content--default { .color-picker-content--default {
padding: 0.5rem; padding: 0.5rem;
display: grid; display: grid;
grid-template-columns: repeat(5, auto); grid-template-columns: repeat(5, 1.875rem);
grid-gap: 0.5rem; grid-gap: 0.25rem;
border-radius: 4px; border-radius: 4px;
&:focus { &:focus {
@ -178,6 +312,27 @@
} }
} }
.color-picker__input-label {
display: grid;
grid-template-columns: auto 1fr auto auto;
gap: 8px;
align-items: center;
border: 1px solid var(--default-border-color);
border-radius: 8px;
padding: 0 12px;
margin: 8px;
box-sizing: border-box;
&:focus-within {
box-shadow: 0 0 0 1px var(--color-primary-darkest);
border-radius: var(--border-radius-lg);
}
}
.color-picker__input-hash {
padding: 0 0.25rem;
}
.color-picker-input { .color-picker-input {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;

View file

@ -0,0 +1,235 @@
import { isTransparent } from "../../utils";
import { ExcalidrawElement } from "../../element/types";
import { AppState } from "../../types";
import { TopPicks } from "./TopPicks";
import { Picker } from "./Picker";
import * as Popover from "@radix-ui/react-popover";
import { useAtom } from "jotai";
import {
activeColorPickerSectionAtom,
ColorPickerType,
} from "./colorPickerUtils";
import { useDevice, useExcalidrawContainer } from "../App";
import { ColorTuple, COLOR_PALETTE, ColorPaletteCustom } from "../../colors";
import PickerHeading from "./PickerHeading";
import { ColorInput } from "./ColorInput";
import { t } from "../../i18n";
import clsx from "clsx";
import "./ColorPicker.scss";
import React from "react";
const isValidColor = (color: string) => {
const style = new Option().style;
style.color = color;
return !!style.color;
};
export const getColor = (color: string): string | null => {
if (isTransparent(color)) {
return color;
}
// testing for `#` first fixes a bug on Electron (more specfically, an
// Obsidian popout window), where a hex color without `#` is (incorrectly)
// considered valid
return isValidColor(`#${color}`)
? `#${color}`
: isValidColor(color)
? color
: null;
};
export interface ColorPickerProps {
type: ColorPickerType;
color: string | null;
onChange: (color: string) => void;
label: string;
elements: readonly ExcalidrawElement[];
appState: AppState;
palette?: ColorPaletteCustom | null;
topPicks?: ColorTuple;
updateData: (formData?: any) => void;
}
const ColorPickerPopupContent = ({
type,
color,
onChange,
label,
elements,
palette = COLOR_PALETTE,
updateData,
}: Pick<
ColorPickerProps,
| "type"
| "color"
| "onChange"
| "label"
| "label"
| "elements"
| "palette"
| "updateData"
>) => {
const [, setActiveColorPickerSection] = useAtom(activeColorPickerSectionAtom);
const { container } = useExcalidrawContainer();
const { isMobile, isLandscape } = useDevice();
const colorInputJSX = (
<div>
<PickerHeading>{t("colorPicker.hexCode")}</PickerHeading>
<ColorInput
color={color}
label={label}
onChange={(color) => {
onChange(color);
}}
/>
</div>
);
return (
<Popover.Portal container={container}>
<Popover.Content
className="focus-visible-none"
data-prevent-outside-click
onCloseAutoFocus={(e) => {
// return focus to excalidraw container
if (container) {
container.focus();
}
e.preventDefault();
e.stopPropagation();
setActiveColorPickerSection(null);
}}
side={isMobile && !isLandscape ? "bottom" : "right"}
align={isMobile && !isLandscape ? "center" : "start"}
alignOffset={-16}
sideOffset={20}
style={{
zIndex: 9999,
backgroundColor: "var(--popup-bg-color)",
maxWidth: "208px",
maxHeight: window.innerHeight,
padding: "12px",
borderRadius: "8px",
boxSizing: "border-box",
overflowY: "auto",
boxShadow:
"0px 7px 14px rgba(0, 0, 0, 0.05), 0px 0px 3.12708px rgba(0, 0, 0, 0.0798), 0px 0px 0.931014px rgba(0, 0, 0, 0.1702)",
}}
>
{palette ? (
<Picker
palette={palette}
color={color || null}
onChange={(changedColor) => {
onChange(changedColor);
}}
label={label}
type={type}
elements={elements}
updateData={updateData}
>
{colorInputJSX}
</Picker>
) : (
colorInputJSX
)}
<Popover.Arrow
width={20}
height={10}
style={{
fill: "var(--popup-bg-color)",
filter: "drop-shadow(rgba(0, 0, 0, 0.05) 0px 3px 2px)",
}}
/>
</Popover.Content>
</Popover.Portal>
);
};
const ColorPickerTrigger = ({
label,
color,
type,
}: {
color: string | null;
label: string;
type: ColorPickerType;
}) => {
return (
<Popover.Trigger
type="button"
className={clsx("color-picker__button active-color", {
"is-transparent": color === "transparent" || !color,
})}
aria-label={label}
style={color ? { "--swatch-color": color } : undefined}
title={
type === "elementStroke"
? t("labels.showStroke")
: t("labels.showBackground")
}
>
<div className="color-picker__button-outline" />
</Popover.Trigger>
);
};
export const ColorPicker = ({
type,
color,
onChange,
label,
elements,
palette = COLOR_PALETTE,
topPicks,
updateData,
appState,
}: ColorPickerProps) => {
return (
<div>
<div role="dialog" aria-modal="true" className="color-picker-container">
<TopPicks
activeColor={color}
onChange={onChange}
type={type}
topPicks={topPicks}
/>
<div
style={{
width: 1,
height: "100%",
backgroundColor: "var(--default-border-color)",
margin: "0 auto",
}}
/>
<Popover.Root
open={appState.openPopup === type}
onOpenChange={(open) => {
updateData({ openPopup: open ? type : null });
}}
>
{/* serves as an active color indicator as well */}
<ColorPickerTrigger color={color} label={label} type={type} />
{/* popup content */}
{appState.openPopup === type && (
<ColorPickerPopupContent
type={type}
color={color}
onChange={onChange}
label={label}
elements={elements}
palette={palette}
updateData={updateData}
/>
)}
</Popover.Root>
</div>
</div>
);
};

View file

@ -0,0 +1,63 @@
import clsx from "clsx";
import { useAtom } from "jotai";
import { useEffect, useRef } from "react";
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
import HotkeyLabel from "./HotkeyLabel";
interface CustomColorListProps {
colors: string[];
color: string | null;
onChange: (color: string) => void;
label: string;
}
export const CustomColorList = ({
colors,
color,
onChange,
label,
}: CustomColorListProps) => {
const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
activeColorPickerSectionAtom,
);
const btnRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (btnRef.current) {
btnRef.current.focus();
}
}, [color, activeColorPickerSection]);
return (
<div className="color-picker-content--default">
{colors.map((c, i) => {
return (
<button
ref={color === c ? btnRef : undefined}
tabIndex={-1}
type="button"
className={clsx(
"color-picker__button color-picker__button--large",
{
active: color === c,
"is-transparent": c === "transparent" || !c,
},
)}
onClick={() => {
onChange(c);
setActiveColorPickerSection("custom");
}}
title={c}
aria-label={label}
style={{ "--swatch-color": c }}
key={i}
>
<div className="color-picker__button-outline" />
<HotkeyLabel color={c} keyLabel={i + 1} isCustomColor />
</button>
);
})}
</div>
);
};

View file

@ -0,0 +1,29 @@
import React from "react";
import { getContrastYIQ } from "./colorPickerUtils";
interface HotkeyLabelProps {
color: string;
keyLabel: string | number;
isCustomColor?: boolean;
isShade?: boolean;
}
const HotkeyLabel = ({
color,
keyLabel,
isCustomColor = false,
isShade = false,
}: HotkeyLabelProps) => {
return (
<div
className="color-picker__button__hotkey-label"
style={{
color: getContrastYIQ(color, isCustomColor),
}}
>
{isShade && "⇧"}
{keyLabel}
</div>
);
};
export default HotkeyLabel;

View file

@ -0,0 +1,156 @@
import React, { useEffect, useState } from "react";
import { t } from "../../i18n";
import { ExcalidrawElement } from "../../element/types";
import { ShadeList } from "./ShadeList";
import PickerColorList from "./PickerColorList";
import { useAtom } from "jotai";
import { CustomColorList } from "./CustomColorList";
import { colorPickerKeyNavHandler } from "./keyboardNavHandlers";
import PickerHeading from "./PickerHeading";
import {
ColorPickerType,
activeColorPickerSectionAtom,
getColorNameAndShadeFromHex,
getMostUsedCustomColors,
isCustomColor,
} from "./colorPickerUtils";
import {
ColorPaletteCustom,
DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX,
DEFAULT_ELEMENT_STROKE_COLOR_INDEX,
} from "../../colors";
interface PickerProps {
color: string | null;
onChange: (color: string) => void;
label: string;
type: ColorPickerType;
elements: readonly ExcalidrawElement[];
palette: ColorPaletteCustom;
updateData: (formData?: any) => void;
children?: React.ReactNode;
}
export const Picker = ({
color,
onChange,
label,
type,
elements,
palette,
updateData,
children,
}: PickerProps) => {
const [customColors] = React.useState(() => {
if (type === "canvasBackground") {
return [];
}
return getMostUsedCustomColors(elements, type, palette);
});
const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
activeColorPickerSectionAtom,
);
const colorObj = getColorNameAndShadeFromHex({
hex: color || "transparent",
palette,
});
useEffect(() => {
if (!activeColorPickerSection) {
const isCustom = isCustomColor({ color, palette });
const isCustomButNotInList =
isCustom && !customColors.includes(color || "");
setActiveColorPickerSection(
isCustomButNotInList
? "hex"
: isCustom
? "custom"
: colorObj?.shade != null
? "shades"
: "baseColors",
);
}
}, [
activeColorPickerSection,
color,
palette,
setActiveColorPickerSection,
colorObj,
customColors,
]);
const [activeShade, setActiveShade] = useState(
colorObj?.shade ??
(type === "elementBackground"
? DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX
: DEFAULT_ELEMENT_STROKE_COLOR_INDEX),
);
useEffect(() => {
if (colorObj?.shade != null) {
setActiveShade(colorObj.shade);
}
}, [colorObj]);
return (
<div role="dialog" aria-modal="true" aria-label={t("labels.colorPicker")}>
<div
onKeyDown={(e) => {
e.preventDefault();
e.stopPropagation();
colorPickerKeyNavHandler({
e,
activeColorPickerSection,
palette,
hex: color,
onChange,
customColors,
setActiveColorPickerSection,
updateData,
activeShade,
});
}}
className="color-picker-content"
// to allow focusing by clicking but not by tabbing
tabIndex={-1}
>
{!!customColors.length && (
<div>
<PickerHeading>
{t("colorPicker.mostUsedCustomColors")}
</PickerHeading>
<CustomColorList
colors={customColors}
color={color}
label={t("colorPicker.mostUsedCustomColors")}
onChange={onChange}
/>
</div>
)}
<div>
<PickerHeading>{t("colorPicker.colors")}</PickerHeading>
<PickerColorList
color={color}
label={label}
palette={palette}
onChange={onChange}
activeShade={activeShade}
/>
</div>
<div>
<PickerHeading>{t("colorPicker.shades")}</PickerHeading>
<ShadeList hex={color} onChange={onChange} palette={palette} />
</div>
{children}
</div>
</div>
);
};

View file

@ -0,0 +1,86 @@
import clsx from "clsx";
import { useAtom } from "jotai";
import { useEffect, useRef } from "react";
import {
activeColorPickerSectionAtom,
colorPickerHotkeyBindings,
getColorNameAndShadeFromHex,
} from "./colorPickerUtils";
import HotkeyLabel from "./HotkeyLabel";
import { ColorPaletteCustom } from "../../colors";
import { t } from "../../i18n";
interface PickerColorListProps {
palette: ColorPaletteCustom;
color: string | null;
onChange: (color: string) => void;
label: string;
activeShade: number;
}
const PickerColorList = ({
palette,
color,
onChange,
label,
activeShade,
}: PickerColorListProps) => {
const colorObj = getColorNameAndShadeFromHex({
hex: color || "transparent",
palette,
});
const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
activeColorPickerSectionAtom,
);
const btnRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (btnRef.current && activeColorPickerSection === "baseColors") {
btnRef.current.focus();
}
}, [colorObj?.colorName, activeColorPickerSection]);
return (
<div className="color-picker-content--default">
{Object.entries(palette).map(([key, value], index) => {
const color =
(Array.isArray(value) ? value[activeShade] : value) || "transparent";
const keybinding = colorPickerHotkeyBindings[index];
const label = t(`colors.${key.replace(/\d+/, "")}`, null, "");
return (
<button
ref={colorObj?.colorName === key ? btnRef : undefined}
tabIndex={-1}
type="button"
className={clsx(
"color-picker__button color-picker__button--large",
{
active: colorObj?.colorName === key,
"is-transparent": color === "transparent" || !color,
},
)}
onClick={() => {
onChange(color);
setActiveColorPickerSection("baseColors");
}}
title={`${label}${
color.startsWith("#") ? ` ${color}` : ""
} ${keybinding}`}
aria-label={`${label}${keybinding}`}
style={color ? { "--swatch-color": color } : undefined}
data-testid={`color-${key}`}
key={key}
>
<div className="color-picker__button-outline" />
<HotkeyLabel color={color} keyLabel={keybinding} />
</button>
);
})}
</div>
);
};
export default PickerColorList;

View file

@ -0,0 +1,7 @@
import { ReactNode } from "react";
const PickerHeading = ({ children }: { children: ReactNode }) => (
<div className="color-picker__heading">{children}</div>
);
export default PickerHeading;

View file

@ -0,0 +1,105 @@
import clsx from "clsx";
import { useAtom } from "jotai";
import { useEffect, useRef } from "react";
import {
activeColorPickerSectionAtom,
getColorNameAndShadeFromHex,
} from "./colorPickerUtils";
import HotkeyLabel from "./HotkeyLabel";
import { t } from "../../i18n";
import { ColorPaletteCustom } from "../../colors";
interface ShadeListProps {
hex: string | null;
onChange: (color: string) => void;
palette: ColorPaletteCustom;
}
export const ShadeList = ({ hex, onChange, palette }: ShadeListProps) => {
const colorObj = getColorNameAndShadeFromHex({
hex: hex || "transparent",
palette,
});
const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
activeColorPickerSectionAtom,
);
const btnRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (btnRef.current && activeColorPickerSection === "shades") {
btnRef.current.focus();
}
}, [colorObj, activeColorPickerSection]);
if (colorObj) {
const { colorName, shade } = colorObj;
const shades = palette[colorName];
if (Array.isArray(shades)) {
return (
<div className="color-picker-content--default shades">
{shades.map((color, i) => (
<button
ref={
i === shade && activeColorPickerSection === "shades"
? btnRef
: undefined
}
tabIndex={-1}
key={i}
type="button"
className={clsx(
"color-picker__button color-picker__button--large",
{ active: i === shade },
)}
aria-label="Shade"
title={`${colorName} - ${i + 1}`}
style={color ? { "--swatch-color": color } : undefined}
onClick={() => {
onChange(color);
setActiveColorPickerSection("shades");
}}
>
<div className="color-picker__button-outline" />
<HotkeyLabel color={color} keyLabel={i + 1} isShade />
</button>
))}
</div>
);
}
}
return (
<div
className="color-picker-content--default"
style={{ position: "relative" }}
tabIndex={-1}
>
<button
type="button"
tabIndex={-1}
className="color-picker__button color-picker__button--large color-picker__button--no-focus-visible"
/>
<div
tabIndex={-1}
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
fontSize: "0.75rem",
}}
>
{t("colorPicker.noShades")}
</div>
</div>
);
};

View file

@ -0,0 +1,64 @@
import clsx from "clsx";
import { ColorPickerType } from "./colorPickerUtils";
import {
DEFAULT_CANVAS_BACKGROUND_PICKS,
DEFAULT_ELEMENT_BACKGROUND_PICKS,
DEFAULT_ELEMENT_STROKE_PICKS,
} from "../../colors";
interface TopPicksProps {
onChange: (color: string) => void;
type: ColorPickerType;
activeColor: string | null;
topPicks?: readonly string[];
}
export const TopPicks = ({
onChange,
type,
activeColor,
topPicks,
}: TopPicksProps) => {
let colors;
if (type === "elementStroke") {
colors = DEFAULT_ELEMENT_STROKE_PICKS;
}
if (type === "elementBackground") {
colors = DEFAULT_ELEMENT_BACKGROUND_PICKS;
}
if (type === "canvasBackground") {
colors = DEFAULT_CANVAS_BACKGROUND_PICKS;
}
// this one can overwrite defaults
if (topPicks) {
colors = topPicks;
}
if (!colors) {
console.error("Invalid type for TopPicks");
return null;
}
return (
<div className="color-picker__top-picks">
{colors.map((color: string) => (
<button
className={clsx("color-picker__button", {
active: color === activeColor,
"is-transparent": color === "transparent" || !color,
})}
style={{ "--swatch-color": color }}
key={color}
type="button"
title={color}
onClick={() => onChange(color)}
>
<div className="color-picker__button-outline" />
</button>
))}
</div>
);
};

View file

@ -0,0 +1,139 @@
import { ExcalidrawElement } from "../../element/types";
import { atom } from "jotai";
import {
ColorPickerColor,
ColorPaletteCustom,
MAX_CUSTOM_COLORS_USED_IN_CANVAS,
} from "../../colors";
export const getColorNameAndShadeFromHex = ({
palette,
hex,
}: {
palette: ColorPaletteCustom;
hex: string;
}): {
colorName: ColorPickerColor;
shade: number | null;
} | null => {
for (const [colorName, colorVal] of Object.entries(palette)) {
if (Array.isArray(colorVal)) {
const shade = colorVal.indexOf(hex);
if (shade > -1) {
return { colorName: colorName as ColorPickerColor, shade };
}
} else if (colorVal === hex) {
return { colorName: colorName as ColorPickerColor, shade: null };
}
}
return null;
};
export const colorPickerHotkeyBindings = [
["q", "w", "e", "r", "t"],
["a", "s", "d", "f", "g"],
["z", "x", "c", "v", "b"],
].flat();
export const isCustomColor = ({
color,
palette,
}: {
color: string | null;
palette: ColorPaletteCustom;
}) => {
if (!color) {
return false;
}
const paletteValues = Object.values(palette).flat();
return !paletteValues.includes(color);
};
export const getMostUsedCustomColors = (
elements: readonly ExcalidrawElement[],
type: "elementBackground" | "elementStroke",
palette: ColorPaletteCustom,
) => {
const elementColorTypeMap = {
elementBackground: "backgroundColor",
elementStroke: "strokeColor",
};
const colors = elements.filter((element) => {
if (element.isDeleted) {
return false;
}
const color =
element[elementColorTypeMap[type] as "backgroundColor" | "strokeColor"];
return isCustomColor({ color, palette });
});
const colorCountMap = new Map<string, number>();
colors.forEach((element) => {
const color =
element[elementColorTypeMap[type] as "backgroundColor" | "strokeColor"];
if (colorCountMap.has(color)) {
colorCountMap.set(color, colorCountMap.get(color)! + 1);
} else {
colorCountMap.set(color, 1);
}
});
return [...colorCountMap.entries()]
.sort((a, b) => b[1] - a[1])
.map((c) => c[0])
.slice(0, MAX_CUSTOM_COLORS_USED_IN_CANVAS);
};
export type ActiveColorPickerSectionAtomType =
| "custom"
| "baseColors"
| "shades"
| "hex"
| null;
export const activeColorPickerSectionAtom =
atom<ActiveColorPickerSectionAtomType>(null);
const calculateContrast = (r: number, g: number, b: number) => {
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
return yiq >= 160 ? "black" : "white";
};
// inspiration from https://stackoverflow.com/a/11868398
export const getContrastYIQ = (bgHex: string, isCustomColor: boolean) => {
if (isCustomColor) {
const style = new Option().style;
style.color = bgHex;
if (style.color) {
const rgb = style.color
.replace(/^(rgb|rgba)\(/, "")
.replace(/\)$/, "")
.replace(/\s/g, "")
.split(",");
const r = parseInt(rgb[0]);
const g = parseInt(rgb[1]);
const b = parseInt(rgb[2]);
return calculateContrast(r, g, b);
}
}
// TODO: ? is this wanted?
if (bgHex === "transparent") {
return "black";
}
const r = parseInt(bgHex.substring(1, 3), 16);
const g = parseInt(bgHex.substring(3, 5), 16);
const b = parseInt(bgHex.substring(5, 7), 16);
return calculateContrast(r, g, b);
};
export type ColorPickerType =
| "canvasBackground"
| "elementBackground"
| "elementStroke";

View file

@ -0,0 +1,249 @@
import {
ColorPickerColor,
ColorPalette,
ColorPaletteCustom,
COLORS_PER_ROW,
COLOR_PALETTE,
} from "../../colors";
import { KEYS } from "../../keys";
import { ValueOf } from "../../utility-types";
import {
ActiveColorPickerSectionAtomType,
colorPickerHotkeyBindings,
getColorNameAndShadeFromHex,
} from "./colorPickerUtils";
const arrowHandler = (
eventKey: string,
currentIndex: number | null,
length: number,
) => {
const rows = Math.ceil(length / COLORS_PER_ROW);
currentIndex = currentIndex ?? -1;
switch (eventKey) {
case "ArrowLeft": {
const prevIndex = currentIndex - 1;
return prevIndex < 0 ? length - 1 : prevIndex;
}
case "ArrowRight": {
return (currentIndex + 1) % length;
}
case "ArrowDown": {
const nextIndex = currentIndex + COLORS_PER_ROW;
return nextIndex >= length ? currentIndex % COLORS_PER_ROW : nextIndex;
}
case "ArrowUp": {
const prevIndex = currentIndex - COLORS_PER_ROW;
const newIndex =
prevIndex < 0 ? COLORS_PER_ROW * rows + prevIndex : prevIndex;
return newIndex >= length ? undefined : newIndex;
}
}
};
interface HotkeyHandlerProps {
e: React.KeyboardEvent;
colorObj: { colorName: ColorPickerColor; shade: number | null } | null;
onChange: (color: string) => void;
palette: ColorPaletteCustom;
customColors: string[];
setActiveColorPickerSection: (
update: React.SetStateAction<ActiveColorPickerSectionAtomType>,
) => void;
activeShade: number;
}
const hotkeyHandler = ({
e,
colorObj,
onChange,
palette,
customColors,
setActiveColorPickerSection,
activeShade,
}: HotkeyHandlerProps) => {
if (colorObj?.shade != null) {
// shift + numpad is extremely messed up on windows apparently
if (
["Digit1", "Digit2", "Digit3", "Digit4", "Digit5"].includes(e.code) &&
e.shiftKey
) {
const newShade = Number(e.code.slice(-1)) - 1;
onChange(palette[colorObj.colorName][newShade]);
setActiveColorPickerSection("shades");
}
}
if (["1", "2", "3", "4", "5"].includes(e.key)) {
const c = customColors[Number(e.key) - 1];
if (c) {
onChange(customColors[Number(e.key) - 1]);
setActiveColorPickerSection("custom");
}
}
if (colorPickerHotkeyBindings.includes(e.key)) {
const index = colorPickerHotkeyBindings.indexOf(e.key);
const paletteKey = Object.keys(palette)[index] as keyof ColorPalette;
const paletteValue = palette[paletteKey];
const r = Array.isArray(paletteValue)
? paletteValue[activeShade]
: paletteValue;
onChange(r);
setActiveColorPickerSection("baseColors");
}
};
interface ColorPickerKeyNavHandlerProps {
e: React.KeyboardEvent;
activeColorPickerSection: ActiveColorPickerSectionAtomType;
palette: ColorPaletteCustom;
hex: string | null;
onChange: (color: string) => void;
customColors: string[];
setActiveColorPickerSection: (
update: React.SetStateAction<ActiveColorPickerSectionAtomType>,
) => void;
updateData: (formData?: any) => void;
activeShade: number;
}
export const colorPickerKeyNavHandler = ({
e,
activeColorPickerSection,
palette,
hex,
onChange,
customColors,
setActiveColorPickerSection,
updateData,
activeShade,
}: ColorPickerKeyNavHandlerProps) => {
if (e.key === KEYS.ESCAPE || !hex) {
updateData({ openPopup: null });
return;
}
const colorObj = getColorNameAndShadeFromHex({ hex, palette });
if (e.key === KEYS.TAB) {
const sectionsMap: Record<
NonNullable<ActiveColorPickerSectionAtomType>,
boolean
> = {
custom: !!customColors.length,
baseColors: true,
shades: colorObj?.shade != null,
hex: true,
};
const sections = Object.entries(sectionsMap).reduce((acc, [key, value]) => {
if (value) {
acc.push(key as ActiveColorPickerSectionAtomType);
}
return acc;
}, [] as ActiveColorPickerSectionAtomType[]);
const activeSectionIndex = sections.indexOf(activeColorPickerSection);
const indexOffset = e.shiftKey ? -1 : 1;
const nextSectionIndex =
activeSectionIndex + indexOffset > sections.length - 1
? 0
: activeSectionIndex + indexOffset < 0
? sections.length - 1
: activeSectionIndex + indexOffset;
const nextSection = sections[nextSectionIndex];
if (nextSection) {
setActiveColorPickerSection(nextSection);
}
if (nextSection === "custom") {
onChange(customColors[0]);
} else if (nextSection === "baseColors") {
const baseColorName = (
Object.entries(palette) as [string, ValueOf<ColorPalette>][]
).find(([name, shades]) => {
if (Array.isArray(shades)) {
return shades.includes(hex);
} else if (shades === hex) {
return name;
}
return null;
});
if (!baseColorName) {
onChange(COLOR_PALETTE.black);
}
}
e.preventDefault();
e.stopPropagation();
return;
}
hotkeyHandler({
e,
colorObj,
onChange,
palette,
customColors,
setActiveColorPickerSection,
activeShade,
});
if (activeColorPickerSection === "shades") {
if (colorObj) {
const { shade } = colorObj;
const newShade = arrowHandler(e.key, shade, COLORS_PER_ROW);
if (newShade !== undefined) {
onChange(palette[colorObj.colorName][newShade]);
}
}
}
if (activeColorPickerSection === "baseColors") {
if (colorObj) {
const { colorName } = colorObj;
const colorNames = Object.keys(palette) as (keyof ColorPalette)[];
const indexOfColorName = colorNames.indexOf(colorName);
const newColorIndex = arrowHandler(
e.key,
indexOfColorName,
colorNames.length,
);
if (newColorIndex !== undefined) {
const newColorName = colorNames[newColorIndex];
const newColorNameValue = palette[newColorName];
onChange(
Array.isArray(newColorNameValue)
? newColorNameValue[activeShade]
: newColorNameValue,
);
}
}
}
if (activeColorPickerSection === "custom") {
const indexOfColor = customColors.indexOf(hex);
const newColorIndex = arrowHandler(
e.key,
indexOfColor,
customColors.length,
);
if (newColorIndex !== undefined) {
const newColor = customColors[newColorIndex];
onChange(newColor);
}
}
};

View file

@ -4,13 +4,17 @@ import { canvasToBlob } from "../data/blob";
import { NonDeletedExcalidrawElement } from "../element/types"; import { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n"; import { t } from "../i18n";
import { getSelectedElements, isSomeElementSelected } from "../scene"; import { getSelectedElements, isSomeElementSelected } from "../scene";
import { BinaryFiles, UIAppState } from "../types"; import { AppClassProperties, BinaryFiles, UIAppState } from "../types";
import { Dialog } from "./Dialog"; import { Dialog } from "./Dialog";
import { clipboard } from "./icons"; import { clipboard } from "./icons";
import Stack from "./Stack"; import Stack from "./Stack";
import OpenColor from "open-color"; import OpenColor from "open-color";
import { CheckboxItem } from "./CheckboxItem"; import { CheckboxItem } from "./CheckboxItem";
import { DEFAULT_EXPORT_PADDING, isFirefox } from "../constants"; import {
DEFAULT_EXPORT_PADDING,
EXPORT_IMAGE_TYPES,
isFirefox,
} from "../constants";
import { nativeFileSystemSupported } from "../data/filesystem"; import { nativeFileSystemSupported } from "../data/filesystem";
import { ActionManager } from "../actions/manager"; import { ActionManager } from "../actions/manager";
import { exportToCanvas } from "../packages/utils"; import { exportToCanvas } from "../packages/utils";
@ -65,21 +69,14 @@ const ImageExportModal = ({
elements, elements,
appState, appState,
files, files,
exportPadding = DEFAULT_EXPORT_PADDING,
actionManager, actionManager,
onExportToPng, onExportImage,
onExportToSvg,
onExportToClipboard,
}: { }: {
appState: UIAppState; appState: UIAppState;
elements: readonly NonDeletedExcalidrawElement[]; elements: readonly NonDeletedExcalidrawElement[];
files: BinaryFiles; files: BinaryFiles;
exportPadding?: number;
actionManager: ActionManager; actionManager: ActionManager;
onExportToPng: ExportCB; onExportImage: AppClassProperties["onExportImage"];
onExportToSvg: ExportCB;
onExportToClipboard: ExportCB;
onCloseRequest: () => void;
}) => { }) => {
const someElementIsSelected = isSomeElementSelected(elements, appState); const someElementIsSelected = isSomeElementSelected(elements, appState);
const [exportSelected, setExportSelected] = useState(someElementIsSelected); const [exportSelected, setExportSelected] = useState(someElementIsSelected);
@ -90,10 +87,6 @@ const ImageExportModal = ({
? getSelectedElements(elements, appState, true) ? getSelectedElements(elements, appState, true)
: elements; : elements;
useEffect(() => {
setExportSelected(someElementIsSelected);
}, [someElementIsSelected]);
useEffect(() => { useEffect(() => {
const previewNode = previewRef.current; const previewNode = previewRef.current;
if (!previewNode) { if (!previewNode) {
@ -107,7 +100,7 @@ const ImageExportModal = ({
elements: exportedElements, elements: exportedElements,
appState, appState,
files, files,
exportPadding, exportPadding: DEFAULT_EXPORT_PADDING,
maxWidthOrHeight: maxWidth, maxWidthOrHeight: maxWidth,
}) })
.then((canvas) => { .then((canvas) => {
@ -122,7 +115,7 @@ const ImageExportModal = ({
console.error(error); console.error(error);
setRenderError(error); setRenderError(error);
}); });
}, [appState, files, exportedElements, exportPadding]); }, [appState, files, exportedElements]);
return ( return (
<div className="ExportDialog"> <div className="ExportDialog">
@ -177,7 +170,9 @@ const ImageExportModal = ({
color="indigo" color="indigo"
title={t("buttons.exportToPng")} title={t("buttons.exportToPng")}
aria-label={t("buttons.exportToPng")} aria-label={t("buttons.exportToPng")}
onClick={() => onExportToPng(exportedElements)} onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.png, exportedElements)
}
> >
PNG PNG
</ExportButton> </ExportButton>
@ -185,7 +180,9 @@ const ImageExportModal = ({
color="red" color="red"
title={t("buttons.exportToSvg")} title={t("buttons.exportToSvg")}
aria-label={t("buttons.exportToSvg")} aria-label={t("buttons.exportToSvg")}
onClick={() => onExportToSvg(exportedElements)} onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.svg, exportedElements)
}
> >
SVG SVG
</ExportButton> </ExportButton>
@ -194,7 +191,9 @@ const ImageExportModal = ({
{(probablySupportsClipboardBlob || isFirefox) && ( {(probablySupportsClipboardBlob || isFirefox) && (
<ExportButton <ExportButton
title={t("buttons.copyPngToClipboard")} title={t("buttons.copyPngToClipboard")}
onClick={() => onExportToClipboard(exportedElements)} onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.clipboard, exportedElements)
}
color="gray" color="gray"
shade={7} shade={7}
> >
@ -209,45 +208,31 @@ const ImageExportModal = ({
export const ImageExportDialog = ({ export const ImageExportDialog = ({
elements, elements,
appState, appState,
setAppState,
files, files,
exportPadding = DEFAULT_EXPORT_PADDING,
actionManager, actionManager,
onExportToPng, onExportImage,
onExportToSvg, onCloseRequest,
onExportToClipboard,
}: { }: {
appState: UIAppState; appState: UIAppState;
setAppState: React.Component<any, UIAppState>["setState"];
elements: readonly NonDeletedExcalidrawElement[]; elements: readonly NonDeletedExcalidrawElement[];
files: BinaryFiles; files: BinaryFiles;
exportPadding?: number;
actionManager: ActionManager; actionManager: ActionManager;
onExportToPng: ExportCB; onExportImage: AppClassProperties["onExportImage"];
onExportToSvg: ExportCB; onCloseRequest: () => void;
onExportToClipboard: ExportCB;
}) => { }) => {
const handleClose = React.useCallback(() => { if (appState.openDialog !== "imageExport") {
setAppState({ openDialog: null }); return null;
}, [setAppState]); }
return ( return (
<> <Dialog onCloseRequest={onCloseRequest} title={t("buttons.exportImage")}>
{appState.openDialog === "imageExport" && (
<Dialog onCloseRequest={handleClose} title={t("buttons.exportImage")}>
<ImageExportModal <ImageExportModal
elements={elements} elements={elements}
appState={appState} appState={appState}
files={files} files={files}
exportPadding={exportPadding}
actionManager={actionManager} actionManager={actionManager}
onExportToPng={onExportToPng} onExportImage={onExportImage}
onExportToSvg={onExportToSvg}
onExportToClipboard={onExportToClipboard}
onCloseRequest={handleClose}
/> />
</Dialog> </Dialog>
)}
</>
); );
}; };

View file

@ -2,23 +2,22 @@ import clsx from "clsx";
import React from "react"; import React from "react";
import { ActionManager } from "../actions/manager"; import { ActionManager } from "../actions/manager";
import { CLASSES, DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_WIDTH } from "../constants"; import { CLASSES, DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_WIDTH } from "../constants";
import { exportCanvas } from "../data";
import { isTextElement, showSelectedShapeActions } from "../element"; import { isTextElement, showSelectedShapeActions } from "../element";
import { NonDeletedExcalidrawElement } from "../element/types"; import { NonDeletedExcalidrawElement } from "../element/types";
import { Language, t } from "../i18n"; import { Language, t } from "../i18n";
import { calculateScrollCenter } from "../scene"; import { calculateScrollCenter } from "../scene";
import { ExportType } from "../scene/types";
import { import {
AppProps, AppProps,
AppState, AppState,
ExcalidrawProps, ExcalidrawProps,
BinaryFiles, BinaryFiles,
UIAppState, UIAppState,
AppClassProperties,
} from "../types"; } from "../types";
import { capitalizeString, isShallowEqual, muteFSAbortError } from "../utils"; import { capitalizeString, isShallowEqual } from "../utils";
import { SelectedShapeActions, ShapesSwitcher } from "./Actions"; import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
import { ErrorDialog } from "./ErrorDialog"; import { ErrorDialog } from "./ErrorDialog";
import { ExportCB, ImageExportDialog } from "./ImageExportDialog"; import { ImageExportDialog } from "./ImageExportDialog";
import { FixedSideContainer } from "./FixedSideContainer"; import { FixedSideContainer } from "./FixedSideContainer";
import { HintViewer } from "./HintViewer"; import { HintViewer } from "./HintViewer";
import { Island } from "./Island"; import { Island } from "./Island";
@ -31,7 +30,6 @@ import { HelpDialog } from "./HelpDialog";
import Stack from "./Stack"; import Stack from "./Stack";
import { UserList } from "./UserList"; import { UserList } from "./UserList";
import { JSONExportDialog } from "./JSONExportDialog"; import { JSONExportDialog } from "./JSONExportDialog";
import { isImageFileHandle } from "../data/blob";
import { PenModeButton } from "./PenModeButton"; import { PenModeButton } from "./PenModeButton";
import { trackEvent } from "../analytics"; import { trackEvent } from "../analytics";
import { useDevice } from "../components/App"; import { useDevice } from "../components/App";
@ -69,6 +67,7 @@ interface LayerUIProps {
renderCustomStats?: ExcalidrawProps["renderCustomStats"]; renderCustomStats?: ExcalidrawProps["renderCustomStats"];
UIOptions: AppProps["UIOptions"]; UIOptions: AppProps["UIOptions"];
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void; onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
onExportImage: AppClassProperties["onExportImage"];
renderWelcomeScreen: boolean; renderWelcomeScreen: boolean;
children?: React.ReactNode; children?: React.ReactNode;
} }
@ -114,6 +113,7 @@ const LayerUI = ({
renderCustomStats, renderCustomStats,
UIOptions, UIOptions,
onImageAction, onImageAction,
onExportImage,
renderWelcomeScreen, renderWelcomeScreen,
children, children,
}: LayerUIProps) => { }: LayerUIProps) => {
@ -143,47 +143,14 @@ const LayerUI = ({
return null; return null;
} }
const createExporter =
(type: ExportType): ExportCB =>
async (exportedElements) => {
trackEvent("export", type, "ui");
const fileHandle = await exportCanvas(
type,
exportedElements,
// FIXME once we split UI canvas from element canvas
appState as AppState,
files,
{
exportBackground: appState.exportBackground,
name: appState.name,
viewBackgroundColor: appState.viewBackgroundColor,
},
)
.catch(muteFSAbortError)
.catch((error) => {
console.error(error);
setAppState({ errorMessage: error.message });
});
if (
appState.exportEmbedScene &&
fileHandle &&
isImageFileHandle(fileHandle)
) {
setAppState({ fileHandle });
}
};
return ( return (
<ImageExportDialog <ImageExportDialog
elements={elements} elements={elements}
appState={appState} appState={appState}
setAppState={setAppState}
files={files} files={files}
actionManager={actionManager} actionManager={actionManager}
onExportToPng={createExporter("png")} onExportImage={onExportImage}
onExportToSvg={createExporter("svg")} onCloseRequest={() => setAppState({ openDialog: null })}
onExportToClipboard={createExporter("clipboard")}
/> />
); );
}; };

View file

@ -1,11 +1,6 @@
@import "open-color/open-color"; @import "open-color/open-color";
.excalidraw { .excalidraw {
.library-menu-items-container {
height: 100%;
width: 100%;
}
.layer-ui__library { .layer-ui__library {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -70,6 +65,16 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 0.625rem; gap: 0.625rem;
position: relative;
&--at-bottom::before {
content: "";
width: calc(100% - 1.5rem);
height: 1px;
position: absolute;
top: -1px;
background: var(--sidebar-border-color);
}
} }
.library-menu-browse-button { .library-menu-browse-button {
@ -126,4 +131,20 @@
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
} }
} }
.layer-ui__library .library-menu-dropdown-container {
position: relative;
&--in-heading {
padding: 0;
position: absolute;
top: 1rem;
right: 0.75rem;
z-index: 1;
.dropdown-menu {
top: 100%;
}
}
}
} }

View file

@ -121,12 +121,11 @@ export const LibraryMenuContent = ({
/> />
{showBtn && ( {showBtn && (
<LibraryMenuControlButtons <LibraryMenuControlButtons
className="library-menu-control-buttons--at-bottom"
style={{ padding: "16px 12px 0 12px" }} style={{ padding: "16px 12px 0 12px" }}
id={id} id={id}
libraryReturnUrl={libraryReturnUrl} libraryReturnUrl={libraryReturnUrl}
theme={appState.theme} theme={appState.theme}
selectedItems={selectedItems}
onSelectItems={onSelectItems}
/> />
)} )}
</LibraryMenuWrapper> </LibraryMenuWrapper>

View file

@ -1,33 +1,33 @@
import { LibraryItem, ExcalidrawProps, UIAppState } from "../types"; import { ExcalidrawProps, UIAppState } from "../types";
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton"; import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent"; import clsx from "clsx";
export const LibraryMenuControlButtons = ({ export const LibraryMenuControlButtons = ({
selectedItems,
onSelectItems,
libraryReturnUrl, libraryReturnUrl,
theme, theme,
id, id,
style, style,
children,
className,
}: { }: {
selectedItems: LibraryItem["id"][];
onSelectItems: (id: LibraryItem["id"][]) => void;
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"]; libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
theme: UIAppState["theme"]; theme: UIAppState["theme"];
id: string; id: string;
style: React.CSSProperties; style: React.CSSProperties;
children?: React.ReactNode;
className?: string;
}) => { }) => {
return ( return (
<div className="library-menu-control-buttons" style={style}> <div
className={clsx("library-menu-control-buttons", className)}
style={style}
>
<LibraryMenuBrowseButton <LibraryMenuBrowseButton
id={id} id={id}
libraryReturnUrl={libraryReturnUrl} libraryReturnUrl={libraryReturnUrl}
theme={theme} theme={theme}
/> />
<LibraryDropdownMenu {children}
selectedItems={selectedItems}
onSelectItems={onSelectItems}
/>
</div> </div>
); );
}; };

View file

@ -23,6 +23,7 @@ import { Dialog } from "./Dialog";
import DropdownMenu from "./dropdownMenu/DropdownMenu"; import DropdownMenu from "./dropdownMenu/DropdownMenu";
import { isLibraryMenuOpenAtom } from "./LibraryMenu"; import { isLibraryMenuOpenAtom } from "./LibraryMenu";
import { useUIAppState } from "../context/ui-appState"; import { useUIAppState } from "../context/ui-appState";
import clsx from "clsx";
const getSelectedItems = ( const getSelectedItems = (
libraryItems: LibraryItems, libraryItems: LibraryItems,
@ -37,6 +38,7 @@ export const LibraryDropdownMenuButton: React.FC<{
resetLibrary: () => void; resetLibrary: () => void;
onSelectItems: (items: LibraryItem["id"][]) => void; onSelectItems: (items: LibraryItem["id"][]) => void;
appState: UIAppState; appState: UIAppState;
className?: string;
}> = ({ }> = ({
setAppState, setAppState,
selectedItems, selectedItems,
@ -45,6 +47,7 @@ export const LibraryDropdownMenuButton: React.FC<{
resetLibrary, resetLibrary,
onSelectItems, onSelectItems,
appState, appState,
className,
}) => { }) => {
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope); const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom( const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom(
@ -236,7 +239,7 @@ export const LibraryDropdownMenuButton: React.FC<{
}; };
return ( return (
<div style={{ position: "relative" }}> <div className={clsx("library-menu-dropdown-container", className)}>
{renderLibraryMenu()} {renderLibraryMenu()}
{selectedItems.length > 0 && ( {selectedItems.length > 0 && (
<div className="library-actions-counter">{selectedItems.length}</div> <div className="library-actions-counter">{selectedItems.length}</div>
@ -270,9 +273,11 @@ export const LibraryDropdownMenuButton: React.FC<{
export const LibraryDropdownMenu = ({ export const LibraryDropdownMenu = ({
selectedItems, selectedItems,
onSelectItems, onSelectItems,
className,
}: { }: {
selectedItems: LibraryItem["id"][]; selectedItems: LibraryItem["id"][];
onSelectItems: (id: LibraryItem["id"][]) => void; onSelectItems: (id: LibraryItem["id"][]) => void;
className?: string;
}) => { }) => {
const { library } = useApp(); const { library } = useApp();
const appState = useUIAppState(); const appState = useUIAppState();
@ -308,6 +313,7 @@ export const LibraryDropdownMenu = ({
removeFromLibrary(libraryItemsData.libraryItems) removeFromLibrary(libraryItemsData.libraryItems)
} }
resetLibrary={resetLibrary} resetLibrary={resetLibrary}
className={className}
/> />
); );
}; };

View file

@ -26,6 +26,7 @@
} }
.library-menu-items-container { .library-menu-items-container {
width: 100%;
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
@ -35,10 +36,14 @@
height: 100%; height: 100%;
justify-content: center; justify-content: center;
margin: 0; margin: 0;
border-bottom: 1px solid var(--sidebar-border-color);
position: relative; position: relative;
& > div {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
&__row { &__row {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
@ -59,6 +64,9 @@
font-size: 1.125rem; font-size: 1.125rem;
font-weight: bold; font-weight: bold;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
width: 100%;
padding-right: 4rem; // due to dropdown button
box-sizing: border-box;
&--excal { &--excal {
margin-top: 2rem; margin-top: 2rem;
@ -75,4 +83,11 @@
color: var(--text-primary-color); color: var(--text-primary-color);
} }
} }
.library-menu-items-private-library-container {
// so that when you toggle between pending item and no items, there's
// no layout shift (this is hardcoded and works only with ENG locale)
min-height: 3.75rem;
width: 100%;
}
} }

View file

@ -15,6 +15,7 @@ import { MIME_TYPES } from "../constants";
import Spinner from "./Spinner"; import Spinner from "./Spinner";
import { duplicateElements } from "../element/newElement"; import { duplicateElements } from "../element/newElement";
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons"; import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent";
import "./LibraryMenuItems.scss"; import "./LibraryMenuItems.scss";
@ -207,6 +208,11 @@ const LibraryMenuItems = ({
const showBtn = !libraryItems.length && !pendingElements.length; const showBtn = !libraryItems.length && !pendingElements.length;
const isLibraryEmpty =
!pendingElements.length &&
!unpublishedItems.length &&
!publishedItems.length;
return ( return (
<div <div
className="library-menu-items-container" className="library-menu-items-container"
@ -218,6 +224,13 @@ const LibraryMenuItems = ({
: { borderBottom: 0 } : { borderBottom: 0 }
} }
> >
{!isLibraryEmpty && (
<LibraryDropdownMenu
selectedItems={selectedItems}
onSelectItems={onSelectItems}
className="library-menu-dropdown-container--in-heading"
/>
)}
<Stack.Col <Stack.Col
className="library-menu-items-container__items" className="library-menu-items-container__items"
align="start" align="start"
@ -228,10 +241,7 @@ const LibraryMenuItems = ({
}} }}
> >
<> <>
<div> {!isLibraryEmpty && (
{(pendingElements.length > 0 ||
unpublishedItems.length > 0 ||
publishedItems.length > 0) && (
<div className="library-menu-items-container__header"> <div className="library-menu-items-container__header">
{t("labels.personalLib")} {t("labels.personalLib")}
</div> </div>
@ -248,7 +258,7 @@ const LibraryMenuItems = ({
<Spinner /> <Spinner />
</div> </div>
)} )}
</div> <div className="library-menu-items-private-library-container">
{!pendingElements.length && !unpublishedItems.length ? ( {!pendingElements.length && !unpublishedItems.length ? (
<div className="library-menu-items__no-items"> <div className="library-menu-items__no-items">
<div className="library-menu-items__no-items__label"> <div className="library-menu-items__no-items__label">
@ -269,6 +279,7 @@ const LibraryMenuItems = ({
...unpublishedItems, ...unpublishedItems,
]) ])
)} )}
</div>
</> </>
<> <>
@ -304,9 +315,12 @@ const LibraryMenuItems = ({
id={id} id={id}
libraryReturnUrl={libraryReturnUrl} libraryReturnUrl={libraryReturnUrl}
theme={theme} theme={theme}
>
<LibraryDropdownMenu
selectedItems={selectedItems} selectedItems={selectedItems}
onSelectItems={onSelectItems} onSelectItems={onSelectItems}
/> />
</LibraryMenuControlButtons>
)} )}
</Stack.Col> </Stack.Col>
</div> </div>

View file

@ -1,5 +1,4 @@
import clsx from "clsx"; import clsx from "clsx";
import oc from "open-color";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useDevice } from "../components/App"; import { useDevice } from "../components/App";
import { exportToSvg } from "../packages/utils"; import { exportToSvg } from "../packages/utils";
@ -7,6 +6,7 @@ import { LibraryItem } from "../types";
import "./LibraryUnit.scss"; import "./LibraryUnit.scss";
import { CheckboxItem } from "./CheckboxItem"; import { CheckboxItem } from "./CheckboxItem";
import { PlusIcon } from "./icons"; import { PlusIcon } from "./icons";
import { COLOR_PALETTE } from "../colors";
export const LibraryUnit = ({ export const LibraryUnit = ({
id, id,
@ -40,7 +40,7 @@ export const LibraryUnit = ({
elements, elements,
appState: { appState: {
exportBackground: false, exportBackground: false,
viewBackgroundColor: oc.white, viewBackgroundColor: COLOR_PALETTE.white,
}, },
files: null, files: null,
}); });

View file

@ -5,6 +5,7 @@ import { focusNearestParent } from "../utils";
import "./ProjectName.scss"; import "./ProjectName.scss";
import { useExcalidrawContainer } from "./App"; import { useExcalidrawContainer } from "./App";
import { KEYS } from "../keys";
type Props = { type Props = {
value: string; value: string;
@ -26,7 +27,7 @@ export const ProjectName = (props: Props) => {
}; };
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => { const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Enter") { if (event.key === KEYS.ENTER) {
event.preventDefault(); event.preventDefault();
if (event.nativeEvent.isComposing || event.keyCode === 229) { if (event.nativeEvent.isComposing || event.keyCode === 229) {
return; return;

View file

@ -46,8 +46,17 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100%; width: 100%;
padding-top: 1rem; padding: 1rem 0.75rem;
padding-bottom: 1rem; position: relative;
&::after {
content: "";
width: calc(100% - 1.5rem);
height: 1px;
background: var(--sidebar-border-color);
position: absolute;
bottom: -1px;
}
} }
.sidebar__header__buttons { .sidebar__header__buttons {
@ -89,7 +98,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 1 auto; flex: 1 1 auto;
padding: 1rem 0.75rem; padding: 1rem 0;
[role="tabpanel"] { [role="tabpanel"] {
flex: 1; flex: 1;
@ -161,9 +170,5 @@
border: none; border: none;
} }
} }
.sidebar__header {
border-bottom: 1px solid var(--sidebar-border-color);
}
} }
} }

View file

@ -48,7 +48,7 @@ const MenuContent = ({
<Island <Island
className="dropdown-menu-container" className="dropdown-menu-container"
padding={2} padding={2}
style={{ zIndex: 1 }} style={{ zIndex: 2 }}
> >
{children} {children}
</Island> </Island>

View file

@ -0,0 +1,100 @@
import { render, queryAllByTestId } from "../../tests/test-utils";
import { Excalidraw, MainMenu } from "../../packages/excalidraw/index";
describe("Test internal component fallback rendering", () => {
it("should render only one menu per excalidraw instance (custom menu first scenario)", async () => {
const { container } = await render(
<div>
<Excalidraw>
<MainMenu>test</MainMenu>
</Excalidraw>
<Excalidraw />
</div>,
);
expect(queryAllByTestId(container, "dropdown-menu-button")?.length).toBe(2);
const excalContainers = container.querySelectorAll<HTMLDivElement>(
".excalidraw-container",
);
expect(
queryAllByTestId(excalContainers[0], "dropdown-menu-button")?.length,
).toBe(1);
expect(
queryAllByTestId(excalContainers[1], "dropdown-menu-button")?.length,
).toBe(1);
});
it("should render only one menu per excalidraw instance (default menu first scenario)", async () => {
const { container } = await render(
<div>
<Excalidraw />
<Excalidraw>
<MainMenu>test</MainMenu>
</Excalidraw>
</div>,
);
expect(queryAllByTestId(container, "dropdown-menu-button")?.length).toBe(2);
const excalContainers = container.querySelectorAll<HTMLDivElement>(
".excalidraw-container",
);
expect(
queryAllByTestId(excalContainers[0], "dropdown-menu-button")?.length,
).toBe(1);
expect(
queryAllByTestId(excalContainers[1], "dropdown-menu-button")?.length,
).toBe(1);
});
it("should render only one menu per excalidraw instance (two custom menus scenario)", async () => {
const { container } = await render(
<div>
<Excalidraw>
<MainMenu>test</MainMenu>
</Excalidraw>
<Excalidraw>
<MainMenu>test</MainMenu>
</Excalidraw>
</div>,
);
expect(queryAllByTestId(container, "dropdown-menu-button")?.length).toBe(2);
const excalContainers = container.querySelectorAll<HTMLDivElement>(
".excalidraw-container",
);
expect(
queryAllByTestId(excalContainers[0], "dropdown-menu-button")?.length,
).toBe(1);
expect(
queryAllByTestId(excalContainers[1], "dropdown-menu-button")?.length,
).toBe(1);
});
it("should render only one menu per excalidraw instance (two default menus scenario)", async () => {
const { container } = await render(
<div>
<Excalidraw />
<Excalidraw />
</div>,
);
expect(queryAllByTestId(container, "dropdown-menu-button")?.length).toBe(2);
const excalContainers = container.querySelectorAll<HTMLDivElement>(
".excalidraw-container",
);
expect(
queryAllByTestId(excalContainers[0], "dropdown-menu-button")?.length,
).toBe(1);
expect(
queryAllByTestId(excalContainers[1], "dropdown-menu-button")?.length,
).toBe(1);
});
});

View file

@ -1,5 +1,5 @@
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";
import React, { useLayoutEffect } from "react"; import React, { useLayoutEffect, useRef } from "react";
import { useTunnels } from "../../context/tunnels"; import { useTunnels } from "../../context/tunnels";
export const withInternalFallback = <P,>( export const withInternalFallback = <P,>(
@ -7,13 +7,6 @@ export const withInternalFallback = <P,>(
Component: React.FC<P>, Component: React.FC<P>,
) => { ) => {
const renderAtom = atom(0); const renderAtom = atom(0);
// flag set on initial render to tell the fallback component to skip the
// render until mount counter are initialized. This is because the counter
// is initialized in an effect, and thus we could end rendering both
// components at the same time until counter is initialized.
let preferHost = false;
let counter = 0;
const WrapperComponent: React.FC< const WrapperComponent: React.FC<
P & { P & {
@ -21,38 +14,52 @@ export const withInternalFallback = <P,>(
} }
> = (props) => { > = (props) => {
const { jotaiScope } = useTunnels(); const { jotaiScope } = useTunnels();
const [, setRender] = useAtom(renderAtom, jotaiScope); // for rerenders
const [, setCounter] = useAtom(renderAtom, jotaiScope);
// for initial & subsequent renders. Tracked as component state
// due to excalidraw multi-instance scanerios.
const metaRef = useRef({
// flag set on initial render to tell the fallback component to skip the
// render until mount counter are initialized. This is because the counter
// is initialized in an effect, and thus we could end rendering both
// components at the same time until counter is initialized.
preferHost: false,
counter: 0,
});
useLayoutEffect(() => { useLayoutEffect(() => {
setRender((c) => { const meta = metaRef.current;
setCounter((c) => {
const next = c + 1; const next = c + 1;
counter = next; meta.counter = next;
return next; return next;
}); });
return () => { return () => {
setRender((c) => { setCounter((c) => {
const next = c - 1; const next = c - 1;
counter = next; meta.counter = next;
if (!next) { if (!next) {
preferHost = false; meta.preferHost = false;
} }
return next; return next;
}); });
}; };
}, [setRender]); }, [setCounter]);
if (!props.__fallback) { if (!props.__fallback) {
preferHost = true; metaRef.current.preferHost = true;
} }
// ensure we don't render fallback and host components at the same time // ensure we don't render fallback and host components at the same time
if ( if (
// either before the counters are initialized // either before the counters are initialized
(!counter && props.__fallback && preferHost) || (!metaRef.current.counter &&
props.__fallback &&
metaRef.current.preferHost) ||
// or after the counters are initialized, and both are rendered // or after the counters are initialized, and both are rendered
// (this is the default when host renders as well) // (this is the default when host renders as well)
(counter > 1 && props.__fallback) (metaRef.current.counter > 1 && props.__fallback)
) { ) {
return null; return null;
} }

View file

@ -1,7 +1,7 @@
import cssVariables from "./css/variables.module.scss"; import cssVariables from "./css/variables.module.scss";
import { AppProps } from "./types"; import { AppProps } from "./types";
import { ExcalidrawElement, FontFamilyValues } from "./element/types"; import { ExcalidrawElement, FontFamilyValues } from "./element/types";
import oc from "open-color"; import { COLOR_PALETTE } from "./colors";
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform); export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
export const isWindows = /^Win/.test(navigator.platform); export const isWindows = /^Win/.test(navigator.platform);
@ -131,6 +131,12 @@ export const MIME_TYPES = {
...IMAGE_MIME_TYPES, ...IMAGE_MIME_TYPES,
} as const; } as const;
export const EXPORT_IMAGE_TYPES = {
png: "png",
svg: "svg",
clipboard: "clipboard",
} as const;
export const EXPORT_DATA_TYPES = { export const EXPORT_DATA_TYPES = {
excalidraw: "excalidraw", excalidraw: "excalidraw",
excalidrawClipboard: "excalidraw/clipboard", excalidrawClipboard: "excalidraw/clipboard",
@ -266,8 +272,8 @@ export const DEFAULT_ELEMENT_PROPS: {
opacity: ExcalidrawElement["opacity"]; opacity: ExcalidrawElement["opacity"];
locked: ExcalidrawElement["locked"]; locked: ExcalidrawElement["locked"];
} = { } = {
strokeColor: oc.black, strokeColor: COLOR_PALETTE.black,
backgroundColor: "transparent", backgroundColor: COLOR_PALETTE.transparent,
fillStyle: "hachure", fillStyle: "hachure",
strokeWidth: 1, strokeWidth: 1,
strokeStyle: "solid", strokeStyle: "solid",

View file

@ -538,6 +538,10 @@
height: 3px; height: 3px;
} }
select::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb); background: var(--scrollbar-thumb);
border-radius: 10px; border-radius: 10px;

View file

@ -35,7 +35,6 @@ import { LinearElementEditor } from "../element/linearElementEditor";
import { bumpVersion } from "../element/mutateElement"; import { bumpVersion } from "../element/mutateElement";
import { getFontString, getUpdatedTimestamp, updateActiveTool } from "../utils"; import { getFontString, getUpdatedTimestamp, updateActiveTool } from "../utils";
import { arrayToMap } from "../utils"; import { arrayToMap } from "../utils";
import oc from "open-color";
import { MarkOptional, Mutable } from "../utility-types"; import { MarkOptional, Mutable } from "../utility-types";
import { import {
detectLineHeight, detectLineHeight,
@ -43,6 +42,7 @@ import {
measureBaseline, measureBaseline,
} from "../element/textElement"; } from "../element/textElement";
import { convertToExcalidrawElements } from "../element/newElement"; import { convertToExcalidrawElements } from "../element/newElement";
import { COLOR_PALETTE } from "../colors";
type RestoredAppState = Omit< type RestoredAppState = Omit<
AppState, AppState,

View file

@ -1521,7 +1521,7 @@ describe("textWysiwyg", () => {
roundness: { roundness: {
type: 3, type: 3,
}, },
strokeColor: "#000000", strokeColor: "#1e1e1e",
strokeStyle: "solid", strokeStyle: "solid",
strokeWidth: 1, strokeWidth: 1,
type: "rectangle", type: "rectangle",

View file

@ -254,7 +254,6 @@ export const textWysiwyg = ({
const initialSelectionStart = editable.selectionStart; const initialSelectionStart = editable.selectionStart;
const initialSelectionEnd = editable.selectionEnd; const initialSelectionEnd = editable.selectionEnd;
const initialLength = editable.value.length; const initialLength = editable.value.length;
editable.value = updatedTextElement.originalText;
// restore cursor position after value updated so it doesn't // restore cursor position after value updated so it doesn't
// go to the end of text when container auto expanded // go to the end of text when container auto expanded
@ -358,6 +357,7 @@ export const textWysiwyg = ({
overflowWrap: "break-word", overflowWrap: "break-word",
boxSizing: "content-box", boxSizing: "content-box",
}); });
editable.value = element.originalText;
updateWysiwygStyle(); updateWysiwygStyle();
if (onChange) { if (onChange) {
@ -636,20 +636,46 @@ export const textWysiwyg = ({
// in that same tick. // in that same tick.
const target = event?.target; const target = event?.target;
const isTargetColorPicker = const isTargetPickerTrigger =
target instanceof HTMLInputElement && target instanceof HTMLElement &&
target.closest(".color-picker-input") && target.classList.contains("active-color");
isWritableElement(target);
setTimeout(() => { setTimeout(() => {
editable.onblur = handleSubmit; editable.onblur = handleSubmit;
if (target && isTargetColorPicker) {
target.onblur = () => { if (isTargetPickerTrigger) {
const callback = (
mutationList: MutationRecord[],
observer: MutationObserver,
) => {
const radixIsRemoved = mutationList.find(
(mutation) =>
mutation.removedNodes.length > 0 &&
(mutation.removedNodes[0] as HTMLElement).dataset
?.radixPopperContentWrapper !== undefined,
);
if (radixIsRemoved) {
// should work without this in theory
// and i think it does actually but radix probably somewhere,
// somehow sets the focus elsewhere
setTimeout(() => {
editable.focus(); editable.focus();
}; });
observer.disconnect();
} }
};
const observer = new MutationObserver(callback);
observer.observe(document.querySelector(".excalidraw-container")!, {
childList: true,
});
}
// case: clicking on the same property → no change → no update → no focus // case: clicking on the same property → no change → no update → no focus
if (!isTargetColorPicker) { if (!isTargetPickerTrigger) {
editable.focus(); editable.focus();
} }
}); });
@ -657,16 +683,16 @@ export const textWysiwyg = ({
// prevent blur when changing properties from the menu // prevent blur when changing properties from the menu
const onPointerDown = (event: MouseEvent) => { const onPointerDown = (event: MouseEvent) => {
const isTargetColorPicker = const isTargetPickerTrigger =
event.target instanceof HTMLInputElement && event.target instanceof HTMLElement &&
event.target.closest(".color-picker-input") && event.target.classList.contains("active-color");
isWritableElement(event.target);
if ( if (
((event.target instanceof HTMLElement || ((event.target instanceof HTMLElement ||
event.target instanceof SVGElement) && event.target instanceof SVGElement) &&
event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) && event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) &&
!isWritableElement(event.target)) || !isWritableElement(event.target)) ||
isTargetColorPicker isTargetPickerTrigger
) { ) {
editable.onblur = null; editable.onblur = null;
window.addEventListener("pointerup", bindBlurEvent); window.addEventListener("pointerup", bindBlurEvent);
@ -680,7 +706,7 @@ export const textWysiwyg = ({
const unbindUpdate = Scene.getScene(element)!.addCallback(() => { const unbindUpdate = Scene.getScene(element)!.addCallback(() => {
updateWysiwygStyle(); updateWysiwygStyle();
const isColorPickerActive = !!document.activeElement?.closest( const isColorPickerActive = !!document.activeElement?.closest(
".color-picker-input", ".color-picker-content",
); );
if (!isColorPickerActive) { if (!isColorPickerActive) {
editable.focus(); editable.focus();

View file

@ -17,6 +17,7 @@ import { trackEvent } from "../../analytics";
import { getFrame } from "../../utils"; import { getFrame } from "../../utils";
import DialogActionButton from "../../components/DialogActionButton"; import DialogActionButton from "../../components/DialogActionButton";
import { useI18n } from "../../i18n"; import { useI18n } from "../../i18n";
import { KEYS } from "../../keys";
const getShareIcon = () => { const getShareIcon = () => {
const navigator = window.navigator as any; const navigator = window.navigator as any;
@ -148,13 +149,15 @@ const RoomDialog = ({
value={username.trim() || ""} value={username.trim() || ""}
className="RoomDialog-username TextInput" className="RoomDialog-username TextInput"
onChange={(event) => onUsernameChange(event.target.value)} onChange={(event) => onUsernameChange(event.target.value)}
onKeyPress={(event) => event.key === "Enter" && handleClose()} onKeyPress={(event) =>
event.key === KEYS.ENTER && handleClose()
}
/> />
</div> </div>
<p> <p>
<span role="img" aria-hidden="true" className="RoomDialog-emoji"> <span role="img" aria-hidden="true" className="RoomDialog-emoji">
{"🔒"} {"🔒"}
</span>{" "} </span>
{t("roomDialog.desc_privacy")} {t("roomDialog.desc_privacy")}
</p> </p>
<p>{t("roomDialog.desc_exitSession")}</p> <p>{t("roomDialog.desc_exitSession")}</p>

View file

@ -1,7 +1,7 @@
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import React from "react"; import React from "react";
import { appLangCodeAtom } from ".."; import { appLangCodeAtom } from "..";
import { defaultLang, useI18n } from "../../i18n"; import { useI18n } from "../../i18n";
import { languages } from "../../i18n"; import { languages } from "../../i18n";
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => { export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
@ -16,9 +16,6 @@ export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
aria-label={t("buttons.selectLanguage")} aria-label={t("buttons.selectLanguage")}
style={style} style={style}
> >
<option key={defaultLang.code} value={defaultLang.code}>
{defaultLang.label}
</option>
{languages.map((lang) => ( {languages.map((lang) => (
<option key={lang.code} value={lang.code}> <option key={lang.code} value={lang.code}>
{lang.label} {lang.label}

View file

@ -14,7 +14,9 @@ export interface Language {
export const defaultLang = { code: "en", label: "English" }; export const defaultLang = { code: "en", label: "English" };
const allLanguages: Language[] = [ export const languages: Language[] = [
defaultLang,
...[
{ code: "ar-SA", label: "العربية", rtl: true }, { code: "ar-SA", label: "العربية", rtl: true },
{ code: "bg-BG", label: "Български" }, { code: "bg-BG", label: "Български" },
{ code: "ca-ES", label: "Català" }, { code: "ca-ES", label: "Català" },
@ -59,15 +61,14 @@ const allLanguages: Language[] = [
{ code: "zh-TW", label: "繁體中文" }, { code: "zh-TW", label: "繁體中文" },
{ code: "vi-VN", label: "Tiếng Việt" }, { code: "vi-VN", label: "Tiếng Việt" },
{ code: "mr-IN", label: "मराठी" }, { code: "mr-IN", label: "मराठी" },
].concat([defaultLang]); ]
export const languages: Language[] = allLanguages
.sort((left, right) => (left.label > right.label ? 1 : -1))
.filter( .filter(
(lang) => (lang) =>
(percentages as Record<string, number>)[lang.code] >= (percentages as Record<string, number>)[lang.code] >=
COMPLETION_THRESHOLD, COMPLETION_THRESHOLD,
); )
.sort((left, right) => (left.label > right.label ? 1 : -1)),
];
const TEST_LANG_CODE = "__test__"; const TEST_LANG_CODE = "__test__";
if (process.env.NODE_ENV === ENV.DEVELOPMENT) { if (process.env.NODE_ENV === ENV.DEVELOPMENT) {
@ -123,7 +124,8 @@ const findPartsForData = (data: any, parts: string[]) => {
export const t = ( export const t = (
path: string, path: string,
replacement?: { [key: string]: string | number }, replacement?: { [key: string]: string | number } | null,
fallback?: string,
) => { ) => {
if (currentLang.code.startsWith(TEST_LANG_CODE)) { if (currentLang.code.startsWith(TEST_LANG_CODE)) {
const name = replacement const name = replacement
@ -135,9 +137,16 @@ export const t = (
const parts = path.split("."); const parts = path.split(".");
let translation = let translation =
findPartsForData(currentLangData, parts) || findPartsForData(currentLangData, parts) ||
findPartsForData(fallbackLangData, parts); findPartsForData(fallbackLangData, parts) ||
fallback;
if (translation === undefined) { if (translation === undefined) {
throw new Error(`Can't find translation for ${path}`); const errorMessage = `Can't find translation for ${path}`;
// in production, don't blow up the app on a missing translation key
if (process.env.NODE_ENV === "production") {
console.warn(errorMessage);
return "";
}
throw new Error(errorMessage);
} }
if (replacement) { if (replacement) {

View file

@ -264,16 +264,11 @@
"canvasTooBigTip": "نصيحة: حاول تحريك العناصر البعيدة بشكل أقرب قليلاً." "canvasTooBigTip": "نصيحة: حاول تحريك العناصر البعيدة بشكل أقرب قليلاً."
}, },
"errorSplash": { "errorSplash": {
"headingMain_pre": "حدث خطأ، حاول مرة أخرى ", "headingMain": "",
"headingMain_button": "إعادة تحميل الصفحة.",
"clearCanvasMessage": "إذا لم تعمل إعادة التحميل، حاول مرة أخرى ", "clearCanvasMessage": "إذا لم تعمل إعادة التحميل، حاول مرة أخرى ",
"clearCanvasMessage_button": "مسح اللوحة.",
"clearCanvasCaveat": " هذا سيؤدي إلى فقدان العمل ", "clearCanvasCaveat": " هذا سيؤدي إلى فقدان العمل ",
"trackedToSentry_pre": "الخطأ ", "trackedToSentry": "",
"trackedToSentry_post": " تم تعقبه على نظامنا.", "openIssueMessage": "",
"openIssueMessage_pre": "كنا حذرين جدا لعدم تضمين معلومات المشهد الخاصة بك في الخطأ. إذا لم يكن المشهد خاصًا ، يرجى النظر في متابعة هذا الأمر ",
"openIssueMessage_button": "متعقّب الخلل.",
"openIssueMessage_post": " يرجى تضمين المعلومات أدناة عن طريق نسخ ولصق المشكلة في GitHub.",
"sceneContent": "محتوى المشهد:" "sceneContent": "محتوى المشهد:"
}, },
"roomDialog": { "roomDialog": {
@ -353,29 +348,16 @@
"required": "مطلوب", "required": "مطلوب",
"website": "أدخل عنوان URL صالح" "website": "أدخل عنوان URL صالح"
}, },
"noteDescription": { "noteDescription": "",
"pre": "", "noteGuidelines": "",
"link": "مستودع المكتبة العامة", "noteLicense": "",
"post": "ليستخدمها الآخرون في رسوماتهم."
},
"noteGuidelines": {
"pre": "يجب الموافقة على المكتبة يدويًا أولاً. يرجى قراءة ",
"link": "الإرشادات",
"post": ""
},
"noteLicense": {
"pre": "",
"link": "رخصة إم أي تي ",
"post": "وهو ما يعني باختصار أنه يمكن لأي شخص استخدامها دون قيود."
},
"noteItems": "يجب أن يكون لكل عنصر مكتبة اسمه الخاص حتى يكون قابلاً للتصفية. سيتم تضمين عناصر المكتبة التالية:", "noteItems": "يجب أن يكون لكل عنصر مكتبة اسمه الخاص حتى يكون قابلاً للتصفية. سيتم تضمين عناصر المكتبة التالية:",
"atleastOneLibItem": "يرجى تحديد عنصر مكتبة واحد على الأقل للبدء", "atleastOneLibItem": "يرجى تحديد عنصر مكتبة واحد على الأقل للبدء",
"republishWarning": "" "republishWarning": ""
}, },
"publishSuccessDialog": { "publishSuccessDialog": {
"title": "تم إرسال المكتبة", "title": "تم إرسال المكتبة",
"content": "شكرا لك {{authorName}}. لقد تم إرسال مكتبتك للمراجعة. يمكنك تتبع الحالة", "content": "شكرا لك {{authorName}}. لقد تم إرسال مكتبتك للمراجعة. يمكنك تتبع الحالة"
"link": "هنا"
}, },
"confirmDialog": { "confirmDialog": {
"resetLibrary": "إعادة ضبط المكتبة", "resetLibrary": "إعادة ضبط المكتبة",
@ -412,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "أبيض",
"f8f9fa": "رمادي 0",
"f1f3f5": "رمادي 1",
"fff5f5": "أحمر 0",
"fff0f6": "وردي 0",
"f8f0fc": "عنبي 0",
"f3f0ff": "بنفسجي 0",
"edf2ff": "نيلي 0",
"e7f5ff": "أزرق 0",
"e3fafc": "سماوي 0",
"e6fcf5": "تركواز 0",
"ebfbee": "أخضر 0",
"f4fce3": "ليموني 0",
"fff9db": "أصفر 0",
"fff4e6": "برتقالي 0",
"transparent": "شفاف", "transparent": "شفاف",
"ced4da": "رمادي 4", "black": "",
"868e96": "رمادي 6", "white": "",
"fa5252": "أحمر 6", "red": "",
"e64980": "وردي 6", "pink": "",
"be4bdb": "عنبي 6", "grape": "",
"7950f2": "بنفسجي 6", "violet": "",
"4c6ef5": "نيلي 6", "gray": "",
"228be6": "أزرق 6", "blue": "",
"15aabf": "سماوي 6", "cyan": "",
"12b886": "تركواز 6", "teal": "",
"40c057": "أخضر 6", "green": "",
"82c91e": "ليموني 6", "yellow": "",
"fab005": "أصفر 6", "orange": "",
"fd7e14": "برتقالي 6", "bronze": ""
"000000": "أسود",
"343a40": "رمادي 8",
"495057": "رمادي 7",
"c92a2a": "أحمر 9",
"a61e4d": "وردي 9",
"862e9c": "عنبي 9",
"5f3dc4": "بنفسجي 9",
"364fc7": "نيلي 9",
"1864ab": "أزرق 9",
"0b7285": "سماوي 9",
"087f5b": "تركواز 9",
"2b8a3e": "أخضر 9",
"5c940d": "ليموني 9",
"e67700": "أصفر 9",
"d9480f": "برتقالي 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -470,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "",
"f8f9fa": "",
"f1f3f5": "",
"fff5f5": "",
"fff0f6": "",
"f8f0fc": "",
"f3f0ff": "",
"edf2ff": "",
"e7f5ff": "",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "",
"f4fce3": "",
"fff9db": "",
"fff4e6": "",
"transparent": "", "transparent": "",
"ced4da": "", "black": "",
"868e96": "", "white": "",
"fa5252": "", "red": "",
"e64980": "", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "", "green": "",
"82c91e": "", "yellow": "",
"fab005": "", "orange": "",
"fd7e14": "", "bronze": ""
"000000": "",
"343a40": "",
"495057": "",
"c92a2a": "",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "",
"0b7285": "",
"087f5b": "",
"2b8a3e": "",
"5c940d": "",
"e67700": "",
"d9480f": ""
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "সাদা",
"f8f9fa": "",
"f1f3f5": "",
"fff5f5": "",
"fff0f6": "",
"f8f0fc": "",
"f3f0ff": "",
"edf2ff": "",
"e7f5ff": "",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "",
"f4fce3": "",
"fff9db": "",
"fff4e6": "",
"transparent": "", "transparent": "",
"ced4da": "", "black": "",
"868e96": "", "white": "",
"fa5252": "", "red": "",
"e64980": "", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "", "green": "",
"82c91e": "", "yellow": "",
"fab005": "", "orange": "",
"fd7e14": "", "bronze": ""
"000000": "কালো",
"343a40": "",
"495057": "",
"c92a2a": "",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "",
"0b7285": "",
"087f5b": "",
"2b8a3e": "",
"5c940d": "",
"e67700": "",
"d9480f": ""
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Fer servir {{shortcut}} per enganxar com un sol element,\no enganxeu-lo en un editor de text existent" "pasteAsSingleElement": "Fer servir {{shortcut}} per enganxar com un sol element,\no enganxeu-lo en un editor de text existent"
}, },
"colors": { "colors": {
"ffffff": "Blanc",
"f8f9fa": "Gris 0",
"f1f3f5": "Gris 1",
"fff5f5": "Vermell 0",
"fff0f6": "Rosa 0",
"f8f0fc": "Malva 0",
"f3f0ff": "Violat 0",
"edf2ff": "Indi 0",
"e7f5ff": "Blau 0",
"e3fafc": "Cian 0",
"e6fcf5": "Xarxet 0",
"ebfbee": "Verd 0",
"f4fce3": "Llima 0",
"fff9db": "Groc 0",
"fff4e6": "Taronja 0",
"transparent": "Transparent", "transparent": "Transparent",
"ced4da": "Gris 4", "black": "",
"868e96": "Gris 6", "white": "",
"fa5252": "Vermell 6", "red": "",
"e64980": "Rosa 6", "pink": "",
"be4bdb": "Malva 6", "grape": "",
"7950f2": "Violat 6", "violet": "",
"4c6ef5": "Indi 6", "gray": "",
"228be6": "Blau 6", "blue": "",
"15aabf": "Cian 6", "cyan": "",
"12b886": "Xarxet 6", "teal": "",
"40c057": "Verd 6", "green": "",
"82c91e": "Llima 6", "yellow": "",
"fab005": "Groc 6", "orange": "",
"fd7e14": "Taronja 6", "bronze": ""
"000000": "Negre",
"343a40": "Gris 8",
"495057": "Gris 7",
"c92a2a": "Vermell 9",
"a61e4d": "Rosa 9",
"862e9c": "Malva 9",
"5f3dc4": "Violat 9",
"364fc7": "Indi 9",
"1864ab": "Blau 9",
"0b7285": "Cian 9",
"087f5b": "Xarxet 9",
"2b8a3e": "Verd 9",
"5c940d": "Llima 9",
"e67700": "Groc 9",
"d9480f": "Taronja 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Selecciona una eina i comença a dibuixar!", "toolbarHint": "Selecciona una eina i comença a dibuixar!",
"helpHint": "Dreceres i ajuda" "helpHint": "Dreceres i ajuda"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "Bílá",
"f8f9fa": "Šedá 0",
"f1f3f5": "Šedá 1",
"fff5f5": "Červená 0",
"fff0f6": "Růžová 0",
"f8f0fc": "Vínová 0",
"f3f0ff": "Fialová 0",
"edf2ff": "Indigová 0",
"e7f5ff": "Modrá 0",
"e3fafc": "Azurová 0",
"e6fcf5": "Modrozelená 0",
"ebfbee": "Zelená 0",
"f4fce3": "Limetková 0",
"fff9db": "Žlutá 0",
"fff4e6": "Oranžová 0",
"transparent": "Průhledná", "transparent": "Průhledná",
"ced4da": "Šedá 4", "black": "",
"868e96": "Šedá 6", "white": "",
"fa5252": "Červená 6", "red": "",
"e64980": "Růžová 6", "pink": "",
"be4bdb": "Vínová 6", "grape": "",
"7950f2": "Fialová 6", "violet": "",
"4c6ef5": "Indigová 6", "gray": "",
"228be6": "Modrá 6", "blue": "",
"15aabf": "Azurová 6", "cyan": "",
"12b886": "Modrozelená 6", "teal": "",
"40c057": "Zelená 6", "green": "",
"82c91e": "Limetková 6", "yellow": "",
"fab005": "Žlutá 6", "orange": "",
"fd7e14": "Oranžová 6", "bronze": ""
"000000": "Černá",
"343a40": "Šedá 8",
"495057": "Šedá 7",
"c92a2a": "Červená 9",
"a61e4d": "Růžová 9",
"862e9c": "Vínová 9",
"5f3dc4": "Fialová 9",
"364fc7": "Indigová 9",
"1864ab": "Modrá 9",
"0b7285": "Azurová 9",
"087f5b": "Modrozelená 9",
"2b8a3e": "Zelená 9",
"5c940d": "Limetková 9",
"e67700": "Žlutá 9",
"d9480f": "Oranzova"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "",
"f8f9fa": "",
"f1f3f5": "",
"fff5f5": "",
"fff0f6": "",
"f8f0fc": "",
"f3f0ff": "",
"edf2ff": "",
"e7f5ff": "",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "",
"f4fce3": "",
"fff9db": "",
"fff4e6": "",
"transparent": "", "transparent": "",
"ced4da": "", "black": "",
"868e96": "", "white": "",
"fa5252": "", "red": "",
"e64980": "", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "", "green": "",
"82c91e": "", "yellow": "",
"fab005": "", "orange": "",
"fd7e14": "", "bronze": ""
"000000": "",
"343a40": "",
"495057": "",
"c92a2a": "",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "",
"0b7285": "",
"087f5b": "",
"2b8a3e": "",
"5c940d": "",
"e67700": "",
"d9480f": ""
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -268,7 +268,7 @@
"clearCanvasMessage": "Wenn das Neuladen nicht funktioniert, versuche <button>die Zeichenfläche zu löschen.</button>", "clearCanvasMessage": "Wenn das Neuladen nicht funktioniert, versuche <button>die Zeichenfläche zu löschen.</button>",
"clearCanvasCaveat": " Dies wird zum Verlust von Daten führen ", "clearCanvasCaveat": " Dies wird zum Verlust von Daten führen ",
"trackedToSentry": "Der Fehler mit der Kennung {{eventId}} wurde in unserem System registriert.", "trackedToSentry": "Der Fehler mit der Kennung {{eventId}} wurde in unserem System registriert.",
"openIssueMessage": "Wir waren sehr vorsichtig und haben deine Zeichnungsinformationen nicht in die Fehlerinformationen aufgenommen. Wenn deine Zeichnung nicht privat ist, unterstütze uns bitte über unseren <button>Bug-Tracker.</button> Bitte teile die unten stehenden Informationen mit uns im GitHub Issue (Kopieren und Einfügen).", "openIssueMessage": "Wir waren sehr vorsichtig und haben deine Zeichnungsinformationen nicht in die Fehlerinformationen aufgenommen. Wenn deine Zeichnung nicht privat ist, unterstütze uns bitte über unseren <button>Bug-Tracker</button>. Bitte teile die unten stehenden Informationen mit uns im GitHub Issue (Kopieren und Einfügen).",
"sceneContent": "Zeichnungsinhalt:" "sceneContent": "Zeichnungsinhalt:"
}, },
"roomDialog": { "roomDialog": {
@ -394,51 +394,21 @@
"pasteAsSingleElement": "Verwende {{shortcut}} , um als einzelnes Element\neinzufügen oder in einen existierenden Texteditor einzufügen" "pasteAsSingleElement": "Verwende {{shortcut}} , um als einzelnes Element\neinzufügen oder in einen existierenden Texteditor einzufügen"
}, },
"colors": { "colors": {
"ffffff": "Weiß",
"f8f9fa": "Grau 0",
"f1f3f5": "Grau 1",
"fff5f5": "Rot 0",
"fff0f6": "Pink 0",
"f8f0fc": "Traube 0",
"f3f0ff": "Violett 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Blau 0",
"e3fafc": "Cyan 0",
"e6fcf5": "Teal 0",
"ebfbee": "Grün 0",
"f4fce3": "Hellgrün 0",
"fff9db": "Gelb 0",
"fff4e6": "Orange 0",
"transparent": "Transparent", "transparent": "Transparent",
"ced4da": "Grau 4", "black": "",
"868e96": "Grau 6", "white": "",
"fa5252": "Rot 6", "red": "",
"e64980": "Pink 6", "pink": "",
"be4bdb": "Traube 6", "grape": "",
"7950f2": "Violett 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Blau 6", "blue": "",
"15aabf": "Cyan 6", "cyan": "",
"12b886": "Teal 6", "teal": "",
"40c057": "Grün 6", "green": "",
"82c91e": "Hellgrün 6", "yellow": "",
"fab005": "Gelb 6", "orange": "",
"fd7e14": "Orange 6", "bronze": ""
"000000": "Schwarz",
"343a40": "Grau 8",
"495057": "Grau 7",
"c92a2a": "Rot 9",
"a61e4d": "Pink 9",
"862e9c": "Traube 9",
"5f3dc4": "Violett 9",
"364fc7": "Indigo 9",
"1864ab": "Blau 9",
"0b7285": "Cyan 9",
"087f5b": "Teal 9",
"2b8a3e": "Grün 9",
"5c940d": "Hellgrün 9",
"e67700": "Gelb 9",
"d9480f": "Orange 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Wähle ein Werkzeug & beginne zu zeichnen!", "toolbarHint": "Wähle ein Werkzeug & beginne zu zeichnen!",
"helpHint": "Kurzbefehle & Hilfe" "helpHint": "Kurzbefehle & Hilfe"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Χρησιμοποίησε το {{shortcut}} για να επικολλήσεις ως ένα μόνο στοιχείο,\nή να επικολλήσεις σε έναν υπάρχοντα επεξεργαστή κειμένου" "pasteAsSingleElement": "Χρησιμοποίησε το {{shortcut}} για να επικολλήσεις ως ένα μόνο στοιχείο,\nή να επικολλήσεις σε έναν υπάρχοντα επεξεργαστή κειμένου"
}, },
"colors": { "colors": {
"ffffff": "Λευκό",
"f8f9fa": "Γκρι 0",
"f1f3f5": "Γκρι 1",
"fff5f5": "Κόκκινο 0",
"fff0f6": "Ροζ 0",
"f8f0fc": "Σταφυλί 0",
"f3f0ff": "Βιολετί 0",
"edf2ff": "Λουλάκι 0",
"e7f5ff": "Μπλε 0",
"e3fafc": "Κυανό 0",
"e6fcf5": "Τιρκουάζ 0",
"ebfbee": "Πράσινο 0",
"f4fce3": "Πρασινοκίτρινο 0",
"fff9db": "Κίτρινο 0",
"fff4e6": "Πορτοκαλί 0",
"transparent": "Διαφανές", "transparent": "Διαφανές",
"ced4da": "Γκρι 4", "black": "",
"868e96": "Γκρι 6", "white": "",
"fa5252": "Κόκκινο 6", "red": "",
"e64980": "Ροζ 6", "pink": "",
"be4bdb": "Σταφυλί 6", "grape": "",
"7950f2": "Βιολετί 6", "violet": "",
"4c6ef5": "Λουλάκι 6", "gray": "",
"228be6": "Μπλε 6", "blue": "",
"15aabf": "Κυανό 6", "cyan": "",
"12b886": "Τιρκουάζ 6", "teal": "",
"40c057": "Πράσινο 6", "green": "",
"82c91e": "Πρασινοκίτρινο 6", "yellow": "",
"fab005": "Κίτρινο 6", "orange": "",
"fd7e14": "Πορτοκαλί 6", "bronze": ""
"000000": "Μαύρο",
"343a40": "Γκρι 8",
"495057": "Γκρι 7",
"c92a2a": "Κόκκινο 9",
"a61e4d": "Ροζ 9",
"862e9c": "Σταφυλί 9",
"5f3dc4": "Βιολετί 9",
"364fc7": "Λουλάκι 9",
"1864ab": "Μπλε 9",
"0b7285": "Κυανό 9",
"087f5b": "Τιρκουάζ 9",
"2b8a3e": "Πράσινο 9",
"5c940d": "Πρασινοκίτρινο 9",
"e67700": "Κίτρινο 9",
"d9480f": "Πορτοκαλί 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Επιλέξτε ένα εργαλείο και ξεκινήστε να σχεδιάζεται!", "toolbarHint": "Επιλέξτε ένα εργαλείο και ξεκινήστε να σχεδιάζεται!",
"helpHint": "Συντομεύσεις και βοήθεια" "helpHint": "Συντομεύσεις και βοήθεια"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Use {{shortcut}} to paste as a single element,\nor paste into an existing text editor" "pasteAsSingleElement": "Use {{shortcut}} to paste as a single element,\nor paste into an existing text editor"
}, },
"colors": { "colors": {
"ffffff": "White",
"f8f9fa": "Gray 0",
"f1f3f5": "Gray 1",
"fff5f5": "Red 0",
"fff0f6": "Pink 0",
"f8f0fc": "Grape 0",
"f3f0ff": "Violet 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Blue 0",
"e3fafc": "Cyan 0",
"e6fcf5": "Teal 0",
"ebfbee": "Green 0",
"f4fce3": "Lime 0",
"fff9db": "Yellow 0",
"fff4e6": "Orange 0",
"transparent": "Transparent", "transparent": "Transparent",
"ced4da": "Gray 4", "black": "Black",
"868e96": "Gray 6", "white": "White",
"fa5252": "Red 6", "red": "Red",
"e64980": "Pink 6", "pink": "Pink",
"be4bdb": "Grape 6", "grape": "Grape",
"7950f2": "Violet 6", "violet": "Violet",
"4c6ef5": "Indigo 6", "gray": "Gray",
"228be6": "Blue 6", "blue": "Blue",
"15aabf": "Cyan 6", "cyan": "Cyan",
"12b886": "Teal 6", "teal": "Teal",
"40c057": "Green 6", "green": "Green",
"82c91e": "Lime 6", "yellow": "Yellow",
"fab005": "Yellow 6", "orange": "Orange",
"fd7e14": "Orange 6", "bronze": "Bronze"
"000000": "Black",
"343a40": "Gray 8",
"495057": "Gray 7",
"c92a2a": "Red 9",
"a61e4d": "Pink 9",
"862e9c": "Grape 9",
"5f3dc4": "Violet 9",
"364fc7": "Indigo 9",
"1864ab": "Blue 9",
"0b7285": "Cyan 9",
"087f5b": "Teal 9",
"2b8a3e": "Green 9",
"5c940d": "Lime 9",
"e67700": "Yellow 9",
"d9480f": "Orange 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Pick a tool & Start drawing!", "toolbarHint": "Pick a tool & Start drawing!",
"helpHint": "Shortcuts & help" "helpHint": "Shortcuts & help"
} }
},
"colorPicker": {
"mostUsedCustomColors": "Most used custom colors",
"colors": "Colors",
"shades": "Shades",
"hexCode": "Hex code",
"noShades": "No shades available for this color"
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Usa {{shortcut}} para pegar como un solo elemento,\no pegar en un editor de texto existente" "pasteAsSingleElement": "Usa {{shortcut}} para pegar como un solo elemento,\no pegar en un editor de texto existente"
}, },
"colors": { "colors": {
"ffffff": "Blanco",
"f8f9fa": "Gris 0",
"f1f3f5": "Gris 1",
"fff5f5": "Rojo 0",
"fff0f6": "Rosa 0",
"f8f0fc": "Uva 0",
"f3f0ff": "Violeta 0",
"edf2ff": "Índigo 0",
"e7f5ff": "Azul 0",
"e3fafc": "Cian 0",
"e6fcf5": "Turquesa 0",
"ebfbee": "Verde 0",
"f4fce3": "Lima 0",
"fff9db": "Amarillo 0",
"fff4e6": "Naranja 0",
"transparent": "Transparente", "transparent": "Transparente",
"ced4da": "Gris 4", "black": "",
"868e96": "Gris 6", "white": "",
"fa5252": "Rojo 6", "red": "",
"e64980": "Rosa 6", "pink": "",
"be4bdb": "Uva 6", "grape": "",
"7950f2": "Violeta 6", "violet": "",
"4c6ef5": "Índigo 6", "gray": "",
"228be6": "Azul 6", "blue": "",
"15aabf": "Cian 6", "cyan": "",
"12b886": "Turquesa 6", "teal": "",
"40c057": "Verde 6", "green": "",
"82c91e": "Lima 6", "yellow": "",
"fab005": "Amarillo 6", "orange": "",
"fd7e14": "Naranja 6", "bronze": ""
"000000": "Negro",
"343a40": "Gris 8",
"495057": "Gris 7",
"c92a2a": "Rojo 9",
"a61e4d": "Rosa 9",
"862e9c": "Uva 9",
"5f3dc4": "Violeta 9",
"364fc7": "Índigo 9",
"1864ab": "Azul 9",
"0b7285": "Cian 9",
"087f5b": "Turquesa 9",
"2b8a3e": "Verde 9",
"5c940d": "Lima 9",
"e67700": "Amarillo 9",
"d9480f": "Naranja 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "¡Elige una herramienta y empieza a dibujar!", "toolbarHint": "¡Elige una herramienta y empieza a dibujar!",
"helpHint": "Atajos y ayuda" "helpHint": "Atajos y ayuda"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -54,7 +54,7 @@
"veryLarge": "Oso handia", "veryLarge": "Oso handia",
"solid": "Solidoa", "solid": "Solidoa",
"hachure": "Itzalduna", "hachure": "Itzalduna",
"zigzag": "", "zigzag": "Sigi-saga",
"crossHatch": "Marraduna", "crossHatch": "Marraduna",
"thin": "Mehea", "thin": "Mehea",
"bold": "Lodia", "bold": "Lodia",
@ -208,10 +208,10 @@
"collabSaveFailed": "Ezin izan da backend datu-basean gorde. Arazoak jarraitzen badu, zure fitxategia lokalean gorde beharko zenuke zure lana ez duzula galtzen ziurtatzeko.", "collabSaveFailed": "Ezin izan da backend datu-basean gorde. Arazoak jarraitzen badu, zure fitxategia lokalean gorde beharko zenuke zure lana ez duzula galtzen ziurtatzeko.",
"collabSaveFailed_sizeExceeded": "Ezin izan da backend datu-basean gorde, ohiala handiegia dela dirudi. Fitxategia lokalean gorde beharko zenuke zure lana galtzen ez duzula ziurtatzeko.", "collabSaveFailed_sizeExceeded": "Ezin izan da backend datu-basean gorde, ohiala handiegia dela dirudi. Fitxategia lokalean gorde beharko zenuke zure lana galtzen ez duzula ziurtatzeko.",
"brave_measure_text_error": { "brave_measure_text_error": {
"line1": "", "line1": "Brave arakatzailea erabiltzen ari zarela dirudi <bold>Blokeatu hatz-markak erasokorki</bold> ezarpena gaituta.",
"line2": "", "line2": "Honek zure marrazkietako <bold>Testu-elementuak</bold> hautsi ditzake.",
"line3": "", "line3": "Ezarpen hau desgaitzea gomendatzen dugu. <link>urrats hauek</link> jarrai ditzakezu hori nola egin jakiteko.",
"line4": "" "line4": "Ezarpen hau desgaituz gero, testu-elementuen bistaratzea konpontzen ez bada, ireki <issueLink>arazo</issueLink> gure GitHub-en edo idatzi iezaguzu <discordLink>Discord</discordLink> helbidera"
} }
}, },
"toolBar": { "toolBar": {
@ -306,8 +306,8 @@
"doubleClick": "klik bikoitza", "doubleClick": "klik bikoitza",
"drag": "arrastatu", "drag": "arrastatu",
"editor": "Editorea", "editor": "Editorea",
"editLineArrowPoints": "", "editLineArrowPoints": "Editatu lerroak/gezi-puntuak",
"editText": "", "editText": "Editatu testua / gehitu etiketa",
"github": "Arazorik izan al duzu? Eman horren berri", "github": "Arazorik izan al duzu? Eman horren berri",
"howto": "Jarraitu gure gidak", "howto": "Jarraitu gure gidak",
"or": "edo", "or": "edo",
@ -394,51 +394,21 @@
"pasteAsSingleElement": "Erabili {{shortcut}} elementu bakar gisa itsasteko,\nedo itsatsi lehendik dagoen testu-editore batean" "pasteAsSingleElement": "Erabili {{shortcut}} elementu bakar gisa itsasteko,\nedo itsatsi lehendik dagoen testu-editore batean"
}, },
"colors": { "colors": {
"ffffff": "Zuria",
"f8f9fa": "Grisa 0",
"f1f3f5": "Grisa 1",
"fff5f5": "Gorria 0",
"fff0f6": "Arrosa 0",
"f8f0fc": "Mahats kolorea 0",
"f3f0ff": "Bioleta 0",
"edf2ff": "Indigoa 0",
"e7f5ff": "Urdina 0",
"e3fafc": "Ziana 0",
"e6fcf5": "Berde urdinxka 0",
"ebfbee": "Berdea 0",
"f4fce3": "Lima 0",
"fff9db": "Horia 0",
"fff4e6": "Laranja 0",
"transparent": "Gardena", "transparent": "Gardena",
"ced4da": "Grisa 4", "black": "",
"868e96": "Grisa 6", "white": "",
"fa5252": "Gorria 6", "red": "",
"e64980": "Arrosa 6", "pink": "",
"be4bdb": "Mahats kolorea 6", "grape": "",
"7950f2": "Bioleta 6", "violet": "",
"4c6ef5": "Indigoa 6", "gray": "",
"228be6": "Urdina 6", "blue": "",
"15aabf": "Ziana 6", "cyan": "",
"12b886": "Berde urdinxka 6", "teal": "",
"40c057": "Berdea 6", "green": "",
"82c91e": "Lima 6", "yellow": "",
"fab005": "Horia 6", "orange": "",
"fd7e14": "Laranja 6", "bronze": ""
"000000": "Beltza",
"343a40": "Grisa 8",
"495057": "Grisa 7",
"c92a2a": "Gorria 9",
"a61e4d": "Arrosa 9",
"862e9c": "Mahats kolorea 9",
"5f3dc4": "Bioleta 9",
"364fc7": "Indigoa 9",
"1864ab": "Urdina 9",
"0b7285": "Ziana 9",
"087f5b": "Berde urdinxka 9",
"2b8a3e": "Berdea 9",
"5c940d": "Lima 9",
"e67700": "Horia 9",
"d9480f": "Laranja 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Aukeratu tresna bat eta hasi marrazten!", "toolbarHint": "Aukeratu tresna bat eta hasi marrazten!",
"helpHint": "Lasterbideak eta laguntza" "helpHint": "Lasterbideak eta laguntza"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -264,16 +264,11 @@
"canvasTooBigTip": "نکته: سعی کنید دورترین عناصر را کمی به همدیگر نزدیک کنید." "canvasTooBigTip": "نکته: سعی کنید دورترین عناصر را کمی به همدیگر نزدیک کنید."
}, },
"errorSplash": { "errorSplash": {
"headingMain_pre": "با مشکلی مواجه شدیم. این را امتحان کنید ", "headingMain": "",
"headingMain_button": "در حال بازنشانی صفحه.",
"clearCanvasMessage": "اگر بازنشانی صفحه مشکل را حل نکرد این را امتحان کنید ", "clearCanvasMessage": "اگر بازنشانی صفحه مشکل را حل نکرد این را امتحان کنید ",
"clearCanvasMessage_button": "در حال تمیز کردن بوم",
"clearCanvasCaveat": " این باعث میشود کارهای شما ذخیره نشود ", "clearCanvasCaveat": " این باعث میشود کارهای شما ذخیره نشود ",
"trackedToSentry_pre": "خطا در شناسه ", "trackedToSentry": "",
"trackedToSentry_post": " در سیستم ما رهگیری شد.", "openIssueMessage": "",
"openIssueMessage_pre": "ما خیلی محتاط هستیم که اطلاعات شما را در خطا قرار ندهیم. با این حال اگر اطلاعات شما خصوصی نیست لطفا پیگیری کنید ",
"openIssueMessage_button": "پیگیری اشکالات.",
"openIssueMessage_post": " لطفا اطلاعات زیر را با کپی کردن در صفحه مشکلات GitHub بگذارید.",
"sceneContent": "محتوای صحنه:" "sceneContent": "محتوای صحنه:"
}, },
"roomDialog": { "roomDialog": {
@ -353,29 +348,16 @@
"required": "لازم", "required": "لازم",
"website": "وارد کردن آدرس درست" "website": "وارد کردن آدرس درست"
}, },
"noteDescription": { "noteDescription": "",
"pre": "کتابخانه خود را ارسال کنید تا در آن گنجانده شود ", "noteGuidelines": "",
"link": "مخزن کتابخانه عمومی", "noteLicense": "",
"post": "تا افراد دیگر در نقاشی های خود از آن استفاده کنند."
},
"noteGuidelines": {
"pre": "کتابخانه باید ابتدا به صورت دستی تایید شود. لطفاً بخوانید ",
"link": "دستورالعمل‌ها",
"post": " قبل از ارسال برای برقراری ارتباط و ایجاد تغییرات در صورت درخواست، به یک حساب GitHub نیاز دارید، اما به شدت الزامی نیست."
},
"noteLicense": {
"pre": "با ارسال، موافقت می کنید که کتابخانه تحت عنوان منتشر شود ",
"link": "پروانهٔ MIT ",
"post": "که به طور خلاصه به این معنی است که هر کسی می تواند بدون محدودیت از آنها استفاده کند."
},
"noteItems": "هر مورد کتابخانه باید نام خاص خود را داشته باشد تا قابل فیلتر باشد. اقلام کتابخانه زیر شامل خواهد شد:", "noteItems": "هر مورد کتابخانه باید نام خاص خود را داشته باشد تا قابل فیلتر باشد. اقلام کتابخانه زیر شامل خواهد شد:",
"atleastOneLibItem": "لطفاً حداقل یک مورد از کتابخانه را برای شروع انتخاب کنید", "atleastOneLibItem": "لطفاً حداقل یک مورد از کتابخانه را برای شروع انتخاب کنید",
"republishWarning": "توجه: برخی از موارد انتخاب شده به عنوان قبلاً منتشر شده/ارسال شده علامت گذاری شده اند. شما فقط باید هنگام به‌روزرسانی یک کتابخانه موجود یا ارسال، موارد را دوباره ارسال کنید." "republishWarning": "توجه: برخی از موارد انتخاب شده به عنوان قبلاً منتشر شده/ارسال شده علامت گذاری شده اند. شما فقط باید هنگام به‌روزرسانی یک کتابخانه موجود یا ارسال، موارد را دوباره ارسال کنید."
}, },
"publishSuccessDialog": { "publishSuccessDialog": {
"title": "کتابخانه ارسال شد", "title": "کتابخانه ارسال شد",
"content": "تشکر از شما {{authorName}}. کتابخانه شما برای بررسی ارسال شده است. می توانید وضعیت را پیگیری کنید", "content": "تشکر از شما {{authorName}}. کتابخانه شما برای بررسی ارسال شده است. می توانید وضعیت را پیگیری کنید"
"link": "اینجا"
}, },
"confirmDialog": { "confirmDialog": {
"resetLibrary": "تنظیم مجدد کتابخانه", "resetLibrary": "تنظیم مجدد کتابخانه",
@ -412,51 +394,21 @@
"pasteAsSingleElement": "از {{shortcut}} برای چسباندن به عنوان یک عنصر استفاده کنید،\nیا در یک ویرایشگر متن موجود جایگذاری کنید" "pasteAsSingleElement": "از {{shortcut}} برای چسباندن به عنوان یک عنصر استفاده کنید،\nیا در یک ویرایشگر متن موجود جایگذاری کنید"
}, },
"colors": { "colors": {
"ffffff": "سفید",
"f8f9fa": "خاکستری 0",
"f1f3f5": "خاکستری 1",
"fff5f5": "قرمز 0",
"fff0f6": "صورتی 0",
"f8f0fc": "انگوری 0",
"f3f0ff": "بنفش 0",
"edf2ff": "نیلی 0",
"e7f5ff": "آبی 0",
"e3fafc": "آبی نفتی 0",
"e6fcf5": "آبی فیروزه ای 0",
"ebfbee": "سبز 0",
"f4fce3": "لیمویی 0",
"fff9db": "زرد 0",
"fff4e6": "نارنجی 0",
"transparent": "شفاف", "transparent": "شفاف",
"ced4da": "خاکستری 4", "black": "",
"868e96": "خاکستری 6", "white": "",
"fa5252": "قرمز 6", "red": "",
"e64980": "صورتی 6", "pink": "",
"be4bdb": "انگوری 6", "grape": "",
"7950f2": "بنفش 6", "violet": "",
"4c6ef5": "نیلی 6", "gray": "",
"228be6": "آبی 6", "blue": "",
"15aabf": "آبی نفتی 6", "cyan": "",
"12b886": "آبی فیروزه ای 6", "teal": "",
"40c057": "سبز 6", "green": "",
"82c91e": "لیمویی 6", "yellow": "",
"fab005": "زرد 6", "orange": "",
"fd7e14": "نارنجی 6", "bronze": ""
"000000": "سیاه",
"343a40": "خاکستری 8",
"495057": "خاکستری 7",
"c92a2a": "قرمز 9",
"a61e4d": "صورتی 9",
"862e9c": "انگوری 9",
"5f3dc4": "بنفش 9",
"364fc7": "نیلی 9",
"1864ab": "آبی 9",
"0b7285": "آبی نفتی 9",
"087f5b": "آبی فیروزه ای 9",
"2b8a3e": "سبز 9",
"5c940d": "لیمویی 9",
"e67700": "زرد 9",
"d9480f": "نارنجی 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -470,5 +422,12 @@
"toolbarHint": "ابزاری را انتخاب کنید و نقاشی را شروع کنید!", "toolbarHint": "ابزاری را انتخاب کنید و نقاشی را شروع کنید!",
"helpHint": "میانبرها و راهنما" "helpHint": "میانبرها و راهنما"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Käytä {{shortcut}} liittääksesi yhtenä elementtinä,\ntai liittääksesi olemassa olevaan tekstieditoriin" "pasteAsSingleElement": "Käytä {{shortcut}} liittääksesi yhtenä elementtinä,\ntai liittääksesi olemassa olevaan tekstieditoriin"
}, },
"colors": { "colors": {
"ffffff": "Valkoinen",
"f8f9fa": "Harmaa 0",
"f1f3f5": "Harmaa 1",
"fff5f5": "Punainen 0",
"fff0f6": "Pinkki 0",
"f8f0fc": "Rypäle 0",
"f3f0ff": "Violetti 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Sininen 0",
"e3fafc": "Syaani 0",
"e6fcf5": "Sinivihreä 0",
"ebfbee": "Vihreä 0",
"f4fce3": "Limenvihreä 0",
"fff9db": "Keltainen 0",
"fff4e6": "Oranssi 0",
"transparent": "Läpinäkyvä", "transparent": "Läpinäkyvä",
"ced4da": "Harmaa 4", "black": "",
"868e96": "Harmaa 6", "white": "",
"fa5252": "Punainen 6", "red": "",
"e64980": "Pinkki 6", "pink": "",
"be4bdb": "Rypäle 6", "grape": "",
"7950f2": "Violetti 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Sininen 6", "blue": "",
"15aabf": "Syaani 6", "cyan": "",
"12b886": "Sinivihreä 6", "teal": "",
"40c057": "Vihreä 6", "green": "",
"82c91e": "Limenvihreä 6", "yellow": "",
"fab005": "Keltainen 6", "orange": "",
"fd7e14": "Oranssi 6", "bronze": ""
"000000": "Musta",
"343a40": "Harmaa 8",
"495057": "Harmaa 7",
"c92a2a": "Punainen 9",
"a61e4d": "Pinkki 9",
"862e9c": "Rypäle 9",
"5f3dc4": "Violetti 9",
"364fc7": "Indigo 9",
"1864ab": "Sininen 9",
"0b7285": "Syaani 9",
"087f5b": "Sinivihreä 9",
"2b8a3e": "Vihreä 9",
"5c940d": "Limenvihreä 9",
"e67700": "Keltainen 9",
"d9480f": "Oranssi 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Valitse työkalu ja aloita piirtäminen!", "toolbarHint": "Valitse työkalu ja aloita piirtäminen!",
"helpHint": "Pikanäppäimet & ohje" "helpHint": "Pikanäppäimet & ohje"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Utiliser {{shortcut}} pour coller comme un seul élément,\nou coller dans un éditeur de texte existant" "pasteAsSingleElement": "Utiliser {{shortcut}} pour coller comme un seul élément,\nou coller dans un éditeur de texte existant"
}, },
"colors": { "colors": {
"ffffff": "Blanc",
"f8f9fa": "Gris 0",
"f1f3f5": "Gris 1",
"fff5f5": "Rouge 0",
"fff0f6": "Rose 0",
"f8f0fc": "Mauve 0",
"f3f0ff": "Violet 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Bleu 0",
"e3fafc": "Cyan 0",
"e6fcf5": "Turquoise 0",
"ebfbee": "Vert 0",
"f4fce3": "Citron vert 0",
"fff9db": "Jaune 0",
"fff4e6": "Orange 0",
"transparent": "Transparent", "transparent": "Transparent",
"ced4da": "Gris 4", "black": "",
"868e96": "Gris 6", "white": "",
"fa5252": "Rouge 6", "red": "",
"e64980": "Rose 6", "pink": "",
"be4bdb": "Mauve 6", "grape": "",
"7950f2": "Violet 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Bleu 6", "blue": "",
"15aabf": "Cyan 6", "cyan": "",
"12b886": "Turquoise 6", "teal": "",
"40c057": "Vert 6", "green": "",
"82c91e": "Citron vert 6", "yellow": "",
"fab005": "Jaune 6", "orange": "",
"fd7e14": "Orange 6", "bronze": ""
"000000": "Noir",
"343a40": "Gris 8",
"495057": "Gris 7",
"c92a2a": "Rouge 9",
"a61e4d": "Rose 9",
"862e9c": "Mauve 9",
"5f3dc4": "Violet 9",
"364fc7": "Indigo 9",
"1864ab": "Bleu 9",
"0b7285": "Cyan 9",
"087f5b": "Turquoise 9",
"2b8a3e": "Vert 9",
"5c940d": "Citron vert 9",
"e67700": "Jaune 9",
"d9480f": "Orange 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Choisissez un outil et commencez à dessiner !", "toolbarHint": "Choisissez un outil et commencez à dessiner !",
"helpHint": "Raccourcis et aide" "helpHint": "Raccourcis et aide"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Usa {{shortcut}} para pegar como un único elemento\nou pega nun editor de texto existente" "pasteAsSingleElement": "Usa {{shortcut}} para pegar como un único elemento\nou pega nun editor de texto existente"
}, },
"colors": { "colors": {
"ffffff": "Branco",
"f8f9fa": "Gris 0",
"f1f3f5": "Gris 1",
"fff5f5": "Vermello 0",
"fff0f6": "Rosa 0",
"f8f0fc": "Uva 0",
"f3f0ff": "Violeta 0",
"edf2ff": "Índigo 0",
"e7f5ff": "Azul 0",
"e3fafc": "Ciano 0",
"e6fcf5": "Turquesa 0",
"ebfbee": "Verde 0",
"f4fce3": "Lima 0",
"fff9db": "Amarelo 0",
"fff4e6": "Laranxa 0",
"transparent": "Transparente", "transparent": "Transparente",
"ced4da": "Gris 4", "black": "",
"868e96": "Gris 6", "white": "",
"fa5252": "Vermello 6", "red": "",
"e64980": "Rosa 6", "pink": "",
"be4bdb": "Uva 6", "grape": "",
"7950f2": "Violeta 6", "violet": "",
"4c6ef5": "Índigo 6", "gray": "",
"228be6": "Azul 6", "blue": "",
"15aabf": "Ciano 6", "cyan": "",
"12b886": "Turquesa 6", "teal": "",
"40c057": "Verde 6", "green": "",
"82c91e": "Lima 6", "yellow": "",
"fab005": "Amarelo 6", "orange": "",
"fd7e14": "Laranxa 6", "bronze": ""
"000000": "Negro",
"343a40": "Gris 8",
"495057": "Gris 7",
"c92a2a": "Vermello 9",
"a61e4d": "Rosa 9",
"862e9c": "Uva 9",
"5f3dc4": "Violeta 9",
"364fc7": "Índigo 9",
"1864ab": "Azul 9",
"0b7285": "Ciano 9",
"087f5b": "Turquesa 9",
"2b8a3e": "Verde 9",
"5c940d": "Lima 9",
"e67700": "Amarelo 9",
"d9480f": "Laranxa 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Escolle unha ferramenta & Comeza a debuxar!", "toolbarHint": "Escolle unha ferramenta & Comeza a debuxar!",
"helpHint": "Atallos & axuda" "helpHint": "Atallos & axuda"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -264,16 +264,11 @@
"canvasTooBigTip": "טיפ: נסה להזיז את הרכיבים הרחוקים ביותר מעט קרוב יותר האחד לשני." "canvasTooBigTip": "טיפ: נסה להזיז את הרכיבים הרחוקים ביותר מעט קרוב יותר האחד לשני."
}, },
"errorSplash": { "errorSplash": {
"headingMain_pre": "אירעה שגיאה. נסה ", "headingMain": "",
"headingMain_button": "טוען את העמוד מחדש.",
"clearCanvasMessage": "אם טעינה מחדש לא עובדת, נסה ", "clearCanvasMessage": "אם טעינה מחדש לא עובדת, נסה ",
"clearCanvasMessage_button": "מנקה את הקנבאס.",
"clearCanvasCaveat": " זה יגרום לאובדן העבודה ", "clearCanvasCaveat": " זה יגרום לאובדן העבודה ",
"trackedToSentry_pre": "השגיאה עם מזהה ", "trackedToSentry": "",
"trackedToSentry_post": " נמצאה במערכת שלנו.", "openIssueMessage": "",
"openIssueMessage_pre": "נזהרנו מאוד שלא לכלול מידע מהקנבאס שלך בשגיאה. אם המידע בקנבאס אינו אישי, שקול לבצע מעקב אחר הטיפול שלנו ",
"openIssueMessage_button": "מעקב באגים.",
"openIssueMessage_post": " בבקשה כלול את המידע מטה באמצעות העתקתה שלו, והדבקה שלו ב- GitHub Issue.",
"sceneContent": "תוכן הקנבאס:" "sceneContent": "תוכן הקנבאס:"
}, },
"roomDialog": { "roomDialog": {
@ -353,29 +348,16 @@
"required": "נדרש", "required": "נדרש",
"website": "הזינו כתובת URL תקינה" "website": "הזינו כתובת URL תקינה"
}, },
"noteDescription": { "noteDescription": "",
"pre": "הגש את הספריה שלך להכללתה ב ", "noteGuidelines": "",
"link": "מאגר הספריה הציבורית", "noteLicense": "",
"post": "לשימושם של אנשים אחרים בציורים שלהם."
},
"noteGuidelines": {
"pre": "הספריה צריכה לקבל אישור ידני קודם לכן. אנא קרא את ",
"link": "הנחיות",
"post": " לפני השליחה. אתה תצטרך לחשבון GitHub כדי לתקשר ולבצע שינויים אם תתבקש, אבל זה לא דרישה הכרחית."
},
"noteLicense": {
"pre": "על ידי שליחה, אתה מסכים שהסיפריה תפורסם תחת ה- ",
"link": "רישיון MIT, ",
"post": "שאומר בקצרה, שכל אחד יכול לעשות בהם שימוש ללא מגבלות."
},
"noteItems": "לכל פריט בסיפריה חייב להיות שם כדי שאפשר יהיה לסנן. הפריטי סיפריה הבאים יהיו כלולים:", "noteItems": "לכל פריט בסיפריה חייב להיות שם כדי שאפשר יהיה לסנן. הפריטי סיפריה הבאים יהיו כלולים:",
"atleastOneLibItem": "אנא בחר לפחות פריט אחד מספריה כדי להתחיל", "atleastOneLibItem": "אנא בחר לפחות פריט אחד מספריה כדי להתחיל",
"republishWarning": "הערה: חלק מהפריטים שבחרת מסומנים ככאלו שכבר פורסמו/נשלחו. אתה צריך לשלוח פריטים מחדש כאשר אתה מעדכן ספריה או הגשה קיימים." "republishWarning": "הערה: חלק מהפריטים שבחרת מסומנים ככאלו שכבר פורסמו/נשלחו. אתה צריך לשלוח פריטים מחדש כאשר אתה מעדכן ספריה או הגשה קיימים."
}, },
"publishSuccessDialog": { "publishSuccessDialog": {
"title": "הספריה הוגשה", "title": "הספריה הוגשה",
"content": "תודה {{authorName}}. הספריה שלך נשלחה לבחינה. תוכל לעקוב אחרי סטטוס הפרסום", "content": "תודה {{authorName}}. הספריה שלך נשלחה לבחינה. תוכל לעקוב אחרי סטטוס הפרסום"
"link": "כאן"
}, },
"confirmDialog": { "confirmDialog": {
"resetLibrary": "איפוס ספריה", "resetLibrary": "איפוס ספריה",
@ -412,51 +394,21 @@
"pasteAsSingleElement": "השתמש ב- {{shortcut}} כדי להדביק כפריט יחיד,\nאו הדבק לתוך עורך טקסט קיים" "pasteAsSingleElement": "השתמש ב- {{shortcut}} כדי להדביק כפריט יחיד,\nאו הדבק לתוך עורך טקסט קיים"
}, },
"colors": { "colors": {
"ffffff": "לבן",
"f8f9fa": "אפור 0",
"f1f3f5": "אפור 1",
"fff5f5": "אדום 0",
"fff0f6": "ורוד 0",
"f8f0fc": "ענבים 0",
"f3f0ff": "סגול 0",
"edf2ff": "כחול כהה 0",
"e7f5ff": "כחול 0",
"e3fafc": "טורקיז 0",
"e6fcf5": "ירקרק 0",
"ebfbee": "ירוק 0",
"f4fce3": "ליים 0",
"fff9db": "צהוב",
"fff4e6": "כתום 0",
"transparent": "שקוף", "transparent": "שקוף",
"ced4da": "אפור 4", "black": "",
"868e96": "אפור 6", "white": "",
"fa5252": "אדום 6", "red": "",
"e64980": "ורוד 6", "pink": "",
"be4bdb": "ענבים 6", "grape": "",
"7950f2": "סגול 6", "violet": "",
"4c6ef5": "כחול כהה 6", "gray": "",
"228be6": "כחול 6", "blue": "",
"15aabf": "טורקיז 6", "cyan": "",
"12b886": "ירקרק 6", "teal": "",
"40c057": "ירוק 6", "green": "",
"82c91e": "ליים 6", "yellow": "",
"fab005": "צהוב 6", "orange": "",
"fd7e14": "כתום 6", "bronze": ""
"000000": "שחור",
"343a40": "אפור 8",
"495057": "אפור 7",
"c92a2a": "אדום 9",
"a61e4d": "ורוד 9",
"862e9c": "ענבים 9",
"5f3dc4": "סגול 9",
"364fc7": "כחול כהה 9",
"1864ab": "כחול 9",
"0b7285": "טורקיז 9",
"087f5b": "ירוק-כחול 9",
"2b8a3e": "ירוק 9",
"5c940d": "ליים 9",
"e67700": "צהוב 9",
"d9480f": "כתום 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -470,5 +422,12 @@
"toolbarHint": "בחר כלי & והתחל לצייר!", "toolbarHint": "בחר כלי & והתחל לצייר!",
"helpHint": "קיצורים & עזרה" "helpHint": "קיצורים & עזרה"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -350,14 +350,14 @@
}, },
"noteDescription": "संग्रह सम्मिलित करने हेतु प्रस्तुत करें <link>सार्वजनिक संग्रहालय</link>अन्य वक्तियों को उनके चित्रकारी में उपयोग के लिये", "noteDescription": "संग्रह सम्मिलित करने हेतु प्रस्तुत करें <link>सार्वजनिक संग्रहालय</link>अन्य वक्तियों को उनके चित्रकारी में उपयोग के लिये",
"noteGuidelines": "संग्रह को पहले स्वीकृति आवश्यक कृपया यह पढ़ें <link>दिशा-निर्देश</link>", "noteGuidelines": "संग्रह को पहले स्वीकृति आवश्यक कृपया यह पढ़ें <link>दिशा-निर्देश</link>",
"noteLicense": "", "noteLicense": "जमा करके, आप सहमत हैं कि संग्रहण को <link>MIT लाइसेंस</link> के तहत प्रकाशित किया जाएगा, जिसका संक्षिप्त अर्थ है कि कोई भी बिना किसी प्रतिबंध के उनका उपयोग कर सकता है।",
"noteItems": "", "noteItems": "",
"atleastOneLibItem": "", "atleastOneLibItem": "",
"republishWarning": "टिप्पणी: कुछ चुने हुवे आइटम पहले ही प्रकाशित/प्रस्तुत किए जा चुके हैं। किसी प्रकाशित संग्रह को अद्यतन करते समय या पहले से प्रस्तुत आइटम को पुन्हा प्रस्तुत करते समय, आप बस उसे केवल अद्यतन करें ।" "republishWarning": "टिप्पणी: कुछ चुने हुवे आइटम पहले ही प्रकाशित/प्रस्तुत किए जा चुके हैं। किसी प्रकाशित संग्रह को अद्यतन करते समय या पहले से प्रस्तुत आइटम को पुन्हा प्रस्तुत करते समय, आप बस उसे केवल अद्यतन करें ।"
}, },
"publishSuccessDialog": { "publishSuccessDialog": {
"title": "", "title": "",
"content": "" "content": "{{authorName}} धन्यवाद. आपका संग्रहण समीक्षा के लिए दर्ज हो चुका है. समीक्षा स्थिति <link>यहाँ</link>जान सकते हैं."
}, },
"confirmDialog": { "confirmDialog": {
"resetLibrary": "", "resetLibrary": "",
@ -394,51 +394,21 @@
"pasteAsSingleElement": "एक अवयव के रूप में चिपकाने के लिए {{shortcut}} का उपयोग करें,\nया किसी मौजूदा पाठ संपादक में चिपकायें" "pasteAsSingleElement": "एक अवयव के रूप में चिपकाने के लिए {{shortcut}} का उपयोग करें,\nया किसी मौजूदा पाठ संपादक में चिपकायें"
}, },
"colors": { "colors": {
"ffffff": "सफेद",
"f8f9fa": "",
"f1f3f5": "",
"fff5f5": "",
"fff0f6": "",
"f8f0fc": "अंगूरी 0",
"f3f0ff": "",
"edf2ff": "नील 0",
"e7f5ff": "नीला 0",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "",
"f4fce3": "",
"fff9db": "",
"fff4e6": "",
"transparent": "", "transparent": "",
"ced4da": "", "black": "",
"868e96": "", "white": "",
"fa5252": "", "red": "",
"e64980": "", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "", "green": "",
"82c91e": "", "yellow": "",
"fab005": "", "orange": "",
"fd7e14": "", "bronze": ""
"000000": "",
"343a40": "",
"495057": "",
"c92a2a": "",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "",
"0b7285": "आसमानी",
"087f5b": "",
"2b8a3e": "हरा",
"5c940d": "",
"e67700": "पीला",
"d9480f": "नारंगी"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "एक औजार चुने और चित्रकारी प्रारंभ करे!", "toolbarHint": "एक औजार चुने और चित्रकारी प्रारंभ करे!",
"helpHint": "शॉर्ट्कट और सहाय्य" "helpHint": "शॉर्ट्कट और सहाय्य"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "Fehér",
"f8f9fa": "Szürke 0",
"f1f3f5": "Szürke 1",
"fff5f5": "Piros 0",
"fff0f6": "Pink 0",
"f8f0fc": "Szőlő 0",
"f3f0ff": "Ibolya 0",
"edf2ff": "Indigó 0",
"e7f5ff": "Kék 0",
"e3fafc": "Cián 0",
"e6fcf5": "Kékes-zöld 0",
"ebfbee": "Zöld 0",
"f4fce3": "Lime 0",
"fff9db": "Sárga 0",
"fff4e6": "Narancs 0",
"transparent": "Átlátszó", "transparent": "Átlátszó",
"ced4da": "Szürke 4", "black": "",
"868e96": "Szürke 6", "white": "",
"fa5252": "Piros 6", "red": "",
"e64980": "Pink 6", "pink": "",
"be4bdb": "Szőlő 6", "grape": "",
"7950f2": "Ibolya 6", "violet": "",
"4c6ef5": "Indigó 6", "gray": "",
"228be6": "Kék 6", "blue": "",
"15aabf": "Cián 6", "cyan": "",
"12b886": "Kékes-zöld 6", "teal": "",
"40c057": "Zöld 6", "green": "",
"82c91e": "Lime 6", "yellow": "",
"fab005": "Sárga 6", "orange": "",
"fd7e14": "Narancs 6", "bronze": ""
"000000": "Fekete",
"343a40": "Szürke 8",
"495057": "Szürke 7",
"c92a2a": "Piros 9",
"a61e4d": "Pink 9",
"862e9c": "Szőlő 9",
"5f3dc4": "Ibolya 9",
"364fc7": "Indigó 9",
"1864ab": "Kék 9",
"0b7285": "Cián 9",
"087f5b": "Kékes-zöld 9",
"2b8a3e": "Zöld 9",
"5c940d": "Lime 9",
"e67700": "Sárga 9",
"d9480f": "Narancs 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Gunakan {{shortcut}} untuk menempelkan sebagai satu elemen,\natau tempelkan ke teks editor yang ada" "pasteAsSingleElement": "Gunakan {{shortcut}} untuk menempelkan sebagai satu elemen,\natau tempelkan ke teks editor yang ada"
}, },
"colors": { "colors": {
"ffffff": "Putih",
"f8f9fa": "Abu-abu 0",
"f1f3f5": "Abu-abu 1",
"fff5f5": "Merah 0",
"fff0f6": "Merah muda 0",
"f8f0fc": "Ungu 0",
"f3f0ff": "Violet 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Biru 0",
"e3fafc": "Cyan 0",
"e6fcf5": "Teal 0",
"ebfbee": "Hijau 0",
"f4fce3": "Lime 0",
"fff9db": "Kuning 0",
"fff4e6": "Jingga 0",
"transparent": "Transparan", "transparent": "Transparan",
"ced4da": "Abu-abu 4", "black": "",
"868e96": "Abu-abu 6", "white": "",
"fa5252": "Merah 6", "red": "",
"e64980": "Merah muda 6", "pink": "",
"be4bdb": "Ungu 6", "grape": "",
"7950f2": "Violet 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Biru 6", "blue": "",
"15aabf": "Cyan 6", "cyan": "",
"12b886": "Teal 6", "teal": "",
"40c057": "Hijau 6", "green": "",
"82c91e": "Lime 6", "yellow": "",
"fab005": "Kuning 6", "orange": "",
"fd7e14": "Jingga 6", "bronze": ""
"000000": "Hitam",
"343a40": "Abu-abu 8",
"495057": "Abu-abu 7",
"c92a2a": "Merah 9",
"a61e4d": "Merah muda 9",
"862e9c": "Ungu 9",
"5f3dc4": "Violet 9",
"364fc7": "Indigo 9",
"1864ab": "Biru 9",
"0b7285": "Cyan 9",
"087f5b": "Teal 9",
"2b8a3e": "Hijau 9",
"5c940d": "Lime 9",
"e67700": "Kuning 9",
"d9480f": "Jingga 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Pilih alat & mulai menggambar!", "toolbarHint": "Pilih alat & mulai menggambar!",
"helpHint": "Pintasan & bantuan" "helpHint": "Pintasan & bantuan"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -208,10 +208,10 @@
"collabSaveFailed": "Impossibile salvare nel database di backend. Se i problemi persistono, dovresti salvare il tuo file localmente per assicurarti di non perdere il tuo lavoro.", "collabSaveFailed": "Impossibile salvare nel database di backend. Se i problemi persistono, dovresti salvare il tuo file localmente per assicurarti di non perdere il tuo lavoro.",
"collabSaveFailed_sizeExceeded": "Impossibile salvare nel database di backend, la tela sembra essere troppo grande. Dovresti salvare il file localmente per assicurarti di non perdere il tuo lavoro.", "collabSaveFailed_sizeExceeded": "Impossibile salvare nel database di backend, la tela sembra essere troppo grande. Dovresti salvare il file localmente per assicurarti di non perdere il tuo lavoro.",
"brave_measure_text_error": { "brave_measure_text_error": {
"line1": "", "line1": "Sembra che tu stia utilizzando il browser Brave con l'impostazione <bold>Blocco aggressivo delle impronte digitali</bold> abilitata.",
"line2": "", "line2": "Ciò potrebbe causare la rottura degli <bold>Elementi di testo</bold> nei tuoi disegni.",
"line3": "", "line3": "Consigliamo vivamente di disabilitare questa impostazione. Puoi seguire <link>questi passaggi</link> su come farlo.",
"line4": "" "line4": "Se la disattivazione di questa impostazione non risolve la visualizzazione degli elementi di testo, apri un <issueLink>problema</issueLink> sul nostro GitHub o scrivici su <discordLink>Discord</discordLink>"
} }
}, },
"toolBar": { "toolBar": {
@ -394,51 +394,21 @@
"pasteAsSingleElement": "Usa {{shortcut}} per incollare come un singolo elemento,\no incollare in un editor di testo esistente" "pasteAsSingleElement": "Usa {{shortcut}} per incollare come un singolo elemento,\no incollare in un editor di testo esistente"
}, },
"colors": { "colors": {
"ffffff": "Bianco",
"f8f9fa": "Grigio 0",
"f1f3f5": "Grigio 1",
"fff5f5": "Rosso 0",
"fff0f6": "Rosa 0",
"f8f0fc": "Uva 0",
"f3f0ff": "Viola 0",
"edf2ff": "Indaco 0",
"e7f5ff": "Blu 0",
"e3fafc": "Ciano 0",
"e6fcf5": "Verde acqua 0",
"ebfbee": "Verde 0",
"f4fce3": "Lime 0",
"fff9db": "Giallo 0",
"fff4e6": "Arancio 0",
"transparent": "Trasparente", "transparent": "Trasparente",
"ced4da": "Grigio 4", "black": "",
"868e96": "Grigio 6", "white": "",
"fa5252": "Rosso 6", "red": "",
"e64980": "Rosa 6", "pink": "",
"be4bdb": "Uva 6", "grape": "",
"7950f2": "Viola 6", "violet": "",
"4c6ef5": "Indaco 6", "gray": "",
"228be6": "Blu 6", "blue": "",
"15aabf": "Ciano 6", "cyan": "",
"12b886": "Verde acqua 6", "teal": "",
"40c057": "Verde 6", "green": "",
"82c91e": "Lime 6", "yellow": "",
"fab005": "Giallo 6", "orange": "",
"fd7e14": "Arancio 6", "bronze": ""
"000000": "Nero",
"343a40": "Grigio 8",
"495057": "Grigio 7",
"c92a2a": "Rosso 9",
"a61e4d": "Rosa 9",
"862e9c": "Uva 9",
"5f3dc4": "Viola 9",
"364fc7": "Indaco 9",
"1864ab": "Blu 9",
"0b7285": "Ciano 9",
"087f5b": "Verde acqua 9",
"2b8a3e": "Verde 9",
"5c940d": "Lime 9",
"e67700": "Giallo 9",
"d9480f": "Arancio 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Scegli uno strumento & Inizia a disegnare!", "toolbarHint": "Scegli uno strumento & Inizia a disegnare!",
"helpHint": "Scorciatoie & aiuto" "helpHint": "Scorciatoie & aiuto"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "{{shortcut}} を使用して単一の要素として貼り付けるか、\n既存のテキストエディタに貼り付け" "pasteAsSingleElement": "{{shortcut}} を使用して単一の要素として貼り付けるか、\n既存のテキストエディタに貼り付け"
}, },
"colors": { "colors": {
"ffffff": "ホワイト",
"f8f9fa": "グレー 0",
"f1f3f5": "グレー 1",
"fff5f5": "レッド 0",
"fff0f6": "ピンク 0",
"f8f0fc": "グレープ 0",
"f3f0ff": "バイオレット 0",
"edf2ff": "インディゴ 0",
"e7f5ff": "ブルー 0",
"e3fafc": "シアン 0",
"e6fcf5": "ティール 0",
"ebfbee": "グリーン 0",
"f4fce3": "ライム 0",
"fff9db": "イエロー 0",
"fff4e6": "オレンジ 0",
"transparent": "透明", "transparent": "透明",
"ced4da": "グレー 4", "black": "",
"868e96": "グレー 6", "white": "",
"fa5252": "レッド 6", "red": "",
"e64980": "ピンク 6", "pink": "",
"be4bdb": "グレープ 6", "grape": "",
"7950f2": "バイオレット 6", "violet": "",
"4c6ef5": "インディゴ 6", "gray": "",
"228be6": "ブルー 6", "blue": "",
"15aabf": "シアン 6", "cyan": "",
"12b886": "ティール 6", "teal": "",
"40c057": "グリーン 6", "green": "",
"82c91e": "ライム 6", "yellow": "",
"fab005": "イエロー 6", "orange": "",
"fd7e14": "オレンジ 6", "bronze": ""
"000000": "ブラック",
"343a40": "グレー 8",
"495057": "グレー 7",
"c92a2a": "レッド 9",
"a61e4d": "ピンク 9",
"862e9c": "グレープ 9",
"5f3dc4": "バイオレット 9",
"364fc7": "インディゴ 9",
"1864ab": "ブルー 9",
"0b7285": "シアン 9",
"087f5b": "ティール 9",
"2b8a3e": "グリーン 9",
"5c940d": "ライム 9",
"e67700": "イエロー 9",
"d9480f": "オレンジ 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "ツールを選んで描き始めよう!", "toolbarHint": "ツールを選んで描き始めよう!",
"helpHint": "ショートカットとヘルプ" "helpHint": "ショートカットとヘルプ"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -264,16 +264,11 @@
"canvasTooBigTip": "" "canvasTooBigTip": ""
}, },
"errorSplash": { "errorSplash": {
"headingMain_pre": "", "headingMain": "",
"headingMain_button": "",
"clearCanvasMessage": "", "clearCanvasMessage": "",
"clearCanvasMessage_button": "",
"clearCanvasCaveat": "", "clearCanvasCaveat": "",
"trackedToSentry_pre": "", "trackedToSentry": "",
"trackedToSentry_post": "", "openIssueMessage": "",
"openIssueMessage_pre": "",
"openIssueMessage_button": "",
"openIssueMessage_post": "",
"sceneContent": "" "sceneContent": ""
}, },
"roomDialog": { "roomDialog": {
@ -353,29 +348,16 @@
"required": "", "required": "",
"website": "" "website": ""
}, },
"noteDescription": { "noteDescription": "",
"pre": "", "noteGuidelines": "",
"link": "", "noteLicense": "",
"post": ""
},
"noteGuidelines": {
"pre": "",
"link": "",
"post": ""
},
"noteLicense": {
"pre": "",
"link": "",
"post": ""
},
"noteItems": "", "noteItems": "",
"atleastOneLibItem": "", "atleastOneLibItem": "",
"republishWarning": "" "republishWarning": ""
}, },
"publishSuccessDialog": { "publishSuccessDialog": {
"title": "", "title": "",
"content": "", "content": ""
"link": "usı jerde"
}, },
"confirmDialog": { "confirmDialog": {
"resetLibrary": "", "resetLibrary": "",
@ -390,13 +372,13 @@
"element": "", "element": "",
"elements": "", "elements": "",
"height": "", "height": "",
"scene": "", "scene": "Saxna",
"selected": "Tańlandı", "selected": "Tańlandı",
"storage": "", "storage": "",
"title": "", "title": "",
"total": "", "total": "",
"version": "Versiya", "version": "Versiya",
"versionCopy": "", "versionCopy": "Kóshirip alıw ushın basıń",
"versionNotAvailable": "", "versionNotAvailable": "",
"width": "" "width": ""
}, },
@ -406,57 +388,27 @@
"copyToClipboard": "", "copyToClipboard": "",
"copyToClipboardAsPng": "", "copyToClipboardAsPng": "",
"fileSaved": "Fayl saqlandı.", "fileSaved": "Fayl saqlandı.",
"fileSavedToFilename": "", "fileSavedToFilename": "{filename} saqlandı",
"canvas": "", "canvas": "",
"selection": "", "selection": "",
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "Aq",
"f8f9fa": "",
"f1f3f5": "",
"fff5f5": "Qızıl 0",
"fff0f6": "",
"f8f0fc": "",
"f3f0ff": "",
"edf2ff": "",
"e7f5ff": "Kók",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "",
"f4fce3": "",
"fff9db": "Sarı 0",
"fff4e6": "Qıılt sarı 0",
"transparent": "", "transparent": "",
"ced4da": "", "black": "",
"868e96": "", "white": "",
"fa5252": "Qızıl 6", "red": "",
"e64980": "", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "Kók 6", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "Jasıl 6", "green": "",
"82c91e": "", "yellow": "",
"fab005": "Sarı 6", "orange": "",
"fd7e14": "", "bronze": ""
"000000": "Qara",
"343a40": "",
"495057": "",
"c92a2a": "Qızıl 9",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "Kók 9",
"0b7285": "",
"087f5b": "",
"2b8a3e": "Jasıl 9",
"5c940d": "",
"e67700": "Sarı 9",
"d9480f": "Qıılt sarı 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -470,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -209,8 +209,8 @@
"collabSaveFailed_sizeExceeded": "Ulamek asekles deg uzadur n yisefka deg ugilal, taɣzut n usuneɣ tettban-d temqer aṭas. Isefk ad teskelseḍ afaylu s wudem adigan akken ad tetḥeqqeḍ ur tesruḥuyeḍ ara amahil-inek•inem.", "collabSaveFailed_sizeExceeded": "Ulamek asekles deg uzadur n yisefka deg ugilal, taɣzut n usuneɣ tettban-d temqer aṭas. Isefk ad teskelseḍ afaylu s wudem adigan akken ad tetḥeqqeḍ ur tesruḥuyeḍ ara amahil-inek•inem.",
"brave_measure_text_error": { "brave_measure_text_error": {
"line1": "", "line1": "",
"line2": "", "line2": "Ayagi yezmer ad d-iglu s truẓi n<bold>Iferdisen n uḍris</bold>deg wunuɣen-inek.",
"line3": "", "line3": "Ad k-nsemter ad tsexsiḍ aɣewwar-agi. Tzemreḍ ad tḍefreḍ<link>isurifen-agi</link> ɣef wamek ara txedmeḍ.",
"line4": "" "line4": ""
} }
}, },
@ -306,8 +306,8 @@
"doubleClick": "ssit snat n tikkal", "doubleClick": "ssit snat n tikkal",
"drag": "zuɣer", "drag": "zuɣer",
"editor": "Amaẓrag", "editor": "Amaẓrag",
"editLineArrowPoints": "", "editLineArrowPoints": "Ẓreg tinqiḍin n yizirig/taneccabt",
"editText": "", "editText": "Ẓreg aḍris/rnu tabzimt",
"github": "Tufiḍ-d ugur? Azen-aɣ-d", "github": "Tufiḍ-d ugur? Azen-aɣ-d",
"howto": "Ḍfer imniren-nneɣ", "howto": "Ḍfer imniren-nneɣ",
"or": "neɣ", "or": "neɣ",
@ -394,63 +394,40 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "Amellal",
"f8f9fa": "Aɣiɣdi 0",
"f1f3f5": "Aɣiɣdi 1",
"fff5f5": "Azeggaɣ",
"fff0f6": "Axuxi 0",
"f8f0fc": "Tiẓurin 0",
"f3f0ff": "Amidadi 0",
"edf2ff": "",
"e7f5ff": "Anili 0",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "Azegzaw 0",
"f4fce3": "Llim 0",
"fff9db": "Awraɣ 0",
"fff4e6": "Aččinawi 0",
"transparent": "Afrawan", "transparent": "Afrawan",
"ced4da": "Aɣiɣdi 4", "black": "",
"868e96": "Aɣiɣdi 6", "white": "",
"fa5252": "Azeggaɣ 6", "red": "",
"e64980": "Axuxi 6", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "Amidadi 6", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "Anili 6", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "Azegzaw 0", "green": "",
"82c91e": "Llim 6", "yellow": "",
"fab005": "Awraɣ 6", "orange": "",
"fd7e14": "Aččinawi 6", "bronze": ""
"000000": "Aberkan",
"343a40": "Aɣiɣdi 8",
"495057": "Aɣiɣdi 7",
"c92a2a": "Azeggaɣ 9",
"a61e4d": "Axuxi 9",
"862e9c": "Tiẓurin 9",
"5f3dc4": "Amidadi 9",
"364fc7": "",
"1864ab": "Anili 9",
"0b7285": "",
"087f5b": "",
"2b8a3e": "Azegzaw 9",
"5c940d": "Llim 9",
"e67700": "Awraɣ 9",
"d9480f": "Aččinawi 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "Akk isefka-inek•inem ttwakelsen s wudem adigan deg yiminig-inek•inem.",
"center_heading_plus": "Tebɣiḍ ad tedduḍ ɣer Excalidraw+ deg umḍiq?", "center_heading_plus": "Tebɣiḍ ad tedduḍ ɣer Excalidraw+ deg umḍiq?",
"menuHint": "Asifeḍ, ismenyifen, tutlayin, ..." "menuHint": "Asifeḍ, ismenyifen, tutlayin, ..."
}, },
"defaults": { "defaults": {
"menuHint": "Asifeḍ, ismenyifen, d wayen-nniḍen...", "menuHint": "Asifeḍ, ismenyifen, d wayen-nniḍen...",
"center_heading": "", "center_heading": "",
"toolbarHint": "", "toolbarHint": "Fren afecku tebduḍ asuneɣ!",
"helpHint": "" "helpHint": "Inegzumen akked tallelt"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "",
"f8f9fa": "",
"f1f3f5": "",
"fff5f5": "",
"fff0f6": "",
"f8f0fc": "",
"f3f0ff": "",
"edf2ff": "",
"e7f5ff": "",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "",
"f4fce3": "",
"fff9db": "",
"fff4e6": "",
"transparent": "", "transparent": "",
"ced4da": "", "black": "",
"868e96": "", "white": "",
"fa5252": "", "red": "",
"e64980": "", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "", "green": "",
"82c91e": "", "yellow": "",
"fab005": "", "orange": "",
"fd7e14": "", "bronze": ""
"000000": "",
"343a40": "",
"495057": "",
"c92a2a": "",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "",
"0b7285": "",
"087f5b": "",
"2b8a3e": "",
"5c940d": "",
"e67700": "",
"d9480f": ""
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -264,16 +264,11 @@
"canvasTooBigTip": "" "canvasTooBigTip": ""
}, },
"errorSplash": { "errorSplash": {
"headingMain_pre": "", "headingMain": "",
"headingMain_button": "",
"clearCanvasMessage": "", "clearCanvasMessage": "",
"clearCanvasMessage_button": "",
"clearCanvasCaveat": "", "clearCanvasCaveat": "",
"trackedToSentry_pre": "", "trackedToSentry": "",
"trackedToSentry_post": "", "openIssueMessage": "",
"openIssueMessage_pre": "",
"openIssueMessage_button": "",
"openIssueMessage_post": "",
"sceneContent": "" "sceneContent": ""
}, },
"roomDialog": { "roomDialog": {
@ -353,29 +348,16 @@
"required": "", "required": "",
"website": "" "website": ""
}, },
"noteDescription": { "noteDescription": "",
"pre": "", "noteGuidelines": "",
"link": "", "noteLicense": "",
"post": ""
},
"noteGuidelines": {
"pre": "",
"link": "",
"post": ""
},
"noteLicense": {
"pre": "",
"link": "",
"post": ""
},
"noteItems": "", "noteItems": "",
"atleastOneLibItem": "", "atleastOneLibItem": "",
"republishWarning": "" "republishWarning": ""
}, },
"publishSuccessDialog": { "publishSuccessDialog": {
"title": "", "title": "",
"content": "", "content": ""
"link": ""
}, },
"confirmDialog": { "confirmDialog": {
"resetLibrary": "", "resetLibrary": "",
@ -412,63 +394,40 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "", "transparent": "ថ្លាមើលធ្លុះ",
"f8f9fa": "", "black": "",
"f1f3f5": "", "white": "",
"fff5f5": "", "red": "",
"fff0f6": "", "pink": "",
"f8f0fc": "", "grape": "",
"f3f0ff": "", "violet": "",
"edf2ff": "", "gray": "",
"e7f5ff": "", "blue": "",
"e3fafc": "", "cyan": "",
"e6fcf5": "", "teal": "",
"ebfbee": "", "green": "",
"f4fce3": "", "yellow": "",
"fff9db": "", "orange": "",
"fff4e6": "", "bronze": ""
"transparent": "",
"ced4da": "",
"868e96": "",
"fa5252": "",
"e64980": "",
"be4bdb": "",
"7950f2": "",
"4c6ef5": "",
"228be6": "",
"15aabf": "",
"12b886": "",
"40c057": "",
"82c91e": "",
"fab005": "",
"fd7e14": "",
"000000": "",
"343a40": "",
"495057": "",
"c92a2a": "",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "",
"0b7285": "",
"087f5b": "",
"2b8a3e": "",
"5c940d": "",
"e67700": "",
"d9480f": ""
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "ទិន្នន័យទាំងអស់របស់អ្នក ត្រូវបានរក្សាទុកនៅក្នុង browser របស់អ្នក ។",
"center_heading_plus": "", "center_heading_plus": "តើ​អ្នក​ចង់​ទៅ Excalidraw+ ​វិញ ឬ មែន?",
"menuHint": "" "menuHint": "នាំចេញ ចំណូលចិត្ត ភាសា ..."
}, },
"defaults": { "defaults": {
"menuHint": "", "menuHint": "ការនាំចេញ ចំណូលចិត្ត និង ច្រើនទៀត...",
"center_heading": "", "center_heading": "ងាយស្រួល ។ ធ្វើ ។​ ដ្យាក្រាម ។",
"toolbarHint": "", "toolbarHint": "ជ្រើសរើសឧបករណ៍មួយ និង ចាប់ផ្តើមគូរ!",
"helpHint": "" "helpHint": "ផ្លូវកាត់ & ជំនួយ"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "단일 요소로 붙여넣거나, 기존 텍스트 에디터에 붙여넣으려면 {{shortcut}} 을 사용하세요." "pasteAsSingleElement": "단일 요소로 붙여넣거나, 기존 텍스트 에디터에 붙여넣으려면 {{shortcut}} 을 사용하세요."
}, },
"colors": { "colors": {
"ffffff": "화이트",
"f8f9fa": "회색 0",
"f1f3f5": "회색 1",
"fff5f5": "빨강색 0",
"fff0f6": "핑크색 0",
"f8f0fc": "그레이프 0",
"f3f0ff": "바이올렛 0",
"edf2ff": "남색 0",
"e7f5ff": "파란색 0",
"e3fafc": "청록색 0",
"e6fcf5": "틸 0",
"ebfbee": "녹색 0",
"f4fce3": "라임 0",
"fff9db": "노란색 0",
"fff4e6": "주황색 0",
"transparent": "투명", "transparent": "투명",
"ced4da": "회색 4", "black": "",
"868e96": "회색 6", "white": "",
"fa5252": "빨강색 6", "red": "",
"e64980": "핑크색 6", "pink": "",
"be4bdb": "그레이프 6", "grape": "",
"7950f2": "바이올렛 6", "violet": "",
"4c6ef5": "남색 6", "gray": "",
"228be6": "파란색 6", "blue": "",
"15aabf": "청록색 6", "cyan": "",
"12b886": "틸 6", "teal": "",
"40c057": "녹색 6", "green": "",
"82c91e": "라임 6", "yellow": "",
"fab005": "노란색 6", "orange": "",
"fd7e14": "주황색 6", "bronze": ""
"000000": "검정색",
"343a40": "회색 8",
"495057": "회색 7",
"c92a2a": "빨강색 9",
"a61e4d": "핑크색 9",
"862e9c": "그레이프 9",
"5f3dc4": "바이올렛 9",
"364fc7": "남색 9",
"1864ab": "파란색 9",
"0b7285": "청록색 9",
"087f5b": "틸 9",
"2b8a3e": "녹색 9",
"5c940d": "라임 9",
"e67700": "노란색 9",
"d9480f": "주황색 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "도구를 선택하고, 그리세요!", "toolbarHint": "도구를 선택하고, 그리세요!",
"helpHint": "단축키 & 도움말" "helpHint": "단축키 & 도움말"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "بۆ دانانەوە وەکو یەک توخم یان دانانەوە بۆ نێو دەسکاریکەرێکی دەق کە بوونی هەیە {{shortcut}} بەکاربهێنە" "pasteAsSingleElement": "بۆ دانانەوە وەکو یەک توخم یان دانانەوە بۆ نێو دەسکاریکەرێکی دەق کە بوونی هەیە {{shortcut}} بەکاربهێنە"
}, },
"colors": { "colors": {
"ffffff": "سپی",
"f8f9fa": "خۆڵەمێشی 0",
"f1f3f5": "خۆڵەمێشی 1",
"fff5f5": "سور 0",
"fff0f6": "پەمەی 0",
"f8f0fc": "مێوژی 0",
"f3f0ff": "مۆر 0",
"edf2ff": "نیلی 0",
"e7f5ff": "شین 0",
"e3fafc": "شینی ئاسمانی 0",
"e6fcf5": "سەوزباوی 0",
"ebfbee": "سه‌وز 0",
"f4fce3": "نارنجی 0",
"fff9db": "زەرد 0",
"fff4e6": "پرتەقاڵی 0",
"transparent": "ڕوون", "transparent": "ڕوون",
"ced4da": "خۆڵەمێشی 4", "black": "",
"868e96": "خۆڵەمێشی 6", "white": "",
"fa5252": "سور 6", "red": "",
"e64980": "پەمەی 6", "pink": "",
"be4bdb": "مێوژی 6", "grape": "",
"7950f2": "مۆر 6", "violet": "",
"4c6ef5": "نیلی 6", "gray": "",
"228be6": "شین 6", "blue": "",
"15aabf": "شینی ئاسمانی 6", "cyan": "",
"12b886": "سەوزباوی 6", "teal": "",
"40c057": "سه‌وز 6", "green": "",
"82c91e": "نارنجی 6", "yellow": "",
"fab005": "زەرد 6", "orange": "",
"fd7e14": "پرتەقاڵی 6", "bronze": ""
"000000": "ڕەش",
"343a40": "خۆڵەمێشی 8",
"495057": "خۆڵەمێشی 7",
"c92a2a": "سور 9",
"a61e4d": "پەمەی 9",
"862e9c": "مێوژی 9",
"5f3dc4": "مۆر 9",
"364fc7": "نیلی 9",
"1864ab": "شین 9",
"0b7285": "شینی ئاسمانی 9",
"087f5b": "سەوزباوی 9",
"2b8a3e": "سه‌وز 9",
"5c940d": "نارنجی 9",
"e67700": "زەرد 9",
"d9480f": "پرتەقاڵی 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "ئامرازێک هەڵبگرە و دەستبکە بە کێشان!", "toolbarHint": "ئامرازێک هەڵبگرە و دەستبکە بە کێشان!",
"helpHint": "قەدبڕەکان و یارمەتی" "helpHint": "قەدبڕەکان و یارمەتی"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "Balta",
"f8f9fa": "Pilka 0",
"f1f3f5": "Pilka 1",
"fff5f5": "Raudona 0",
"fff0f6": "Rožinė 0",
"f8f0fc": "",
"f3f0ff": "Violetinė 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Mėlyna 0",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "Žalia 0",
"f4fce3": "",
"fff9db": "Geltona 0",
"fff4e6": "Oranžinė 0",
"transparent": "Permatoma", "transparent": "Permatoma",
"ced4da": "Pilka 4", "black": "",
"868e96": "Pilka 6", "white": "",
"fa5252": "Raudona 6", "red": "",
"e64980": "Rožinė 6", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "Violetinė 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Mėlyna 6", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "Žalia 6", "green": "",
"82c91e": "", "yellow": "",
"fab005": "Geltona 6", "orange": "",
"fd7e14": "Oranžinė 6", "bronze": ""
"000000": "Juoda",
"343a40": "Pilna 8",
"495057": "Pilka 7",
"c92a2a": "Raudona 9",
"a61e4d": "Rožinė 9",
"862e9c": "",
"5f3dc4": "Violetinė 9",
"364fc7": "Indigo 9",
"1864ab": "Mėlyna 9",
"0b7285": "",
"087f5b": "",
"2b8a3e": "Žalia 9",
"5c940d": "",
"e67700": "Geltona 9",
"d9480f": "Oranžinė 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Izmantojiet {{shortcut}}, lai ielīmētu kā jaunu elementu, vai ielīmētu esošā teksta lauciņā" "pasteAsSingleElement": "Izmantojiet {{shortcut}}, lai ielīmētu kā jaunu elementu, vai ielīmētu esošā teksta lauciņā"
}, },
"colors": { "colors": {
"ffffff": "Balts",
"f8f9fa": "Pelēks 0",
"f1f3f5": "Pelēks 1",
"fff5f5": "Sarkans 0",
"fff0f6": "Rozā 0",
"f8f0fc": "Vīnogu 0",
"f3f0ff": "Violets 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Zils 0",
"e3fafc": "Ciāns 0",
"e6fcf5": "Zilganzaļš 0",
"ebfbee": "Zaļš 0",
"f4fce3": "Laims 0",
"fff9db": "Dzeltens 0",
"fff4e6": "Oranžs 0",
"transparent": "Caurspīdīgs", "transparent": "Caurspīdīgs",
"ced4da": "Pelēks 4", "black": "",
"868e96": "Pelēks 6", "white": "",
"fa5252": "Sarkans 6", "red": "",
"e64980": "Rozā 6", "pink": "",
"be4bdb": "Vīnogu 6", "grape": "",
"7950f2": "Violets 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Zils 6", "blue": "",
"15aabf": "Ciāns 6", "cyan": "",
"12b886": "Zilganzaļš 6", "teal": "",
"40c057": "Zaļš 6", "green": "",
"82c91e": "Laims 6", "yellow": "",
"fab005": "Dzeltens 6", "orange": "",
"fd7e14": "Oranžs 6", "bronze": ""
"000000": "Melns",
"343a40": "Pelēks 8",
"495057": "Pelēks 7",
"c92a2a": "Sarkans 9",
"a61e4d": "Rozā 9",
"862e9c": "Vīnogu 9",
"5f3dc4": "Violets 9",
"364fc7": "Indigo 9",
"1864ab": "Zils 9",
"0b7285": "Ciāns 9",
"087f5b": "Zilganzaļš 9",
"2b8a3e": "Zaļš 9",
"5c940d": "Laims 9",
"e67700": "Dzeltens 9",
"d9480f": "Oranžs 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Izvēlies rīku un sāc zīmēt!", "toolbarHint": "Izvēlies rīku un sāc zīmēt!",
"helpHint": "Īsceļi un palīdzība" "helpHint": "Īsceļi un palīdzība"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "एक घटक म्हणून चिपकावण्या साठी {{shortcut}} वापरा,\nकिंवा विद्यमान मजकूर संपादकात चिपकवा" "pasteAsSingleElement": "एक घटक म्हणून चिपकावण्या साठी {{shortcut}} वापरा,\nकिंवा विद्यमान मजकूर संपादकात चिपकवा"
}, },
"colors": { "colors": {
"ffffff": "पांढरा",
"f8f9fa": "करडा 0",
"f1f3f5": "करडा 1",
"fff5f5": "लाल 0",
"fff0f6": "गुलाबी 0",
"f8f0fc": "अंगूरी 0",
"f3f0ff": "जांभळा 0",
"edf2ff": "बैंगनी 0",
"e7f5ff": "नीळा 0",
"e3fafc": "आसमानी 0",
"e6fcf5": "पाचू 0",
"ebfbee": "हिरवा 0",
"f4fce3": "लिंबु 0",
"fff9db": "पिवळा 0",
"fff4e6": "नारंगी 0",
"transparent": "पारदर्शक", "transparent": "पारदर्शक",
"ced4da": "करडा 4", "black": "",
"868e96": "करडा 6", "white": "",
"fa5252": "लाल 6", "red": "",
"e64980": "गुलाबी 6", "pink": "",
"be4bdb": "अंगूरी 6", "grape": "",
"7950f2": "जांभळा 6", "violet": "",
"4c6ef5": "बैंगनी 6", "gray": "",
"228be6": "नीळा 6", "blue": "",
"15aabf": "आसमानी 6", "cyan": "",
"12b886": "पाचू 6", "teal": "",
"40c057": "हिरवा 6", "green": "",
"82c91e": "लिंबु 6", "yellow": "",
"fab005": "पिवळा 6", "orange": "",
"fd7e14": "नारंगी 6", "bronze": ""
"000000": "काळा",
"343a40": "करडा 8",
"495057": "करडा 7",
"c92a2a": "लाल 9",
"a61e4d": "गुलाबी 9",
"862e9c": "अंगूरी 9",
"5f3dc4": "जामुनी 9",
"364fc7": "बैंगनी 9",
"1864ab": "नीला 9",
"0b7285": "आसमानी 9",
"087f5b": "पाचू 9",
"2b8a3e": "हरा 9",
"5c940d": "नीबू 9",
"e67700": "पीला 9",
"d9480f": "नारंगी 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "एक साधन निवडा आणि चित्रीकरण सुरु करा!", "toolbarHint": "एक साधन निवडा आणि चित्रीकरण सुरु करा!",
"helpHint": "शॉर्टकट आणि सहाय्य" "helpHint": "शॉर्टकट आणि सहाय्य"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "",
"f8f9fa": "",
"f1f3f5": "",
"fff5f5": "",
"fff0f6": "",
"f8f0fc": "",
"f3f0ff": "",
"edf2ff": "",
"e7f5ff": "",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "",
"f4fce3": "",
"fff9db": "",
"fff4e6": "",
"transparent": "", "transparent": "",
"ced4da": "", "black": "",
"868e96": "", "white": "",
"fa5252": "", "red": "",
"e64980": "", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "", "green": "",
"82c91e": "", "yellow": "",
"fab005": "", "orange": "",
"fd7e14": "", "bronze": ""
"000000": "",
"343a40": "",
"495057": "",
"c92a2a": "",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "",
"0b7285": "",
"087f5b": "",
"2b8a3e": "",
"5c940d": "",
"e67700": "",
"d9480f": ""
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Bruk {{shortcut}} for å lime inn som ett enkelt element,\neller lim inn i en eksisterende tekstbehandler" "pasteAsSingleElement": "Bruk {{shortcut}} for å lime inn som ett enkelt element,\neller lim inn i en eksisterende tekstbehandler"
}, },
"colors": { "colors": {
"ffffff": "Hvit",
"f8f9fa": "Grå 0",
"f1f3f5": "Grå 1",
"fff5f5": "Rød 0",
"fff0f6": "Rosa 0",
"f8f0fc": "Drue 0",
"f3f0ff": "Fiolett 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Blå 0",
"e3fafc": "Turkis 0",
"e6fcf5": "Blågrønn 0",
"ebfbee": "Grønn 0",
"f4fce3": "Limegrønn 0",
"fff9db": "Gul 0",
"fff4e6": "Oransje 0",
"transparent": "Gjennomsiktig", "transparent": "Gjennomsiktig",
"ced4da": "Grå 4", "black": "",
"868e96": "Grå 6", "white": "",
"fa5252": "Rød 6", "red": "",
"e64980": "Rosa 6", "pink": "",
"be4bdb": "Drue 6", "grape": "",
"7950f2": "Fiolett 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Blå 6", "blue": "",
"15aabf": "Turkis 6", "cyan": "",
"12b886": "Blågrønn 6", "teal": "",
"40c057": "Grønn 6", "green": "",
"82c91e": "Limegrønn 6", "yellow": "",
"fab005": "Gul 6", "orange": "",
"fd7e14": "Oransje 6", "bronze": ""
"000000": "Sort",
"343a40": "Grå 8",
"495057": "Grå 7",
"c92a2a": "Rød 9",
"a61e4d": "Rosa 9",
"862e9c": "Drue 9",
"5f3dc4": "Fiolett 9",
"364fc7": "Indigo 9",
"1864ab": "Blå 9",
"0b7285": "Turkis 9",
"087f5b": "Blågrønn 9",
"2b8a3e": "Grønn 9",
"5c940d": "Limegrønn 9",
"e67700": "Gul 9",
"d9480f": "Oransje 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Velg et verktøy og start å tegne!", "toolbarHint": "Velg et verktøy og start å tegne!",
"helpHint": "Snarveier & hjelp" "helpHint": "Snarveier & hjelp"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Gebruik {{shortcut}} om te plakken als een enkel element,\nof plak in een bestaande teksteditor" "pasteAsSingleElement": "Gebruik {{shortcut}} om te plakken als een enkel element,\nof plak in een bestaande teksteditor"
}, },
"colors": { "colors": {
"ffffff": "Wit",
"f8f9fa": "Grijs 0",
"f1f3f5": "Grijs 1",
"fff5f5": "Rood 0",
"fff0f6": "Roze 0",
"f8f0fc": "Druiven 0",
"f3f0ff": "Violet 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Blauw 0",
"e3fafc": "Cyaan 0",
"e6fcf5": "Groenblauw 0",
"ebfbee": "Groen 0",
"f4fce3": "Limoen 0",
"fff9db": "Geel 0",
"fff4e6": "Oranje 0",
"transparent": "Transparant", "transparent": "Transparant",
"ced4da": "Grijs 4", "black": "",
"868e96": "Grijs 6", "white": "",
"fa5252": "Rood 6", "red": "",
"e64980": "Roze 6", "pink": "",
"be4bdb": "Druiven 6", "grape": "",
"7950f2": "Violet", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Blauw 6", "blue": "",
"15aabf": "Cyaan 6", "cyan": "",
"12b886": "Groenblauw 6", "teal": "",
"40c057": "Groen 6", "green": "",
"82c91e": "Limoen 6", "yellow": "",
"fab005": "Geel 6", "orange": "",
"fd7e14": "Oranje 6", "bronze": ""
"000000": "Zwart",
"343a40": "Grijs 8",
"495057": "Grijs 7",
"c92a2a": "Rood 9",
"a61e4d": "Roze 9",
"862e9c": "Druiven 9",
"5f3dc4": "Violet 9",
"364fc7": "Indigo 9",
"1864ab": "Blauw 9",
"0b7285": "Cyaan 9",
"087f5b": "Groenblauw 9",
"2b8a3e": "Groen 9",
"5c940d": "Limoen 9",
"e67700": "Geel 9",
"d9480f": "Oranje 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Kies een tool & begin met tekenen!", "toolbarHint": "Kies een tool & begin met tekenen!",
"helpHint": "Snelkoppelingen en hulp" "helpHint": "Snelkoppelingen en hulp"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "Kvit",
"f8f9fa": "Grå 0",
"f1f3f5": "Grå 1",
"fff5f5": "Raud 0",
"fff0f6": "Rosa 0",
"f8f0fc": "Drue 0",
"f3f0ff": "Fiolett 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Blå 0",
"e3fafc": "Turkis 0",
"e6fcf5": "Blågrøn 0",
"ebfbee": "Grøn 0",
"f4fce3": "Limegrøn 0",
"fff9db": "Gul 0",
"fff4e6": "Oransje 0",
"transparent": "Gjennomsiktig", "transparent": "Gjennomsiktig",
"ced4da": "Grå 4", "black": "",
"868e96": "Grå 6", "white": "",
"fa5252": "Raud 6", "red": "",
"e64980": "Rosa 6", "pink": "",
"be4bdb": "Drue 6", "grape": "",
"7950f2": "Fiolett 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Blå 6", "blue": "",
"15aabf": "Turkis 6", "cyan": "",
"12b886": "Blågrøn 6", "teal": "",
"40c057": "Grøn 6", "green": "",
"82c91e": "Limegrøn 6", "yellow": "",
"fab005": "Gul 6", "orange": "",
"fd7e14": "Oransje 6", "bronze": ""
"000000": "Svart",
"343a40": "Grå 8",
"495057": "Grå 7",
"c92a2a": "Raud 9",
"a61e4d": "Rosa 9",
"862e9c": "Drue 9",
"5f3dc4": "Fiolett 9",
"364fc7": "Indigo 9",
"1864ab": "Blå 9",
"0b7285": "Turkis 9",
"087f5b": "Blågrøn 9",
"2b8a3e": "Grøn 9",
"5c940d": "Limegrøn 9",
"e67700": "Gul 9",
"d9480f": "Oransj 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "Blanc",
"f8f9fa": "Gris 0",
"f1f3f5": "Gris 0",
"fff5f5": "Roge 0",
"fff0f6": "Ròse 0",
"f8f0fc": "Bordèu 0",
"f3f0ff": "Violet 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Blau 0",
"e3fafc": "Cian 0",
"e6fcf5": "Esmerauda 0",
"ebfbee": "Verd 0",
"f4fce3": "Verd citron 0",
"fff9db": "Jaune 0",
"fff4e6": "Irange 0",
"transparent": "Transparéncia", "transparent": "Transparéncia",
"ced4da": "Gris 4", "black": "",
"868e96": "Gris 6", "white": "",
"fa5252": "Roge 6", "red": "",
"e64980": "Ròse 6", "pink": "",
"be4bdb": "Bordèu 6", "grape": "",
"7950f2": "Violet 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Blau 6", "blue": "",
"15aabf": "Cian 6", "cyan": "",
"12b886": "Esmerauda 6", "teal": "",
"40c057": "Verd 6", "green": "",
"82c91e": "Verd citron 6", "yellow": "",
"fab005": "Jaune 6", "orange": "",
"fd7e14": "Irange 6", "bronze": ""
"000000": "Negre",
"343a40": "Gris 8",
"495057": "Gris 7",
"c92a2a": "Roge 9",
"a61e4d": "Ròse 9",
"862e9c": "Bordèu 9",
"5f3dc4": "Violet 9",
"364fc7": "Indigo 9",
"1864ab": "Blau 9",
"0b7285": "Cian 9",
"087f5b": "Esmerauda 9",
"2b8a3e": "Verd 9",
"5c940d": "Verd citron 9",
"e67700": "Jaune 9",
"d9480f": "Irange 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Prenètz un esplech e començatz de dessenhar!", "toolbarHint": "Prenètz un esplech e començatz de dessenhar!",
"helpHint": "Acorchis e ajuda" "helpHint": "Acorchis e ajuda"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"labels": { "labels": {
"paste": "ਪੇਸਟ ਕਰੋ", "paste": "ਪੇਸਟ ਕਰੋ",
"pasteAsPlaintext": "", "pasteAsPlaintext": "ਸਾਦੇ ਪਾਠ ਵਜੋਂ ਪੇਸਟ ਕਰੋ",
"pasteCharts": "ਚਾਰਟ ਪੇਸਟ ਕਰੋ", "pasteCharts": "ਚਾਰਟ ਪੇਸਟ ਕਰੋ",
"selectAll": "ਸਾਰੇ ਚੁਣੋ", "selectAll": "ਸਾਰੇ ਚੁਣੋ",
"multiSelect": "ਐਲੀਮੈਂਟ ਨੂੰ ਚੋਣ ਵਿੱਚ ਜੋੜੋ", "multiSelect": "ਐਲੀਮੈਂਟ ਨੂੰ ਚੋਣ ਵਿੱਚ ਜੋੜੋ",
@ -10,7 +10,7 @@
"copy": "ਕਾਪੀ ਕਰੋ", "copy": "ਕਾਪੀ ਕਰੋ",
"copyAsPng": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ PNG ਵਜੋਂ ਕਾਪੀ ਕਰੋ", "copyAsPng": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ PNG ਵਜੋਂ ਕਾਪੀ ਕਰੋ",
"copyAsSvg": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ SVG ਵਜੋਂ ਕਾਪੀ ਕਰੋ", "copyAsSvg": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ SVG ਵਜੋਂ ਕਾਪੀ ਕਰੋ",
"copyText": "", "copyText": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਪਾਠ ਵਜੋਂ ਕਾਪੀ ਕਰੋ",
"bringForward": "ਅੱਗੇ ਲਿਆਓ", "bringForward": "ਅੱਗੇ ਲਿਆਓ",
"sendToBack": "ਸਭ ਤੋਂ ਪਿੱਛੇ ਭੇਜੋ", "sendToBack": "ਸਭ ਤੋਂ ਪਿੱਛੇ ਭੇਜੋ",
"bringToFront": "ਸਭ ਤੋਂ ਅੱਗੇ ਲਿਆਓ", "bringToFront": "ਸਭ ਤੋਂ ਅੱਗੇ ਲਿਆਓ",
@ -73,7 +73,7 @@
"layers": "ਪਰਤਾਂ", "layers": "ਪਰਤਾਂ",
"actions": "ਕਾਰਵਾਈਆਂ", "actions": "ਕਾਰਵਾਈਆਂ",
"language": "ਭਾਸ਼ਾ", "language": "ਭਾਸ਼ਾ",
"liveCollaboration": "", "liveCollaboration": "ਲਾਇਵ ਸਹਿਯੋਗ...",
"duplicateSelection": "ਡੁਪਲੀਕੇਟ ਬਣਾਓ", "duplicateSelection": "ਡੁਪਲੀਕੇਟ ਬਣਾਓ",
"untitled": "ਬੇ-ਸਿਰਨਾਵਾਂ", "untitled": "ਬੇ-ਸਿਰਨਾਵਾਂ",
"name": "ਨਾਂ", "name": "ਨਾਂ",
@ -109,37 +109,37 @@
"excalidrawLib": "ਐਕਸਕਲੀਡਰਾਅ ਲਾਇਬ੍ਰੇਰੀ", "excalidrawLib": "ਐਕਸਕਲੀਡਰਾਅ ਲਾਇਬ੍ਰੇਰੀ",
"decreaseFontSize": "ਫੌਂਟ ਦਾ ਅਕਾਰ ਘਟਾਓ", "decreaseFontSize": "ਫੌਂਟ ਦਾ ਅਕਾਰ ਘਟਾਓ",
"increaseFontSize": "ਫੌਂਟ ਦਾ ਅਕਾਰ ਵਧਾਓ", "increaseFontSize": "ਫੌਂਟ ਦਾ ਅਕਾਰ ਵਧਾਓ",
"unbindText": "", "unbindText": "ਪਾਠ ਨੂੰ ਵੱਖ ਕਰੋ",
"bindText": "ਪਾਠ ਨੂੰ ਕੰਟੇਨਰ ਨਾਲ ਬੰਨ੍ਹੋ", "bindText": "ਪਾਠ ਨੂੰ ਕੰਟੇਨਰ ਨਾਲ ਬੰਨ੍ਹੋ",
"createContainerFromText": "", "createContainerFromText": "ਪਾਠ ਨੂੰ ਕੰਟੇਨਰ ਵਿੱਚ ਇਕੱਠਾ ਕਰੋ",
"link": { "link": {
"edit": "ਕੜੀ ਸੋਧੋ", "edit": "ਕੜੀ ਸੋਧੋ",
"create": "ਕੜੀ ਬਣਾਓ", "create": "ਕੜੀ ਬਣਾਓ",
"label": "ਕੜੀ" "label": "ਕੜੀ"
}, },
"lineEditor": { "lineEditor": {
"edit": "", "edit": "ਪੰਕਤੀ ਸੋਧੋ",
"exit": "" "exit": "ਪੰਕਤੀ ਸੋਧਕ 'ਤੋਂ ਬਾਹਰ ਨਿਕਲੋ"
}, },
"elementLock": { "elementLock": {
"lock": "", "lock": "ਲਾਕ ਕਰੋ",
"unlock": "", "unlock": "ਅਨਲਾਕ ਕਰੋ",
"lockAll": "", "lockAll": "ਸਭ ਲਾਕ ਕਰੋ",
"unlockAll": "" "unlockAll": "ਸਭ ਅਨਲਾਕ ਕਰੋ"
}, },
"statusPublished": "", "statusPublished": "ਪ੍ਰਕਾਸ਼ਤ ਹੈ",
"sidebarLock": "" "sidebarLock": "ਸਾਈਡਬਾਰ ਨੂੰ ਖੁੱਲ੍ਹਾ ਰੱਖੋ"
}, },
"library": { "library": {
"noItems": "", "noItems": "ਹਾਲੇ ਤੱਕ ਕੋਈ ਚੀਜ ਜੋੜੀ ਨਹੀਂ ਗਈ...",
"hint_emptyLibrary": "", "hint_emptyLibrary": "",
"hint_emptyPrivateLibrary": "" "hint_emptyPrivateLibrary": ""
}, },
"buttons": { "buttons": {
"clearReset": "ਕੈਨਵਸ ਰੀਸੈੱਟ ਕਰੋ", "clearReset": "ਕੈਨਵਸ ਰੀਸੈੱਟ ਕਰੋ",
"exportJSON": "ਫਾਈਲ ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ", "exportJSON": "ਫਾਈਲ ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ",
"exportImage": "", "exportImage": "ਤਸਵੀਰ ਨਿਰਯਾਤ ਕਰੋ...",
"export": "", "export": "ਇਸ ਵਿੱਚ ਸਾਂਭੋ...",
"exportToPng": "PNG ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ", "exportToPng": "PNG ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ",
"exportToSvg": "SVG ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ", "exportToSvg": "SVG ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ",
"copyToClipboard": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਕਰੋ", "copyToClipboard": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਕਰੋ",
@ -147,7 +147,7 @@
"scale": "ਪੈਮਾਇਸ਼", "scale": "ਪੈਮਾਇਸ਼",
"save": "ਮੌਜੂਦਾ ਫਾਈਲ ਵਿੱਚ ਸਾਂਭੋ", "save": "ਮੌਜੂਦਾ ਫਾਈਲ ਵਿੱਚ ਸਾਂਭੋ",
"saveAs": "ਇਸ ਵਜੋਂ ਸਾਂਭੋ", "saveAs": "ਇਸ ਵਜੋਂ ਸਾਂਭੋ",
"load": "", "load": "ਖੋਲ੍ਹੋ",
"getShareableLink": "ਸਾਂਝੀ ਕਰਨ ਵਾਲੀ ਲਿੰਕ ਲਵੋ", "getShareableLink": "ਸਾਂਝੀ ਕਰਨ ਵਾਲੀ ਲਿੰਕ ਲਵੋ",
"close": "ਬੰਦ ਕਰੋ", "close": "ਬੰਦ ਕਰੋ",
"selectLanguage": "ਭਾਸ਼ਾ ਚੁਣੋ", "selectLanguage": "ਭਾਸ਼ਾ ਚੁਣੋ",
@ -181,7 +181,7 @@
"couldNotLoadInvalidFile": "ਨਜਾਇਜ਼ ਫਾਈਲ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕੇ", "couldNotLoadInvalidFile": "ਨਜਾਇਜ਼ ਫਾਈਲ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕੇ",
"importBackendFailed": "ਬੈਕਐੱਨਡ ਤੋਂ ਆਯਾਤ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ।", "importBackendFailed": "ਬੈਕਐੱਨਡ ਤੋਂ ਆਯਾਤ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ।",
"cannotExportEmptyCanvas": "ਖਾਲੀ ਕੈਨਵਸ ਨਿਰਯਾਤ ਨਹੀਂ ਕਰ ਸਕਦੇ।", "cannotExportEmptyCanvas": "ਖਾਲੀ ਕੈਨਵਸ ਨਿਰਯਾਤ ਨਹੀਂ ਕਰ ਸਕਦੇ।",
"couldNotCopyToClipboard": "", "couldNotCopyToClipboard": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।",
"decryptFailed": "ਡਾਟਾ ਡੀਕਰਿਪਟ ਨਹੀਂ ਕਰ ਸਕੇ।", "decryptFailed": "ਡਾਟਾ ਡੀਕਰਿਪਟ ਨਹੀਂ ਕਰ ਸਕੇ।",
"uploadedSecurly": "ਅੱਪਲੋਡ ਸਿਰੇ-ਤੋਂ-ਸਿਰੇ ਤੱਕ ਇਨਕਰਿਪਸ਼ਨ ਨਾਲ ਸੁਰੱਖਿਅਤ ਕੀਤੀ ਹੋਈ ਹੈ, ਜਿਸਦਾ ਮਤਲਬ ਇਹ ਹੈ ਕਿ Excalidraw ਸਰਵਰ ਅਤੇ ਤੀਜੀ ਧਿਰ ਦੇ ਬੰਦੇ ਸਮੱਗਰੀ ਨੂੰ ਪੜ੍ਹ ਨਹੀਂ ਸਕਦੇ।", "uploadedSecurly": "ਅੱਪਲੋਡ ਸਿਰੇ-ਤੋਂ-ਸਿਰੇ ਤੱਕ ਇਨਕਰਿਪਸ਼ਨ ਨਾਲ ਸੁਰੱਖਿਅਤ ਕੀਤੀ ਹੋਈ ਹੈ, ਜਿਸਦਾ ਮਤਲਬ ਇਹ ਹੈ ਕਿ Excalidraw ਸਰਵਰ ਅਤੇ ਤੀਜੀ ਧਿਰ ਦੇ ਬੰਦੇ ਸਮੱਗਰੀ ਨੂੰ ਪੜ੍ਹ ਨਹੀਂ ਸਕਦੇ।",
"loadSceneOverridePrompt": "ਬਾਹਰੀ ਡਰਾਇੰਗ ਨੂੰ ਲੋਡ ਕਰਨਾ ਤੁਹਾਡੀ ਮੌਜੂਦਾ ਸਮੱਗਰੀ ਦੀ ਥਾਂ ਲੈ ਲਵੇਗਾ। ਕੀ ਤੁਸੀਂ ਜਾਰੀ ਰੱਖਣਾ ਚਾਹੁੰਦੇ ਹੋ?", "loadSceneOverridePrompt": "ਬਾਹਰੀ ਡਰਾਇੰਗ ਨੂੰ ਲੋਡ ਕਰਨਾ ਤੁਹਾਡੀ ਮੌਜੂਦਾ ਸਮੱਗਰੀ ਦੀ ਥਾਂ ਲੈ ਲਵੇਗਾ। ਕੀ ਤੁਸੀਂ ਜਾਰੀ ਰੱਖਣਾ ਚਾਹੁੰਦੇ ਹੋ?",
@ -198,13 +198,13 @@
"collabOfflineWarning": "" "collabOfflineWarning": ""
}, },
"errors": { "errors": {
"unsupportedFileType": "", "unsupportedFileType": "ਫਾਈਲ ਦੀ ਕਿਸਮ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ।",
"imageInsertError": "", "imageInsertError": "ਚਿੱਤਰ ਸ਼ਾਮਲ ਨਹੀਂ ਜਾ ਸਕਿਆ, ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ...",
"fileTooBig": "", "fileTooBig": "ਫਾਈਲ ਬਹੁਤ ਜ਼ਿਆਦਾ ਵੱਡੀ ਹੈ। ਵੱਧ-ਤੋਂ-ਵੱਧ ਪ੍ਰਵਾਨਤ ਅਕਾਰ {{maxSize}} ਹੈ।",
"svgImageInsertError": "", "svgImageInsertError": "",
"invalidSVGString": "SVG ਨਜਾਇਜ਼ ਹੈ।", "invalidSVGString": "SVG ਨਜਾਇਜ਼ ਹੈ।",
"cannotResolveCollabServer": "", "cannotResolveCollabServer": "",
"importLibraryError": "", "importLibraryError": "ਲਾਇਬ੍ਰੇਰੀ ਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ",
"collabSaveFailed": "", "collabSaveFailed": "",
"collabSaveFailed_sizeExceeded": "", "collabSaveFailed_sizeExceeded": "",
"brave_measure_text_error": { "brave_measure_text_error": {
@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "ਚਿੱਟਾ",
"f8f9fa": "ਸੁਰਮਈ 0",
"f1f3f5": "ਸੁਰਮਈ 1",
"fff5f5": "ਲਾਲ 0",
"fff0f6": "ਗੁਲਾਬੀ 0",
"f8f0fc": "ਅੰਗੂਰੀ 0",
"f3f0ff": "ਜਾਮਣੀ 0",
"edf2ff": "ਗੂੜ੍ਹਾ ਨੀਲਾ 0",
"e7f5ff": "ਨੀਲਾ 0",
"e3fafc": "ਫਿਰੋਜੀ 0",
"e6fcf5": "ਟੀਲ 0",
"ebfbee": "ਹਰਾ 0",
"f4fce3": "ਲਾਇਮ 0",
"fff9db": "ਪੀਲਾ 0",
"fff4e6": "ਸੰਤਰੀ 0",
"transparent": "ਪਾਰਦਰਸ਼ੀ", "transparent": "ਪਾਰਦਰਸ਼ੀ",
"ced4da": "ਸੁਰਮਈ 4", "black": "",
"868e96": "ਸੁਰਮਈ 6", "white": "",
"fa5252": "ਲਾਲ 6", "red": "",
"e64980": "ਗੁਲਾਬੀ 6", "pink": "",
"be4bdb": "ਅੰਗੂਰੀ 6", "grape": "",
"7950f2": "ਜਾਮਣੀ 6", "violet": "",
"4c6ef5": "ਗੂੜ੍ਹਾ ਨੀਲਾ 6", "gray": "",
"228be6": "ਨੀਲਾ 6", "blue": "",
"15aabf": "ਫਿਰੋਜੀ 6", "cyan": "",
"12b886": "ਟੀਲ 6", "teal": "",
"40c057": "ਹਰਾ 6", "green": "",
"82c91e": "ਲਾਇਮ 6", "yellow": "",
"fab005": "ਪੀਲਾ 6", "orange": "",
"fd7e14": "ਸੰਤਰੀ 6", "bronze": ""
"000000": "ਕਾਲਾ",
"343a40": "ਸੁਰਮਈ 8",
"495057": "ਸੁਰਮਈ 7",
"c92a2a": "ਲਾਲ 9",
"a61e4d": "ਗੁਲਾਬੀ 9",
"862e9c": "ਅੰਗੂਰੀ 9",
"5f3dc4": "ਜਾਮਣੀ 9",
"364fc7": "ਗੂੜ੍ਹਾ ਨੀਲਾ 9",
"1864ab": "ਨੀਲਾ 9",
"0b7285": "ਫਿਰੋਜੀ 9",
"087f5b": "ਟੀਲ 9",
"2b8a3e": "ਹਰਾ 9",
"5c940d": "ਲਾਇਮ 9",
"e67700": "ਪੀਲਾ 9",
"d9480f": "ਸੰਤਰੀ 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -1,55 +1,55 @@
{ {
"ar-SA": 90, "ar-SA": 83,
"bg-BG": 53, "bg-BG": 57,
"bn-BD": 59, "bn-BD": 63,
"ca-ES": 98, "ca-ES": 92,
"cs-CZ": 73, "cs-CZ": 67,
"da-DK": 32, "da-DK": 35,
"de-DE": 100, "de-DE": 94,
"el-GR": 97, "el-GR": 92,
"en": 100, "en": 100,
"es-ES": 99, "es-ES": 93,
"eu-ES": 98, "eu-ES": 94,
"fa-IR": 100, "fa-IR": 93,
"fi-FI": 98, "fi-FI": 92,
"fr-FR": 98, "fr-FR": 93,
"gl-ES": 98, "gl-ES": 92,
"he-IL": 98, "he-IL": 91,
"hi-IN": 71, "hi-IN": 74,
"hu-HU": 87, "hu-HU": 80,
"id-ID": 97, "id-ID": 92,
"it-IT": 98, "it-IT": 94,
"ja-JP": 98, "ja-JP": 92,
"kaa": 20, "kaa": 18,
"kab-KAB": 93, "kab-KAB": 92,
"kk-KZ": 20, "kk-KZ": 22,
"km-KH": 0, "km-KH": 2,
"ko-KR": 100, "ko-KR": 94,
"ku-TR": 100, "ku-TR": 94,
"lt-LT": 62, "lt-LT": 59,
"lv-LV": 99, "lv-LV": 93,
"mr-IN": 99, "mr-IN": 93,
"my-MM": 40, "my-MM": 43,
"nb-NO": 100, "nb-NO": 94,
"nl-NL": 91, "nl-NL": 87,
"nn-NO": 87, "nn-NO": 81,
"oc-FR": 96, "oc-FR": 91,
"pa-IN": 81, "pa-IN": 82,
"pl-PL": 88, "pl-PL": 83,
"pt-BR": 98, "pt-BR": 94,
"pt-PT": 99, "pt-PT": 93,
"ro-RO": 100, "ro-RO": 94,
"ru-RU": 100, "ru-RU": 94,
"si-LK": 8, "si-LK": 9,
"sk-SK": 100, "sk-SK": 94,
"sl-SI": 100, "sl-SI": 94,
"sv-SE": 100, "sv-SE": 94,
"ta-IN": 92, "ta-IN": 86,
"th-TH": 40, "th-TH": 41,
"tr-TR": 96, "tr-TR": 91,
"uk-UA": 95, "uk-UA": 94,
"vi-VN": 56, "vi-VN": 59,
"zh-CN": 100, "zh-CN": 94,
"zh-HK": 25, "zh-HK": 27,
"zh-TW": 100 "zh-TW": 94
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "Biały",
"f8f9fa": "Szary 0",
"f1f3f5": "Szary 1",
"fff5f5": "Czerwony 0",
"fff0f6": "Różowy 0",
"f8f0fc": "Bordowy 0",
"f3f0ff": "Fioletowy 0",
"edf2ff": "Granatowy 0",
"e7f5ff": "Niebieski 0",
"e3fafc": "Błękitny 0",
"e6fcf5": "Turkusowy 0",
"ebfbee": "Zielony 0",
"f4fce3": "Limonkowy 0",
"fff9db": "Żółty 0",
"fff4e6": "Pomarańczowy 0",
"transparent": "Przezroczysty", "transparent": "Przezroczysty",
"ced4da": "Szary 4", "black": "",
"868e96": "Szary 6", "white": "",
"fa5252": "Czerwony 6", "red": "",
"e64980": "Różowy 6", "pink": "",
"be4bdb": "Bordowy 6", "grape": "",
"7950f2": "Fioletowy 6", "violet": "",
"4c6ef5": "Granatowy 6", "gray": "",
"228be6": "Niebieski 6", "blue": "",
"15aabf": "Błękitny 6", "cyan": "",
"12b886": "Turkusowy 6", "teal": "",
"40c057": "Zielony 6", "green": "",
"82c91e": "Limonkowy 6", "yellow": "",
"fab005": "Żółty 6", "orange": "",
"fd7e14": "Pomarańczowy 6", "bronze": ""
"000000": "Czarny",
"343a40": "Szary 8",
"495057": "Szary 7",
"c92a2a": "Czerwony 9",
"a61e4d": "Różowy 9",
"862e9c": "Bordowy 9",
"5f3dc4": "Fioletowy 9",
"364fc7": "Granatowy 9",
"1864ab": "Niebieski 9",
"0b7285": "Błękitny 9",
"087f5b": "Turkusowy 9",
"2b8a3e": "Zielony 9",
"5c940d": "Limonkowy 9",
"e67700": "Żółty 9",
"d9480f": "Pomarańczowy 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -54,7 +54,7 @@
"veryLarge": "Muito grande", "veryLarge": "Muito grande",
"solid": "Sólido", "solid": "Sólido",
"hachure": "Hachura", "hachure": "Hachura",
"zigzag": "", "zigzag": "Zigue-zague",
"crossHatch": "Hachura cruzada", "crossHatch": "Hachura cruzada",
"thin": "Fino", "thin": "Fino",
"bold": "Espesso", "bold": "Espesso",
@ -111,7 +111,7 @@
"increaseFontSize": "Aumentar o tamanho da fonte", "increaseFontSize": "Aumentar o tamanho da fonte",
"unbindText": "Desvincular texto", "unbindText": "Desvincular texto",
"bindText": "Vincular texto ao contêiner", "bindText": "Vincular texto ao contêiner",
"createContainerFromText": "", "createContainerFromText": "Envolver texto em um contêiner",
"link": { "link": {
"edit": "Editar link", "edit": "Editar link",
"create": "Criar link", "create": "Criar link",
@ -208,10 +208,10 @@
"collabSaveFailed": "Não foi possível salvar no banco de dados do servidor. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.", "collabSaveFailed": "Não foi possível salvar no banco de dados do servidor. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.",
"collabSaveFailed_sizeExceeded": "Não foi possível salvar no banco de dados do servidor, a tela parece ser muito grande. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.", "collabSaveFailed_sizeExceeded": "Não foi possível salvar no banco de dados do servidor, a tela parece ser muito grande. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.",
"brave_measure_text_error": { "brave_measure_text_error": {
"line1": "", "line1": "Parece que você está usando o navegador Brave com a configuração <bold>Bloquear Impressões Digitais</bold> no modo agressivo.",
"line2": "", "line2": "Isso pode acabar quebrando <bold>Elementos de Texto</bold> em seus desenhos.",
"line3": "", "line3": "Recomendamos fortemente desativar essa configuração. Você pode acessar o <link>passo a passo</link> sobre como fazer isso.",
"line4": "" "line4": "Se desativar essa configuração não corrigir a exibição de elementos de texto, por favor abra uma <issueLink>issue</issueLink> em nosso GitHub, ou mande uma mensagem em nosso <discordLink>Discord</discordLink>"
} }
}, },
"toolBar": { "toolBar": {
@ -306,8 +306,8 @@
"doubleClick": "clique duplo", "doubleClick": "clique duplo",
"drag": "arrastar", "drag": "arrastar",
"editor": "Editor", "editor": "Editor",
"editLineArrowPoints": "", "editLineArrowPoints": "Editar linha/ponta da seta",
"editText": "", "editText": "Editar texto / adicionar etiqueta",
"github": "Encontrou algum problema? Nos informe", "github": "Encontrou algum problema? Nos informe",
"howto": "Siga nossos guias", "howto": "Siga nossos guias",
"or": "ou", "or": "ou",
@ -394,51 +394,21 @@
"pasteAsSingleElement": "Use {{shortcut}} para colar como um único elemento,\nou cole em um editor de texto já existente" "pasteAsSingleElement": "Use {{shortcut}} para colar como um único elemento,\nou cole em um editor de texto já existente"
}, },
"colors": { "colors": {
"ffffff": "Braco",
"f8f9fa": "Cinza 0",
"f1f3f5": "Cinza 1",
"fff5f5": "Vermelho 0",
"fff0f6": "Rosa 0",
"f8f0fc": "Uva 0",
"f3f0ff": "Violeta 0",
"edf2ff": "Índigo 0",
"e7f5ff": "Azul 0",
"e3fafc": "Ciano 0",
"e6fcf5": "Verde-azulado 0",
"ebfbee": "Verde 0",
"f4fce3": "Lima 0",
"fff9db": "Amarelo 0",
"fff4e6": "Laranja 0",
"transparent": "Transparente", "transparent": "Transparente",
"ced4da": "Cinza 4", "black": "",
"868e96": "Cinza 6", "white": "",
"fa5252": "Vermelho 6", "red": "",
"e64980": "Rosa 6", "pink": "",
"be4bdb": "Uva 6", "grape": "",
"7950f2": "Violeta 6", "violet": "",
"4c6ef5": "Índigo 6", "gray": "",
"228be6": "Azul 6", "blue": "",
"15aabf": "Ciano 6", "cyan": "",
"12b886": "Verde-azulado 6", "teal": "",
"40c057": "Verde 6", "green": "",
"82c91e": "Lima 6", "yellow": "",
"fab005": "Amarelo 6", "orange": "",
"fd7e14": "Laranja 6", "bronze": ""
"000000": "Preto",
"343a40": "Cinza 8",
"495057": "Cinza 7",
"c92a2a": "Vermelho 9",
"a61e4d": "Rosa 9",
"862e9c": "Uva 9",
"5f3dc4": "Violeta 9",
"364fc7": "Índigo 9",
"1864ab": "Azul 9",
"0b7285": "Ciano 9",
"087f5b": "Verde-azulado 9",
"2b8a3e": "Verde 9",
"5c940d": "Lima 9",
"e67700": "Amarelo 9",
"d9480f": "Laranja 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Escolha uma ferramenta e comece a desenhar!", "toolbarHint": "Escolha uma ferramenta e comece a desenhar!",
"helpHint": "Atalhos e ajuda" "helpHint": "Atalhos e ajuda"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Usar {{shortcut}} para colar como um único elemento,\nou colar num editor de texto existente" "pasteAsSingleElement": "Usar {{shortcut}} para colar como um único elemento,\nou colar num editor de texto existente"
}, },
"colors": { "colors": {
"ffffff": "Branco",
"f8f9fa": "Cinza 0",
"f1f3f5": "Cinza 1",
"fff5f5": "Vermelho 0",
"fff0f6": "Rosa 0",
"f8f0fc": "Uva 0",
"f3f0ff": "Violeta 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Azul 0",
"e3fafc": "Ciano 0",
"e6fcf5": "Verde-azulado 0",
"ebfbee": "Verde 0",
"f4fce3": "Lima 0",
"fff9db": "Amarelo 0",
"fff4e6": "Laranja 0",
"transparent": "Transparente", "transparent": "Transparente",
"ced4da": "Cinza 4", "black": "",
"868e96": "Cinza 6", "white": "",
"fa5252": "Vermelho 6", "red": "",
"e64980": "Rosa 6", "pink": "",
"be4bdb": "Uva 6", "grape": "",
"7950f2": "Violeta 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Azul 6", "blue": "",
"15aabf": "Ciano 6", "cyan": "",
"12b886": "Verde-azulado 6", "teal": "",
"40c057": "Verde 6", "green": "",
"82c91e": "Lima 6", "yellow": "",
"fab005": "Amarelo 6", "orange": "",
"fd7e14": "Laranja 6", "bronze": ""
"000000": "Preto",
"343a40": "Cinza 8",
"495057": "Cinza 7",
"c92a2a": "Vermelho 9",
"a61e4d": "Rosa 9",
"862e9c": "Uva 9",
"5f3dc4": "Violeta 9",
"364fc7": "Indigo 9",
"1864ab": "Azul 9",
"0b7285": "Ciano 9",
"087f5b": "Verde-azulado 9",
"2b8a3e": "Verde 9",
"5c940d": "Lima 9",
"e67700": "Amarelo 9",
"d9480f": "Laranja 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Escolha uma ferramenta e comece a desenhar!", "toolbarHint": "Escolha uma ferramenta e comece a desenhar!",
"helpHint": "Atalhos e ajuda" "helpHint": "Atalhos e ajuda"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -264,11 +264,11 @@
"canvasTooBigTip": "Sfat: încearcă să apropii puțin mai mult elementele cele mai îndepărtate." "canvasTooBigTip": "Sfat: încearcă să apropii puțin mai mult elementele cele mai îndepărtate."
}, },
"errorSplash": { "errorSplash": {
"headingMain": "A apărut o eroare. Încearcă <button>să reîncarci pagina.</button>", "headingMain": "A apărut o eroare. Încearcă <button>să reîncarci pagina</button>.",
"clearCanvasMessage": "Dacă reîncărcarea nu funcționează, încearcă <button>să golești pânza.</button>", "clearCanvasMessage": "Dacă reîncărcarea nu funcționează, încearcă <button>să ștergi pânza</button>.",
"clearCanvasCaveat": " Acest lucru va duce la pierderea progresului ", "clearCanvasCaveat": " Acest lucru va duce la pierderea progresului ",
"trackedToSentry": "Eroarea cu identificatorul {{eventId}} a fost urmărită în sistemul nostru.", "trackedToSentry": "Eroarea cu identificatorul {{eventId}} a fost urmărită în sistemul nostru.",
"openIssueMessage": "Am luat măsuri de precauție pentru a nu include informații despre scenă în eroare. Dacă scena nu este privată, te rugăm să ne oferi mai multe informații în <button>monitorul nostru pentru erori.</button> Te rugăm să incluzi informațiile de mai jos prin copierea și lipirea în problema GitHub.", "openIssueMessage": "Am luat măsuri de precauție pentru a nu include informații despre scenă în eroare. Dacă scena nu este privată, oferă-ne mai multe informații în <button>monitorul nostru pentru erori</button>. Include informațiile de mai jos copiindu-le și lipindu-le în tichetul cu problemă de pe GitHub.",
"sceneContent": "Conținutul scenei:" "sceneContent": "Conținutul scenei:"
}, },
"roomDialog": { "roomDialog": {
@ -348,8 +348,8 @@
"required": "Obligatoriu", "required": "Obligatoriu",
"website": "Introdu un URL valid" "website": "Introdu un URL valid"
}, },
"noteDescription": "Trimite-ți biblioteca pentru fi inclus în <link>depozitul de biblioteci publice</link>pentru utilizarea de către alte persoane în desenele lor.", "noteDescription": "Trimite-ți biblioteca pentru a fi inclusă în <link>depozitul de biblioteci publice</link> în vederea utilizării de către alte persoane în desenele lor.",
"noteGuidelines": "Biblioteca trebuie aprobată manual. Citește <link>orientările</link> înainte de trimitere. Vei avea nevoie de un cont GitHub pentru a comunica și efectua modificări, dacă este cazul, însă nu este strict necesar.", "noteGuidelines": "Biblioteca trebuie aprobată manual mai întâi. Citește <link>orientările</link> înainte de trimitere. Vei avea nevoie de un cont GitHub pentru a comunica și efectua modificări, dacă este cazul, însă nu este strict necesar.",
"noteLicense": "Prin trimiterea bibliotecii, ești de acord că aceasta va fi publicată sub <link>Licența MIT, </link>care, pe scurt, înseamnă că oricine o poate folosi fără restricții.", "noteLicense": "Prin trimiterea bibliotecii, ești de acord că aceasta va fi publicată sub <link>Licența MIT, </link>care, pe scurt, înseamnă că oricine o poate folosi fără restricții.",
"noteItems": "Fiecare element din bibliotecă trebuie să aibă propriul nume astfel încât să fie filtrabil. Următoarele elemente din bibliotecă vor fi incluse:", "noteItems": "Fiecare element din bibliotecă trebuie să aibă propriul nume astfel încât să fie filtrabil. Următoarele elemente din bibliotecă vor fi incluse:",
"atleastOneLibItem": "Selectează cel puțin un element din bibliotecă pentru a începe", "atleastOneLibItem": "Selectează cel puțin un element din bibliotecă pentru a începe",
@ -357,7 +357,7 @@
}, },
"publishSuccessDialog": { "publishSuccessDialog": {
"title": "Bibliotecă trimisă", "title": "Bibliotecă trimisă",
"content": "Îți mulțumim, {{authorName}}. Biblioteca ta a fost trimisă spre revizuire. Poți urmări starea<link>aici</link>" "content": "Îți mulțumim, {{authorName}}. Biblioteca a fost trimisă spre revizuire. Poți urmări starea <link>aici</link>"
}, },
"confirmDialog": { "confirmDialog": {
"resetLibrary": "Resetare bibliotecă", "resetLibrary": "Resetare bibliotecă",
@ -394,51 +394,21 @@
"pasteAsSingleElement": "Folosește {{shortcut}} pentru a insera ca un singur element\nsau insera într-un editor de text existent" "pasteAsSingleElement": "Folosește {{shortcut}} pentru a insera ca un singur element\nsau insera într-un editor de text existent"
}, },
"colors": { "colors": {
"ffffff": "Alb",
"f8f9fa": "Gri 0",
"f1f3f5": "Gri 1",
"fff5f5": "Roșu 0",
"fff0f6": "Roz 0",
"f8f0fc": "Struguriu 0",
"f3f0ff": "Violet 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Albastru 0",
"e3fafc": "Cyan 0",
"e6fcf5": "Cyan-verde",
"ebfbee": "Verde 0",
"f4fce3": "Verde-limetă",
"fff9db": "Galben 0",
"fff4e6": "Portocaliu 0",
"transparent": "Transparent", "transparent": "Transparent",
"ced4da": "Gri 4", "black": "",
"868e96": "Gri 6", "white": "",
"fa5252": "Roșu 6", "red": "",
"e64980": "Roz 6", "pink": "",
"be4bdb": "Struguriu 6", "grape": "",
"7950f2": "Violet 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Albastru 6", "blue": "",
"15aabf": "Cyan 6", "cyan": "",
"12b886": "Cyan-verde 6", "teal": "",
"40c057": "Verde 6", "green": "",
"82c91e": "Verde-limetă 6", "yellow": "",
"fab005": "Galben 6", "orange": "",
"fd7e14": "Portocaliu 6", "bronze": ""
"000000": "Negru",
"343a40": "Gri 8",
"495057": "Gri 7",
"c92a2a": "Roșu 9",
"a61e4d": "Roz 9",
"862e9c": "Struguriu 9",
"5f3dc4": "Violet 9",
"364fc7": "Indigo 9",
"1864ab": "Albastru 9",
"0b7285": "Cyan 9",
"087f5b": "Cyan-verde 9",
"2b8a3e": "Verde 9",
"5c940d": "Verde-limetă 9",
"e67700": "Galben 9",
"d9480f": "Portocaliu 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Alege un instrument și începe să desenezi!", "toolbarHint": "Alege un instrument și începe să desenezi!",
"helpHint": "Comenzi rapide și ajutor" "helpHint": "Comenzi rapide și ajutor"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Используйте {{shortcut}}, чтобы вставить один объект,\nили вставьте в существующий текстовый редактор" "pasteAsSingleElement": "Используйте {{shortcut}}, чтобы вставить один объект,\nили вставьте в существующий текстовый редактор"
}, },
"colors": { "colors": {
"ffffff": "Белый",
"f8f9fa": "Серый 0",
"f1f3f5": "Серый 1",
"fff5f5": "Красный 0",
"fff0f6": "Розовый 0",
"f8f0fc": "Виноградный 0",
"f3f0ff": "Фиолетовый 0",
"edf2ff": "Индиго 0",
"e7f5ff": "Синий 0",
"e3fafc": "Голубой 0",
"e6fcf5": "Бирюзовый 0",
"ebfbee": "Зелёный 0",
"f4fce3": "Лайм 0",
"fff9db": "Жёлтый 0",
"fff4e6": "Оранжевый 0",
"transparent": "Прозрачный", "transparent": "Прозрачный",
"ced4da": "Серый 4", "black": "",
"868e96": "Серый 6", "white": "",
"fa5252": "Красный 6", "red": "",
"e64980": "Розовый 6", "pink": "",
"be4bdb": "Виноградный 6", "grape": "",
"7950f2": "Фиолетовый 6", "violet": "",
"4c6ef5": "Индиго 6", "gray": "",
"228be6": "Синий 6", "blue": "",
"15aabf": "Голубой 6", "cyan": "",
"12b886": "Бирюзовый 6", "teal": "",
"40c057": "Зелёный 6", "green": "",
"82c91e": "Лайм 6", "yellow": "",
"fab005": "Жёлтый 6", "orange": "",
"fd7e14": "Оранжевый 6", "bronze": ""
"000000": "Чёрный",
"343a40": "Серый 8",
"495057": "Серый 7",
"c92a2a": "Красный 9",
"a61e4d": "Розовый 9",
"862e9c": "Виноградный 9",
"5f3dc4": "Фиолетовый 9",
"364fc7": "Индиго 9",
"1864ab": "Синий 9",
"0b7285": "Голубой 9",
"087f5b": "Бирюзовый 9",
"2b8a3e": "Зелёный 9",
"5c940d": "Лайм 9",
"e67700": "Жёлтый 9",
"d9480f": "Оранжевый 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Выберите инструмент и начните рисовать!", "toolbarHint": "Выберите инструмент и начните рисовать!",
"helpHint": "Сочетания клавиш и помощь" "helpHint": "Сочетания клавиш и помощь"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "" "pasteAsSingleElement": ""
}, },
"colors": { "colors": {
"ffffff": "",
"f8f9fa": "",
"f1f3f5": "",
"fff5f5": "",
"fff0f6": "",
"f8f0fc": "",
"f3f0ff": "",
"edf2ff": "",
"e7f5ff": "",
"e3fafc": "",
"e6fcf5": "",
"ebfbee": "",
"f4fce3": "",
"fff9db": "",
"fff4e6": "",
"transparent": "", "transparent": "",
"ced4da": "", "black": "",
"868e96": "", "white": "",
"fa5252": "", "red": "",
"e64980": "", "pink": "",
"be4bdb": "", "grape": "",
"7950f2": "", "violet": "",
"4c6ef5": "", "gray": "",
"228be6": "", "blue": "",
"15aabf": "", "cyan": "",
"12b886": "", "teal": "",
"40c057": "", "green": "",
"82c91e": "", "yellow": "",
"fab005": "", "orange": "",
"fd7e14": "", "bronze": ""
"000000": "",
"343a40": "",
"495057": "",
"c92a2a": "",
"a61e4d": "",
"862e9c": "",
"5f3dc4": "",
"364fc7": "",
"1864ab": "",
"0b7285": "",
"087f5b": "",
"2b8a3e": "",
"5c940d": "",
"e67700": "",
"d9480f": ""
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "", "toolbarHint": "",
"helpHint": "" "helpHint": ""
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Použitím {{shortcut}} vložte ako samostatný prvok alebo vložte do existujúceho editovaného textu" "pasteAsSingleElement": "Použitím {{shortcut}} vložte ako samostatný prvok alebo vložte do existujúceho editovaného textu"
}, },
"colors": { "colors": {
"ffffff": "Biela",
"f8f9fa": "Sivá 0",
"f1f3f5": "Sivá 1",
"fff5f5": "Červená 0",
"fff0f6": "Ružová 0",
"f8f0fc": "Hroznová fialová 0",
"f3f0ff": "Fialová 0",
"edf2ff": "Tmavomodrá 0",
"e7f5ff": "Modrá 0",
"e3fafc": "Azúrová 0",
"e6fcf5": "Modrozelená 0",
"ebfbee": "Zelená 0",
"f4fce3": "Limetková 0",
"fff9db": "Žltá 0",
"fff4e6": "Oranžová 0",
"transparent": "Priehľadná", "transparent": "Priehľadná",
"ced4da": "Sivá 4", "black": "",
"868e96": "Sivá 6", "white": "",
"fa5252": "Červená 6", "red": "",
"e64980": "Ružová 6", "pink": "",
"be4bdb": "Hroznová fialová 6", "grape": "",
"7950f2": "Fialová 6", "violet": "",
"4c6ef5": "Tmavomodrá 6", "gray": "",
"228be6": "Modrá 6", "blue": "",
"15aabf": "Azúrová 6", "cyan": "",
"12b886": "Modrozelená 6", "teal": "",
"40c057": "Zelená 6", "green": "",
"82c91e": "Limetková 6", "yellow": "",
"fab005": "Žltá 6", "orange": "",
"fd7e14": "Oranžová 6", "bronze": ""
"000000": "Čierna",
"343a40": "Sivá 8",
"495057": "Sivá 7",
"c92a2a": "Červená 9",
"a61e4d": "Ružová 9",
"862e9c": "Hroznová fialová 9",
"5f3dc4": "Fialová 9",
"364fc7": "Tmavomodrá 9",
"1864ab": "Modrá 9",
"0b7285": "Azúrová 9",
"087f5b": "Modrozelená 9",
"2b8a3e": "Zelená 9",
"5c940d": "Limetková 9",
"e67700": "Žltá 9",
"d9480f": "Oranžová 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Zvoľte nástroj a začnite kresliť!", "toolbarHint": "Zvoľte nástroj a začnite kresliť!",
"helpHint": "Klávesové skratky a pomocník" "helpHint": "Klávesové skratky a pomocník"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

View file

@ -394,51 +394,21 @@
"pasteAsSingleElement": "Uporabite {{shortcut}}, da prilepite kot en element,\n ali prilepite v obstoječ urejevalnik besedil" "pasteAsSingleElement": "Uporabite {{shortcut}}, da prilepite kot en element,\n ali prilepite v obstoječ urejevalnik besedil"
}, },
"colors": { "colors": {
"ffffff": "Bela",
"f8f9fa": "Siva 0",
"f1f3f5": "Siva 1",
"fff5f5": "Rdeča 0",
"fff0f6": "Roza 0",
"f8f0fc": "Grozdje 0",
"f3f0ff": "Vijolična 0",
"edf2ff": "Indigo 0",
"e7f5ff": "Modra 0",
"e3fafc": "Cijan 0",
"e6fcf5": "Modrozelena 0",
"ebfbee": "Zelena 0",
"f4fce3": "Limeta 0",
"fff9db": "Rumena 0",
"fff4e6": "Oranžna 0",
"transparent": "Prosojno", "transparent": "Prosojno",
"ced4da": "Siva 4", "black": "",
"868e96": "Siva 6", "white": "",
"fa5252": "Rdeča 6", "red": "",
"e64980": "Roza 6", "pink": "",
"be4bdb": "Grozdje 6", "grape": "",
"7950f2": "Vijolična 6", "violet": "",
"4c6ef5": "Indigo 6", "gray": "",
"228be6": "Modra 6", "blue": "",
"15aabf": "Cijan 6", "cyan": "",
"12b886": "Modrozelena 6", "teal": "",
"40c057": "Zelena 6", "green": "",
"82c91e": "Limeta 6", "yellow": "",
"fab005": "Rumena 6", "orange": "",
"fd7e14": "Oranžna 6", "bronze": ""
"000000": "Črna",
"343a40": "Siva 8",
"495057": "Siva 7",
"c92a2a": "Rdeča 9",
"a61e4d": "Roza 9",
"862e9c": "Grozdje 9",
"5f3dc4": "Vijolična 9",
"364fc7": "Indigo 9",
"1864ab": "Modra 9",
"0b7285": "Cijan 9",
"087f5b": "Modrozelena 9",
"2b8a3e": "Zelena 9",
"5c940d": "Limeta 9",
"e67700": "Rumena 9",
"d9480f": "Oranžna 9"
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
@ -452,5 +422,12 @@
"toolbarHint": "Izberi orodje in začni z risanjem!", "toolbarHint": "Izberi orodje in začni z risanjem!",
"helpHint": "Bližnjice in pomoč" "helpHint": "Bližnjice in pomoč"
} }
},
"colorPicker": {
"mostUsedCustomColors": "",
"colors": "",
"shades": "",
"hexCode": "",
"noShades": ""
} }
} }

Some files were not shown because too many files have changed in this diff Show more