mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Merge remote-tracking branch 'origin/master' into aakansha-create-text-containers-programmatically
This commit is contained in:
commit
acde193a64
137 changed files with 4087 additions and 3653 deletions
14
.github/workflows/publish-docker.yml
vendored
14
.github/workflows/publish-docker.yml
vendored
|
@ -12,24 +12,14 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
excalidraw/excalidraw
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: excalidraw/excalidraw:latest
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dwelle/tunnel-rat": "0.1.1",
|
"@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",
|
||||||
"@testing-library/jest-dom": "5.16.2",
|
"@testing-library/jest-dom": "5.16.2",
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
"roughjs": "4.5.2",
|
"roughjs": "4.5.2",
|
||||||
"sass": "1.51.0",
|
"sass": "1.51.0",
|
||||||
"socket.io-client": "2.3.1",
|
"socket.io-client": "2.3.1",
|
||||||
"tunnel-rat": "0.1.0",
|
"tunnel-rat": "0.1.2",
|
||||||
"workbox-background-sync": "^6.5.4",
|
"workbox-background-sync": "^6.5.4",
|
||||||
"workbox-broadcast-update": "^6.5.4",
|
"workbox-broadcast-update": "^6.5.4",
|
||||||
"workbox-cacheable-response": "^6.5.4",
|
"workbox-cacheable-response": "^6.5.4",
|
||||||
|
|
|
@ -58,7 +58,7 @@ export const getDefaultAppState = (): Omit<
|
||||||
fileHandle: null,
|
fileHandle: null,
|
||||||
gridSize: null,
|
gridSize: null,
|
||||||
isBindingEnabled: true,
|
isBindingEnabled: true,
|
||||||
isSidebarDocked: false,
|
defaultSidebarDockedPreference: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
isRotating: false,
|
isRotating: false,
|
||||||
|
@ -150,7 +150,11 @@ const APP_STATE_STORAGE_CONF = (<
|
||||||
gridSize: { browser: true, export: true, server: true },
|
gridSize: { browser: true, export: true, server: true },
|
||||||
height: { browser: false, export: false, server: false },
|
height: { browser: false, export: false, server: false },
|
||||||
isBindingEnabled: { browser: false, export: false, server: false },
|
isBindingEnabled: { browser: false, export: false, server: false },
|
||||||
isSidebarDocked: { browser: true, export: false, server: false },
|
defaultSidebarDockedPreference: {
|
||||||
|
browser: true,
|
||||||
|
export: false,
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
isLoading: { browser: false, export: false, server: false },
|
isLoading: { browser: false, export: false, server: false },
|
||||||
isResizing: { browser: false, export: false, server: false },
|
isResizing: { browser: false, export: false, server: false },
|
||||||
isRotating: { browser: false, export: false, server: false },
|
isRotating: { browser: false, export: false, server: false },
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
hasText,
|
hasText,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
import { SHAPES } from "../shapes";
|
import { SHAPES } from "../shapes";
|
||||||
import { AppState, Zoom } from "../types";
|
import { UIAppState, Zoom } from "../types";
|
||||||
import {
|
import {
|
||||||
capitalizeString,
|
capitalizeString,
|
||||||
isTransparent,
|
isTransparent,
|
||||||
|
@ -28,19 +28,20 @@ import { trackEvent } from "../analytics";
|
||||||
import { hasBoundTextElement } from "../element/typeChecks";
|
import { hasBoundTextElement } from "../element/typeChecks";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { actionToggleZenMode } from "../actions";
|
import { actionToggleZenMode } from "../actions";
|
||||||
import "./Actions.scss";
|
|
||||||
import { Tooltip } from "./Tooltip";
|
import { Tooltip } from "./Tooltip";
|
||||||
import {
|
import {
|
||||||
shouldAllowVerticalAlign,
|
shouldAllowVerticalAlign,
|
||||||
suppportsHorizontalAlign,
|
suppportsHorizontalAlign,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
|
|
||||||
|
import "./Actions.scss";
|
||||||
|
|
||||||
export const SelectedShapeActions = ({
|
export const SelectedShapeActions = ({
|
||||||
appState,
|
appState,
|
||||||
elements,
|
elements,
|
||||||
renderAction,
|
renderAction,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
renderAction: ActionManager["renderAction"];
|
renderAction: ActionManager["renderAction"];
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -215,10 +216,10 @@ export const ShapesSwitcher = ({
|
||||||
appState,
|
appState,
|
||||||
}: {
|
}: {
|
||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
activeTool: AppState["activeTool"];
|
activeTool: UIAppState["activeTool"];
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
onImageAction: (data: { pointerType: PointerType | null }) => void;
|
onImageAction: (data: { pointerType: PointerType | null }) => void;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
||||||
|
|
|
@ -211,6 +211,8 @@ import {
|
||||||
PointerDownState,
|
PointerDownState,
|
||||||
SceneData,
|
SceneData,
|
||||||
Device,
|
Device,
|
||||||
|
SidebarName,
|
||||||
|
SidebarTabName,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
|
@ -300,6 +302,9 @@ import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||||
|
|
||||||
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
|
|
||||||
const deviceContextInitialValue = {
|
const deviceContextInitialValue = {
|
||||||
isSmScreen: false,
|
isSmScreen: false,
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
|
@ -341,6 +346,8 @@ const ExcalidrawActionManagerContext = React.createContext<ActionManager>(
|
||||||
);
|
);
|
||||||
ExcalidrawActionManagerContext.displayName = "ExcalidrawActionManagerContext";
|
ExcalidrawActionManagerContext.displayName = "ExcalidrawActionManagerContext";
|
||||||
|
|
||||||
|
export const useApp = () => useContext(AppContext);
|
||||||
|
export const useAppProps = () => useContext(AppPropsContext);
|
||||||
export const useDevice = () => useContext<Device>(DeviceContext);
|
export const useDevice = () => useContext<Device>(DeviceContext);
|
||||||
export const useExcalidrawContainer = () =>
|
export const useExcalidrawContainer = () =>
|
||||||
useContext(ExcalidrawContainerContext);
|
useContext(ExcalidrawContainerContext);
|
||||||
|
@ -401,7 +408,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
private nearestScrollableContainer: HTMLElement | Document | undefined;
|
private nearestScrollableContainer: HTMLElement | Document | undefined;
|
||||||
public library: AppClassProperties["library"];
|
public library: AppClassProperties["library"];
|
||||||
public libraryItemsFromStorage: LibraryItems | undefined;
|
public libraryItemsFromStorage: LibraryItems | undefined;
|
||||||
private id: string;
|
public id: string;
|
||||||
private history: History;
|
private history: History;
|
||||||
private excalidrawContainerValue: {
|
private excalidrawContainerValue: {
|
||||||
container: HTMLDivElement | null;
|
container: HTMLDivElement | null;
|
||||||
|
@ -439,7 +446,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
showHyperlinkPopup: false,
|
showHyperlinkPopup: false,
|
||||||
isSidebarDocked: false,
|
defaultSidebarDockedPreference: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.id = nanoid();
|
this.id = nanoid();
|
||||||
|
@ -470,7 +477,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
setActiveTool: this.setActiveTool,
|
setActiveTool: this.setActiveTool,
|
||||||
setCursor: this.setCursor,
|
setCursor: this.setCursor,
|
||||||
resetCursor: this.resetCursor,
|
resetCursor: this.resetCursor,
|
||||||
toggleMenu: this.toggleMenu,
|
toggleSidebar: this.toggleSidebar,
|
||||||
} as const;
|
} as const;
|
||||||
if (typeof excalidrawRef === "function") {
|
if (typeof excalidrawRef === "function") {
|
||||||
excalidrawRef(api);
|
excalidrawRef(api);
|
||||||
|
@ -578,101 +585,91 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.props.handleKeyboardGlobally ? undefined : this.onKeyDown
|
this.props.handleKeyboardGlobally ? undefined : this.onKeyDown
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ExcalidrawContainerContext.Provider
|
<AppContext.Provider value={this}>
|
||||||
value={this.excalidrawContainerValue}
|
<AppPropsContext.Provider value={this.props}>
|
||||||
>
|
<ExcalidrawContainerContext.Provider
|
||||||
<DeviceContext.Provider value={this.device}>
|
value={this.excalidrawContainerValue}
|
||||||
<ExcalidrawSetAppStateContext.Provider value={this.setAppState}>
|
>
|
||||||
<ExcalidrawAppStateContext.Provider value={this.state}>
|
<DeviceContext.Provider value={this.device}>
|
||||||
<ExcalidrawElementsContext.Provider
|
<ExcalidrawSetAppStateContext.Provider value={this.setAppState}>
|
||||||
value={this.scene.getNonDeletedElements()}
|
<ExcalidrawAppStateContext.Provider value={this.state}>
|
||||||
>
|
<ExcalidrawElementsContext.Provider
|
||||||
<ExcalidrawActionManagerContext.Provider
|
value={this.scene.getNonDeletedElements()}
|
||||||
value={this.actionManager}
|
|
||||||
>
|
|
||||||
<LayerUI
|
|
||||||
canvas={this.canvas}
|
|
||||||
appState={this.state}
|
|
||||||
files={this.files}
|
|
||||||
setAppState={this.setAppState}
|
|
||||||
actionManager={this.actionManager}
|
|
||||||
elements={this.scene.getNonDeletedElements()}
|
|
||||||
onLockToggle={this.toggleLock}
|
|
||||||
onPenModeToggle={this.togglePenMode}
|
|
||||||
onHandToolToggle={this.onHandToolToggle}
|
|
||||||
onInsertElements={(elements) =>
|
|
||||||
this.addElementsFromPasteOrLibrary({
|
|
||||||
elements,
|
|
||||||
position: "center",
|
|
||||||
files: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
langCode={getLanguage().code}
|
|
||||||
renderTopRightUI={renderTopRightUI}
|
|
||||||
renderCustomStats={renderCustomStats}
|
|
||||||
renderCustomSidebar={this.props.renderSidebar}
|
|
||||||
showExitZenModeBtn={
|
|
||||||
typeof this.props?.zenModeEnabled === "undefined" &&
|
|
||||||
this.state.zenModeEnabled
|
|
||||||
}
|
|
||||||
libraryReturnUrl={this.props.libraryReturnUrl}
|
|
||||||
UIOptions={this.props.UIOptions}
|
|
||||||
focusContainer={this.focusContainer}
|
|
||||||
library={this.library}
|
|
||||||
id={this.id}
|
|
||||||
onImageAction={this.onImageAction}
|
|
||||||
renderWelcomeScreen={
|
|
||||||
!this.state.isLoading &&
|
|
||||||
this.state.showWelcomeScreen &&
|
|
||||||
this.state.activeTool.type === "selection" &&
|
|
||||||
!this.scene.getElementsIncludingDeleted().length
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{this.props.children}
|
<ExcalidrawActionManagerContext.Provider
|
||||||
</LayerUI>
|
value={this.actionManager}
|
||||||
<div className="excalidraw-textEditorContainer" />
|
>
|
||||||
<div className="excalidraw-contextMenuContainer" />
|
<LayerUI
|
||||||
{selectedElement.length === 1 &&
|
canvas={this.canvas}
|
||||||
!this.state.contextMenu &&
|
appState={this.state}
|
||||||
this.state.showHyperlinkPopup && (
|
files={this.files}
|
||||||
<Hyperlink
|
|
||||||
key={selectedElement[0].id}
|
|
||||||
element={selectedElement[0]}
|
|
||||||
setAppState={this.setAppState}
|
setAppState={this.setAppState}
|
||||||
onLinkOpen={this.props.onLinkOpen}
|
actionManager={this.actionManager}
|
||||||
/>
|
elements={this.scene.getNonDeletedElements()}
|
||||||
)}
|
onLockToggle={this.toggleLock}
|
||||||
{this.state.toast !== null && (
|
onPenModeToggle={this.togglePenMode}
|
||||||
<Toast
|
onHandToolToggle={this.onHandToolToggle}
|
||||||
message={this.state.toast.message}
|
langCode={getLanguage().code}
|
||||||
onClose={() => this.setToast(null)}
|
renderTopRightUI={renderTopRightUI}
|
||||||
duration={this.state.toast.duration}
|
renderCustomStats={renderCustomStats}
|
||||||
closable={this.state.toast.closable}
|
showExitZenModeBtn={
|
||||||
/>
|
typeof this.props?.zenModeEnabled === "undefined" &&
|
||||||
)}
|
this.state.zenModeEnabled
|
||||||
{this.state.contextMenu && (
|
}
|
||||||
<ContextMenu
|
UIOptions={this.props.UIOptions}
|
||||||
items={this.state.contextMenu.items}
|
onImageAction={this.onImageAction}
|
||||||
top={this.state.contextMenu.top}
|
renderWelcomeScreen={
|
||||||
left={this.state.contextMenu.left}
|
!this.state.isLoading &&
|
||||||
actionManager={this.actionManager}
|
this.state.showWelcomeScreen &&
|
||||||
/>
|
this.state.activeTool.type === "selection" &&
|
||||||
)}
|
!this.scene.getElementsIncludingDeleted().length
|
||||||
<main>{this.renderCanvas()}</main>
|
}
|
||||||
</ExcalidrawActionManagerContext.Provider>
|
>
|
||||||
</ExcalidrawElementsContext.Provider>{" "}
|
{this.props.children}
|
||||||
</ExcalidrawAppStateContext.Provider>
|
</LayerUI>
|
||||||
</ExcalidrawSetAppStateContext.Provider>
|
<div className="excalidraw-textEditorContainer" />
|
||||||
</DeviceContext.Provider>
|
<div className="excalidraw-contextMenuContainer" />
|
||||||
</ExcalidrawContainerContext.Provider>
|
{selectedElement.length === 1 &&
|
||||||
|
!this.state.contextMenu &&
|
||||||
|
this.state.showHyperlinkPopup && (
|
||||||
|
<Hyperlink
|
||||||
|
key={selectedElement[0].id}
|
||||||
|
element={selectedElement[0]}
|
||||||
|
setAppState={this.setAppState}
|
||||||
|
onLinkOpen={this.props.onLinkOpen}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{this.state.toast !== null && (
|
||||||
|
<Toast
|
||||||
|
message={this.state.toast.message}
|
||||||
|
onClose={() => this.setToast(null)}
|
||||||
|
duration={this.state.toast.duration}
|
||||||
|
closable={this.state.toast.closable}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{this.state.contextMenu && (
|
||||||
|
<ContextMenu
|
||||||
|
items={this.state.contextMenu.items}
|
||||||
|
top={this.state.contextMenu.top}
|
||||||
|
left={this.state.contextMenu.left}
|
||||||
|
actionManager={this.actionManager}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<main>{this.renderCanvas()}</main>
|
||||||
|
</ExcalidrawActionManagerContext.Provider>
|
||||||
|
</ExcalidrawElementsContext.Provider>{" "}
|
||||||
|
</ExcalidrawAppStateContext.Provider>
|
||||||
|
</ExcalidrawSetAppStateContext.Provider>
|
||||||
|
</DeviceContext.Provider>
|
||||||
|
</ExcalidrawContainerContext.Provider>
|
||||||
|
</AppPropsContext.Provider>
|
||||||
|
</AppContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public focusContainer: AppClassProperties["focusContainer"] = () => {
|
public focusContainer: AppClassProperties["focusContainer"] = () => {
|
||||||
if (this.props.autoFocus) {
|
this.excalidrawContainerRef.current?.focus();
|
||||||
this.excalidrawContainerRef.current?.focus();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public getSceneElementsIncludingDeleted = () => {
|
public getSceneElementsIncludingDeleted = () => {
|
||||||
|
@ -683,6 +680,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
return this.scene.getNonDeletedElements();
|
return this.scene.getNonDeletedElements();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public onInsertElements = (elements: readonly ExcalidrawElement[]) => {
|
||||||
|
this.addElementsFromPasteOrLibrary({
|
||||||
|
elements,
|
||||||
|
position: "center",
|
||||||
|
files: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private syncActionResult = withBatchedUpdates(
|
private syncActionResult = withBatchedUpdates(
|
||||||
(actionResult: ActionResult) => {
|
(actionResult: ActionResult) => {
|
||||||
if (this.unmounted || actionResult === false) {
|
if (this.unmounted || actionResult === false) {
|
||||||
|
@ -952,7 +957,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.scene.addCallback(this.onSceneUpdated);
|
this.scene.addCallback(this.onSceneUpdated);
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
|
|
||||||
if (this.excalidrawContainerRef.current) {
|
if (this.props.autoFocus && this.excalidrawContainerRef.current) {
|
||||||
this.focusContainer();
|
this.focusContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1680,7 +1685,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
openSidebar:
|
openSidebar:
|
||||||
this.state.openSidebar &&
|
this.state.openSidebar &&
|
||||||
this.device.canDeviceFitSidebar &&
|
this.device.canDeviceFitSidebar &&
|
||||||
this.state.isSidebarDocked
|
this.state.defaultSidebarDockedPreference
|
||||||
? this.state.openSidebar
|
? this.state.openSidebar
|
||||||
: null,
|
: null,
|
||||||
selectedElementIds: newElements.reduce(
|
selectedElementIds: newElements.reduce(
|
||||||
|
@ -2020,30 +2025,24 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
/**
|
/**
|
||||||
* @returns whether the menu was toggled on or off
|
* @returns whether the menu was toggled on or off
|
||||||
*/
|
*/
|
||||||
public toggleMenu = (
|
public toggleSidebar = ({
|
||||||
type: "library" | "customSidebar",
|
name,
|
||||||
force?: boolean,
|
tab,
|
||||||
): boolean => {
|
force,
|
||||||
if (type === "customSidebar" && !this.props.renderSidebar) {
|
}: {
|
||||||
console.warn(
|
name: SidebarName;
|
||||||
`attempting to toggle "customSidebar", but no "props.renderSidebar" is defined`,
|
tab?: SidebarTabName;
|
||||||
);
|
force?: boolean;
|
||||||
return false;
|
}): boolean => {
|
||||||
|
let nextName;
|
||||||
|
if (force === undefined) {
|
||||||
|
nextName = this.state.openSidebar?.name === name ? null : name;
|
||||||
|
} else {
|
||||||
|
nextName = force ? name : null;
|
||||||
}
|
}
|
||||||
|
this.setState({ openSidebar: nextName ? { name: nextName, tab } : null });
|
||||||
|
|
||||||
if (type === "library" || type === "customSidebar") {
|
return !!nextName;
|
||||||
let nextValue;
|
|
||||||
if (force === undefined) {
|
|
||||||
nextValue = this.state.openSidebar === type ? null : type;
|
|
||||||
} else {
|
|
||||||
nextValue = force ? type : null;
|
|
||||||
}
|
|
||||||
this.setState({ openSidebar: nextValue });
|
|
||||||
|
|
||||||
return !!nextValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateCurrentCursorPosition = withBatchedUpdates(
|
private updateCurrentCursorPosition = withBatchedUpdates(
|
||||||
|
|
|
@ -1,39 +1,40 @@
|
||||||
import { t } from "../i18n";
|
import Trans from "./Trans";
|
||||||
|
|
||||||
const BraveMeasureTextError = () => {
|
const BraveMeasureTextError = () => {
|
||||||
return (
|
return (
|
||||||
<div data-testid="brave-measure-text-error">
|
<div data-testid="brave-measure-text-error">
|
||||||
<p>
|
<p>
|
||||||
{t("errors.brave_measure_text_error.start")}
|
<Trans
|
||||||
<span style={{ fontWeight: 600 }}>
|
i18nKey="errors.brave_measure_text_error.line1"
|
||||||
{t("errors.brave_measure_text_error.aggressive_block_fingerprint")}
|
bold={(el) => <span style={{ fontWeight: 600 }}>{el}</span>}
|
||||||
</span>{" "}
|
/>
|
||||||
{t("errors.brave_measure_text_error.setting_enabled")}.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
{t("errors.brave_measure_text_error.break")}{" "}
|
|
||||||
<span style={{ fontWeight: 600 }}>
|
|
||||||
{t("errors.brave_measure_text_error.text_elements")}
|
|
||||||
</span>{" "}
|
|
||||||
{t("errors.brave_measure_text_error.in_your_drawings")}.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{t("errors.brave_measure_text_error.strongly_recommend")}{" "}
|
<Trans
|
||||||
<a href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser">
|
i18nKey="errors.brave_measure_text_error.line2"
|
||||||
{" "}
|
bold={(el) => <span style={{ fontWeight: 600 }}>{el}</span>}
|
||||||
{t("errors.brave_measure_text_error.steps")}
|
/>
|
||||||
</a>{" "}
|
|
||||||
{t("errors.brave_measure_text_error.how")}.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{t("errors.brave_measure_text_error.disable_setting")}{" "}
|
<Trans
|
||||||
<a href="https://github.com/excalidraw/excalidraw/issues/new">
|
i18nKey="errors.brave_measure_text_error.line3"
|
||||||
{t("errors.brave_measure_text_error.issue")}
|
link={(el) => (
|
||||||
</a>{" "}
|
<a href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser">
|
||||||
{t("errors.brave_measure_text_error.write")}{" "}
|
{el}
|
||||||
<a href="https://discord.gg/UexuTaE">
|
</a>
|
||||||
{t("errors.brave_measure_text_error.discord")}
|
)}
|
||||||
</a>
|
/>
|
||||||
.
|
</p>
|
||||||
|
<p>
|
||||||
|
<Trans
|
||||||
|
i18nKey="errors.brave_measure_text_error.line4"
|
||||||
|
issueLink={(el) => (
|
||||||
|
<a href="https://github.com/excalidraw/excalidraw/issues/new">
|
||||||
|
{el}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
discordLink={(el) => <a href="https://discord.gg/UexuTaE">{el}.</a>}
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { composeEventHandlers } from "../utils";
|
||||||
import "./Button.scss";
|
import "./Button.scss";
|
||||||
|
|
||||||
interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
type?: "button" | "submit" | "reset";
|
type?: "button" | "submit" | "reset";
|
||||||
onSelect: () => any;
|
onSelect: () => any;
|
||||||
|
/** whether button is in active state */
|
||||||
|
selected?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
@ -15,18 +19,18 @@ interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
export const Button = ({
|
export const Button = ({
|
||||||
type = "button",
|
type = "button",
|
||||||
onSelect,
|
onSelect,
|
||||||
|
selected,
|
||||||
children,
|
children,
|
||||||
className = "",
|
className = "",
|
||||||
...rest
|
...rest
|
||||||
}: ButtonProps) => {
|
}: ButtonProps) => {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={(event) => {
|
onClick={composeEventHandlers(rest.onClick, (event) => {
|
||||||
onSelect();
|
onSelect();
|
||||||
rest.onClick?.(event);
|
})}
|
||||||
}}
|
|
||||||
type={type}
|
type={type}
|
||||||
className={`excalidraw-button ${className}`}
|
className={clsx("excalidraw-button", className, { selected })}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { Dialog, DialogProps } from "./Dialog";
|
||||||
import "./ConfirmDialog.scss";
|
import "./ConfirmDialog.scss";
|
||||||
import DialogActionButton from "./DialogActionButton";
|
import DialogActionButton from "./DialogActionButton";
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { isLibraryMenuOpenAtom } from "./LibraryMenuHeaderContent";
|
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||||
import { useExcalidrawSetAppState } from "./App";
|
import { useExcalidrawContainer, useExcalidrawSetAppState } from "./App";
|
||||||
import { jotaiScope } from "../jotai";
|
import { jotaiScope } from "../jotai";
|
||||||
|
|
||||||
interface Props extends Omit<DialogProps, "onCloseRequest"> {
|
interface Props extends Omit<DialogProps, "onCloseRequest"> {
|
||||||
|
@ -26,6 +26,7 @@ const ConfirmDialog = (props: Props) => {
|
||||||
} = props;
|
} = props;
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope);
|
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope);
|
||||||
|
const { container } = useExcalidrawContainer();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
|
@ -42,6 +43,7 @@ const ConfirmDialog = (props: Props) => {
|
||||||
setAppState({ openMenu: null });
|
setAppState({ openMenu: null });
|
||||||
setIsLibraryMenuOpen(false);
|
setIsLibraryMenuOpen(false);
|
||||||
onCancel();
|
onCancel();
|
||||||
|
container?.focus();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<DialogActionButton
|
<DialogActionButton
|
||||||
|
@ -50,6 +52,7 @@ const ConfirmDialog = (props: Props) => {
|
||||||
setAppState({ openMenu: null });
|
setAppState({ openMenu: null });
|
||||||
setIsLibraryMenuOpen(false);
|
setIsLibraryMenuOpen(false);
|
||||||
onConfirm();
|
onConfirm();
|
||||||
|
container?.focus();
|
||||||
}}
|
}}
|
||||||
actionType="danger"
|
actionType="danger"
|
||||||
/>
|
/>
|
||||||
|
|
144
src/components/DefaultSidebar.test.tsx
Normal file
144
src/components/DefaultSidebar.test.tsx
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import React from "react";
|
||||||
|
import { DEFAULT_SIDEBAR } from "../constants";
|
||||||
|
import { DefaultSidebar } from "../packages/excalidraw/index";
|
||||||
|
import {
|
||||||
|
fireEvent,
|
||||||
|
waitFor,
|
||||||
|
withExcalidrawDimensions,
|
||||||
|
} from "../tests/test-utils";
|
||||||
|
import {
|
||||||
|
assertExcalidrawWithSidebar,
|
||||||
|
assertSidebarDockButton,
|
||||||
|
} from "./Sidebar/Sidebar.test";
|
||||||
|
|
||||||
|
const { h } = window;
|
||||||
|
|
||||||
|
describe("DefaultSidebar", () => {
|
||||||
|
it("when `docked={undefined}` & `onDock={undefined}`, should allow docking", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<DefaultSidebar />,
|
||||||
|
DEFAULT_SIDEBAR.name,
|
||||||
|
async () => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
|
||||||
|
const { dockButton } = await assertSidebarDockButton(true);
|
||||||
|
|
||||||
|
fireEvent.click(dockButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(true);
|
||||||
|
expect(dockButton).toHaveClass("selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(dockButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
expect(dockButton).not.toHaveClass("selected");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when `docked={undefined}` & `onDock`, should allow docking", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<DefaultSidebar onDock={() => {}} />,
|
||||||
|
DEFAULT_SIDEBAR.name,
|
||||||
|
async () => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
|
||||||
|
const { dockButton } = await assertSidebarDockButton(true);
|
||||||
|
|
||||||
|
fireEvent.click(dockButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(true);
|
||||||
|
expect(dockButton).toHaveClass("selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(dockButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
expect(dockButton).not.toHaveClass("selected");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when `docked={true}` & `onDock`, should allow docking", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<DefaultSidebar onDock={() => {}} />,
|
||||||
|
DEFAULT_SIDEBAR.name,
|
||||||
|
async () => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
|
||||||
|
const { dockButton } = await assertSidebarDockButton(true);
|
||||||
|
|
||||||
|
fireEvent.click(dockButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(true);
|
||||||
|
expect(dockButton).toHaveClass("selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(dockButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
expect(dockButton).not.toHaveClass("selected");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when `onDock={false}`, should disable docking", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<DefaultSidebar onDock={false} />,
|
||||||
|
DEFAULT_SIDEBAR.name,
|
||||||
|
async () => {
|
||||||
|
await withExcalidrawDimensions(
|
||||||
|
{ width: 1920, height: 1080 },
|
||||||
|
async () => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
|
||||||
|
await assertSidebarDockButton(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when `docked={true}` & `onDock={false}`, should force-dock sidebar", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<DefaultSidebar docked onDock={false} />,
|
||||||
|
DEFAULT_SIDEBAR.name,
|
||||||
|
async () => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
|
||||||
|
const { sidebar } = await assertSidebarDockButton(false);
|
||||||
|
expect(sidebar).toHaveClass("sidebar--docked");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when `docked={true}` & `onDock={undefined}`, should force-dock sidebar", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<DefaultSidebar docked />,
|
||||||
|
DEFAULT_SIDEBAR.name,
|
||||||
|
async () => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
|
||||||
|
const { sidebar } = await assertSidebarDockButton(false);
|
||||||
|
expect(sidebar).toHaveClass("sidebar--docked");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when `docked={false}` & `onDock={undefined}`, should force-undock sidebar", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<DefaultSidebar docked={false} />,
|
||||||
|
DEFAULT_SIDEBAR.name,
|
||||||
|
async () => {
|
||||||
|
expect(h.state.defaultSidebarDockedPreference).toBe(false);
|
||||||
|
|
||||||
|
const { sidebar } = await assertSidebarDockButton(false);
|
||||||
|
expect(sidebar).not.toHaveClass("sidebar--docked");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
118
src/components/DefaultSidebar.tsx
Normal file
118
src/components/DefaultSidebar.tsx
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_TAB } from "../constants";
|
||||||
|
import { useTunnels } from "../context/tunnels";
|
||||||
|
import { useUIAppState } from "../context/ui-appState";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { MarkOptional, Merge } from "../utility-types";
|
||||||
|
import { composeEventHandlers } from "../utils";
|
||||||
|
import { useExcalidrawSetAppState } from "./App";
|
||||||
|
import { withInternalFallback } from "./hoc/withInternalFallback";
|
||||||
|
import { LibraryMenu } from "./LibraryMenu";
|
||||||
|
import { SidebarProps, SidebarTriggerProps } from "./Sidebar/common";
|
||||||
|
import { Sidebar } from "./Sidebar/Sidebar";
|
||||||
|
|
||||||
|
const DefaultSidebarTrigger = withInternalFallback(
|
||||||
|
"DefaultSidebarTrigger",
|
||||||
|
(
|
||||||
|
props: Omit<SidebarTriggerProps, "name"> &
|
||||||
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
) => {
|
||||||
|
const { DefaultSidebarTriggerTunnel } = useTunnels();
|
||||||
|
return (
|
||||||
|
<DefaultSidebarTriggerTunnel.In>
|
||||||
|
<Sidebar.Trigger
|
||||||
|
{...props}
|
||||||
|
className="default-sidebar-trigger"
|
||||||
|
name={DEFAULT_SIDEBAR.name}
|
||||||
|
/>
|
||||||
|
</DefaultSidebarTriggerTunnel.In>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
DefaultSidebarTrigger.displayName = "DefaultSidebarTrigger";
|
||||||
|
|
||||||
|
const DefaultTabTriggers = ({
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
}: { children: React.ReactNode } & React.HTMLAttributes<HTMLDivElement>) => {
|
||||||
|
const { DefaultSidebarTabTriggersTunnel } = useTunnels();
|
||||||
|
return (
|
||||||
|
<DefaultSidebarTabTriggersTunnel.In>
|
||||||
|
<Sidebar.TabTriggers {...rest}>{children}</Sidebar.TabTriggers>
|
||||||
|
</DefaultSidebarTabTriggersTunnel.In>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
DefaultTabTriggers.displayName = "DefaultTabTriggers";
|
||||||
|
|
||||||
|
export const DefaultSidebar = Object.assign(
|
||||||
|
withInternalFallback(
|
||||||
|
"DefaultSidebar",
|
||||||
|
({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
onDock,
|
||||||
|
docked,
|
||||||
|
...rest
|
||||||
|
}: Merge<
|
||||||
|
MarkOptional<Omit<SidebarProps, "name">, "children">,
|
||||||
|
{
|
||||||
|
/** pass `false` to disable docking */
|
||||||
|
onDock?: SidebarProps["onDock"] | false;
|
||||||
|
}
|
||||||
|
>) => {
|
||||||
|
const appState = useUIAppState();
|
||||||
|
const setAppState = useExcalidrawSetAppState();
|
||||||
|
|
||||||
|
const { DefaultSidebarTabTriggersTunnel } = useTunnels();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sidebar
|
||||||
|
{...rest}
|
||||||
|
name="default"
|
||||||
|
key="default"
|
||||||
|
className={clsx("default-sidebar", className)}
|
||||||
|
docked={docked ?? appState.defaultSidebarDockedPreference}
|
||||||
|
onDock={
|
||||||
|
// `onDock=false` disables docking.
|
||||||
|
// if `docked` passed, but no onDock passed, disable manual docking.
|
||||||
|
onDock === false || (!onDock && docked != null)
|
||||||
|
? undefined
|
||||||
|
: // compose to allow the host app to listen on default behavior
|
||||||
|
composeEventHandlers(onDock, (docked) => {
|
||||||
|
setAppState({ defaultSidebarDockedPreference: docked });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Sidebar.Tabs>
|
||||||
|
<Sidebar.Header>
|
||||||
|
{rest.__fallback && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: "var(--color-primary)",
|
||||||
|
fontSize: "1.2em",
|
||||||
|
fontWeight: "bold",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
overflow: "hidden",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
paddingRight: "1em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("toolBar.library")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DefaultSidebarTabTriggersTunnel.Out />
|
||||||
|
</Sidebar.Header>
|
||||||
|
<Sidebar.Tab tab={LIBRARY_SIDEBAR_TAB}>
|
||||||
|
<LibraryMenu />
|
||||||
|
</Sidebar.Tab>
|
||||||
|
{children}
|
||||||
|
</Sidebar.Tabs>
|
||||||
|
</Sidebar>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
Trigger: DefaultSidebarTrigger,
|
||||||
|
TabTriggers: DefaultTabTriggers,
|
||||||
|
},
|
||||||
|
);
|
|
@ -15,7 +15,7 @@ import { Modal } from "./Modal";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { queryFocusableElements } from "../utils";
|
import { queryFocusableElements } from "../utils";
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { isLibraryMenuOpenAtom } from "./LibraryMenuHeaderContent";
|
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||||
import { jotaiScope } from "../jotai";
|
import { jotaiScope } from "../jotai";
|
||||||
|
|
||||||
export interface DialogProps {
|
export interface DialogProps {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
|
import { Device, UIAppState } from "../types";
|
||||||
import "./HintViewer.scss";
|
|
||||||
import { AppState, Device } from "../types";
|
|
||||||
import {
|
import {
|
||||||
isImageElement,
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
|
@ -13,8 +11,10 @@ import {
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import { isEraserActive } from "../appState";
|
import { isEraserActive } from "../appState";
|
||||||
|
|
||||||
|
import "./HintViewer.scss";
|
||||||
|
|
||||||
interface HintViewerProps {
|
interface HintViewerProps {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
device: Device;
|
device: Device;
|
||||||
|
@ -29,7 +29,7 @@ const getHints = ({
|
||||||
const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
|
const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
|
||||||
const multiMode = appState.multiElement !== null;
|
const multiMode = appState.multiElement !== null;
|
||||||
|
|
||||||
if (appState.openSidebar === "library" && !device.canDeviceFitSidebar) {
|
if (appState.openSidebar && !device.canDeviceFitSidebar) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,10 @@ 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 { AppState, BinaryFiles } from "../types";
|
import { 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 "./ExportDialog.scss";
|
|
||||||
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, isFirefox } from "../constants";
|
||||||
|
@ -16,6 +15,8 @@ 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";
|
||||||
|
|
||||||
|
import "./ExportDialog.scss";
|
||||||
|
|
||||||
const supportsContextFilters =
|
const supportsContextFilters =
|
||||||
"filter" in document.createElement("canvas").getContext("2d")!;
|
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||||
|
|
||||||
|
@ -70,7 +71,7 @@ const ImageExportModal = ({
|
||||||
onExportToSvg,
|
onExportToSvg,
|
||||||
onExportToClipboard,
|
onExportToClipboard,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
|
@ -216,8 +217,8 @@ export const ImageExportDialog = ({
|
||||||
onExportToSvg,
|
onExportToSvg,
|
||||||
onExportToClipboard,
|
onExportToClipboard,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from "react";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
import { AppState, ExportOpts, BinaryFiles } from "../types";
|
import { ExportOpts, BinaryFiles, UIAppState } from "../types";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { exportToFileIcon, LinkIcon } from "./icons";
|
import { exportToFileIcon, LinkIcon } from "./icons";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
|
@ -28,7 +28,7 @@ const JSONExportModal = ({
|
||||||
exportOpts,
|
exportOpts,
|
||||||
canvas,
|
canvas,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
|
@ -96,12 +96,12 @@ export const JSONExportDialog = ({
|
||||||
setAppState,
|
setAppState,
|
||||||
}: {
|
}: {
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
exportOpts: ExportOpts;
|
exportOpts: ExportOpts;
|
||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
}) => {
|
}) => {
|
||||||
const handleClose = React.useCallback(() => {
|
const handleClose = React.useCallback(() => {
|
||||||
setAppState({ openDialog: null });
|
setAppState({ openDialog: null });
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { CLASSES, LIBRARY_SIDEBAR_WIDTH } from "../constants";
|
import { CLASSES, DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_WIDTH } from "../constants";
|
||||||
import { exportCanvas } from "../data";
|
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 { ExportType } from "../scene/types";
|
||||||
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
import {
|
||||||
import { isShallowEqual, muteFSAbortError } from "../utils";
|
AppProps,
|
||||||
|
AppState,
|
||||||
|
ExcalidrawProps,
|
||||||
|
BinaryFiles,
|
||||||
|
UIAppState,
|
||||||
|
} from "../types";
|
||||||
|
import { capitalizeString, isShallowEqual, muteFSAbortError } 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 { ExportCB, ImageExportDialog } from "./ImageExportDialog";
|
||||||
|
@ -24,32 +30,32 @@ import { Section } from "./Section";
|
||||||
import { HelpDialog } from "./HelpDialog";
|
import { HelpDialog } from "./HelpDialog";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { UserList } from "./UserList";
|
import { UserList } from "./UserList";
|
||||||
import Library from "../data/library";
|
|
||||||
import { JSONExportDialog } from "./JSONExportDialog";
|
import { JSONExportDialog } from "./JSONExportDialog";
|
||||||
import { LibraryButton } from "./LibraryButton";
|
|
||||||
import { isImageFileHandle } from "../data/blob";
|
import { isImageFileHandle } from "../data/blob";
|
||||||
import { LibraryMenu } from "./LibraryMenu";
|
|
||||||
|
|
||||||
import "./LayerUI.scss";
|
|
||||||
import "./Toolbar.scss";
|
|
||||||
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";
|
||||||
import { Stats } from "./Stats";
|
import { Stats } from "./Stats";
|
||||||
import { actionToggleStats } from "../actions/actionToggleStats";
|
import { actionToggleStats } from "../actions/actionToggleStats";
|
||||||
import Footer from "./footer/Footer";
|
import Footer from "./footer/Footer";
|
||||||
import { hostSidebarCountersAtom } from "./Sidebar/Sidebar";
|
import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
||||||
import { jotaiScope } from "../jotai";
|
import { jotaiScope } from "../jotai";
|
||||||
import { Provider, useAtom } from "jotai";
|
import { Provider, useAtomValue } from "jotai";
|
||||||
import MainMenu from "./main-menu/MainMenu";
|
import MainMenu from "./main-menu/MainMenu";
|
||||||
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
||||||
import { HandButton } from "./HandButton";
|
import { HandButton } from "./HandButton";
|
||||||
import { isHandToolActive } from "../appState";
|
import { isHandToolActive } from "../appState";
|
||||||
import { TunnelsContext, useInitializeTunnels } from "./context/tunnels";
|
import { TunnelsContext, useInitializeTunnels } from "../context/tunnels";
|
||||||
|
import { LibraryIcon } from "./icons";
|
||||||
|
import { UIAppStateContext } from "../context/ui-appState";
|
||||||
|
import { DefaultSidebar } from "./DefaultSidebar";
|
||||||
|
|
||||||
|
import "./LayerUI.scss";
|
||||||
|
import "./Toolbar.scss";
|
||||||
|
|
||||||
interface LayerUIProps {
|
interface LayerUIProps {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
@ -57,17 +63,11 @@ interface LayerUIProps {
|
||||||
onLockToggle: () => void;
|
onLockToggle: () => void;
|
||||||
onHandToolToggle: () => void;
|
onHandToolToggle: () => void;
|
||||||
onPenModeToggle: () => void;
|
onPenModeToggle: () => void;
|
||||||
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
|
||||||
showExitZenModeBtn: boolean;
|
showExitZenModeBtn: boolean;
|
||||||
langCode: Language["code"];
|
langCode: Language["code"];
|
||||||
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
|
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
|
||||||
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
||||||
renderCustomSidebar?: ExcalidrawProps["renderSidebar"];
|
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
|
||||||
UIOptions: AppProps["UIOptions"];
|
UIOptions: AppProps["UIOptions"];
|
||||||
focusContainer: () => void;
|
|
||||||
library: Library;
|
|
||||||
id: string;
|
|
||||||
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
||||||
renderWelcomeScreen: boolean;
|
renderWelcomeScreen: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
@ -109,16 +109,10 @@ const LayerUI = ({
|
||||||
onLockToggle,
|
onLockToggle,
|
||||||
onHandToolToggle,
|
onHandToolToggle,
|
||||||
onPenModeToggle,
|
onPenModeToggle,
|
||||||
onInsertElements,
|
|
||||||
showExitZenModeBtn,
|
showExitZenModeBtn,
|
||||||
renderTopRightUI,
|
renderTopRightUI,
|
||||||
renderCustomStats,
|
renderCustomStats,
|
||||||
renderCustomSidebar,
|
|
||||||
libraryReturnUrl,
|
|
||||||
UIOptions,
|
UIOptions,
|
||||||
focusContainer,
|
|
||||||
library,
|
|
||||||
id,
|
|
||||||
onImageAction,
|
onImageAction,
|
||||||
renderWelcomeScreen,
|
renderWelcomeScreen,
|
||||||
children,
|
children,
|
||||||
|
@ -156,7 +150,8 @@ const LayerUI = ({
|
||||||
const fileHandle = await exportCanvas(
|
const fileHandle = await exportCanvas(
|
||||||
type,
|
type,
|
||||||
exportedElements,
|
exportedElements,
|
||||||
appState,
|
// FIXME once we split UI canvas from element canvas
|
||||||
|
appState as AppState,
|
||||||
files,
|
files,
|
||||||
{
|
{
|
||||||
exportBackground: appState.exportBackground,
|
exportBackground: appState.exportBackground,
|
||||||
|
@ -197,8 +192,8 @@ const LayerUI = ({
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
{/* wrapping to Fragment stops React from occasionally complaining
|
{/* wrapping to Fragment stops React from occasionally complaining
|
||||||
about identical Keys */}
|
about identical Keys */}
|
||||||
<tunnels.mainMenuTunnel.Out />
|
<tunnels.MainMenuTunnel.Out />
|
||||||
{renderWelcomeScreen && <tunnels.welcomeScreenMenuHintTunnel.Out />}
|
{renderWelcomeScreen && <tunnels.WelcomeScreenMenuHintTunnel.Out />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -250,7 +245,7 @@ const LayerUI = ({
|
||||||
{(heading: React.ReactNode) => (
|
{(heading: React.ReactNode) => (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
{renderWelcomeScreen && (
|
{renderWelcomeScreen && (
|
||||||
<tunnels.welcomeScreenToolbarHintTunnel.Out />
|
<tunnels.WelcomeScreenToolbarHintTunnel.Out />
|
||||||
)}
|
)}
|
||||||
<Stack.Col gap={4} align="start">
|
<Stack.Col gap={4} align="start">
|
||||||
<Stack.Row
|
<Stack.Row
|
||||||
|
@ -324,9 +319,12 @@ const LayerUI = ({
|
||||||
>
|
>
|
||||||
<UserList collaborators={appState.collaborators} />
|
<UserList collaborators={appState.collaborators} />
|
||||||
{renderTopRightUI?.(device.isMobile, appState)}
|
{renderTopRightUI?.(device.isMobile, appState)}
|
||||||
{!appState.viewModeEnabled && (
|
{!appState.viewModeEnabled &&
|
||||||
<LibraryButton appState={appState} setAppState={setAppState} />
|
// hide button when sidebar docked
|
||||||
)}
|
(!isSidebarDocked ||
|
||||||
|
appState.openSidebar?.name !== DEFAULT_SIDEBAR.name) && (
|
||||||
|
<tunnels.DefaultSidebarTriggerTunnel.Out />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FixedSideContainer>
|
</FixedSideContainer>
|
||||||
|
@ -334,21 +332,21 @@ const LayerUI = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSidebars = () => {
|
const renderSidebars = () => {
|
||||||
return appState.openSidebar === "customSidebar" ? (
|
return (
|
||||||
renderCustomSidebar?.() || null
|
<DefaultSidebar
|
||||||
) : appState.openSidebar === "library" ? (
|
__fallback
|
||||||
<LibraryMenu
|
onDock={(docked) => {
|
||||||
appState={appState}
|
trackEvent(
|
||||||
onInsertElements={onInsertElements}
|
"sidebar",
|
||||||
libraryReturnUrl={libraryReturnUrl}
|
`toggleDock (${docked ? "dock" : "undock"})`,
|
||||||
focusContainer={focusContainer}
|
`(${device.isMobile ? "mobile" : "desktop"})`,
|
||||||
library={library}
|
);
|
||||||
id={id}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null;
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [hostSidebarCounters] = useAtom(hostSidebarCountersAtom, jotaiScope);
|
const isSidebarDocked = useAtomValue(isSidebarDockedAtom, jotaiScope);
|
||||||
|
|
||||||
const layerUIJSX = (
|
const layerUIJSX = (
|
||||||
<>
|
<>
|
||||||
|
@ -358,8 +356,25 @@ const LayerUI = ({
|
||||||
{children}
|
{children}
|
||||||
{/* render component fallbacks. Can be rendered anywhere as they'll be
|
{/* render component fallbacks. Can be rendered anywhere as they'll be
|
||||||
tunneled away. We only render tunneled components that actually
|
tunneled away. We only render tunneled components that actually
|
||||||
have defaults when host do not render anything. */}
|
have defaults when host do not render anything. */}
|
||||||
<DefaultMainMenu UIOptions={UIOptions} />
|
<DefaultMainMenu UIOptions={UIOptions} />
|
||||||
|
<DefaultSidebar.Trigger
|
||||||
|
__fallback
|
||||||
|
icon={LibraryIcon}
|
||||||
|
title={capitalizeString(t("toolBar.library"))}
|
||||||
|
onToggle={(open) => {
|
||||||
|
if (open) {
|
||||||
|
trackEvent(
|
||||||
|
"sidebar",
|
||||||
|
`${DEFAULT_SIDEBAR.name} (open)`,
|
||||||
|
`button (${device.isMobile ? "mobile" : "desktop"})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
tab={DEFAULT_SIDEBAR.defaultTab}
|
||||||
|
>
|
||||||
|
{t("toolBar.library")}
|
||||||
|
</DefaultSidebar.Trigger>
|
||||||
{/* ------------------------------------------------------------------ */}
|
{/* ------------------------------------------------------------------ */}
|
||||||
|
|
||||||
{appState.isLoading && <LoadingMessage delay={250} />}
|
{appState.isLoading && <LoadingMessage delay={250} />}
|
||||||
|
@ -382,7 +397,6 @@ const LayerUI = ({
|
||||||
<PasteChartDialog
|
<PasteChartDialog
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
onInsertChart={onInsertElements}
|
|
||||||
onClose={() =>
|
onClose={() =>
|
||||||
setAppState({
|
setAppState({
|
||||||
pasteDialog: { shown: false, data: null },
|
pasteDialog: { shown: false, data: null },
|
||||||
|
@ -410,7 +424,6 @@ const LayerUI = ({
|
||||||
renderWelcomeScreen={renderWelcomeScreen}
|
renderWelcomeScreen={renderWelcomeScreen}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!device.isMobile && (
|
{!device.isMobile && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
@ -422,15 +435,14 @@ const LayerUI = ({
|
||||||
!isTextElement(appState.editingElement)),
|
!isTextElement(appState.editingElement)),
|
||||||
})}
|
})}
|
||||||
style={
|
style={
|
||||||
((appState.openSidebar === "library" &&
|
appState.openSidebar &&
|
||||||
appState.isSidebarDocked) ||
|
isSidebarDocked &&
|
||||||
hostSidebarCounters.docked) &&
|
|
||||||
device.canDeviceFitSidebar
|
device.canDeviceFitSidebar
|
||||||
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
|
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{renderWelcomeScreen && <tunnels.welcomeScreenCenterTunnel.Out />}
|
{renderWelcomeScreen && <tunnels.WelcomeScreenCenterTunnel.Out />}
|
||||||
{renderFixedSideContainer()}
|
{renderFixedSideContainer()}
|
||||||
<Footer
|
<Footer
|
||||||
appState={appState}
|
appState={appState}
|
||||||
|
@ -453,9 +465,9 @@ const LayerUI = ({
|
||||||
<button
|
<button
|
||||||
className="scroll-back-to-content"
|
className="scroll-back-to-content"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAppState({
|
setAppState((appState) => ({
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
...calculateScrollCenter(elements, appState, canvas),
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("buttons.scrollBackToContent")}
|
{t("buttons.scrollBackToContent")}
|
||||||
|
@ -469,19 +481,25 @@ const LayerUI = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider scope={tunnels.jotaiScope}>
|
<UIAppStateContext.Provider value={appState}>
|
||||||
<TunnelsContext.Provider value={tunnels}>
|
<Provider scope={tunnels.jotaiScope}>
|
||||||
{layerUIJSX}
|
<TunnelsContext.Provider value={tunnels}>
|
||||||
</TunnelsContext.Provider>
|
{layerUIJSX}
|
||||||
</Provider>
|
</TunnelsContext.Provider>
|
||||||
|
</Provider>
|
||||||
|
</UIAppStateContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const stripIrrelevantAppStateProps = (
|
const stripIrrelevantAppStateProps = (appState: AppState): UIAppState => {
|
||||||
appState: AppState,
|
const {
|
||||||
): Partial<AppState> => {
|
suggestedBindings,
|
||||||
const { suggestedBindings, startBoundElement, cursorButton, ...ret } =
|
startBoundElement,
|
||||||
appState;
|
cursorButton,
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
...ret
|
||||||
|
} = appState;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -491,24 +509,19 @@ const areEqual = (prevProps: LayerUIProps, nextProps: LayerUIProps) => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { canvas: _prevCanvas, appState: prevAppState, ...prev } = prevProps;
|
||||||
canvas: _prevCanvas,
|
const { canvas: _nextCanvas, appState: nextAppState, ...next } = nextProps;
|
||||||
// not stable, but shouldn't matter in our case
|
|
||||||
onInsertElements: _prevOnInsertElements,
|
|
||||||
appState: prevAppState,
|
|
||||||
...prev
|
|
||||||
} = prevProps;
|
|
||||||
const {
|
|
||||||
canvas: _nextCanvas,
|
|
||||||
onInsertElements: _nextOnInsertElements,
|
|
||||||
appState: nextAppState,
|
|
||||||
...next
|
|
||||||
} = nextProps;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isShallowEqual(
|
isShallowEqual(
|
||||||
stripIrrelevantAppStateProps(prevAppState),
|
// asserting AppState because we're being passed the whole AppState
|
||||||
stripIrrelevantAppStateProps(nextAppState),
|
// but resolve to only the UI-relevant props
|
||||||
|
stripIrrelevantAppStateProps(prevAppState as AppState),
|
||||||
|
stripIrrelevantAppStateProps(nextAppState as AppState),
|
||||||
|
{
|
||||||
|
selectedElementIds: isShallowEqual,
|
||||||
|
selectedGroupIds: isShallowEqual,
|
||||||
|
},
|
||||||
) && isShallowEqual(prev, next)
|
) && isShallowEqual(prev, next)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
@import "../css/variables.module";
|
|
||||||
|
|
||||||
.library-button {
|
|
||||||
@include outlineButtonStyles;
|
|
||||||
|
|
||||||
background-color: var(--island-bg-color);
|
|
||||||
|
|
||||||
width: auto;
|
|
||||||
height: var(--lg-button-size);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
line-height: 0;
|
|
||||||
|
|
||||||
font-size: 0.75rem;
|
|
||||||
letter-spacing: 0.4px;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: var(--lg-icon-size);
|
|
||||||
height: var(--lg-icon-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
@media screen and (min-width: 1024px) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import { AppState } from "../types";
|
|
||||||
import { capitalizeString } from "../utils";
|
|
||||||
import { trackEvent } from "../analytics";
|
|
||||||
import { useDevice } from "./App";
|
|
||||||
import "./LibraryButton.scss";
|
|
||||||
import { LibraryIcon } from "./icons";
|
|
||||||
|
|
||||||
export const LibraryButton: React.FC<{
|
|
||||||
appState: AppState;
|
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
|
||||||
isMobile?: boolean;
|
|
||||||
}> = ({ appState, setAppState, isMobile }) => {
|
|
||||||
const device = useDevice();
|
|
||||||
const showLabel = !isMobile;
|
|
||||||
|
|
||||||
// TODO barnabasmolnar/redesign
|
|
||||||
// not great, toolbar jumps in a jarring manner
|
|
||||||
if (appState.isSidebarDocked && appState.openSidebar === "library") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label title={`${capitalizeString(t("toolBar.library"))}`}>
|
|
||||||
<input
|
|
||||||
className="ToolIcon_type_checkbox"
|
|
||||||
type="checkbox"
|
|
||||||
name="editor-library"
|
|
||||||
onChange={(event) => {
|
|
||||||
document
|
|
||||||
.querySelector(".layer-ui__wrapper")
|
|
||||||
?.classList.remove("animate");
|
|
||||||
const isOpen = event.target.checked;
|
|
||||||
setAppState({ openSidebar: isOpen ? "library" : null });
|
|
||||||
// track only openings
|
|
||||||
if (isOpen) {
|
|
||||||
trackEvent(
|
|
||||||
"library",
|
|
||||||
"toggleLibrary (open)",
|
|
||||||
`toolbar (${device.isMobile ? "mobile" : "desktop"})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
checked={appState.openSidebar === "library"}
|
|
||||||
aria-label={capitalizeString(t("toolBar.library"))}
|
|
||||||
aria-keyshortcuts="0"
|
|
||||||
/>
|
|
||||||
<div className="library-button">
|
|
||||||
<div>{LibraryIcon}</div>
|
|
||||||
{showLabel && (
|
|
||||||
<div className="library-button__label">{t("toolBar.library")}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,9 +1,9 @@
|
||||||
@import "open-color/open-color";
|
@import "open-color/open-color";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.layer-ui__library-sidebar {
|
.library-menu-items-container {
|
||||||
display: flex;
|
height: 100%;
|
||||||
flex-direction: column;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-ui__library {
|
.layer-ui__library {
|
||||||
|
@ -11,28 +11,6 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
.layer-ui__library-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
margin: 2px 0 15px 0;
|
|
||||||
.Spinner {
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
// 2px from the left to account for focus border of left-most button
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.layer-ui__sidebar {
|
|
||||||
.library-menu-items-container {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-actions-counter {
|
.library-actions-counter {
|
||||||
|
@ -87,10 +65,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-menu-browse-button {
|
.library-menu-control-buttons {
|
||||||
margin: 1rem auto;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
padding: 0.875rem 1rem;
|
.library-menu-browse-button {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
height: var(--lg-button-size);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -122,30 +107,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-menu-browse-button--mobile {
|
&.excalidraw--mobile .library-menu-browse-button {
|
||||||
min-height: 22px;
|
height: var(--default-button-size);
|
||||||
margin-left: auto;
|
|
||||||
a {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-ui__sidebar__header .dropdown-menu {
|
.layer-ui__library .dropdown-menu {
|
||||||
&.dropdown-menu--mobile {
|
width: auto;
|
||||||
top: 100%;
|
top: initial;
|
||||||
}
|
right: 0;
|
||||||
|
left: initial;
|
||||||
|
bottom: 100%;
|
||||||
|
margin-bottom: 0.625rem;
|
||||||
|
|
||||||
.dropdown-menu-container {
|
.dropdown-menu-container {
|
||||||
--gap: 0;
|
|
||||||
z-index: 1;
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
|
||||||
right: 0;
|
|
||||||
left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
width: 196px;
|
width: 196px;
|
||||||
box-shadow: var(--library-dropdown-shadow);
|
box-shadow: var(--library-dropdown-shadow);
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-lg);
|
||||||
|
|
|
@ -1,77 +1,39 @@
|
||||||
import {
|
import React, { useState, useCallback } from "react";
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useCallback,
|
|
||||||
RefObject,
|
|
||||||
forwardRef,
|
|
||||||
} from "react";
|
|
||||||
import Library, {
|
import Library, {
|
||||||
distributeLibraryItemsOnSquareGrid,
|
distributeLibraryItemsOnSquareGrid,
|
||||||
libraryItemsAtom,
|
libraryItemsAtom,
|
||||||
} from "../data/library";
|
} from "../data/library";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { randomId } from "../random";
|
import { randomId } from "../random";
|
||||||
import { LibraryItems, LibraryItem, AppState, ExcalidrawProps } from "../types";
|
import {
|
||||||
|
LibraryItems,
|
||||||
import "./LibraryMenu.scss";
|
LibraryItem,
|
||||||
|
ExcalidrawProps,
|
||||||
|
UIAppState,
|
||||||
|
} from "../types";
|
||||||
import LibraryMenuItems from "./LibraryMenuItems";
|
import LibraryMenuItems from "./LibraryMenuItems";
|
||||||
import { EVENT } from "../constants";
|
|
||||||
import { KEYS } from "../keys";
|
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { jotaiScope } from "../jotai";
|
import { jotaiScope } from "../jotai";
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
import {
|
import {
|
||||||
useDevice,
|
useApp,
|
||||||
|
useAppProps,
|
||||||
useExcalidrawElements,
|
useExcalidrawElements,
|
||||||
useExcalidrawSetAppState,
|
useExcalidrawSetAppState,
|
||||||
} from "./App";
|
} from "./App";
|
||||||
import { Sidebar } from "./Sidebar/Sidebar";
|
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { useUIAppState } from "../context/ui-appState";
|
||||||
import { LibraryMenuHeader } from "./LibraryMenuHeaderContent";
|
|
||||||
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
|
|
||||||
|
|
||||||
const useOnClickOutside = (
|
import "./LibraryMenu.scss";
|
||||||
ref: RefObject<HTMLElement>,
|
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
||||||
cb: (event: MouseEvent) => void,
|
|
||||||
) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const listener = (event: MouseEvent) => {
|
|
||||||
if (!ref.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
export const isLibraryMenuOpenAtom = atom(false);
|
||||||
event.target instanceof Element &&
|
|
||||||
(ref.current.contains(event.target) ||
|
|
||||||
!document.body.contains(event.target))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(event);
|
const LibraryMenuWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
};
|
return <div className="layer-ui__library">{children}</div>;
|
||||||
document.addEventListener("pointerdown", listener, false);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("pointerdown", listener);
|
|
||||||
};
|
|
||||||
}, [ref, cb]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const LibraryMenuWrapper = forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
{ children: React.ReactNode }
|
|
||||||
>(({ children }, ref) => {
|
|
||||||
return (
|
|
||||||
<div ref={ref} className="layer-ui__library">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const LibraryMenuContent = ({
|
export const LibraryMenuContent = ({
|
||||||
onInsertLibraryItems,
|
onInsertLibraryItems,
|
||||||
pendingElements,
|
pendingElements,
|
||||||
|
@ -87,11 +49,11 @@ export const LibraryMenuContent = ({
|
||||||
pendingElements: LibraryItem["elements"];
|
pendingElements: LibraryItem["elements"];
|
||||||
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
||||||
onAddToLibrary: () => void;
|
onAddToLibrary: () => void;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
library: Library;
|
library: Library;
|
||||||
id: string;
|
id: string;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
selectedItems: LibraryItem["id"][];
|
selectedItems: LibraryItem["id"][];
|
||||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -158,81 +120,31 @@ export const LibraryMenuContent = ({
|
||||||
theme={appState.theme}
|
theme={appState.theme}
|
||||||
/>
|
/>
|
||||||
{showBtn && (
|
{showBtn && (
|
||||||
<LibraryMenuBrowseButton
|
<LibraryMenuControlButtons
|
||||||
|
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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LibraryMenu: React.FC<{
|
/**
|
||||||
appState: AppState;
|
* This component is meant to be rendered inside <Sidebar.Tab/> inside our
|
||||||
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
* <DefaultSidebar/> or host apps Sidebar components.
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
*/
|
||||||
focusContainer: () => void;
|
export const LibraryMenu = () => {
|
||||||
library: Library;
|
const { library, id, onInsertElements } = useApp();
|
||||||
id: string;
|
const appProps = useAppProps();
|
||||||
}> = ({
|
const appState = useUIAppState();
|
||||||
appState,
|
|
||||||
onInsertElements,
|
|
||||||
libraryReturnUrl,
|
|
||||||
focusContainer,
|
|
||||||
library,
|
|
||||||
id,
|
|
||||||
}) => {
|
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
const elements = useExcalidrawElements();
|
const elements = useExcalidrawElements();
|
||||||
const device = useDevice();
|
|
||||||
|
|
||||||
const [selectedItems, setSelectedItems] = useState<LibraryItem["id"][]>([]);
|
const [selectedItems, setSelectedItems] = useState<LibraryItem["id"][]>([]);
|
||||||
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
|
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
const closeLibrary = useCallback(() => {
|
|
||||||
const isDialogOpen = !!document.querySelector(".Dialog");
|
|
||||||
|
|
||||||
// Prevent closing if any dialog is open
|
|
||||||
if (isDialogOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setAppState({ openSidebar: null });
|
|
||||||
}, [setAppState]);
|
|
||||||
|
|
||||||
useOnClickOutside(
|
|
||||||
ref,
|
|
||||||
useCallback(
|
|
||||||
(event) => {
|
|
||||||
// If click on the library icon, do nothing so that LibraryButton
|
|
||||||
// can toggle library menu
|
|
||||||
if ((event.target as Element).closest(".ToolIcon__library")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!appState.isSidebarDocked || !device.canDeviceFitSidebar) {
|
|
||||||
closeLibrary();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[closeLibrary, appState.isSidebarDocked, device.canDeviceFitSidebar],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
|
||||||
if (
|
|
||||||
event.key === KEYS.ESCAPE &&
|
|
||||||
(!appState.isSidebarDocked || !device.canDeviceFitSidebar)
|
|
||||||
) {
|
|
||||||
closeLibrary();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener(EVENT.KEYDOWN, handleKeyDown);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener(EVENT.KEYDOWN, handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [closeLibrary, appState.isSidebarDocked, device.canDeviceFitSidebar]);
|
|
||||||
|
|
||||||
const deselectItems = useCallback(() => {
|
const deselectItems = useCallback(() => {
|
||||||
setAppState({
|
setAppState({
|
||||||
|
@ -241,69 +153,20 @@ export const LibraryMenu: React.FC<{
|
||||||
});
|
});
|
||||||
}, [setAppState]);
|
}, [setAppState]);
|
||||||
|
|
||||||
const removeFromLibrary = useCallback(
|
|
||||||
async (libraryItems: LibraryItems) => {
|
|
||||||
const nextItems = libraryItems.filter(
|
|
||||||
(item) => !selectedItems.includes(item.id),
|
|
||||||
);
|
|
||||||
library.setLibrary(nextItems).catch(() => {
|
|
||||||
setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") });
|
|
||||||
});
|
|
||||||
setSelectedItems([]);
|
|
||||||
},
|
|
||||||
[library, setAppState, selectedItems, setSelectedItems],
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetLibrary = useCallback(() => {
|
|
||||||
library.resetLibrary();
|
|
||||||
focusContainer();
|
|
||||||
}, [library, focusContainer]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar
|
<LibraryMenuContent
|
||||||
__isInternal
|
pendingElements={getSelectedElements(elements, appState, true)}
|
||||||
// necessary to remount when switching between internal
|
onInsertLibraryItems={(libraryItems) => {
|
||||||
// and custom (host app) sidebar, so that the `props.onClose`
|
onInsertElements(distributeLibraryItemsOnSquareGrid(libraryItems));
|
||||||
// is colled correctly
|
|
||||||
key="library"
|
|
||||||
className="layer-ui__library-sidebar"
|
|
||||||
initialDockedState={appState.isSidebarDocked}
|
|
||||||
onDock={(docked) => {
|
|
||||||
trackEvent(
|
|
||||||
"library",
|
|
||||||
`toggleLibraryDock (${docked ? "dock" : "undock"})`,
|
|
||||||
`sidebar (${device.isMobile ? "mobile" : "desktop"})`,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
ref={ref}
|
onAddToLibrary={deselectItems}
|
||||||
>
|
setAppState={setAppState}
|
||||||
<Sidebar.Header className="layer-ui__library-header">
|
libraryReturnUrl={appProps.libraryReturnUrl}
|
||||||
<LibraryMenuHeader
|
library={library}
|
||||||
appState={appState}
|
id={id}
|
||||||
setAppState={setAppState}
|
appState={appState}
|
||||||
selectedItems={selectedItems}
|
selectedItems={selectedItems}
|
||||||
onSelectItems={setSelectedItems}
|
onSelectItems={setSelectedItems}
|
||||||
library={library}
|
/>
|
||||||
onRemoveFromLibrary={() =>
|
|
||||||
removeFromLibrary(libraryItemsData.libraryItems)
|
|
||||||
}
|
|
||||||
resetLibrary={resetLibrary}
|
|
||||||
/>
|
|
||||||
</Sidebar.Header>
|
|
||||||
<LibraryMenuContent
|
|
||||||
pendingElements={getSelectedElements(elements, appState, true)}
|
|
||||||
onInsertLibraryItems={(libraryItems) => {
|
|
||||||
onInsertElements(distributeLibraryItemsOnSquareGrid(libraryItems));
|
|
||||||
}}
|
|
||||||
onAddToLibrary={deselectItems}
|
|
||||||
setAppState={setAppState}
|
|
||||||
libraryReturnUrl={libraryReturnUrl}
|
|
||||||
library={library}
|
|
||||||
id={id}
|
|
||||||
appState={appState}
|
|
||||||
selectedItems={selectedItems}
|
|
||||||
onSelectItems={setSelectedItems}
|
|
||||||
/>
|
|
||||||
</Sidebar>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { VERSIONS } from "../constants";
|
import { VERSIONS } from "../constants";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { ExcalidrawProps, UIAppState } from "../types";
|
||||||
|
|
||||||
const LibraryMenuBrowseButton = ({
|
const LibraryMenuBrowseButton = ({
|
||||||
theme,
|
theme,
|
||||||
|
@ -8,7 +8,7 @@ const LibraryMenuBrowseButton = ({
|
||||||
libraryReturnUrl,
|
libraryReturnUrl,
|
||||||
}: {
|
}: {
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
theme: AppState["theme"];
|
theme: UIAppState["theme"];
|
||||||
id: string;
|
id: string;
|
||||||
}) => {
|
}) => {
|
||||||
const referrer =
|
const referrer =
|
||||||
|
|
33
src/components/LibraryMenuControlButtons.tsx
Normal file
33
src/components/LibraryMenuControlButtons.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { LibraryItem, ExcalidrawProps, UIAppState } from "../types";
|
||||||
|
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
|
||||||
|
import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent";
|
||||||
|
|
||||||
|
export const LibraryMenuControlButtons = ({
|
||||||
|
selectedItems,
|
||||||
|
onSelectItems,
|
||||||
|
libraryReturnUrl,
|
||||||
|
theme,
|
||||||
|
id,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
selectedItems: LibraryItem["id"][];
|
||||||
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
|
theme: UIAppState["theme"];
|
||||||
|
id: string;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="library-menu-control-buttons" style={style}>
|
||||||
|
<LibraryMenuBrowseButton
|
||||||
|
id={id}
|
||||||
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<LibraryDropdownMenu
|
||||||
|
selectedItems={selectedItems}
|
||||||
|
onSelectItems={onSelectItems}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,8 +1,11 @@
|
||||||
import React, { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import Trans from "./Trans";
|
||||||
|
import { jotaiScope } from "../jotai";
|
||||||
|
import { LibraryItem, LibraryItems, UIAppState } from "../types";
|
||||||
|
import { useApp, useExcalidrawSetAppState } from "./App";
|
||||||
import { saveLibraryAsJSON } from "../data/json";
|
import { saveLibraryAsJSON } from "../data/json";
|
||||||
import Library, { libraryItemsAtom } from "../data/library";
|
import Library, { libraryItemsAtom } from "../data/library";
|
||||||
import { t } from "../i18n";
|
|
||||||
import { AppState, LibraryItem, LibraryItems } from "../types";
|
|
||||||
import {
|
import {
|
||||||
DotsIcon,
|
DotsIcon,
|
||||||
ExportIcon,
|
ExportIcon,
|
||||||
|
@ -13,29 +16,27 @@ import {
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { fileOpen } from "../data/filesystem";
|
import { fileOpen } from "../data/filesystem";
|
||||||
import { muteFSAbortError } from "../utils";
|
import { muteFSAbortError } from "../utils";
|
||||||
import { atom, useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { jotaiScope } from "../jotai";
|
|
||||||
import ConfirmDialog from "./ConfirmDialog";
|
import ConfirmDialog from "./ConfirmDialog";
|
||||||
import PublishLibrary from "./PublishLibrary";
|
import PublishLibrary from "./PublishLibrary";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
|
|
||||||
import DropdownMenu from "./dropdownMenu/DropdownMenu";
|
import DropdownMenu from "./dropdownMenu/DropdownMenu";
|
||||||
|
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||||
export const isLibraryMenuOpenAtom = atom(false);
|
import { useUIAppState } from "../context/ui-appState";
|
||||||
|
|
||||||
const getSelectedItems = (
|
const getSelectedItems = (
|
||||||
libraryItems: LibraryItems,
|
libraryItems: LibraryItems,
|
||||||
selectedItems: LibraryItem["id"][],
|
selectedItems: LibraryItem["id"][],
|
||||||
) => libraryItems.filter((item) => selectedItems.includes(item.id));
|
) => libraryItems.filter((item) => selectedItems.includes(item.id));
|
||||||
|
|
||||||
export const LibraryMenuHeader: React.FC<{
|
export const LibraryDropdownMenuButton: React.FC<{
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
selectedItems: LibraryItem["id"][];
|
selectedItems: LibraryItem["id"][];
|
||||||
library: Library;
|
library: Library;
|
||||||
onRemoveFromLibrary: () => void;
|
onRemoveFromLibrary: () => void;
|
||||||
resetLibrary: () => void;
|
resetLibrary: () => void;
|
||||||
onSelectItems: (items: LibraryItem["id"][]) => void;
|
onSelectItems: (items: LibraryItem["id"][]) => void;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
}> = ({
|
}> = ({
|
||||||
setAppState,
|
setAppState,
|
||||||
selectedItems,
|
selectedItems,
|
||||||
|
@ -50,6 +51,7 @@ export const LibraryMenuHeader: React.FC<{
|
||||||
isLibraryMenuOpenAtom,
|
isLibraryMenuOpenAtom,
|
||||||
jotaiScope,
|
jotaiScope,
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderRemoveLibAlert = useCallback(() => {
|
const renderRemoveLibAlert = useCallback(() => {
|
||||||
const content = selectedItems.length
|
const content = selectedItems.length
|
||||||
? t("alerts.removeItemsFromsLibrary", { count: selectedItems.length })
|
? t("alerts.removeItemsFromsLibrary", { count: selectedItems.length })
|
||||||
|
@ -104,16 +106,19 @@ export const LibraryMenuHeader: React.FC<{
|
||||||
small={true}
|
small={true}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
{t("publishSuccessDialog.content", {
|
<Trans
|
||||||
authorName: publishLibSuccess!.authorName,
|
i18nKey="publishSuccessDialog.content"
|
||||||
})}{" "}
|
authorName={publishLibSuccess!.authorName}
|
||||||
<a
|
link={(el) => (
|
||||||
href={publishLibSuccess?.url}
|
<a
|
||||||
target="_blank"
|
href={publishLibSuccess?.url}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
>
|
rel="noopener noreferrer"
|
||||||
{t("publishSuccessDialog.link")}
|
>
|
||||||
</a>
|
{el}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -181,7 +186,6 @@ export const LibraryMenuHeader: React.FC<{
|
||||||
return (
|
return (
|
||||||
<DropdownMenu open={isLibraryMenuOpen}>
|
<DropdownMenu open={isLibraryMenuOpen}>
|
||||||
<DropdownMenu.Trigger
|
<DropdownMenu.Trigger
|
||||||
className="Sidebar__dropdown-btn"
|
|
||||||
onToggle={() => setIsLibraryMenuOpen(!isLibraryMenuOpen)}
|
onToggle={() => setIsLibraryMenuOpen(!isLibraryMenuOpen)}
|
||||||
>
|
>
|
||||||
{DotsIcon}
|
{DotsIcon}
|
||||||
|
@ -230,6 +234,7 @@ export const LibraryMenuHeader: React.FC<{
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
{renderLibraryMenu()}
|
{renderLibraryMenu()}
|
||||||
|
@ -261,3 +266,48 @@ export const LibraryMenuHeader: React.FC<{
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LibraryDropdownMenu = ({
|
||||||
|
selectedItems,
|
||||||
|
onSelectItems,
|
||||||
|
}: {
|
||||||
|
selectedItems: LibraryItem["id"][];
|
||||||
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
|
}) => {
|
||||||
|
const { library } = useApp();
|
||||||
|
const appState = useUIAppState();
|
||||||
|
const setAppState = useExcalidrawSetAppState();
|
||||||
|
|
||||||
|
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
|
||||||
|
|
||||||
|
const removeFromLibrary = useCallback(
|
||||||
|
async (libraryItems: LibraryItems) => {
|
||||||
|
const nextItems = libraryItems.filter(
|
||||||
|
(item) => !selectedItems.includes(item.id),
|
||||||
|
);
|
||||||
|
library.setLibrary(nextItems).catch(() => {
|
||||||
|
setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") });
|
||||||
|
});
|
||||||
|
onSelectItems([]);
|
||||||
|
},
|
||||||
|
[library, setAppState, selectedItems, onSelectItems],
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetLibrary = useCallback(() => {
|
||||||
|
library.resetLibrary();
|
||||||
|
}, [library]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LibraryDropdownMenuButton
|
||||||
|
appState={appState}
|
||||||
|
setAppState={setAppState}
|
||||||
|
selectedItems={selectedItems}
|
||||||
|
onSelectItems={onSelectItems}
|
||||||
|
library={library}
|
||||||
|
onRemoveFromLibrary={() =>
|
||||||
|
removeFromLibrary(libraryItemsData.libraryItems)
|
||||||
|
}
|
||||||
|
resetLibrary={resetLibrary}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
&__items {
|
&__items {
|
||||||
row-gap: 0.5rem;
|
row-gap: 0.5rem;
|
||||||
padding: var(--container-padding-y) var(--container-padding-x);
|
padding: var(--container-padding-y) 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
|
||||||
&--excal {
|
&--excal {
|
||||||
margin-top: 2.5rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,21 @@ import React, { useState } from "react";
|
||||||
import { serializeLibraryAsJSON } from "../data/json";
|
import { serializeLibraryAsJSON } from "../data/json";
|
||||||
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { AppState, ExcalidrawProps, LibraryItem, LibraryItems } from "../types";
|
import {
|
||||||
|
ExcalidrawProps,
|
||||||
|
LibraryItem,
|
||||||
|
LibraryItems,
|
||||||
|
UIAppState,
|
||||||
|
} from "../types";
|
||||||
import { arrayToMap, chunk } from "../utils";
|
import { arrayToMap, chunk } from "../utils";
|
||||||
import { LibraryUnit } from "./LibraryUnit";
|
import { LibraryUnit } from "./LibraryUnit";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
|
|
||||||
import "./LibraryMenuItems.scss";
|
|
||||||
import { MIME_TYPES } from "../constants";
|
import { MIME_TYPES } from "../constants";
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import { duplicateElements } from "../element/newElement";
|
import { duplicateElements } from "../element/newElement";
|
||||||
|
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
||||||
|
|
||||||
|
import "./LibraryMenuItems.scss";
|
||||||
|
|
||||||
const CELLS_PER_ROW = 4;
|
const CELLS_PER_ROW = 4;
|
||||||
|
|
||||||
|
@ -36,7 +40,7 @@ const LibraryMenuItems = ({
|
||||||
selectedItems: LibraryItem["id"][];
|
selectedItems: LibraryItem["id"][];
|
||||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
theme: AppState["theme"];
|
theme: UIAppState["theme"];
|
||||||
id: string;
|
id: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [lastSelectedItem, setLastSelectedItem] = useState<
|
const [lastSelectedItem, setLastSelectedItem] = useState<
|
||||||
|
@ -201,11 +205,7 @@ const LibraryMenuItems = ({
|
||||||
(item) => item.status === "published",
|
(item) => item.status === "published",
|
||||||
);
|
);
|
||||||
|
|
||||||
const showBtn =
|
const showBtn = !libraryItems.length && !pendingElements.length;
|
||||||
!libraryItems.length &&
|
|
||||||
!unpublishedItems.length &&
|
|
||||||
!publishedItems.length &&
|
|
||||||
!pendingElements.length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -215,7 +215,7 @@ const LibraryMenuItems = ({
|
||||||
unpublishedItems.length ||
|
unpublishedItems.length ||
|
||||||
publishedItems.length
|
publishedItems.length
|
||||||
? { justifyContent: "flex-start" }
|
? { justifyContent: "flex-start" }
|
||||||
: {}
|
: { borderBottom: 0 }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Stack.Col
|
<Stack.Col
|
||||||
|
@ -251,11 +251,7 @@ const LibraryMenuItems = ({
|
||||||
</div>
|
</div>
|
||||||
{!pendingElements.length && !unpublishedItems.length ? (
|
{!pendingElements.length && !unpublishedItems.length ? (
|
||||||
<div className="library-menu-items__no-items">
|
<div className="library-menu-items__no-items">
|
||||||
<div
|
<div className="library-menu-items__no-items__label">
|
||||||
className={clsx({
|
|
||||||
"library-menu-items__no-items__label": showBtn,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t("library.noItems")}
|
{t("library.noItems")}
|
||||||
</div>
|
</div>
|
||||||
<div className="library-menu-items__no-items__hint">
|
<div className="library-menu-items__no-items__hint">
|
||||||
|
@ -303,10 +299,13 @@ const LibraryMenuItems = ({
|
||||||
</>
|
</>
|
||||||
|
|
||||||
{showBtn && (
|
{showBtn && (
|
||||||
<LibraryMenuBrowseButton
|
<LibraryMenuControlButtons
|
||||||
|
style={{ padding: "16px 0", width: "100%" }}
|
||||||
id={id}
|
id={id}
|
||||||
libraryReturnUrl={libraryReturnUrl}
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
selectedItems={selectedItems}
|
||||||
|
onSelectItems={onSelectItems}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { AppState, Device, ExcalidrawProps } from "../types";
|
import { AppState, Device, ExcalidrawProps, UIAppState } from "../types";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
|
@ -13,16 +13,15 @@ import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||||
import { Section } from "./Section";
|
import { Section } from "./Section";
|
||||||
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
||||||
import { LockButton } from "./LockButton";
|
import { LockButton } from "./LockButton";
|
||||||
import { LibraryButton } from "./LibraryButton";
|
|
||||||
import { PenModeButton } from "./PenModeButton";
|
import { PenModeButton } from "./PenModeButton";
|
||||||
import { Stats } from "./Stats";
|
import { Stats } from "./Stats";
|
||||||
import { actionToggleStats } from "../actions";
|
import { actionToggleStats } from "../actions";
|
||||||
import { HandButton } from "./HandButton";
|
import { HandButton } from "./HandButton";
|
||||||
import { isHandToolActive } from "../appState";
|
import { isHandToolActive } from "../appState";
|
||||||
import { useTunnels } from "./context/tunnels";
|
import { useTunnels } from "../context/tunnels";
|
||||||
|
|
||||||
type MobileMenuProps = {
|
type MobileMenuProps = {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
renderJSONExportDialog: () => React.ReactNode;
|
renderJSONExportDialog: () => React.ReactNode;
|
||||||
renderImageExportDialog: () => React.ReactNode;
|
renderImageExportDialog: () => React.ReactNode;
|
||||||
|
@ -36,7 +35,7 @@ type MobileMenuProps = {
|
||||||
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
||||||
renderTopRightUI?: (
|
renderTopRightUI?: (
|
||||||
isMobile: boolean,
|
isMobile: boolean,
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
) => JSX.Element | null;
|
) => JSX.Element | null;
|
||||||
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
||||||
renderSidebars: () => JSX.Element | null;
|
renderSidebars: () => JSX.Element | null;
|
||||||
|
@ -60,11 +59,15 @@ export const MobileMenu = ({
|
||||||
device,
|
device,
|
||||||
renderWelcomeScreen,
|
renderWelcomeScreen,
|
||||||
}: MobileMenuProps) => {
|
}: MobileMenuProps) => {
|
||||||
const { welcomeScreenCenterTunnel, mainMenuTunnel } = useTunnels();
|
const {
|
||||||
|
WelcomeScreenCenterTunnel,
|
||||||
|
MainMenuTunnel,
|
||||||
|
DefaultSidebarTriggerTunnel,
|
||||||
|
} = useTunnels();
|
||||||
const renderToolbar = () => {
|
const renderToolbar = () => {
|
||||||
return (
|
return (
|
||||||
<FixedSideContainer side="top" className="App-top-bar">
|
<FixedSideContainer side="top" className="App-top-bar">
|
||||||
{renderWelcomeScreen && <welcomeScreenCenterTunnel.Out />}
|
{renderWelcomeScreen && <WelcomeScreenCenterTunnel.Out />}
|
||||||
<Section heading="shapes">
|
<Section heading="shapes">
|
||||||
{(heading: React.ReactNode) => (
|
{(heading: React.ReactNode) => (
|
||||||
<Stack.Col gap={4} align="center">
|
<Stack.Col gap={4} align="center">
|
||||||
|
@ -88,11 +91,7 @@ export const MobileMenu = ({
|
||||||
{renderTopRightUI && renderTopRightUI(true, appState)}
|
{renderTopRightUI && renderTopRightUI(true, appState)}
|
||||||
<div className="mobile-misc-tools-container">
|
<div className="mobile-misc-tools-container">
|
||||||
{!appState.viewModeEnabled && (
|
{!appState.viewModeEnabled && (
|
||||||
<LibraryButton
|
<DefaultSidebarTriggerTunnel.Out />
|
||||||
appState={appState}
|
|
||||||
setAppState={setAppState}
|
|
||||||
isMobile
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<PenModeButton
|
<PenModeButton
|
||||||
checked={appState.penMode}
|
checked={appState.penMode}
|
||||||
|
@ -132,14 +131,14 @@ export const MobileMenu = ({
|
||||||
if (appState.viewModeEnabled) {
|
if (appState.viewModeEnabled) {
|
||||||
return (
|
return (
|
||||||
<div className="App-toolbar-content">
|
<div className="App-toolbar-content">
|
||||||
<mainMenuTunnel.Out />
|
<MainMenuTunnel.Out />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App-toolbar-content">
|
<div className="App-toolbar-content">
|
||||||
<mainMenuTunnel.Out />
|
<MainMenuTunnel.Out />
|
||||||
{actionManager.renderAction("toggleEditMenu")}
|
{actionManager.renderAction("toggleEditMenu")}
|
||||||
{actionManager.renderAction("undo")}
|
{actionManager.renderAction("undo")}
|
||||||
{actionManager.renderAction("redo")}
|
{actionManager.renderAction("redo")}
|
||||||
|
@ -190,13 +189,13 @@ export const MobileMenu = ({
|
||||||
{renderAppToolbar()}
|
{renderAppToolbar()}
|
||||||
{appState.scrolledOutside &&
|
{appState.scrolledOutside &&
|
||||||
!appState.openMenu &&
|
!appState.openMenu &&
|
||||||
appState.openSidebar !== "library" && (
|
!appState.openSidebar && (
|
||||||
<button
|
<button
|
||||||
className="scroll-back-to-content"
|
className="scroll-back-to-content"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAppState({
|
setAppState((appState) => ({
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
...calculateScrollCenter(elements, appState, canvas),
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("buttons.scrollBackToContent")}
|
{t("buttons.scrollBackToContent")}
|
||||||
|
|
|
@ -5,8 +5,10 @@ import { ChartElements, renderSpreadsheet, Spreadsheet } from "../charts";
|
||||||
import { ChartType } from "../element/types";
|
import { ChartType } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { exportToSvg } from "../scene/export";
|
import { exportToSvg } from "../scene/export";
|
||||||
import { AppState, LibraryItem } from "../types";
|
import { UIAppState } from "../types";
|
||||||
|
import { useApp } from "./App";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
|
|
||||||
import "./PasteChartDialog.scss";
|
import "./PasteChartDialog.scss";
|
||||||
|
|
||||||
type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void;
|
type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void;
|
||||||
|
@ -78,13 +80,12 @@ export const PasteChartDialog = ({
|
||||||
setAppState,
|
setAppState,
|
||||||
appState,
|
appState,
|
||||||
onClose,
|
onClose,
|
||||||
onInsertChart,
|
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
onInsertChart: (elements: LibraryItem["elements"]) => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const { onInsertElements } = useApp();
|
||||||
const handleClose = React.useCallback(() => {
|
const handleClose = React.useCallback(() => {
|
||||||
if (onClose) {
|
if (onClose) {
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -92,7 +93,7 @@ export const PasteChartDialog = ({
|
||||||
}, [onClose]);
|
}, [onClose]);
|
||||||
|
|
||||||
const handleChartClick = (chartType: ChartType, elements: ChartElements) => {
|
const handleChartClick = (chartType: ChartType, elements: ChartElements) => {
|
||||||
onInsertChart(elements);
|
onInsertElements(elements);
|
||||||
trackEvent("magic", "chart", chartType);
|
trackEvent("magic", "chart", chartType);
|
||||||
setAppState({
|
setAppState({
|
||||||
currentChartType: chartType,
|
currentChartType: chartType,
|
||||||
|
|
|
@ -3,8 +3,9 @@ import OpenColor from "open-color";
|
||||||
|
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import Trans from "./Trans";
|
||||||
|
|
||||||
import { AppState, LibraryItems, LibraryItem } from "../types";
|
import { LibraryItems, LibraryItem, UIAppState } from "../types";
|
||||||
import { exportToCanvas, exportToSvg } from "../packages/utils";
|
import { exportToCanvas, exportToSvg } from "../packages/utils";
|
||||||
import {
|
import {
|
||||||
EXPORT_DATA_TYPES,
|
EXPORT_DATA_TYPES,
|
||||||
|
@ -135,7 +136,7 @@ const SingleLibraryItem = ({
|
||||||
onRemove,
|
onRemove,
|
||||||
}: {
|
}: {
|
||||||
libItem: LibraryItem;
|
libItem: LibraryItem;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
index: number;
|
index: number;
|
||||||
onChange: (val: string, index: number) => void;
|
onChange: (val: string, index: number) => void;
|
||||||
onRemove: (id: string) => void;
|
onRemove: (id: string) => void;
|
||||||
|
@ -231,7 +232,7 @@ const PublishLibrary = ({
|
||||||
}: {
|
}: {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
libraryItems: LibraryItems;
|
libraryItems: LibraryItems;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
onSuccess: (data: {
|
onSuccess: (data: {
|
||||||
url: string;
|
url: string;
|
||||||
authorName: string;
|
authorName: string;
|
||||||
|
@ -402,26 +403,32 @@ const PublishLibrary = ({
|
||||||
{shouldRenderForm ? (
|
{shouldRenderForm ? (
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<div className="publish-library-note">
|
<div className="publish-library-note">
|
||||||
{t("publishDialog.noteDescription.pre")}
|
<Trans
|
||||||
<a
|
i18nKey="publishDialog.noteDescription"
|
||||||
href="https://libraries.excalidraw.com"
|
link={(el) => (
|
||||||
target="_blank"
|
<a
|
||||||
rel="noopener noreferrer"
|
href="https://libraries.excalidraw.com"
|
||||||
>
|
target="_blank"
|
||||||
{t("publishDialog.noteDescription.link")}
|
rel="noopener noreferrer"
|
||||||
</a>{" "}
|
>
|
||||||
{t("publishDialog.noteDescription.post")}
|
{el}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="publish-library-note">
|
<span className="publish-library-note">
|
||||||
{t("publishDialog.noteGuidelines.pre")}
|
<Trans
|
||||||
<a
|
i18nKey="publishDialog.noteGuidelines"
|
||||||
href="https://github.com/excalidraw/excalidraw-libraries#guidelines"
|
link={(el) => (
|
||||||
target="_blank"
|
<a
|
||||||
rel="noopener noreferrer"
|
href="https://github.com/excalidraw/excalidraw-libraries#guidelines"
|
||||||
>
|
target="_blank"
|
||||||
{t("publishDialog.noteGuidelines.link")}
|
rel="noopener noreferrer"
|
||||||
</a>
|
>
|
||||||
{t("publishDialog.noteGuidelines.post")}
|
{el}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="publish-library-note">
|
<div className="publish-library-note">
|
||||||
|
@ -515,15 +522,18 @@ const PublishLibrary = ({
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<span className="publish-library-note">
|
<span className="publish-library-note">
|
||||||
{t("publishDialog.noteLicense.pre")}
|
<Trans
|
||||||
<a
|
i18nKey="publishDialog.noteLicense"
|
||||||
href="https://github.com/excalidraw/excalidraw-libraries/blob/main/LICENSE"
|
link={(el) => (
|
||||||
target="_blank"
|
<a
|
||||||
rel="noopener noreferrer"
|
href="https://github.com/excalidraw/excalidraw-libraries/blob/main/LICENSE"
|
||||||
>
|
target="_blank"
|
||||||
{t("publishDialog.noteLicense.link")}
|
rel="noopener noreferrer"
|
||||||
</a>
|
>
|
||||||
{t("publishDialog.noteLicense.post")}
|
{el}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="publish-library__buttons">
|
<div className="publish-library__buttons">
|
||||||
|
|
|
@ -2,67 +2,26 @@
|
||||||
@import "../../css/variables.module";
|
@import "../../css/variables.module";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.Sidebar {
|
.sidebar {
|
||||||
&__close-btn,
|
display: flex;
|
||||||
&__pin-btn,
|
flex-direction: column;
|
||||||
&__dropdown-btn {
|
|
||||||
@include outlineButtonStyles;
|
|
||||||
width: var(--lg-button-size);
|
|
||||||
height: var(--lg-button-size);
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: var(--lg-icon-size);
|
|
||||||
height: var(--lg-icon-size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__pin-btn {
|
|
||||||
&--pinned {
|
|
||||||
background-color: var(--color-primary);
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:active {
|
|
||||||
background-color: var(--color-primary-darker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.theme--dark {
|
|
||||||
.Sidebar {
|
|
||||||
&__pin-btn {
|
|
||||||
&--pinned {
|
|
||||||
svg {
|
|
||||||
color: var(--color-gray-90);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.layer-ui__sidebar {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
background-color: var(--sidebar-bg-color);
|
||||||
|
box-shadow: var(--sidebar-shadow);
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: auto;
|
right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
background-color: var(--sidebar-bg-color);
|
|
||||||
|
|
||||||
box-shadow: var(--sidebar-shadow);
|
|
||||||
|
|
||||||
&--docked {
|
&--docked {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -77,52 +36,134 @@
|
||||||
border-right: 1px solid var(--sidebar-border-color);
|
border-right: 1px solid var(--sidebar-border-color);
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.Island {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ToolIcon__icon {
|
|
||||||
border-radius: var(--border-radius-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ToolIcon__icon__close {
|
|
||||||
.Modal__close {
|
|
||||||
width: calc(var(--space-factor) * 7);
|
|
||||||
height: calc(var(--space-factor) * 7);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.Island {
|
|
||||||
--padding: 0;
|
|
||||||
background-color: var(--island-bg-color);
|
|
||||||
border-radius: var(--border-radius-lg);
|
|
||||||
padding: calc(var(--padding) * var(--space-factor));
|
|
||||||
position: relative;
|
|
||||||
transition: box-shadow 0.5s ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-ui__sidebar__header {
|
// ---------------------------- sidebar header ------------------------------
|
||||||
|
|
||||||
|
.sidebar__header {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 1rem;
|
padding-top: 1rem;
|
||||||
border-bottom: 1px solid var(--sidebar-border-color);
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-ui__sidebar__header__buttons {
|
.sidebar__header__buttons {
|
||||||
|
gap: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.625rem;
|
margin-left: auto;
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include outlineButtonStyles;
|
||||||
|
--button-bg: transparent;
|
||||||
|
border: 0 !important;
|
||||||
|
|
||||||
|
width: var(--lg-button-size);
|
||||||
|
height: var(--lg-button-size);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: var(--lg-icon-size);
|
||||||
|
height: var(--lg-icon-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--button-hover-bg, var(--island-bg-color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__dock.selected {
|
||||||
|
svg {
|
||||||
|
stroke: var(--color-primary);
|
||||||
|
fill: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------- sidebar tabs ------------------------------
|
||||||
|
|
||||||
|
.sidebar-tabs-root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding: 1rem 0.75rem;
|
||||||
|
|
||||||
|
[role="tabpanel"] {
|
||||||
|
flex: 1;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="tabpanel"][data-state="inactive"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="tablist"] {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tabs-root > .sidebar__header {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tab-trigger {
|
||||||
|
--button-width: auto;
|
||||||
|
--button-bg: transparent;
|
||||||
|
--button-hover-bg: transparent;
|
||||||
|
--button-active-bg: var(--color-primary);
|
||||||
|
--button-hover-color: var(--color-primary);
|
||||||
|
--button-hover-border: var(--color-primary);
|
||||||
|
|
||||||
|
&[data-state="active"] {
|
||||||
|
--button-bg: var(--color-primary);
|
||||||
|
--button-hover-bg: var(--color-primary-darker);
|
||||||
|
--button-hover-color: var(--color-icon-white);
|
||||||
|
--button-border: var(--color-primary);
|
||||||
|
color: var(--color-icon-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------- default sidebar ------------------------------
|
||||||
|
|
||||||
|
.default-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.sidebar-triggers {
|
||||||
|
$padding: 2px;
|
||||||
|
$border: 1px;
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
padding: $padding;
|
||||||
|
// offset by padding + border to vertically center the list with sibling
|
||||||
|
// buttons (both from top and bototm, due to flex layout)
|
||||||
|
margin-top: -#{$padding + $border};
|
||||||
|
margin-bottom: -#{$padding + $border};
|
||||||
|
border: $border solid var(--sidebar-border-color);
|
||||||
|
background: var(--default-bg-color);
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
|
||||||
|
.sidebar-tab-trigger {
|
||||||
|
height: var(--lg-button-size);
|
||||||
|
width: var(--lg-button-size);
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__header {
|
||||||
|
border-bottom: 1px solid var(--sidebar-border-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { DEFAULT_SIDEBAR } from "../../constants";
|
||||||
import { Excalidraw, Sidebar } from "../../packages/excalidraw/index";
|
import { Excalidraw, Sidebar } from "../../packages/excalidraw/index";
|
||||||
import {
|
import {
|
||||||
act,
|
|
||||||
fireEvent,
|
fireEvent,
|
||||||
|
GlobalTestState,
|
||||||
queryAllByTestId,
|
queryAllByTestId,
|
||||||
queryByTestId,
|
queryByTestId,
|
||||||
render,
|
render,
|
||||||
|
@ -10,346 +11,321 @@ import {
|
||||||
withExcalidrawDimensions,
|
withExcalidrawDimensions,
|
||||||
} from "../../tests/test-utils";
|
} from "../../tests/test-utils";
|
||||||
|
|
||||||
|
export const assertSidebarDockButton = async <T extends boolean>(
|
||||||
|
hasDockButton: T,
|
||||||
|
): Promise<
|
||||||
|
T extends false
|
||||||
|
? { dockButton: null; sidebar: HTMLElement }
|
||||||
|
: { dockButton: HTMLElement; sidebar: HTMLElement }
|
||||||
|
> => {
|
||||||
|
const sidebar =
|
||||||
|
GlobalTestState.renderResult.container.querySelector<HTMLElement>(
|
||||||
|
".sidebar",
|
||||||
|
);
|
||||||
|
expect(sidebar).not.toBe(null);
|
||||||
|
const dockButton = queryByTestId(sidebar!, "sidebar-dock");
|
||||||
|
if (hasDockButton) {
|
||||||
|
expect(dockButton).not.toBe(null);
|
||||||
|
return { dockButton: dockButton!, sidebar: sidebar! } as any;
|
||||||
|
}
|
||||||
|
expect(dockButton).toBe(null);
|
||||||
|
return { dockButton: null, sidebar: sidebar! } as any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const assertExcalidrawWithSidebar = async (
|
||||||
|
sidebar: React.ReactNode,
|
||||||
|
name: string,
|
||||||
|
test: () => void,
|
||||||
|
) => {
|
||||||
|
await render(
|
||||||
|
<Excalidraw initialData={{ appState: { openSidebar: { name } } }}>
|
||||||
|
{sidebar}
|
||||||
|
</Excalidraw>,
|
||||||
|
);
|
||||||
|
await withExcalidrawDimensions({ width: 1920, height: 1080 }, test);
|
||||||
|
};
|
||||||
|
|
||||||
describe("Sidebar", () => {
|
describe("Sidebar", () => {
|
||||||
it("should render custom sidebar", async () => {
|
describe("General behavior", () => {
|
||||||
const { container } = await render(
|
it("should render custom sidebar", async () => {
|
||||||
<Excalidraw
|
const { container } = await render(
|
||||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
<Excalidraw
|
||||||
renderSidebar={() => (
|
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||||
<Sidebar>
|
>
|
||||||
|
<Sidebar name="customSidebar">
|
||||||
<div id="test-sidebar-content">42</div>
|
<div id="test-sidebar-content">42</div>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)}
|
</Excalidraw>,
|
||||||
/>,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const node = container.querySelector("#test-sidebar-content");
|
const node = container.querySelector("#test-sidebar-content");
|
||||||
expect(node).not.toBe(null);
|
expect(node).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render only one sidebar and prefer the custom one", async () => {
|
||||||
|
const { container } = await render(
|
||||||
|
<Excalidraw
|
||||||
|
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||||
|
>
|
||||||
|
<Sidebar name="customSidebar">
|
||||||
|
<div id="test-sidebar-content">42</div>
|
||||||
|
</Sidebar>
|
||||||
|
</Excalidraw>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
// make sure the custom sidebar is rendered
|
||||||
|
const node = container.querySelector("#test-sidebar-content");
|
||||||
|
expect(node).not.toBe(null);
|
||||||
|
|
||||||
|
// make sure only one sidebar is rendered
|
||||||
|
const sidebars = container.querySelectorAll(".sidebar");
|
||||||
|
expect(sidebars.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should toggle sidebar using props.toggleMenu()", async () => {
|
||||||
|
const { container } = await render(
|
||||||
|
<Excalidraw>
|
||||||
|
<Sidebar name="customSidebar">
|
||||||
|
<div id="test-sidebar-content">42</div>
|
||||||
|
</Sidebar>
|
||||||
|
</Excalidraw>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// sidebar isn't rendered initially
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
await waitFor(() => {
|
||||||
|
const node = container.querySelector("#test-sidebar-content");
|
||||||
|
expect(node).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// toggle sidebar on
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
expect(window.h.app.toggleSidebar({ name: "customSidebar" })).toBe(true);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const node = container.querySelector("#test-sidebar-content");
|
||||||
|
expect(node).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// toggle sidebar off
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
expect(window.h.app.toggleSidebar({ name: "customSidebar" })).toBe(false);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const node = container.querySelector("#test-sidebar-content");
|
||||||
|
expect(node).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// force-toggle sidebar off (=> still hidden)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
expect(
|
||||||
|
window.h.app.toggleSidebar({ name: "customSidebar", force: false }),
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const node = container.querySelector("#test-sidebar-content");
|
||||||
|
expect(node).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// force-toggle sidebar on
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
expect(
|
||||||
|
window.h.app.toggleSidebar({ name: "customSidebar", force: true }),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
window.h.app.toggleSidebar({ name: "customSidebar", force: true }),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const node = container.querySelector("#test-sidebar-content");
|
||||||
|
expect(node).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// toggle library (= hide custom sidebar)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
expect(window.h.app.toggleSidebar({ name: DEFAULT_SIDEBAR.name })).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const node = container.querySelector("#test-sidebar-content");
|
||||||
|
expect(node).toBe(null);
|
||||||
|
|
||||||
|
// make sure only one sidebar is rendered
|
||||||
|
const sidebars = container.querySelectorAll(".sidebar");
|
||||||
|
expect(sidebars.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render custom sidebar header", async () => {
|
describe("<Sidebar.Header/>", () => {
|
||||||
const { container } = await render(
|
it("should render custom sidebar header", async () => {
|
||||||
<Excalidraw
|
const { container } = await render(
|
||||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
<Excalidraw
|
||||||
renderSidebar={() => (
|
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||||
<Sidebar>
|
>
|
||||||
|
<Sidebar name="customSidebar">
|
||||||
<Sidebar.Header>
|
<Sidebar.Header>
|
||||||
<div id="test-sidebar-header-content">42</div>
|
<div id="test-sidebar-header-content">42</div>
|
||||||
</Sidebar.Header>
|
</Sidebar.Header>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)}
|
</Excalidraw>,
|
||||||
/>,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const node = container.querySelector("#test-sidebar-header-content");
|
const node = container.querySelector("#test-sidebar-header-content");
|
||||||
expect(node).not.toBe(null);
|
|
||||||
// make sure we don't render the default fallback header,
|
|
||||||
// just the custom one
|
|
||||||
expect(queryAllByTestId(container, "sidebar-header").length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render only one sidebar and prefer the custom one", async () => {
|
|
||||||
const { container } = await render(
|
|
||||||
<Excalidraw
|
|
||||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
|
||||||
renderSidebar={() => (
|
|
||||||
<Sidebar>
|
|
||||||
<div id="test-sidebar-content">42</div>
|
|
||||||
</Sidebar>
|
|
||||||
)}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
// make sure the custom sidebar is rendered
|
|
||||||
const node = container.querySelector("#test-sidebar-content");
|
|
||||||
expect(node).not.toBe(null);
|
expect(node).not.toBe(null);
|
||||||
|
// make sure we don't render the default fallback header,
|
||||||
// make sure only one sidebar is rendered
|
// just the custom one
|
||||||
const sidebars = container.querySelectorAll(".layer-ui__sidebar");
|
expect(queryAllByTestId(container, "sidebar-header").length).toBe(1);
|
||||||
expect(sidebars.length).toBe(1);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it("should always render custom sidebar with close button & close on click", async () => {
|
it("should not render <Sidebar.Header> for custom sidebars by default", async () => {
|
||||||
const onClose = jest.fn();
|
const CustomExcalidraw = () => {
|
||||||
const CustomExcalidraw = () => {
|
return (
|
||||||
return (
|
<Excalidraw
|
||||||
<Excalidraw
|
initialData={{
|
||||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
appState: { openSidebar: { name: "customSidebar" } },
|
||||||
renderSidebar={() => (
|
}}
|
||||||
<Sidebar className="test-sidebar" onClose={onClose}>
|
>
|
||||||
|
<Sidebar name="customSidebar" className="test-sidebar">
|
||||||
hello
|
hello
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)}
|
</Excalidraw>
|
||||||
/>
|
);
|
||||||
);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const { container } = await render(<CustomExcalidraw />);
|
const { container } = await render(<CustomExcalidraw />);
|
||||||
|
|
||||||
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
|
||||||
expect(sidebar).not.toBe(null);
|
|
||||||
const closeButton = queryByTestId(sidebar!, "sidebar-close")!;
|
|
||||||
expect(closeButton).not.toBe(null);
|
|
||||||
|
|
||||||
fireEvent.click(closeButton);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(container.querySelector<HTMLElement>(".test-sidebar")).toBe(null);
|
|
||||||
expect(onClose).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render custom sidebar with dock (irrespective of onDock prop)", async () => {
|
|
||||||
const CustomExcalidraw = () => {
|
|
||||||
return (
|
|
||||||
<Excalidraw
|
|
||||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
|
||||||
renderSidebar={() => (
|
|
||||||
<Sidebar className="test-sidebar">hello</Sidebar>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { container } = await render(<CustomExcalidraw />);
|
|
||||||
|
|
||||||
// should show dock button when the sidebar fits to be docked
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
await withExcalidrawDimensions({ width: 1920, height: 1080 }, () => {
|
|
||||||
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
||||||
expect(sidebar).not.toBe(null);
|
expect(sidebar).not.toBe(null);
|
||||||
const closeButton = queryByTestId(sidebar!, "sidebar-dock");
|
const closeButton = queryByTestId(sidebar!, "sidebar-close");
|
||||||
expect(closeButton).not.toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
// should not show dock button when the sidebar does not fit to be docked
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
await withExcalidrawDimensions({ width: 400, height: 1080 }, () => {
|
|
||||||
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
|
||||||
expect(sidebar).not.toBe(null);
|
|
||||||
const closeButton = queryByTestId(sidebar!, "sidebar-dock");
|
|
||||||
expect(closeButton).toBe(null);
|
expect(closeButton).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it("should support controlled docking", async () => {
|
it("<Sidebar.Header> should render close button", async () => {
|
||||||
let _setDockable: (dockable: boolean) => void = null!;
|
const onStateChange = jest.fn();
|
||||||
|
const CustomExcalidraw = () => {
|
||||||
const CustomExcalidraw = () => {
|
return (
|
||||||
const [dockable, setDockable] = React.useState(false);
|
<Excalidraw
|
||||||
_setDockable = setDockable;
|
initialData={{
|
||||||
return (
|
appState: { openSidebar: { name: "customSidebar" } },
|
||||||
<Excalidraw
|
}}
|
||||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
>
|
||||||
renderSidebar={() => (
|
|
||||||
<Sidebar
|
<Sidebar
|
||||||
|
name="customSidebar"
|
||||||
className="test-sidebar"
|
className="test-sidebar"
|
||||||
docked={false}
|
onStateChange={onStateChange}
|
||||||
dockable={dockable}
|
|
||||||
>
|
>
|
||||||
hello
|
<Sidebar.Header />
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)}
|
</Excalidraw>
|
||||||
/>
|
);
|
||||||
);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const { container } = await render(<CustomExcalidraw />);
|
const { container } = await render(<CustomExcalidraw />);
|
||||||
|
|
||||||
await withExcalidrawDimensions({ width: 1920, height: 1080 }, async () => {
|
// initial open
|
||||||
// should not show dock button when `dockable` is `false`
|
expect(onStateChange).toHaveBeenCalledWith({ name: "customSidebar" });
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
act(() => {
|
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
||||||
_setDockable(false);
|
expect(sidebar).not.toBe(null);
|
||||||
});
|
const closeButton = queryByTestId(sidebar!, "sidebar-close")!;
|
||||||
|
expect(closeButton).not.toBe(null);
|
||||||
|
|
||||||
|
fireEvent.click(closeButton);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
expect(container.querySelector<HTMLElement>(".test-sidebar")).toBe(
|
||||||
expect(sidebar).not.toBe(null);
|
null,
|
||||||
const closeButton = queryByTestId(sidebar!, "sidebar-dock");
|
);
|
||||||
expect(closeButton).toBe(null);
|
expect(onStateChange).toHaveBeenCalledWith(null);
|
||||||
});
|
|
||||||
|
|
||||||
// should show dock button when `dockable` is `true`, even if `docked`
|
|
||||||
// prop is set
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
_setDockable(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
|
||||||
expect(sidebar).not.toBe(null);
|
|
||||||
const closeButton = queryByTestId(sidebar!, "sidebar-dock");
|
|
||||||
expect(closeButton).not.toBe(null);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should support controlled docking", async () => {
|
describe("Docking behavior", () => {
|
||||||
let _setDocked: (docked?: boolean) => void = null!;
|
it("shouldn't be user-dockable if `onDock` not supplied", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<Sidebar name="customSidebar">
|
||||||
|
<Sidebar.Header />
|
||||||
|
</Sidebar>,
|
||||||
|
"customSidebar",
|
||||||
|
async () => {
|
||||||
|
await assertSidebarDockButton(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const CustomExcalidraw = () => {
|
it("shouldn't be user-dockable if `onDock` not supplied & `docked={true}`", async () => {
|
||||||
const [docked, setDocked] = React.useState<boolean | undefined>();
|
await assertExcalidrawWithSidebar(
|
||||||
_setDocked = setDocked;
|
<Sidebar name="customSidebar" docked={true}>
|
||||||
return (
|
<Sidebar.Header />
|
||||||
|
</Sidebar>,
|
||||||
|
"customSidebar",
|
||||||
|
async () => {
|
||||||
|
await assertSidebarDockButton(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't be user-dockable if `onDock` not supplied & docked={false}`", async () => {
|
||||||
|
await assertExcalidrawWithSidebar(
|
||||||
|
<Sidebar name="customSidebar" docked={false}>
|
||||||
|
<Sidebar.Header />
|
||||||
|
</Sidebar>,
|
||||||
|
"customSidebar",
|
||||||
|
async () => {
|
||||||
|
await assertSidebarDockButton(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be user-dockable when both `onDock` and `docked` supplied", async () => {
|
||||||
|
await render(
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||||
renderSidebar={() => (
|
>
|
||||||
<Sidebar className="test-sidebar" docked={docked}>
|
<Sidebar
|
||||||
hello
|
name="customSidebar"
|
||||||
</Sidebar>
|
className="test-sidebar"
|
||||||
)}
|
onDock={() => {}}
|
||||||
/>
|
docked
|
||||||
);
|
>
|
||||||
};
|
<Sidebar.Header />
|
||||||
|
|
||||||
const { container } = await render(<CustomExcalidraw />);
|
|
||||||
|
|
||||||
const { h } = window;
|
|
||||||
|
|
||||||
await withExcalidrawDimensions({ width: 1920, height: 1080 }, async () => {
|
|
||||||
const dockButton = await waitFor(() => {
|
|
||||||
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
|
||||||
expect(sidebar).not.toBe(null);
|
|
||||||
const dockBotton = queryByTestId(sidebar!, "sidebar-dock");
|
|
||||||
expect(dockBotton).not.toBe(null);
|
|
||||||
return dockBotton!;
|
|
||||||
});
|
|
||||||
|
|
||||||
const dockButtonInput = dockButton.querySelector("input")!;
|
|
||||||
|
|
||||||
// should not show dock button when `dockable` is `false`
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
expect(h.state.isSidebarDocked).toBe(false);
|
|
||||||
|
|
||||||
fireEvent.click(dockButtonInput);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.isSidebarDocked).toBe(true);
|
|
||||||
expect(dockButtonInput).toBeChecked();
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.click(dockButtonInput);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.isSidebarDocked).toBe(false);
|
|
||||||
expect(dockButtonInput).not.toBeChecked();
|
|
||||||
});
|
|
||||||
|
|
||||||
// shouldn't update `appState.isSidebarDocked` when the sidebar
|
|
||||||
// is controlled (`docked` prop is set), as host apps should handle
|
|
||||||
// the state themselves
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
_setDocked(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(dockButtonInput).toBeChecked();
|
|
||||||
expect(h.state.isSidebarDocked).toBe(false);
|
|
||||||
expect(dockButtonInput).toBeChecked();
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.click(dockButtonInput);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.isSidebarDocked).toBe(false);
|
|
||||||
expect(dockButtonInput).toBeChecked();
|
|
||||||
});
|
|
||||||
|
|
||||||
// the `appState.isSidebarDocked` should remain untouched when
|
|
||||||
// `props.docked` is set to `false`, and user toggles
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
_setDocked(false);
|
|
||||||
h.setState({ isSidebarDocked: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.isSidebarDocked).toBe(true);
|
|
||||||
expect(dockButtonInput).not.toBeChecked();
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.click(dockButtonInput);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(dockButtonInput).not.toBeChecked();
|
|
||||||
expect(h.state.isSidebarDocked).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should toggle sidebar using props.toggleMenu()", async () => {
|
|
||||||
const { container } = await render(
|
|
||||||
<Excalidraw
|
|
||||||
renderSidebar={() => (
|
|
||||||
<Sidebar>
|
|
||||||
<div id="test-sidebar-content">42</div>
|
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)}
|
</Excalidraw>,
|
||||||
/>,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// sidebar isn't rendered initially
|
await withExcalidrawDimensions(
|
||||||
// -------------------------------------------------------------------------
|
{ width: 1920, height: 1080 },
|
||||||
await waitFor(() => {
|
async () => {
|
||||||
const node = container.querySelector("#test-sidebar-content");
|
await assertSidebarDockButton(true);
|
||||||
expect(node).toBe(null);
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// toggle sidebar on
|
it("shouldn't be user-dockable when only `onDock` supplied w/o `docked`", async () => {
|
||||||
// -------------------------------------------------------------------------
|
await render(
|
||||||
expect(window.h.app.toggleMenu("customSidebar")).toBe(true);
|
<Excalidraw
|
||||||
|
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||||
|
>
|
||||||
|
<Sidebar
|
||||||
|
name="customSidebar"
|
||||||
|
className="test-sidebar"
|
||||||
|
onDock={() => {}}
|
||||||
|
>
|
||||||
|
<Sidebar.Header />
|
||||||
|
</Sidebar>
|
||||||
|
</Excalidraw>,
|
||||||
|
);
|
||||||
|
|
||||||
await waitFor(() => {
|
await withExcalidrawDimensions(
|
||||||
const node = container.querySelector("#test-sidebar-content");
|
{ width: 1920, height: 1080 },
|
||||||
expect(node).not.toBe(null);
|
async () => {
|
||||||
});
|
await assertSidebarDockButton(false);
|
||||||
|
},
|
||||||
// toggle sidebar off
|
);
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
expect(window.h.app.toggleMenu("customSidebar")).toBe(false);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const node = container.querySelector("#test-sidebar-content");
|
|
||||||
expect(node).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
// force-toggle sidebar off (=> still hidden)
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
expect(window.h.app.toggleMenu("customSidebar", false)).toBe(false);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const node = container.querySelector("#test-sidebar-content");
|
|
||||||
expect(node).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
// force-toggle sidebar on
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
expect(window.h.app.toggleMenu("customSidebar", true)).toBe(true);
|
|
||||||
expect(window.h.app.toggleMenu("customSidebar", true)).toBe(true);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const node = container.querySelector("#test-sidebar-content");
|
|
||||||
expect(node).not.toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
// toggle library (= hide custom sidebar)
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
expect(window.h.app.toggleMenu("library")).toBe(true);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const node = container.querySelector("#test-sidebar-content");
|
|
||||||
expect(node).toBe(null);
|
|
||||||
|
|
||||||
// make sure only one sidebar is rendered
|
|
||||||
const sidebars = container.querySelectorAll(".layer-ui__sidebar");
|
|
||||||
expect(sidebars.length).toBe(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,151 +1,246 @@
|
||||||
import {
|
import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
useImperativeHandle,
|
||||||
|
useCallback,
|
||||||
|
RefObject,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Island } from ".././Island";
|
import { Island } from ".././Island";
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useSetAtom } from "jotai";
|
||||||
import { jotaiScope } from "../../jotai";
|
import { jotaiScope } from "../../jotai";
|
||||||
import {
|
import {
|
||||||
SidebarPropsContext,
|
SidebarPropsContext,
|
||||||
SidebarProps,
|
SidebarProps,
|
||||||
SidebarPropsContextValue,
|
SidebarPropsContextValue,
|
||||||
} from "./common";
|
} from "./common";
|
||||||
|
import { SidebarHeader } from "./SidebarHeader";
|
||||||
import { SidebarHeaderComponents } from "./SidebarHeader";
|
import clsx from "clsx";
|
||||||
|
import { useDevice, useExcalidrawSetAppState } from "../App";
|
||||||
|
import { updateObject } from "../../utils";
|
||||||
|
import { KEYS } from "../../keys";
|
||||||
|
import { EVENT } from "../../constants";
|
||||||
|
import { SidebarTrigger } from "./SidebarTrigger";
|
||||||
|
import { SidebarTabTriggers } from "./SidebarTabTriggers";
|
||||||
|
import { SidebarTabTrigger } from "./SidebarTabTrigger";
|
||||||
|
import { SidebarTabs } from "./SidebarTabs";
|
||||||
|
import { SidebarTab } from "./SidebarTab";
|
||||||
|
|
||||||
import "./Sidebar.scss";
|
import "./Sidebar.scss";
|
||||||
import clsx from "clsx";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
import { useExcalidrawSetAppState } from "../App";
|
|
||||||
import { updateObject } from "../../utils";
|
|
||||||
|
|
||||||
/** using a counter instead of boolean to handle race conditions where
|
// FIXME replace this with the implem from ColorPicker once it's merged
|
||||||
* the host app may render (mount/unmount) multiple different sidebar */
|
const useOnClickOutside = (
|
||||||
export const hostSidebarCountersAtom = atom({ rendered: 0, docked: 0 });
|
ref: RefObject<HTMLElement>,
|
||||||
|
cb: (event: MouseEvent) => void,
|
||||||
export const Sidebar = Object.assign(
|
) => {
|
||||||
forwardRef(
|
useEffect(() => {
|
||||||
(
|
const listener = (event: MouseEvent) => {
|
||||||
{
|
if (!ref.current) {
|
||||||
children,
|
return;
|
||||||
onClose,
|
|
||||||
onDock,
|
|
||||||
docked,
|
|
||||||
/** Undocumented, may be removed later. Generally should either be
|
|
||||||
* `props.docked` or `appState.isSidebarDocked`. Currently serves to
|
|
||||||
* prevent unwanted animation of the shadow if initially docked. */
|
|
||||||
//
|
|
||||||
// NOTE we'll want to remove this after we sort out how to subscribe to
|
|
||||||
// individual appState properties
|
|
||||||
initialDockedState = docked,
|
|
||||||
dockable = true,
|
|
||||||
className,
|
|
||||||
__isInternal,
|
|
||||||
}: SidebarProps<{
|
|
||||||
// NOTE sidebars we use internally inside the editor must have this flag set.
|
|
||||||
// It indicates that this sidebar should have lower precedence over host
|
|
||||||
// sidebars, if both are open.
|
|
||||||
/** @private internal */
|
|
||||||
__isInternal?: boolean;
|
|
||||||
}>,
|
|
||||||
ref: React.ForwardedRef<HTMLDivElement>,
|
|
||||||
) => {
|
|
||||||
const [hostSidebarCounters, setHostSidebarCounters] = useAtom(
|
|
||||||
hostSidebarCountersAtom,
|
|
||||||
jotaiScope,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setAppState = useExcalidrawSetAppState();
|
|
||||||
|
|
||||||
const [isDockedFallback, setIsDockedFallback] = useState(
|
|
||||||
docked ?? initialDockedState ?? false,
|
|
||||||
);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (docked === undefined) {
|
|
||||||
// ugly hack to get initial state out of AppState without subscribing
|
|
||||||
// to it as a whole (once we have granular subscriptions, we'll move
|
|
||||||
// to that)
|
|
||||||
//
|
|
||||||
// NOTE this means that is updated `state.isSidebarDocked` changes outside
|
|
||||||
// of this compoent, it won't be reflected here. Currently doesn't happen.
|
|
||||||
setAppState((state) => {
|
|
||||||
setIsDockedFallback(state.isSidebarDocked);
|
|
||||||
// bail from update
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [setAppState, docked]);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (!__isInternal) {
|
|
||||||
setHostSidebarCounters((s) => ({
|
|
||||||
rendered: s.rendered + 1,
|
|
||||||
docked: isDockedFallback ? s.docked + 1 : s.docked,
|
|
||||||
}));
|
|
||||||
return () => {
|
|
||||||
setHostSidebarCounters((s) => ({
|
|
||||||
rendered: s.rendered - 1,
|
|
||||||
docked: isDockedFallback ? s.docked - 1 : s.docked,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [__isInternal, setHostSidebarCounters, isDockedFallback]);
|
|
||||||
|
|
||||||
const onCloseRef = useRef(onClose);
|
|
||||||
onCloseRef.current = onClose;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
onCloseRef.current?.();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const headerPropsRef = useRef<SidebarPropsContextValue>({});
|
|
||||||
headerPropsRef.current.onClose = () => {
|
|
||||||
setAppState({ openSidebar: null });
|
|
||||||
};
|
|
||||||
headerPropsRef.current.onDock = (isDocked) => {
|
|
||||||
if (docked === undefined) {
|
|
||||||
setAppState({ isSidebarDocked: isDocked });
|
|
||||||
setIsDockedFallback(isDocked);
|
|
||||||
}
|
|
||||||
onDock?.(isDocked);
|
|
||||||
};
|
|
||||||
// renew the ref object if the following props change since we want to
|
|
||||||
// rerender. We can't pass down as component props manually because
|
|
||||||
// the <Sidebar.Header/> can be rendered upsream.
|
|
||||||
headerPropsRef.current = updateObject(headerPropsRef.current, {
|
|
||||||
docked: docked ?? isDockedFallback,
|
|
||||||
dockable,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hostSidebarCounters.rendered > 0 && __isInternal) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (
|
||||||
<Island
|
event.target instanceof Element &&
|
||||||
className={clsx(
|
(ref.current.contains(event.target) ||
|
||||||
"layer-ui__sidebar",
|
!document.body.contains(event.target))
|
||||||
{ "layer-ui__sidebar--docked": isDockedFallback },
|
) {
|
||||||
className,
|
return;
|
||||||
)}
|
}
|
||||||
ref={ref}
|
|
||||||
>
|
cb(event);
|
||||||
<SidebarPropsContext.Provider value={headerPropsRef.current}>
|
};
|
||||||
<SidebarHeaderComponents.Context>
|
document.addEventListener("pointerdown", listener, false);
|
||||||
<SidebarHeaderComponents.Component __isFallback />
|
|
||||||
{children}
|
return () => {
|
||||||
</SidebarHeaderComponents.Context>
|
document.removeEventListener("pointerdown", listener);
|
||||||
</SidebarPropsContext.Provider>
|
};
|
||||||
</Island>
|
}, [ref, cb]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags whether the currently rendered Sidebar is docked or not, for use
|
||||||
|
* in upstream components that need to act on this (e.g. LayerUI to shift the
|
||||||
|
* UI). We use an atom because of potential host app sidebars (for the default
|
||||||
|
* sidebar we could just read from appState.defaultSidebarDockedPreference).
|
||||||
|
*
|
||||||
|
* Since we can only render one Sidebar at a time, we can use a simple flag.
|
||||||
|
*/
|
||||||
|
export const isSidebarDockedAtom = atom(false);
|
||||||
|
|
||||||
|
export const SidebarInner = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
children,
|
||||||
|
onDock,
|
||||||
|
docked,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}: SidebarProps & Omit<React.RefAttributes<HTMLDivElement>, "onSelect">,
|
||||||
|
ref: React.ForwardedRef<HTMLDivElement>,
|
||||||
|
) => {
|
||||||
|
if (process.env.NODE_ENV === "development" && onDock && docked == null) {
|
||||||
|
console.warn(
|
||||||
|
"Sidebar: `docked` must be set when `onDock` is supplied for the sidebar to be user-dockable. To hide this message, either pass `docked` or remove `onDock`",
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
),
|
|
||||||
{
|
const setAppState = useExcalidrawSetAppState();
|
||||||
Header: SidebarHeaderComponents.Component,
|
|
||||||
|
const setIsSidebarDockedAtom = useSetAtom(isSidebarDockedAtom, jotaiScope);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setIsSidebarDockedAtom(!!docked);
|
||||||
|
return () => {
|
||||||
|
setIsSidebarDockedAtom(false);
|
||||||
|
};
|
||||||
|
}, [setIsSidebarDockedAtom, docked]);
|
||||||
|
|
||||||
|
const headerPropsRef = useRef<SidebarPropsContextValue>(
|
||||||
|
{} as SidebarPropsContextValue,
|
||||||
|
);
|
||||||
|
headerPropsRef.current.onCloseRequest = () => {
|
||||||
|
setAppState({ openSidebar: null });
|
||||||
|
};
|
||||||
|
headerPropsRef.current.onDock = (isDocked) => onDock?.(isDocked);
|
||||||
|
// renew the ref object if the following props change since we want to
|
||||||
|
// rerender. We can't pass down as component props manually because
|
||||||
|
// the <Sidebar.Header/> can be rendered upstream.
|
||||||
|
headerPropsRef.current = updateObject(headerPropsRef.current, {
|
||||||
|
docked,
|
||||||
|
// explicit prop to rerender on update
|
||||||
|
shouldRenderDockButton: !!onDock && docked != null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const islandRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => {
|
||||||
|
return islandRef.current!;
|
||||||
|
});
|
||||||
|
|
||||||
|
const device = useDevice();
|
||||||
|
|
||||||
|
const closeLibrary = useCallback(() => {
|
||||||
|
const isDialogOpen = !!document.querySelector(".Dialog");
|
||||||
|
|
||||||
|
// Prevent closing if any dialog is open
|
||||||
|
if (isDialogOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAppState({ openSidebar: null });
|
||||||
|
}, [setAppState]);
|
||||||
|
|
||||||
|
useOnClickOutside(
|
||||||
|
islandRef,
|
||||||
|
useCallback(
|
||||||
|
(event) => {
|
||||||
|
// If click on the library icon, do nothing so that LibraryButton
|
||||||
|
// can toggle library menu
|
||||||
|
if ((event.target as Element).closest(".sidebar-trigger")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!docked || !device.canDeviceFitSidebar) {
|
||||||
|
closeLibrary();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[closeLibrary, docked, device.canDeviceFitSidebar],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (
|
||||||
|
event.key === KEYS.ESCAPE &&
|
||||||
|
(!docked || !device.canDeviceFitSidebar)
|
||||||
|
) {
|
||||||
|
closeLibrary();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener(EVENT.KEYDOWN, handleKeyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener(EVENT.KEYDOWN, handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [closeLibrary, docked, device.canDeviceFitSidebar]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Island
|
||||||
|
{...rest}
|
||||||
|
className={clsx("sidebar", { "sidebar--docked": docked }, className)}
|
||||||
|
ref={islandRef}
|
||||||
|
>
|
||||||
|
<SidebarPropsContext.Provider value={headerPropsRef.current}>
|
||||||
|
{children}
|
||||||
|
</SidebarPropsContext.Provider>
|
||||||
|
</Island>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
SidebarInner.displayName = "SidebarInner";
|
||||||
|
|
||||||
|
export const Sidebar = Object.assign(
|
||||||
|
forwardRef((props: SidebarProps, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||||
|
const appState = useUIAppState();
|
||||||
|
|
||||||
|
const { onStateChange } = props;
|
||||||
|
|
||||||
|
const refPrevOpenSidebar = useRef(appState.openSidebar);
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
// closing sidebar
|
||||||
|
((!appState.openSidebar &&
|
||||||
|
refPrevOpenSidebar?.current?.name === props.name) ||
|
||||||
|
// opening current sidebar
|
||||||
|
(appState.openSidebar?.name === props.name &&
|
||||||
|
refPrevOpenSidebar?.current?.name !== props.name) ||
|
||||||
|
// switching tabs or switching to a different sidebar
|
||||||
|
refPrevOpenSidebar.current?.name === props.name) &&
|
||||||
|
appState.openSidebar !== refPrevOpenSidebar.current
|
||||||
|
) {
|
||||||
|
onStateChange?.(
|
||||||
|
appState.openSidebar?.name !== props.name
|
||||||
|
? null
|
||||||
|
: appState.openSidebar,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
refPrevOpenSidebar.current = appState.openSidebar;
|
||||||
|
}, [appState.openSidebar, onStateChange, props.name]);
|
||||||
|
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
return () => setMounted(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// We want to render in the next tick (hence `mounted` flag) so that it's
|
||||||
|
// guaranteed to happen after unmount of the previous sidebar (in case the
|
||||||
|
// previous sidebar is mounted after the next one). This is necessary to
|
||||||
|
// prevent flicker of subcomponents that support fallbacks
|
||||||
|
// (e.g. SidebarHeader). This is because we're using flags to determine
|
||||||
|
// whether prefer the fallback component or not (otherwise both will render
|
||||||
|
// initially), and the flag won't be reset in time if the unmount order
|
||||||
|
// it not correct.
|
||||||
|
//
|
||||||
|
// Alternative, and more general solution would be to namespace the fallback
|
||||||
|
// HoC so that state is not shared between subcomponents when the wrapping
|
||||||
|
// component is of the same type (e.g. Sidebar -> SidebarHeader).
|
||||||
|
const shouldRender = mounted && appState.openSidebar?.name === props.name;
|
||||||
|
|
||||||
|
if (!shouldRender) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SidebarInner {...props} ref={ref} key={props.name} />;
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
Header: SidebarHeader,
|
||||||
|
TabTriggers: SidebarTabTriggers,
|
||||||
|
TabTrigger: SidebarTabTrigger,
|
||||||
|
Tabs: SidebarTabs,
|
||||||
|
Tab: SidebarTab,
|
||||||
|
Trigger: SidebarTrigger,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Sidebar.displayName = "Sidebar";
|
||||||
|
|
|
@ -4,86 +4,54 @@ import { t } from "../../i18n";
|
||||||
import { useDevice } from "../App";
|
import { useDevice } from "../App";
|
||||||
import { SidebarPropsContext } from "./common";
|
import { SidebarPropsContext } from "./common";
|
||||||
import { CloseIcon, PinIcon } from "../icons";
|
import { CloseIcon, PinIcon } from "../icons";
|
||||||
import { withUpstreamOverride } from "../hoc/withUpstreamOverride";
|
|
||||||
import { Tooltip } from "../Tooltip";
|
import { Tooltip } from "../Tooltip";
|
||||||
|
import { Button } from "../Button";
|
||||||
|
|
||||||
export const SidebarDockButton = (props: {
|
export const SidebarHeader = ({
|
||||||
checked: boolean;
|
children,
|
||||||
onChange?(): void;
|
className,
|
||||||
}) => {
|
}: {
|
||||||
return (
|
|
||||||
<div className="layer-ui__sidebar-dock-button" data-testid="sidebar-dock">
|
|
||||||
<Tooltip label={t("labels.sidebarLock")}>
|
|
||||||
<label
|
|
||||||
className={clsx(
|
|
||||||
"ToolIcon ToolIcon__lock ToolIcon_type_floating",
|
|
||||||
`ToolIcon_size_medium`,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="ToolIcon_type_checkbox"
|
|
||||||
type="checkbox"
|
|
||||||
onChange={props.onChange}
|
|
||||||
checked={props.checked}
|
|
||||||
aria-label={t("labels.sidebarLock")}
|
|
||||||
/>{" "}
|
|
||||||
<div
|
|
||||||
className={clsx("Sidebar__pin-btn", {
|
|
||||||
"Sidebar__pin-btn--pinned": props.checked,
|
|
||||||
})}
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
{PinIcon}
|
|
||||||
</div>{" "}
|
|
||||||
</label>{" "}
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _SidebarHeader: React.FC<{
|
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}> = ({ children, className }) => {
|
}) => {
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
const props = useContext(SidebarPropsContext);
|
const props = useContext(SidebarPropsContext);
|
||||||
|
|
||||||
const renderDockButton = !!(device.canDeviceFitSidebar && props.dockable);
|
const renderDockButton = !!(
|
||||||
const renderCloseButton = !!props.onClose;
|
device.canDeviceFitSidebar && props.shouldRenderDockButton
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx("layer-ui__sidebar__header", className)}
|
className={clsx("sidebar__header", className)}
|
||||||
data-testid="sidebar-header"
|
data-testid="sidebar-header"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{(renderDockButton || renderCloseButton) && (
|
<div className="sidebar__header__buttons">
|
||||||
<div className="layer-ui__sidebar__header__buttons">
|
{renderDockButton && (
|
||||||
{renderDockButton && (
|
<Tooltip label={t("labels.sidebarLock")}>
|
||||||
<SidebarDockButton
|
<Button
|
||||||
checked={!!props.docked}
|
onSelect={() => props.onDock?.(!props.docked)}
|
||||||
onChange={() => {
|
selected={!!props.docked}
|
||||||
props.onDock?.(!props.docked);
|
className="sidebar__dock"
|
||||||
}}
|
data-testid="sidebar-dock"
|
||||||
/>
|
aria-label={t("labels.sidebarLock")}
|
||||||
)}
|
|
||||||
{renderCloseButton && (
|
|
||||||
<button
|
|
||||||
data-testid="sidebar-close"
|
|
||||||
className="Sidebar__close-btn"
|
|
||||||
onClick={props.onClose}
|
|
||||||
aria-label={t("buttons.close")}
|
|
||||||
>
|
>
|
||||||
{CloseIcon}
|
{PinIcon}
|
||||||
</button>
|
</Button>
|
||||||
)}
|
</Tooltip>
|
||||||
</div>
|
)}
|
||||||
)}
|
<Button
|
||||||
|
data-testid="sidebar-close"
|
||||||
|
className="sidebar__close"
|
||||||
|
onSelect={props.onCloseRequest}
|
||||||
|
aria-label={t("buttons.close")}
|
||||||
|
>
|
||||||
|
{CloseIcon}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [Context, Component] = withUpstreamOverride(_SidebarHeader);
|
SidebarHeader.displayName = "SidebarHeader";
|
||||||
|
|
||||||
/** @private */
|
|
||||||
export const SidebarHeaderComponents = { Context, Component };
|
|
||||||
|
|
18
src/components/Sidebar/SidebarTab.tsx
Normal file
18
src/components/Sidebar/SidebarTab.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import * as RadixTabs from "@radix-ui/react-tabs";
|
||||||
|
import { SidebarTabName } from "../../types";
|
||||||
|
|
||||||
|
export const SidebarTab = ({
|
||||||
|
tab,
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
tab: SidebarTabName;
|
||||||
|
children: React.ReactNode;
|
||||||
|
} & React.HTMLAttributes<HTMLDivElement>) => {
|
||||||
|
return (
|
||||||
|
<RadixTabs.Content {...rest} value={tab}>
|
||||||
|
{children}
|
||||||
|
</RadixTabs.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
SidebarTab.displayName = "SidebarTab";
|
26
src/components/Sidebar/SidebarTabTrigger.tsx
Normal file
26
src/components/Sidebar/SidebarTabTrigger.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import * as RadixTabs from "@radix-ui/react-tabs";
|
||||||
|
import { SidebarTabName } from "../../types";
|
||||||
|
|
||||||
|
export const SidebarTabTrigger = ({
|
||||||
|
children,
|
||||||
|
tab,
|
||||||
|
onSelect,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
tab: SidebarTabName;
|
||||||
|
onSelect?: React.ReactEventHandler<HTMLButtonElement> | undefined;
|
||||||
|
} & Omit<React.HTMLAttributes<HTMLButtonElement>, "onSelect">) => {
|
||||||
|
return (
|
||||||
|
<RadixTabs.Trigger value={tab} asChild onSelect={onSelect}>
|
||||||
|
<button
|
||||||
|
type={"button"}
|
||||||
|
className={`excalidraw-button sidebar-tab-trigger`}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
</RadixTabs.Trigger>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
SidebarTabTrigger.displayName = "SidebarTabTrigger";
|
16
src/components/Sidebar/SidebarTabTriggers.tsx
Normal file
16
src/components/Sidebar/SidebarTabTriggers.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import * as RadixTabs from "@radix-ui/react-tabs";
|
||||||
|
|
||||||
|
export const SidebarTabTriggers = ({
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
}: { children: React.ReactNode } & Omit<
|
||||||
|
React.RefAttributes<HTMLDivElement>,
|
||||||
|
"onSelect"
|
||||||
|
>) => {
|
||||||
|
return (
|
||||||
|
<RadixTabs.List className="sidebar-triggers" {...rest}>
|
||||||
|
{children}
|
||||||
|
</RadixTabs.List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
SidebarTabTriggers.displayName = "SidebarTabTriggers";
|
36
src/components/Sidebar/SidebarTabs.tsx
Normal file
36
src/components/Sidebar/SidebarTabs.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import * as RadixTabs from "@radix-ui/react-tabs";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
import { useExcalidrawSetAppState } from "../App";
|
||||||
|
|
||||||
|
export const SidebarTabs = ({
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
} & Omit<React.RefAttributes<HTMLDivElement>, "onSelect">) => {
|
||||||
|
const appState = useUIAppState();
|
||||||
|
const setAppState = useExcalidrawSetAppState();
|
||||||
|
|
||||||
|
if (!appState.openSidebar) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name } = appState.openSidebar;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadixTabs.Root
|
||||||
|
className="sidebar-tabs-root"
|
||||||
|
value={appState.openSidebar.tab}
|
||||||
|
onValueChange={(tab) =>
|
||||||
|
setAppState((state) => ({
|
||||||
|
...state,
|
||||||
|
openSidebar: { ...state.openSidebar, name, tab },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RadixTabs.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
SidebarTabs.displayName = "SidebarTabs";
|
34
src/components/Sidebar/SidebarTrigger.scss
Normal file
34
src/components/Sidebar/SidebarTrigger.scss
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
@import "../../css/variables.module";
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.sidebar-trigger {
|
||||||
|
@include outlineButtonStyles;
|
||||||
|
|
||||||
|
background-color: var(--island-bg-color);
|
||||||
|
|
||||||
|
width: auto;
|
||||||
|
height: var(--lg-button-size);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
line-height: 0;
|
||||||
|
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: var(--lg-icon-size);
|
||||||
|
height: var(--lg-icon-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-sidebar-trigger .sidebar-trigger__label {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media screen and (min-width: 1024px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
src/components/Sidebar/SidebarTrigger.tsx
Normal file
45
src/components/Sidebar/SidebarTrigger.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { useExcalidrawSetAppState } from "../App";
|
||||||
|
import { SidebarTriggerProps } from "./common";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import "./SidebarTrigger.scss";
|
||||||
|
|
||||||
|
export const SidebarTrigger = ({
|
||||||
|
name,
|
||||||
|
tab,
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
onToggle,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
}: SidebarTriggerProps) => {
|
||||||
|
const setAppState = useExcalidrawSetAppState();
|
||||||
|
const appState = useUIAppState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label title={title}>
|
||||||
|
<input
|
||||||
|
className="ToolIcon_type_checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
onChange={(event) => {
|
||||||
|
document
|
||||||
|
.querySelector(".layer-ui__wrapper")
|
||||||
|
?.classList.remove("animate");
|
||||||
|
const isOpen = event.target.checked;
|
||||||
|
setAppState({ openSidebar: isOpen ? { name, tab } : null });
|
||||||
|
onToggle?.(isOpen);
|
||||||
|
}}
|
||||||
|
checked={appState.openSidebar?.name === name}
|
||||||
|
aria-label={title}
|
||||||
|
aria-keyshortcuts="0"
|
||||||
|
/>
|
||||||
|
<div className={clsx("sidebar-trigger", className)} style={style}>
|
||||||
|
{icon && <div>{icon}</div>}
|
||||||
|
{children && <div className="sidebar-trigger__label">{children}</div>}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
SidebarTrigger.displayName = "SidebarTrigger";
|
|
@ -1,23 +1,41 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { AppState, SidebarName, SidebarTabName } from "../../types";
|
||||||
|
|
||||||
|
export type SidebarTriggerProps = {
|
||||||
|
name: SidebarName;
|
||||||
|
tab?: SidebarTabName;
|
||||||
|
icon?: JSX.Element;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
title?: string;
|
||||||
|
className?: string;
|
||||||
|
onToggle?: (open: boolean) => void;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
export type SidebarProps<P = {}> = {
|
export type SidebarProps<P = {}> = {
|
||||||
|
name: SidebarName;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
/**
|
/**
|
||||||
* Called on sidebar close (either by user action or by the editor).
|
* Called on sidebar open/close or tab change.
|
||||||
|
*/
|
||||||
|
onStateChange?: (state: AppState["openSidebar"]) => void;
|
||||||
|
/**
|
||||||
|
* supply alongside `docked` prop in order to make the Sidebar user-dockable
|
||||||
*/
|
*/
|
||||||
onClose?: () => void | boolean;
|
|
||||||
/** if not supplied, sidebar won't be dockable */
|
|
||||||
onDock?: (docked: boolean) => void;
|
onDock?: (docked: boolean) => void;
|
||||||
docked?: boolean;
|
docked?: boolean;
|
||||||
initialDockedState?: boolean;
|
|
||||||
dockable?: boolean;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
// NOTE sidebars we use internally inside the editor must have this flag set.
|
||||||
|
// It indicates that this sidebar should have lower precedence over host
|
||||||
|
// sidebars, if both are open.
|
||||||
|
/** @private internal */
|
||||||
|
__fallback?: boolean;
|
||||||
} & P;
|
} & P;
|
||||||
|
|
||||||
export type SidebarPropsContextValue = Pick<
|
export type SidebarPropsContextValue = Pick<
|
||||||
SidebarProps,
|
SidebarProps,
|
||||||
"onClose" | "onDock" | "docked" | "dockable"
|
"onDock" | "docked"
|
||||||
>;
|
> & { onCloseRequest: () => void; shouldRenderDockButton: boolean };
|
||||||
|
|
||||||
export const SidebarPropsContext =
|
export const SidebarPropsContext =
|
||||||
React.createContext<SidebarPropsContextValue>({});
|
React.createContext<SidebarPropsContextValue>({} as SidebarPropsContextValue);
|
||||||
|
|
|
@ -3,14 +3,14 @@ import { getCommonBounds } from "../element/bounds";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { getTargetElements } from "../scene";
|
import { getTargetElements } from "../scene";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { ExcalidrawProps, UIAppState } from "../types";
|
||||||
import { CloseIcon } from "./icons";
|
import { CloseIcon } from "./icons";
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
import "./Stats.scss";
|
import "./Stats.scss";
|
||||||
|
|
||||||
export const Stats = (props: {
|
export const Stats = (props: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Sentry from "@sentry/browser";
|
import * as Sentry from "@sentry/browser";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import Trans from "./Trans";
|
||||||
|
|
||||||
interface TopErrorBoundaryState {
|
interface TopErrorBoundaryState {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
|
@ -74,25 +75,31 @@ export class TopErrorBoundary extends React.Component<
|
||||||
<div className="ErrorSplash excalidraw">
|
<div className="ErrorSplash excalidraw">
|
||||||
<div className="ErrorSplash-messageContainer">
|
<div className="ErrorSplash-messageContainer">
|
||||||
<div className="ErrorSplash-paragraph bigger align-center">
|
<div className="ErrorSplash-paragraph bigger align-center">
|
||||||
{t("errorSplash.headingMain_pre")}
|
<Trans
|
||||||
<button onClick={() => window.location.reload()}>
|
i18nKey="errorSplash.headingMain"
|
||||||
{t("errorSplash.headingMain_button")}
|
button={(el) => (
|
||||||
</button>
|
<button onClick={() => window.location.reload()}>{el}</button>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ErrorSplash-paragraph align-center">
|
<div className="ErrorSplash-paragraph align-center">
|
||||||
{t("errorSplash.clearCanvasMessage")}
|
<Trans
|
||||||
<button
|
i18nKey="errorSplash.clearCanvasMessage"
|
||||||
onClick={() => {
|
button={(el) => (
|
||||||
try {
|
<button
|
||||||
localStorage.clear();
|
onClick={() => {
|
||||||
window.location.reload();
|
try {
|
||||||
} catch (error: any) {
|
localStorage.clear();
|
||||||
console.error(error);
|
window.location.reload();
|
||||||
}
|
} catch (error: any) {
|
||||||
}}
|
console.error(error);
|
||||||
>
|
}
|
||||||
{t("errorSplash.clearCanvasMessage_button")}
|
}}
|
||||||
</button>
|
>
|
||||||
|
{el}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
<div className="smaller">
|
<div className="smaller">
|
||||||
<span role="img" aria-label="warning">
|
<span role="img" aria-label="warning">
|
||||||
|
@ -106,16 +113,17 @@ export class TopErrorBoundary extends React.Component<
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="ErrorSplash-paragraph">
|
<div className="ErrorSplash-paragraph">
|
||||||
{t("errorSplash.trackedToSentry_pre")}
|
{t("errorSplash.trackedToSentry", {
|
||||||
{this.state.sentryEventId}
|
eventId: this.state.sentryEventId,
|
||||||
{t("errorSplash.trackedToSentry_post")}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="ErrorSplash-paragraph">
|
<div className="ErrorSplash-paragraph">
|
||||||
{t("errorSplash.openIssueMessage_pre")}
|
<Trans
|
||||||
<button onClick={() => this.createGithubIssue()}>
|
i18nKey="errorSplash.openIssueMessage"
|
||||||
{t("errorSplash.openIssueMessage_button")}
|
button={(el) => (
|
||||||
</button>
|
<button onClick={() => this.createGithubIssue()}>{el}</button>
|
||||||
{t("errorSplash.openIssueMessage_post")}
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ErrorSplash-paragraph">
|
<div className="ErrorSplash-paragraph">
|
||||||
<div className="ErrorSplash-details">
|
<div className="ErrorSplash-details">
|
||||||
|
|
67
src/components/Trans.test.tsx
Normal file
67
src/components/Trans.test.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
|
||||||
|
import fallbackLangData from "../locales/en.json";
|
||||||
|
|
||||||
|
import Trans from "./Trans";
|
||||||
|
|
||||||
|
describe("Test <Trans/>", () => {
|
||||||
|
it("should translate the the strings correctly", () => {
|
||||||
|
//@ts-ignore
|
||||||
|
fallbackLangData.transTest = {
|
||||||
|
key1: "Hello {{audience}}",
|
||||||
|
key2: "Please <link>click the button</link> to continue.",
|
||||||
|
key3: "Please <link>click {{location}}</link> to continue.",
|
||||||
|
key4: "Please <link>click <bold>{{location}}</bold></link> to continue.",
|
||||||
|
key5: "Please <connect-link>click the button</connect-link> to continue.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<>
|
||||||
|
<div data-testid="test1">
|
||||||
|
<Trans i18nKey="transTest.key1" audience="world" />
|
||||||
|
</div>
|
||||||
|
<div data-testid="test2">
|
||||||
|
<Trans
|
||||||
|
i18nKey="transTest.key2"
|
||||||
|
link={(el) => <a href="https://example.com">{el}</a>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div data-testid="test3">
|
||||||
|
<Trans
|
||||||
|
i18nKey="transTest.key3"
|
||||||
|
link={(el) => <a href="https://example.com">{el}</a>}
|
||||||
|
location="the button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div data-testid="test4">
|
||||||
|
<Trans
|
||||||
|
i18nKey="transTest.key4"
|
||||||
|
link={(el) => <a href="https://example.com">{el}</a>}
|
||||||
|
location="the button"
|
||||||
|
bold={(el) => <strong>{el}</strong>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div data-testid="test5">
|
||||||
|
<Trans
|
||||||
|
i18nKey="transTest.key5"
|
||||||
|
connect-link={(el) => <a href="https://example.com">{el}</a>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getByTestId("test1").innerHTML).toEqual("Hello world");
|
||||||
|
expect(getByTestId("test2").innerHTML).toEqual(
|
||||||
|
`Please <a href="https://example.com">click the button</a> to continue.`,
|
||||||
|
);
|
||||||
|
expect(getByTestId("test3").innerHTML).toEqual(
|
||||||
|
`Please <a href="https://example.com">click the button</a> to continue.`,
|
||||||
|
);
|
||||||
|
expect(getByTestId("test4").innerHTML).toEqual(
|
||||||
|
`Please <a href="https://example.com">click <strong>the button</strong></a> to continue.`,
|
||||||
|
);
|
||||||
|
expect(getByTestId("test5").innerHTML).toEqual(
|
||||||
|
`Please <a href="https://example.com">click the button</a> to continue.`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
169
src/components/Trans.tsx
Normal file
169
src/components/Trans.tsx
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { useI18n } from "../i18n";
|
||||||
|
|
||||||
|
// Used for splitting i18nKey into tokens in Trans component
|
||||||
|
// Example:
|
||||||
|
// "Please <link>click {{location}}</link> to continue.".split(SPLIT_REGEX).filter(Boolean)
|
||||||
|
// produces
|
||||||
|
// ["Please ", "<link>", "click ", "{{location}}", "</link>", " to continue."]
|
||||||
|
const SPLIT_REGEX = /({{[\w-]+}})|(<[\w-]+>)|(<\/[\w-]+>)/g;
|
||||||
|
// Used for extracting "location" from "{{location}}"
|
||||||
|
const KEY_REGEXP = /{{([\w-]+)}}/;
|
||||||
|
// Used for extracting "link" from "<link>"
|
||||||
|
const TAG_START_REGEXP = /<([\w-]+)>/;
|
||||||
|
// Used for extracting "link" from "</link>"
|
||||||
|
const TAG_END_REGEXP = /<\/([\w-]+)>/;
|
||||||
|
|
||||||
|
const getTransChildren = (
|
||||||
|
format: string,
|
||||||
|
props: {
|
||||||
|
[key: string]: React.ReactNode | ((el: React.ReactNode) => React.ReactNode);
|
||||||
|
},
|
||||||
|
): React.ReactNode[] => {
|
||||||
|
const stack: { name: string; children: React.ReactNode[] }[] = [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
format
|
||||||
|
.split(SPLIT_REGEX)
|
||||||
|
.filter(Boolean)
|
||||||
|
.forEach((match) => {
|
||||||
|
const tagStartMatch = match.match(TAG_START_REGEXP);
|
||||||
|
const tagEndMatch = match.match(TAG_END_REGEXP);
|
||||||
|
const keyMatch = match.match(KEY_REGEXP);
|
||||||
|
|
||||||
|
if (tagStartMatch !== null) {
|
||||||
|
// The match is <tag>. Set the tag name as the name if it's one of the
|
||||||
|
// props, e.g. for "Please <link>click the button</link> to continue"
|
||||||
|
// tagStartMatch[1] = "link" and props contain "link" then it will be
|
||||||
|
// pushed to stack.
|
||||||
|
const name = tagStartMatch[1];
|
||||||
|
if (props.hasOwnProperty(name)) {
|
||||||
|
stack.push({
|
||||||
|
name,
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`Trans: missed to pass in prop ${name} for interpolating ${format}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (tagEndMatch !== null) {
|
||||||
|
// If tag end match is found, this means we need to replace the content with
|
||||||
|
// its actual value in prop e.g. format = "Please <link>click the
|
||||||
|
// button</link> to continue", tagEndMatch is for "</link>", stack last item name =
|
||||||
|
// "link" and props.link = (el) => <a
|
||||||
|
// href="https://example.com">{el}</a> then its prop value will be
|
||||||
|
// pushed to "link"'s children so on DOM when rendering it's rendered as
|
||||||
|
// <a href="https://example.com">click the button</a>
|
||||||
|
const name = tagEndMatch[1];
|
||||||
|
if (name === stack[stack.length - 1].name) {
|
||||||
|
const item = stack.pop()!;
|
||||||
|
const itemChildren = React.createElement(
|
||||||
|
React.Fragment,
|
||||||
|
{},
|
||||||
|
...item.children,
|
||||||
|
);
|
||||||
|
const fn = props[item.name];
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
stack[stack.length - 1].children.push(fn(itemChildren));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`Trans: unexpected end tag ${match} for interpolating ${format}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (keyMatch !== null) {
|
||||||
|
// The match is for {{key}}. Check if the key is present in props and set
|
||||||
|
// the prop value as children of last stack item e.g. format = "Hello
|
||||||
|
// {{name}}", key = "name" and props.name = "Excalidraw" then its prop
|
||||||
|
// value will be pushed to "name"'s children so it's rendered on DOM as
|
||||||
|
// "Hello Excalidraw"
|
||||||
|
const name = keyMatch[1];
|
||||||
|
if (props.hasOwnProperty(name)) {
|
||||||
|
stack[stack.length - 1].children.push(props[name] as React.ReactNode);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`Trans: key ${name} not in props for interpolating ${format}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If none of cases match means we just need to push the string
|
||||||
|
// to stack eg - "Hello {{name}} Whats up?" "Hello", "Whats up" will be pushed
|
||||||
|
stack[stack.length - 1].children.push(match);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (stack.length !== 1) {
|
||||||
|
console.warn(`Trans: stack not empty for interpolating ${format}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack[0].children;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Trans component is used for translating JSX.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"example1": "Hello {{audience}}",
|
||||||
|
"example2": "Please <link>click the button</link> to continue.",
|
||||||
|
"example3": "Please <link>click {{location}}</link> to continue.",
|
||||||
|
"example4": "Please <link>click <bold>{{location}}</bold></link> to continue.",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<Trans i18nKey="example1" audience="world" />
|
||||||
|
|
||||||
|
<Trans
|
||||||
|
i18nKey="example2"
|
||||||
|
connectLink={(el) => <a href="https://example.com">{el}</a>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Trans
|
||||||
|
i18nKey="example3"
|
||||||
|
connectLink={(el) => <a href="https://example.com">{el}</a>}
|
||||||
|
location="the button"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Trans
|
||||||
|
i18nKey="example4"
|
||||||
|
connectLink={(el) => <a href="https://example.com">{el}</a>}
|
||||||
|
location="the button"
|
||||||
|
bold={(el) => <strong>{el}</strong>}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```html
|
||||||
|
Hello world
|
||||||
|
Please <a href="https://example.com">click the button</a> to continue.
|
||||||
|
Please <a href="https://example.com">click the button</a> to continue.
|
||||||
|
Please <a href="https://example.com">click <strong>the button</strong></a> to continue.
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
const Trans = ({
|
||||||
|
i18nKey,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
i18nKey: string;
|
||||||
|
[key: string]: React.ReactNode | ((el: React.ReactNode) => React.ReactNode);
|
||||||
|
}) => {
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
// This is needed to avoid unique key error in list which gets rendered from getTransChildren
|
||||||
|
return React.createElement(
|
||||||
|
React.Fragment,
|
||||||
|
{},
|
||||||
|
...getTransChildren(t(i18nKey), props),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Trans;
|
|
@ -5,59 +5,46 @@ exports[`Test <App/> should show error modal when using brave and measureText AP
|
||||||
data-testid="brave-measure-text-error"
|
data-testid="brave-measure-text-error"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Looks like you are using Brave browser with the
|
Looks like you are using Brave browser with the
|
||||||
|
|
||||||
<span
|
<span
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Aggressively Block Fingerprinting
|
Aggressively Block Fingerprinting
|
||||||
</span>
|
</span>
|
||||||
|
setting enabled.
|
||||||
setting enabled
|
</p>
|
||||||
.
|
<p>
|
||||||
<br />
|
This could result in breaking the
|
||||||
<br />
|
|
||||||
This could result in breaking the
|
|
||||||
|
|
||||||
<span
|
<span
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Text Elements
|
Text Elements
|
||||||
</span>
|
</span>
|
||||||
|
in your drawings.
|
||||||
in your drawings
|
|
||||||
.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We strongly recommend disabling this setting. You can follow
|
We strongly recommend disabling this setting. You can follow
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser"
|
href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser"
|
||||||
>
|
>
|
||||||
|
|
||||||
these steps
|
these steps
|
||||||
</a>
|
</a>
|
||||||
|
on how to do so.
|
||||||
on how to do so
|
|
||||||
.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If disabling this setting doesn't fix the display of text elements, please open an
|
If disabling this setting doesn't fix the display of text elements, please open an
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="https://github.com/excalidraw/excalidraw/issues/new"
|
href="https://github.com/excalidraw/excalidraw/issues/new"
|
||||||
>
|
>
|
||||||
issue
|
issue
|
||||||
</a>
|
</a>
|
||||||
|
on our GitHub, or write us on
|
||||||
on our GitHub, or write us on
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="https://discord.gg/UexuTaE"
|
href="https://discord.gg/UexuTaE"
|
||||||
>
|
>
|
||||||
Discord
|
Discord
|
||||||
|
.
|
||||||
</a>
|
</a>
|
||||||
.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import tunnel from "@dwelle/tunnel-rat";
|
|
||||||
|
|
||||||
type Tunnel = ReturnType<typeof tunnel>;
|
|
||||||
|
|
||||||
type TunnelsContextValue = {
|
|
||||||
mainMenuTunnel: Tunnel;
|
|
||||||
welcomeScreenMenuHintTunnel: Tunnel;
|
|
||||||
welcomeScreenToolbarHintTunnel: Tunnel;
|
|
||||||
welcomeScreenHelpHintTunnel: Tunnel;
|
|
||||||
welcomeScreenCenterTunnel: Tunnel;
|
|
||||||
footerCenterTunnel: Tunnel;
|
|
||||||
jotaiScope: symbol;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TunnelsContext = React.createContext<TunnelsContextValue>(null!);
|
|
||||||
|
|
||||||
export const useTunnels = () => React.useContext(TunnelsContext);
|
|
||||||
|
|
||||||
export const useInitializeTunnels = () => {
|
|
||||||
return React.useMemo((): TunnelsContextValue => {
|
|
||||||
return {
|
|
||||||
mainMenuTunnel: tunnel(),
|
|
||||||
welcomeScreenMenuHintTunnel: tunnel(),
|
|
||||||
welcomeScreenToolbarHintTunnel: tunnel(),
|
|
||||||
welcomeScreenHelpHintTunnel: tunnel(),
|
|
||||||
welcomeScreenCenterTunnel: tunnel(),
|
|
||||||
footerCenterTunnel: tunnel(),
|
|
||||||
jotaiScope: Symbol(),
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
};
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useOutsideClickHook } from "../../hooks/useOutsideClick";
|
import { useOutsideClick } from "../../hooks/useOutsideClick";
|
||||||
import { Island } from "../Island";
|
import { Island } from "../Island";
|
||||||
|
|
||||||
import { useDevice } from "../App";
|
import { useDevice } from "../App";
|
||||||
|
@ -24,7 +24,7 @@ const MenuContent = ({
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
}) => {
|
}) => {
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
const menuRef = useOutsideClickHook(() => {
|
const menuRef = useOutsideClick(() => {
|
||||||
onClickOutside?.();
|
onClickOutside?.();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useDevice, useExcalidrawAppState } from "../App";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
import { useDevice } from "../App";
|
||||||
|
|
||||||
const MenuTrigger = ({
|
const MenuTrigger = ({
|
||||||
className = "",
|
className = "",
|
||||||
|
@ -10,7 +11,7 @@ const MenuTrigger = ({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
const classNames = clsx(
|
const classNames = clsx(
|
||||||
`dropdown-menu-button ${className}`,
|
`dropdown-menu-button ${className}`,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { actionShortcuts } from "../../actions";
|
import { actionShortcuts } from "../../actions";
|
||||||
import { ActionManager } from "../../actions/manager";
|
import { ActionManager } from "../../actions/manager";
|
||||||
import { AppState } from "../../types";
|
|
||||||
import {
|
import {
|
||||||
ExitZenModeAction,
|
ExitZenModeAction,
|
||||||
FinalizeAction,
|
FinalizeAction,
|
||||||
|
@ -9,10 +8,11 @@ import {
|
||||||
ZoomActions,
|
ZoomActions,
|
||||||
} from "../Actions";
|
} from "../Actions";
|
||||||
import { useDevice } from "../App";
|
import { useDevice } from "../App";
|
||||||
import { useTunnels } from "../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import { HelpButton } from "../HelpButton";
|
import { HelpButton } from "../HelpButton";
|
||||||
import { Section } from "../Section";
|
import { Section } from "../Section";
|
||||||
import Stack from "../Stack";
|
import Stack from "../Stack";
|
||||||
|
import { UIAppState } from "../../types";
|
||||||
|
|
||||||
const Footer = ({
|
const Footer = ({
|
||||||
appState,
|
appState,
|
||||||
|
@ -20,12 +20,12 @@ const Footer = ({
|
||||||
showExitZenModeBtn,
|
showExitZenModeBtn,
|
||||||
renderWelcomeScreen,
|
renderWelcomeScreen,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
showExitZenModeBtn: boolean;
|
showExitZenModeBtn: boolean;
|
||||||
renderWelcomeScreen: boolean;
|
renderWelcomeScreen: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { footerCenterTunnel, welcomeScreenHelpHintTunnel } = useTunnels();
|
const { FooterCenterTunnel, WelcomeScreenHelpHintTunnel } = useTunnels();
|
||||||
|
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
const showFinalize =
|
const showFinalize =
|
||||||
|
@ -70,14 +70,14 @@ const Footer = ({
|
||||||
</Section>
|
</Section>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
</div>
|
</div>
|
||||||
<footerCenterTunnel.Out />
|
<FooterCenterTunnel.Out />
|
||||||
<div
|
<div
|
||||||
className={clsx("layer-ui__wrapper__footer-right zen-mode-transition", {
|
className={clsx("layer-ui__wrapper__footer-right zen-mode-transition", {
|
||||||
"transition-right disable-pointerEvents": appState.zenModeEnabled,
|
"transition-right disable-pointerEvents": appState.zenModeEnabled,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
{renderWelcomeScreen && <welcomeScreenHelpHintTunnel.Out />}
|
{renderWelcomeScreen && <WelcomeScreenHelpHintTunnel.Out />}
|
||||||
<HelpButton
|
<HelpButton
|
||||||
onClick={() => actionManager.executeAction(actionShortcuts)}
|
onClick={() => actionManager.executeAction(actionShortcuts)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useExcalidrawAppState } from "../App";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import { useTunnels } from "../context/tunnels";
|
|
||||||
import "./FooterCenter.scss";
|
import "./FooterCenter.scss";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
|
const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
|
||||||
const { footerCenterTunnel } = useTunnels();
|
const { FooterCenterTunnel } = useTunnels();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
return (
|
return (
|
||||||
<footerCenterTunnel.In>
|
<FooterCenterTunnel.In>
|
||||||
<div
|
<div
|
||||||
className={clsx("footer-center zen-mode-transition", {
|
className={clsx("footer-center zen-mode-transition", {
|
||||||
"layer-ui__wrapper__footer-left--transition-bottom":
|
"layer-ui__wrapper__footer-left--transition-bottom":
|
||||||
|
@ -16,7 +16,7 @@ const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</footerCenterTunnel.In>
|
</FooterCenterTunnel.In>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,46 @@
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import React, { useLayoutEffect } from "react";
|
import React, { useLayoutEffect } from "react";
|
||||||
import { useTunnels } from "../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
|
|
||||||
export const withInternalFallback = <P,>(
|
export const withInternalFallback = <P,>(
|
||||||
componentName: string,
|
componentName: string,
|
||||||
Component: React.FC<P>,
|
Component: React.FC<P>,
|
||||||
) => {
|
) => {
|
||||||
const counterAtom = atom(0);
|
const renderAtom = atom(0);
|
||||||
// flag set on initial render to tell the fallback component to skip the
|
// flag set on initial render to tell the fallback component to skip the
|
||||||
// render until mount counter are initialized. This is because the counter
|
// render until mount counter are initialized. This is because the counter
|
||||||
// is initialized in an effect, and thus we could end rendering both
|
// is initialized in an effect, and thus we could end rendering both
|
||||||
// components at the same time until counter is initialized.
|
// components at the same time until counter is initialized.
|
||||||
let preferHost = false;
|
let preferHost = false;
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
const WrapperComponent: React.FC<
|
const WrapperComponent: React.FC<
|
||||||
P & {
|
P & {
|
||||||
__fallback?: boolean;
|
__fallback?: boolean;
|
||||||
}
|
}
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { jotaiScope } = useTunnels();
|
const { jotaiScope } = useTunnels();
|
||||||
const [counter, setCounter] = useAtom(counterAtom, jotaiScope);
|
const [, setRender] = useAtom(renderAtom, jotaiScope);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setCounter((counter) => counter + 1);
|
setRender((c) => {
|
||||||
|
const next = c + 1;
|
||||||
|
counter = next;
|
||||||
|
|
||||||
|
return next;
|
||||||
|
});
|
||||||
return () => {
|
return () => {
|
||||||
setCounter((counter) => counter - 1);
|
setRender((c) => {
|
||||||
|
const next = c - 1;
|
||||||
|
counter = next;
|
||||||
|
if (!next) {
|
||||||
|
preferHost = false;
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, [setCounter]);
|
}, [setRender]);
|
||||||
|
|
||||||
if (!props.__fallback) {
|
if (!props.__fallback) {
|
||||||
preferHost = true;
|
preferHost = true;
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
import React, {
|
|
||||||
useMemo,
|
|
||||||
useContext,
|
|
||||||
useLayoutEffect,
|
|
||||||
useState,
|
|
||||||
createContext,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
export const withUpstreamOverride = <P,>(Component: React.ComponentType<P>) => {
|
|
||||||
type ContextValue = [boolean, React.Dispatch<React.SetStateAction<boolean>>];
|
|
||||||
|
|
||||||
const DefaultComponentContext = createContext<ContextValue>([
|
|
||||||
false,
|
|
||||||
() => {},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ComponentContext: React.FC<{ children: React.ReactNode }> = ({
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const [isRenderedUpstream, setIsRenderedUpstream] = useState(false);
|
|
||||||
const contextValue: ContextValue = useMemo(
|
|
||||||
() => [isRenderedUpstream, setIsRenderedUpstream],
|
|
||||||
[isRenderedUpstream],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DefaultComponentContext.Provider value={contextValue}>
|
|
||||||
{children}
|
|
||||||
</DefaultComponentContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DefaultComponent = (
|
|
||||||
props: P & {
|
|
||||||
// indicates whether component should render when not rendered upstream
|
|
||||||
/** @private internal */
|
|
||||||
__isFallback?: boolean;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const [isRenderedUpstream, setIsRenderedUpstream] = useContext(
|
|
||||||
DefaultComponentContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (!props.__isFallback) {
|
|
||||||
setIsRenderedUpstream(true);
|
|
||||||
return () => setIsRenderedUpstream(false);
|
|
||||||
}
|
|
||||||
}, [props.__isFallback, setIsRenderedUpstream]);
|
|
||||||
|
|
||||||
if (props.__isFallback && isRenderedUpstream) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Component {...props} />;
|
|
||||||
};
|
|
||||||
if (Component.name) {
|
|
||||||
DefaultComponent.displayName = `${Component.name}_upstreamOverrideWrapper`;
|
|
||||||
ComponentContext.displayName = `${Component.name}_upstreamOverrideContextWrapper`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [ComponentContext, DefaultComponent] as const;
|
|
||||||
};
|
|
|
@ -3,9 +3,9 @@ import { usersIcon } from "../icons";
|
||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useExcalidrawAppState } from "../App";
|
|
||||||
|
|
||||||
import "./LiveCollaborationTrigger.scss";
|
import "./LiveCollaborationTrigger.scss";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
const LiveCollaborationTrigger = ({
|
const LiveCollaborationTrigger = ({
|
||||||
isCollaborating,
|
isCollaborating,
|
||||||
|
@ -15,7 +15,7 @@ const LiveCollaborationTrigger = ({
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
onSelect: () => void;
|
onSelect: () => void;
|
||||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { useI18n } from "../../i18n";
|
import { useI18n } from "../../i18n";
|
||||||
import {
|
import { useExcalidrawSetAppState, useExcalidrawActionManager } from "../App";
|
||||||
useExcalidrawAppState,
|
|
||||||
useExcalidrawSetAppState,
|
|
||||||
useExcalidrawActionManager,
|
|
||||||
} from "../App";
|
|
||||||
import {
|
import {
|
||||||
ExportIcon,
|
ExportIcon,
|
||||||
ExportImageIcon,
|
ExportImageIcon,
|
||||||
|
@ -32,6 +28,7 @@ import clsx from "clsx";
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||||
import { jotaiScope } from "../../jotai";
|
import { jotaiScope } from "../../jotai";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
export const LoadScene = () => {
|
export const LoadScene = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -139,7 +136,7 @@ ClearCanvas.displayName = "ClearCanvas";
|
||||||
|
|
||||||
export const ToggleTheme = () => {
|
export const ToggleTheme = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
if (!actionManager.isActionEnabled(actionToggleTheme)) {
|
if (!actionManager.isActionEnabled(actionToggleTheme)) {
|
||||||
|
@ -172,7 +169,7 @@ ToggleTheme.displayName = "ToggleTheme";
|
||||||
|
|
||||||
export const ChangeCanvasBackground = () => {
|
export const ChangeCanvasBackground = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
if (appState.viewModeEnabled) {
|
if (appState.viewModeEnabled) {
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { useDevice, useExcalidrawSetAppState } from "../App";
|
||||||
useDevice,
|
|
||||||
useExcalidrawAppState,
|
|
||||||
useExcalidrawSetAppState,
|
|
||||||
} from "../App";
|
|
||||||
import DropdownMenu from "../dropdownMenu/DropdownMenu";
|
import DropdownMenu from "../dropdownMenu/DropdownMenu";
|
||||||
|
|
||||||
import * as DefaultItems from "./DefaultItems";
|
import * as DefaultItems from "./DefaultItems";
|
||||||
|
@ -13,7 +9,8 @@ import { t } from "../../i18n";
|
||||||
import { HamburgerMenuIcon } from "../icons";
|
import { HamburgerMenuIcon } from "../icons";
|
||||||
import { withInternalFallback } from "../hoc/withInternalFallback";
|
import { withInternalFallback } from "../hoc/withInternalFallback";
|
||||||
import { composeEventHandlers } from "../../utils";
|
import { composeEventHandlers } from "../../utils";
|
||||||
import { useTunnels } from "../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
const MainMenu = Object.assign(
|
const MainMenu = Object.assign(
|
||||||
withInternalFallback(
|
withInternalFallback(
|
||||||
|
@ -28,16 +25,16 @@ const MainMenu = Object.assign(
|
||||||
*/
|
*/
|
||||||
onSelect?: (event: Event) => void;
|
onSelect?: (event: Event) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { mainMenuTunnel } = useTunnels();
|
const { MainMenuTunnel } = useTunnels();
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
const onClickOutside = device.isMobile
|
const onClickOutside = device.isMobile
|
||||||
? undefined
|
? undefined
|
||||||
: () => setAppState({ openMenu: null });
|
: () => setAppState({ openMenu: null });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mainMenuTunnel.In>
|
<MainMenuTunnel.In>
|
||||||
<DropdownMenu open={appState.openMenu === "canvas"}>
|
<DropdownMenu open={appState.openMenu === "canvas"}>
|
||||||
<DropdownMenu.Trigger
|
<DropdownMenu.Trigger
|
||||||
onToggle={() => {
|
onToggle={() => {
|
||||||
|
@ -66,7 +63,7 @@ const MainMenu = Object.assign(
|
||||||
)}
|
)}
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</mainMenuTunnel.In>
|
</MainMenuTunnel.In>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import { actionLoadScene, actionShortcuts } from "../../actions";
|
import { actionLoadScene, actionShortcuts } from "../../actions";
|
||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { t, useI18n } from "../../i18n";
|
import { t, useI18n } from "../../i18n";
|
||||||
import {
|
import { useDevice, useExcalidrawActionManager } from "../App";
|
||||||
useDevice,
|
import { useTunnels } from "../../context/tunnels";
|
||||||
useExcalidrawActionManager,
|
|
||||||
useExcalidrawAppState,
|
|
||||||
} from "../App";
|
|
||||||
import { useTunnels } from "../context/tunnels";
|
|
||||||
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
|
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
const WelcomeScreenMenuItemContent = ({
|
const WelcomeScreenMenuItemContent = ({
|
||||||
icon,
|
icon,
|
||||||
|
@ -89,9 +86,9 @@ const WelcomeScreenMenuItemLink = ({
|
||||||
WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink";
|
WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink";
|
||||||
|
|
||||||
const Center = ({ children }: { children?: React.ReactNode }) => {
|
const Center = ({ children }: { children?: React.ReactNode }) => {
|
||||||
const { welcomeScreenCenterTunnel } = useTunnels();
|
const { WelcomeScreenCenterTunnel } = useTunnels();
|
||||||
return (
|
return (
|
||||||
<welcomeScreenCenterTunnel.In>
|
<WelcomeScreenCenterTunnel.In>
|
||||||
<div className="welcome-screen-center">
|
<div className="welcome-screen-center">
|
||||||
{children || (
|
{children || (
|
||||||
<>
|
<>
|
||||||
|
@ -104,7 +101,7 @@ const Center = ({ children }: { children?: React.ReactNode }) => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</welcomeScreenCenterTunnel.In>
|
</WelcomeScreenCenterTunnel.In>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Center.displayName = "Center";
|
Center.displayName = "Center";
|
||||||
|
@ -148,7 +145,7 @@ const MenuItemHelp = () => {
|
||||||
MenuItemHelp.displayName = "MenuItemHelp";
|
MenuItemHelp.displayName = "MenuItemHelp";
|
||||||
|
|
||||||
const MenuItemLoadScene = () => {
|
const MenuItemLoadScene = () => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
if (appState.viewModeEnabled) {
|
if (appState.viewModeEnabled) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { t } from "../../i18n";
|
import { t } from "../../i18n";
|
||||||
import { useTunnels } from "../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import {
|
import {
|
||||||
WelcomeScreenHelpArrow,
|
WelcomeScreenHelpArrow,
|
||||||
WelcomeScreenMenuArrow,
|
WelcomeScreenMenuArrow,
|
||||||
|
@ -7,44 +7,44 @@ import {
|
||||||
} from "../icons";
|
} from "../icons";
|
||||||
|
|
||||||
const MenuHint = ({ children }: { children?: React.ReactNode }) => {
|
const MenuHint = ({ children }: { children?: React.ReactNode }) => {
|
||||||
const { welcomeScreenMenuHintTunnel } = useTunnels();
|
const { WelcomeScreenMenuHintTunnel } = useTunnels();
|
||||||
return (
|
return (
|
||||||
<welcomeScreenMenuHintTunnel.In>
|
<WelcomeScreenMenuHintTunnel.In>
|
||||||
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu">
|
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu">
|
||||||
{WelcomeScreenMenuArrow}
|
{WelcomeScreenMenuArrow}
|
||||||
<div className="welcome-screen-decor-hint__label">
|
<div className="welcome-screen-decor-hint__label">
|
||||||
{children || t("welcomeScreen.defaults.menuHint")}
|
{children || t("welcomeScreen.defaults.menuHint")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</welcomeScreenMenuHintTunnel.In>
|
</WelcomeScreenMenuHintTunnel.In>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
MenuHint.displayName = "MenuHint";
|
MenuHint.displayName = "MenuHint";
|
||||||
|
|
||||||
const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
|
const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
|
||||||
const { welcomeScreenToolbarHintTunnel } = useTunnels();
|
const { WelcomeScreenToolbarHintTunnel } = useTunnels();
|
||||||
return (
|
return (
|
||||||
<welcomeScreenToolbarHintTunnel.In>
|
<WelcomeScreenToolbarHintTunnel.In>
|
||||||
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar">
|
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar">
|
||||||
<div className="welcome-screen-decor-hint__label">
|
<div className="welcome-screen-decor-hint__label">
|
||||||
{children || t("welcomeScreen.defaults.toolbarHint")}
|
{children || t("welcomeScreen.defaults.toolbarHint")}
|
||||||
</div>
|
</div>
|
||||||
{WelcomeScreenTopToolbarArrow}
|
{WelcomeScreenTopToolbarArrow}
|
||||||
</div>
|
</div>
|
||||||
</welcomeScreenToolbarHintTunnel.In>
|
</WelcomeScreenToolbarHintTunnel.In>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
ToolbarHint.displayName = "ToolbarHint";
|
ToolbarHint.displayName = "ToolbarHint";
|
||||||
|
|
||||||
const HelpHint = ({ children }: { children?: React.ReactNode }) => {
|
const HelpHint = ({ children }: { children?: React.ReactNode }) => {
|
||||||
const { welcomeScreenHelpHintTunnel } = useTunnels();
|
const { WelcomeScreenHelpHintTunnel } = useTunnels();
|
||||||
return (
|
return (
|
||||||
<welcomeScreenHelpHintTunnel.In>
|
<WelcomeScreenHelpHintTunnel.In>
|
||||||
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">
|
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">
|
||||||
<div>{children || t("welcomeScreen.defaults.helpHint")}</div>
|
<div>{children || t("welcomeScreen.defaults.helpHint")}</div>
|
||||||
{WelcomeScreenHelpArrow}
|
{WelcomeScreenHelpArrow}
|
||||||
</div>
|
</div>
|
||||||
</welcomeScreenHelpHintTunnel.In>
|
</WelcomeScreenHelpHintTunnel.In>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
HelpHint.displayName = "HelpHint";
|
HelpHint.displayName = "HelpHint";
|
||||||
|
|
|
@ -275,3 +275,10 @@ export const DEFAULT_ELEMENT_PROPS: {
|
||||||
opacity: 100,
|
opacity: 100,
|
||||||
locked: false,
|
locked: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LIBRARY_SIDEBAR_TAB = "library";
|
||||||
|
|
||||||
|
export const DEFAULT_SIDEBAR = {
|
||||||
|
name: "default",
|
||||||
|
defaultTab: LIBRARY_SIDEBAR_TAB,
|
||||||
|
} as const;
|
||||||
|
|
36
src/context/tunnels.ts
Normal file
36
src/context/tunnels.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import React from "react";
|
||||||
|
import tunnel from "tunnel-rat";
|
||||||
|
|
||||||
|
export type Tunnel = ReturnType<typeof tunnel>;
|
||||||
|
|
||||||
|
type TunnelsContextValue = {
|
||||||
|
MainMenuTunnel: Tunnel;
|
||||||
|
WelcomeScreenMenuHintTunnel: Tunnel;
|
||||||
|
WelcomeScreenToolbarHintTunnel: Tunnel;
|
||||||
|
WelcomeScreenHelpHintTunnel: Tunnel;
|
||||||
|
WelcomeScreenCenterTunnel: Tunnel;
|
||||||
|
FooterCenterTunnel: Tunnel;
|
||||||
|
DefaultSidebarTriggerTunnel: Tunnel;
|
||||||
|
DefaultSidebarTabTriggersTunnel: Tunnel;
|
||||||
|
jotaiScope: symbol;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TunnelsContext = React.createContext<TunnelsContextValue>(null!);
|
||||||
|
|
||||||
|
export const useTunnels = () => React.useContext(TunnelsContext);
|
||||||
|
|
||||||
|
export const useInitializeTunnels = () => {
|
||||||
|
return React.useMemo((): TunnelsContextValue => {
|
||||||
|
return {
|
||||||
|
MainMenuTunnel: tunnel(),
|
||||||
|
WelcomeScreenMenuHintTunnel: tunnel(),
|
||||||
|
WelcomeScreenToolbarHintTunnel: tunnel(),
|
||||||
|
WelcomeScreenHelpHintTunnel: tunnel(),
|
||||||
|
WelcomeScreenCenterTunnel: tunnel(),
|
||||||
|
FooterCenterTunnel: tunnel(),
|
||||||
|
DefaultSidebarTriggerTunnel: tunnel(),
|
||||||
|
DefaultSidebarTabTriggersTunnel: tunnel(),
|
||||||
|
jotaiScope: Symbol(),
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
5
src/context/ui-appState.ts
Normal file
5
src/context/ui-appState.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
import { UIAppState } from "../types";
|
||||||
|
|
||||||
|
export const UIAppStateContext = React.createContext<UIAppState>(null!);
|
||||||
|
export const useUIAppState = () => React.useContext(UIAppStateContext);
|
|
@ -567,7 +567,7 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-button {
|
.default-sidebar-trigger {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,10 +78,13 @@
|
||||||
|
|
||||||
--color-selection: #6965db;
|
--color-selection: #6965db;
|
||||||
|
|
||||||
|
--color-icon-white: #{$oc-white};
|
||||||
|
|
||||||
--color-primary: #6965db;
|
--color-primary: #6965db;
|
||||||
--color-primary-darker: #5b57d1;
|
--color-primary-darker: #5b57d1;
|
||||||
--color-primary-darkest: #4a47b1;
|
--color-primary-darkest: #4a47b1;
|
||||||
--color-primary-light: #e3e2fe;
|
--color-primary-light: #e3e2fe;
|
||||||
|
--color-primary-light-darker: #d7d5ff;
|
||||||
|
|
||||||
--color-gray-10: #f5f5f5;
|
--color-gray-10: #f5f5f5;
|
||||||
--color-gray-20: #ebebeb;
|
--color-gray-20: #ebebeb;
|
||||||
|
@ -161,10 +164,13 @@
|
||||||
// will be inverted to a lighter color.
|
// will be inverted to a lighter color.
|
||||||
--color-selection: #3530c4;
|
--color-selection: #3530c4;
|
||||||
|
|
||||||
|
--color-icon-white: var(--color-gray-90);
|
||||||
|
|
||||||
--color-primary: #a8a5ff;
|
--color-primary: #a8a5ff;
|
||||||
--color-primary-darker: #b2aeff;
|
--color-primary-darker: #b2aeff;
|
||||||
--color-primary-darkest: #beb9ff;
|
--color-primary-darkest: #beb9ff;
|
||||||
--color-primary-light: #4f4d6f;
|
--color-primary-light: #4f4d6f;
|
||||||
|
--color-primary-light-darker: #43415e;
|
||||||
|
|
||||||
--color-text-warning: var(--color-gray-80);
|
--color-text-warning: var(--color-gray-80);
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,14 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--button-hover-bg, var(--island-bg-color));
|
background-color: var(--button-hover-bg, var(--island-bg-color));
|
||||||
border-color: var(--button-hover-border, var(--default-border-color));
|
border-color: var(
|
||||||
|
--button-hover-border,
|
||||||
|
var(--button-border, var(--default-border-color))
|
||||||
|
);
|
||||||
|
color: var(
|
||||||
|
--button-hover-color,
|
||||||
|
var(--button-color, var(--text-primary-color, inherit))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
|
@ -81,11 +88,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--color-primary-light);
|
background-color: var(--button-selected-bg, var(--color-primary-light));
|
||||||
border-color: var(--color-primary-light);
|
border-color: var(--button-selected-border, var(--color-primary-light));
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-primary-light);
|
background-color: var(
|
||||||
|
--button-selected-hover-bg,
|
||||||
|
var(--color-primary-light)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
|
|
@ -14,7 +14,14 @@ import { getCommonBoundingBox } from "../element/bounds";
|
||||||
import { AbortError } from "../errors";
|
import { AbortError } from "../errors";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { URL_HASH_KEYS, URL_QUERY_KEYS, APP_NAME, EVENT } from "../constants";
|
import {
|
||||||
|
URL_HASH_KEYS,
|
||||||
|
URL_QUERY_KEYS,
|
||||||
|
APP_NAME,
|
||||||
|
EVENT,
|
||||||
|
DEFAULT_SIDEBAR,
|
||||||
|
LIBRARY_SIDEBAR_TAB,
|
||||||
|
} from "../constants";
|
||||||
|
|
||||||
export const libraryItemsAtom = atom<{
|
export const libraryItemsAtom = atom<{
|
||||||
status: "loading" | "loaded";
|
status: "loading" | "loaded";
|
||||||
|
@ -148,7 +155,9 @@ class Library {
|
||||||
defaultStatus?: "unpublished" | "published";
|
defaultStatus?: "unpublished" | "published";
|
||||||
}): Promise<LibraryItems> => {
|
}): Promise<LibraryItems> => {
|
||||||
if (openLibraryMenu) {
|
if (openLibraryMenu) {
|
||||||
this.app.setState({ openSidebar: "library" });
|
this.app.setState({
|
||||||
|
openSidebar: { name: DEFAULT_SIDEBAR.name, tab: LIBRARY_SIDEBAR_TAB },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.setLibrary(() => {
|
return this.setLibrary(() => {
|
||||||
|
@ -174,6 +183,13 @@ class Library {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
if (prompt) {
|
||||||
|
// focus container if we've prompted. We focus conditionally
|
||||||
|
// lest `props.autoFocus` is disabled (in which case we should
|
||||||
|
// focus only on user action such as prompt confirm)
|
||||||
|
this.app.focusContainer();
|
||||||
|
}
|
||||||
|
|
||||||
if (merge) {
|
if (merge) {
|
||||||
resolve(mergeLibraryItems(this.lastLibraryItems, nextItems));
|
resolve(mergeLibraryItems(this.lastLibraryItems, nextItems));
|
||||||
} else {
|
} else {
|
||||||
|
@ -186,8 +202,6 @@ class Library {
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).finally(() => {
|
|
||||||
this.app.focusContainer();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
PRECEDING_ELEMENT_KEY,
|
PRECEDING_ELEMENT_KEY,
|
||||||
FONT_FAMILY,
|
FONT_FAMILY,
|
||||||
ROUNDNESS,
|
ROUNDNESS,
|
||||||
|
DEFAULT_SIDEBAR,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
|
@ -440,21 +441,15 @@ const LegacyAppStateMigrations: {
|
||||||
defaultAppState: ReturnType<typeof getDefaultAppState>,
|
defaultAppState: ReturnType<typeof getDefaultAppState>,
|
||||||
) => [LegacyAppState[K][1], AppState[LegacyAppState[K][1]]];
|
) => [LegacyAppState[K][1], AppState[LegacyAppState[K][1]]];
|
||||||
} = {
|
} = {
|
||||||
isLibraryOpen: (appState, defaultAppState) => {
|
isSidebarDocked: (appState, defaultAppState) => {
|
||||||
return [
|
return [
|
||||||
"openSidebar",
|
"defaultSidebarDockedPreference",
|
||||||
"isLibraryOpen" in appState
|
appState.isSidebarDocked ??
|
||||||
? appState.isLibraryOpen
|
coalesceAppStateValue(
|
||||||
? "library"
|
"defaultSidebarDockedPreference",
|
||||||
: null
|
appState,
|
||||||
: coalesceAppStateValue("openSidebar", appState, defaultAppState),
|
defaultAppState,
|
||||||
];
|
),
|
||||||
},
|
|
||||||
isLibraryMenuDocked: (appState, defaultAppState) => {
|
|
||||||
return [
|
|
||||||
"isSidebarDocked",
|
|
||||||
appState.isLibraryMenuDocked ??
|
|
||||||
coalesceAppStateValue("isSidebarDocked", appState, defaultAppState),
|
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -526,13 +521,10 @@ export const restoreAppState = (
|
||||||
: appState.zoom?.value
|
: appState.zoom?.value
|
||||||
? appState.zoom
|
? appState.zoom
|
||||||
: defaultAppState.zoom,
|
: defaultAppState.zoom,
|
||||||
// when sidebar docked and user left it open in last session,
|
|
||||||
// keep it open. If not docked, keep it closed irrespective of last state.
|
|
||||||
openSidebar:
|
openSidebar:
|
||||||
nextAppState.openSidebar === "library"
|
// string (legacy)
|
||||||
? nextAppState.isSidebarDocked
|
typeof (appState.openSidebar as any as string) === "string"
|
||||||
? "library"
|
? { name: DEFAULT_SIDEBAR.name }
|
||||||
: null
|
|
||||||
: nextAppState.openSidebar,
|
: nextAppState.openSidebar,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,10 +27,8 @@ export interface ExportedDataState {
|
||||||
* Don't consume on its own.
|
* Don't consume on its own.
|
||||||
*/
|
*/
|
||||||
export type LegacyAppState = {
|
export type LegacyAppState = {
|
||||||
/** @deprecated #5663 TODO remove 22-12-15 */
|
/** @deprecated #6213 TODO remove 23-06-01 */
|
||||||
isLibraryOpen: [boolean, "openSidebar"];
|
isSidebarDocked: [boolean, "defaultSidebarDockedPreference"];
|
||||||
/** @deprecated #5663 TODO remove 22-12-15 */
|
|
||||||
isLibraryMenuDocked: [boolean, "isSidebarDocked"];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ImportedDataState {
|
export interface ImportedDataState {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppState, ExcalidrawProps, Point } from "../types";
|
import { AppState, ExcalidrawProps, Point, UIAppState } from "../types";
|
||||||
import {
|
import {
|
||||||
getShortcutKey,
|
getShortcutKey,
|
||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
|
@ -297,10 +297,11 @@ export const getContextMenuLabel = (
|
||||||
: "labels.link.create";
|
: "labels.link.create";
|
||||||
return label;
|
return label;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLinkHandleFromCoords = (
|
export const getLinkHandleFromCoords = (
|
||||||
[x1, y1, x2, y2]: Bounds,
|
[x1, y1, x2, y2]: Bounds,
|
||||||
angle: number,
|
angle: number,
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
): [x: number, y: number, width: number, height: number] => {
|
): [x: number, y: number, width: number, height: number] => {
|
||||||
const size = DEFAULT_LINK_SIZE;
|
const size = DEFAULT_LINK_SIZE;
|
||||||
const linkWidth = size / appState.zoom.value;
|
const linkWidth = size / appState.zoom.value;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { AppState } from "../types";
|
|
||||||
import { NonDeletedExcalidrawElement } from "./types";
|
import { NonDeletedExcalidrawElement } from "./types";
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
|
import { UIAppState } from "../types";
|
||||||
|
|
||||||
export const showSelectedShapeActions = (
|
export const showSelectedShapeActions = (
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
) =>
|
) =>
|
||||||
Boolean(
|
Boolean(
|
||||||
|
|
|
@ -7,8 +7,9 @@ import {
|
||||||
import { DEFAULT_VERSION } from "../constants";
|
import { DEFAULT_VERSION } from "../constants";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { copyTextToSystemClipboard } from "../clipboard";
|
import { copyTextToSystemClipboard } from "../clipboard";
|
||||||
import { AppState } from "../types";
|
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
|
import { UIAppState } from "../types";
|
||||||
|
|
||||||
type StorageSizes = { scene: number; total: number };
|
type StorageSizes = { scene: number; total: number };
|
||||||
|
|
||||||
const STORAGE_SIZE_TIMEOUT = 500;
|
const STORAGE_SIZE_TIMEOUT = 500;
|
||||||
|
@ -23,7 +24,7 @@ const getStorageSizes = debounce((cb: (sizes: StorageSizes) => void) => {
|
||||||
type Props = {
|
type Props = {
|
||||||
setToast: (message: string) => void;
|
setToast: (message: string) => void;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
};
|
};
|
||||||
const CustomStats = (props: Props) => {
|
const CustomStats = (props: Props) => {
|
||||||
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
|
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { getFrame } from "../../utils";
|
||||||
|
|
||||||
const exportToExcalidrawPlus = async (
|
const exportToExcalidrawPlus = async (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => {
|
) => {
|
||||||
const firebase = await loadFirebaseStorage();
|
const firebase = await loadFirebaseStorage();
|
||||||
|
@ -75,7 +75,7 @@ const exportToExcalidrawPlus = async (
|
||||||
|
|
||||||
export const ExportToExcalidrawPlus: React.FC<{
|
export const ExportToExcalidrawPlus: React.FC<{
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: Partial<AppState>;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
onError: (error: Error) => void;
|
onError: (error: Error) => void;
|
||||||
}> = ({ elements, appState, files, onError }) => {
|
}> = ({ elements, appState, files, onError }) => {
|
||||||
|
|
|
@ -285,7 +285,7 @@ export const loadScene = async (
|
||||||
|
|
||||||
export const exportToBackend = async (
|
export const exportToBackend = async (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => {
|
) => {
|
||||||
const encryptionKey = await generateEncryptionKey("string");
|
const encryptionKey = await generateEncryptionKey("string");
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
ExcalidrawImperativeAPI,
|
ExcalidrawImperativeAPI,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
ExcalidrawInitialDataState,
|
ExcalidrawInitialDataState,
|
||||||
|
UIAppState,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
|
@ -552,7 +553,7 @@ const ExcalidrawWrapper = () => {
|
||||||
|
|
||||||
const onExportToBackend = async (
|
const onExportToBackend = async (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
) => {
|
) => {
|
||||||
|
@ -583,7 +584,7 @@ const ExcalidrawWrapper = () => {
|
||||||
|
|
||||||
const renderCustomStats = (
|
const renderCustomStats = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
<CustomStats
|
<CustomStats
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
export const useOutsideClickHook = (handler: (event: Event) => void) => {
|
export const useOutsideClick = (handler: (event: Event) => void) => {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "تعذر الحفظ في قاعدة البيانات. إذا استمرت المشاكل، يفضل أن تحفظ ملفك محليا كي لا تفقد عملك.",
|
"collabSaveFailed": "تعذر الحفظ في قاعدة البيانات. إذا استمرت المشاكل، يفضل أن تحفظ ملفك محليا كي لا تفقد عملك.",
|
||||||
"collabSaveFailed_sizeExceeded": "تعذر الحفظ في قاعدة البيانات، يبدو أن القماش كبير للغاية، يفضّل حفظ الملف محليا كي لا تفقد عملك.",
|
"collabSaveFailed_sizeExceeded": "تعذر الحفظ في قاعدة البيانات، يبدو أن القماش كبير للغاية، يفضّل حفظ الملف محليا كي لا تفقد عملك.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "",
|
"collabSaveFailed": "",
|
||||||
"collabSaveFailed_sizeExceeded": "",
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Подсказка: пробвайте да приближите далечните елементи по-близко."
|
"canvasTooBigTip": "Подсказка: пробвайте да приближите далечните елементи по-близко."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Среща грешка. Опитайте ",
|
"headingMain": "Среща грешка. Опитайте <button>презареждане на страницата.</button>",
|
||||||
"headingMain_button": "презареждане на страницата.",
|
"clearCanvasMessage": "Ако презареждането не работи, опитайте <button>изчистване на платното.</button>",
|
||||||
"clearCanvasMessage": "Ако презареждането не работи, опитайте ",
|
|
||||||
"clearCanvasMessage_button": "изчистване на платното.",
|
|
||||||
"clearCanvasCaveat": " Това ще доведе до загуба на работа ",
|
"clearCanvasCaveat": " Това ще доведе до загуба на работа ",
|
||||||
"trackedToSentry_pre": "Грешката с идентификатор ",
|
"trackedToSentry": "Грешката с идентификатор {{eventId}} беше проследен в нашата система.",
|
||||||
"trackedToSentry_post": " беше проследен в нашата система.",
|
"openIssueMessage": "Бяхме много предпазливи да не включите информацията за вашата сцена при грешката. Ако сцената ви не е частна, моля, помислете за последващи действия на нашата <button>тракер за грешки.</button> Моля, включете информация по-долу, като я копирате и добавите в GitHub.",
|
||||||
"openIssueMessage_pre": "Бяхме много предпазливи да не включите информацията за вашата сцена при грешката. Ако сцената ви не е частна, моля, помислете за последващи действия на нашата ",
|
|
||||||
"openIssueMessage_button": "тракер за грешки.",
|
|
||||||
"openIssueMessage_post": " Моля, включете информация по-долу, като я копирате и добавите в GitHub.",
|
|
||||||
"sceneContent": "Съдържание на сцената:"
|
"sceneContent": "Съдържание на сцената:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,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": "",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "পেস্ট করুন",
|
"paste": "পেস্ট করুন",
|
||||||
"pasteAsPlaintext": "",
|
"pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন",
|
||||||
"pasteCharts": "চার্ট পেস্ট করুন",
|
"pasteCharts": "চার্ট পেস্ট করুন",
|
||||||
"selectAll": "সবটা সিলেক্ট করুন",
|
"selectAll": "সবটা সিলেক্ট করুন",
|
||||||
"multiSelect": "একাধিক সিলেক্ট করুন",
|
"multiSelect": "একাধিক সিলেক্ট করুন",
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
"veryLarge": "অনেক বড়",
|
"veryLarge": "অনেক বড়",
|
||||||
"solid": "দৃঢ়",
|
"solid": "দৃঢ়",
|
||||||
"hachure": "ভ্রুলেখা",
|
"hachure": "ভ্রুলেখা",
|
||||||
"zigzag": "",
|
"zigzag": "আঁকাবাঁকা",
|
||||||
"crossHatch": "ক্রস হ্যাচ",
|
"crossHatch": "ক্রস হ্যাচ",
|
||||||
"thin": "পাতলা",
|
"thin": "পাতলা",
|
||||||
"bold": "পুরু",
|
"bold": "পুরু",
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
"layers": "মাত্রা",
|
"layers": "মাত্রা",
|
||||||
"actions": "ক্রিয়া",
|
"actions": "ক্রিয়া",
|
||||||
"language": "ভাষা",
|
"language": "ভাষা",
|
||||||
"liveCollaboration": "",
|
"liveCollaboration": "সরাসরি পারস্পরিক সহযোগিতা...",
|
||||||
"duplicateSelection": "সদৃশ সিলেক্ট",
|
"duplicateSelection": "সদৃশ সিলেক্ট",
|
||||||
"untitled": "অনামী",
|
"untitled": "অনামী",
|
||||||
"name": "নাম",
|
"name": "নাম",
|
||||||
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "",
|
"collabSaveFailed": "",
|
||||||
"collabSaveFailed_sizeExceeded": "",
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "বিশেষ্য: দূরতম উপাদানগুলোকে একটু কাছাকাছি নিয়ে যাওয়ার চেষ্টা করুন।"
|
"canvasTooBigTip": "বিশেষ্য: দূরতম উপাদানগুলোকে একটু কাছাকাছি নিয়ে যাওয়ার চেষ্টা করুন।"
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "একটি ত্রুটির সম্মুখীন হয়েছে৷ চেষ্টা করুন ",
|
"headingMain": "একটি ত্রুটির সম্মুখীন হয়েছে৷ চেষ্টা করুন <button>পৃষ্ঠাটি পুনরায় লোড করার।</button>",
|
||||||
"headingMain_button": "পৃষ্ঠাটি পুনরায় লোড করার।",
|
"clearCanvasMessage": "যদি পুনরায় লোড করা কাজ না করে, চেষ্টা করুন <button>ক্যানভাস পরিষ্কার করার।</button>",
|
||||||
"clearCanvasMessage": "যদি পুনরায় লোড করা কাজ না করে, চেষ্টা করুন ",
|
|
||||||
"clearCanvasMessage_button": "ক্যানভাস পরিষ্কার করার।",
|
|
||||||
"clearCanvasCaveat": " এর ফলে কাজের ক্ষতি হবে ",
|
"clearCanvasCaveat": " এর ফলে কাজের ক্ষতি হবে ",
|
||||||
"trackedToSentry_pre": "ত্রুটি ",
|
"trackedToSentry": "ত্রুটি {{eventId}} আমাদের সিস্টেমে ট্র্যাক করা হয়েছিল।",
|
||||||
"trackedToSentry_post": " আমাদের সিস্টেমে ট্র্যাক করা হয়েছিল।",
|
"openIssueMessage": "আমরা ত্রুটিতে আপনার দৃশ্যের তথ্য অন্তর্ভুক্ত না করার জন্য খুব সতর্ক ছিলাম। আপনার দৃশ্য ব্যক্তিগত না হলে, আমাদের অনুসরণ করার কথা বিবেচনা করুন <button>ত্রুটি ইতিবৃত্ত।</button> অনুগ্রহ করে GitHub ইস্যুতে অনুলিপি এবং পেস্ট করে নীচের তথ্য অন্তর্ভুক্ত করুন।",
|
||||||
"openIssueMessage_pre": "আমরা ত্রুটিতে আপনার দৃশ্যের তথ্য অন্তর্ভুক্ত না করার জন্য খুব সতর্ক ছিলাম। আপনার দৃশ্য ব্যক্তিগত না হলে, আমাদের অনুসরণ করার কথা বিবেচনা করুন ",
|
|
||||||
"openIssueMessage_button": "ত্রুটি ইতিবৃত্ত।",
|
|
||||||
"openIssueMessage_post": " অনুগ্রহ করে GitHub ইস্যুতে অনুলিপি এবং পেস্ট করে নীচের তথ্য অন্তর্ভুক্ত করুন।",
|
|
||||||
"sceneContent": "দৃশ্য বিষয়বস্তু:"
|
"sceneContent": "দৃশ্য বিষয়বস্তু:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,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": "",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "No s'ha pogut desar a la base de dades de fons. Si els problemes persisteixen, hauríeu de desar el fitxer localment per assegurar-vos que no perdeu el vostre treball.",
|
"collabSaveFailed": "No s'ha pogut desar a la base de dades de fons. Si els problemes persisteixen, hauríeu de desar el fitxer localment per assegurar-vos que no perdeu el vostre treball.",
|
||||||
"collabSaveFailed_sizeExceeded": "No s'ha pogut desar a la base de dades de fons, sembla que el llenç és massa gran. Hauríeu de desar el fitxer localment per assegurar-vos que no perdeu el vostre treball.",
|
"collabSaveFailed_sizeExceeded": "No s'ha pogut desar a la base de dades de fons, sembla que el llenç és massa gran. Hauríeu de desar el fitxer localment per assegurar-vos que no perdeu el vostre treball.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Consell: proveu d’acostar una mica els elements més allunyats."
|
"canvasTooBigTip": "Consell: proveu d’acostar una mica els elements més allunyats."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "S'ha produït un error. Proveu ",
|
"headingMain": "S'ha produït un error. Proveu <button>recarregar la pàgina.</button>",
|
||||||
"headingMain_button": "recarregar la pàgina.",
|
"clearCanvasMessage": "Si la recàrrega no funciona, proveu <button>esborrar el llenç.</button>",
|
||||||
"clearCanvasMessage": "Si la recàrrega no funciona, proveu ",
|
|
||||||
"clearCanvasMessage_button": "esborrar el llenç.",
|
|
||||||
"clearCanvasCaveat": " Això resultarà en la pèrdua de feina ",
|
"clearCanvasCaveat": " Això resultarà en la pèrdua de feina ",
|
||||||
"trackedToSentry_pre": "L'error amb l'identificador ",
|
"trackedToSentry": "L'error amb l'identificador {{eventId}} s'ha rastrejat en el nostre sistema.",
|
||||||
"trackedToSentry_post": " s'ha rastrejat en el nostre sistema.",
|
"openIssueMessage": "Anàvem amb molta cura de no incloure la informació de la vostra escena en l'error. Si l'escena no és privada, podeu fer-ne el seguiment al nostre <button>rastrejador d'errors.</button> Incloeu la informació a continuació copiant i enganxant a GitHub Issues.",
|
||||||
"openIssueMessage_pre": "Anàvem amb molta cura de no incloure la informació de la vostra escena en l'error. Si l'escena no és privada, podeu fer-ne el seguiment al nostre ",
|
|
||||||
"openIssueMessage_button": "rastrejador d'errors.",
|
|
||||||
"openIssueMessage_post": " Incloeu la informació a continuació copiant i enganxant a GitHub Issues.",
|
|
||||||
"sceneContent": "Contingut de l'escena:"
|
"sceneContent": "Contingut de l'escena:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Requerit",
|
"required": "Requerit",
|
||||||
"website": "Introduïu una URL vàlida"
|
"website": "Introduïu una URL vàlida"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Envieu la vostra biblioteca perquè sigui inclosa al <link>repositori públic</link>per tal que altres persones puguin fer-ne ús en els seus dibuixos.",
|
||||||
"pre": "Envieu la vostra biblioteca perquè sigui inclosa al ",
|
"noteGuidelines": "La biblioteca ha de ser aprovada manualment. Si us plau, llegiu les <link>directrius</link> abans d'enviar-hi res. Necessitareu un compte de GitHub per a comunicar i fer-hi canvis si cal, però no és requisit imprescindible.",
|
||||||
"link": "repositori públic",
|
"noteLicense": "Quan l'envieu, accepteu que la biblioteca sigui publicada sota la <link>llicència MIT, </link>que, en resum, vol dir que qualsevol persona pot fer-ne ús sense restriccions.",
|
||||||
"post": "per tal que altres persones puguin fer-ne ús en els seus dibuixos."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "La biblioteca ha de ser aprovada manualment. Si us plau, llegiu les ",
|
|
||||||
"link": "directrius",
|
|
||||||
"post": " abans d'enviar-hi res. Necessitareu un compte de GitHub per a comunicar i fer-hi canvis si cal, però no és requisit imprescindible."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Quan l'envieu, accepteu que la biblioteca sigui publicada sota la ",
|
|
||||||
"link": "llicència MIT, ",
|
|
||||||
"post": "que, en resum, vol dir que qualsevol persona pot fer-ne ús sense restriccions."
|
|
||||||
},
|
|
||||||
"noteItems": "Cada element de la biblioteca ha de tenir el seu propi nom per tal que sigui filtrable. S'hi inclouran els elements següents:",
|
"noteItems": "Cada element de la biblioteca ha de tenir el seu propi nom per tal que sigui filtrable. S'hi inclouran els elements següents:",
|
||||||
"atleastOneLibItem": "Si us plau, seleccioneu si més no un element de la biblioteca per a començar",
|
"atleastOneLibItem": "Si us plau, seleccioneu si més no un element de la biblioteca per a començar",
|
||||||
"republishWarning": "Nota: alguns dels elements seleccionats s'han marcat com a publicats/enviats. Només hauríeu de reenviar elements quan actualitzeu una biblioteca existent."
|
"republishWarning": "Nota: alguns dels elements seleccionats s'han marcat com a publicats/enviats. Només hauríeu de reenviar elements quan actualitzeu una biblioteca existent."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Biblioteca enviada",
|
"title": "Biblioteca enviada",
|
||||||
"content": "Gràcies, {{authorName}}. La vostra biblioteca ha estat enviada per a ser revisada. Podeu comprovar-ne l'estat",
|
"content": "Gràcies, {{authorName}}. La vostra biblioteca ha estat enviada per a ser revisada. Podeu comprovar-ne l'estat<link>aquí</link>"
|
||||||
"link": "aquí"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Restableix la biblioteca",
|
"resetLibrary": "Restableix la biblioteca",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "",
|
"collabSaveFailed": "",
|
||||||
"collabSaveFailed_sizeExceeded": "",
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": ""
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "",
|
"headingMain": "",
|
||||||
"headingMain_button": "",
|
|
||||||
"clearCanvasMessage": "",
|
"clearCanvasMessage": "",
|
||||||
"clearCanvasMessage_button": "",
|
|
||||||
"clearCanvasCaveat": "",
|
"clearCanvasCaveat": "",
|
||||||
"trackedToSentry_pre": "Chyba identifikátoru ",
|
"trackedToSentry": "Chyba identifikátoru {{eventId}} byl zaznamenán v našem systému.",
|
||||||
"trackedToSentry_post": " byl zaznamenán v našem systému.",
|
"openIssueMessage": "",
|
||||||
"openIssueMessage_pre": "",
|
|
||||||
"openIssueMessage_button": "",
|
|
||||||
"openIssueMessage_post": "",
|
|
||||||
"sceneContent": ""
|
"sceneContent": ""
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Povinné",
|
"required": "Povinné",
|
||||||
"website": "Zadejte platnou URL adresu"
|
"website": "Zadejte platnou URL adresu"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Odešlete svou knihovnu, pro zařazení do <link>veřejného úložiště knihoven</link>, odkud ji budou moci při kreslení využít i ostatní uživatelé.",
|
||||||
"pre": "Odešlete svou knihovnu, pro zařazení do ",
|
"noteGuidelines": "Knihovna musí být nejdříve ručně schválena. Přečtěte si prosím <link>pokyny</link>",
|
||||||
"link": "veřejného úložiště knihoven",
|
"noteLicense": "",
|
||||||
"post": ", odkud ji budou moci při kreslení využít i ostatní uživatelé."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "Knihovna musí být nejdříve ručně schválena. Přečtěte si prosím ",
|
|
||||||
"link": "pokyny",
|
|
||||||
"post": ""
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "",
|
|
||||||
"link": "",
|
|
||||||
"post": ""
|
|
||||||
},
|
|
||||||
"noteItems": "",
|
"noteItems": "",
|
||||||
"atleastOneLibItem": "",
|
"atleastOneLibItem": "",
|
||||||
"republishWarning": ""
|
"republishWarning": ""
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Knihovna byla odeslána",
|
"title": "Knihovna byla odeslána",
|
||||||
"content": "",
|
"content": ""
|
||||||
"link": ""
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "",
|
"resetLibrary": "",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "",
|
"collabSaveFailed": "",
|
||||||
"collabSaveFailed_sizeExceeded": "",
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": ""
|
"canvasTooBigTip": ""
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "",
|
"headingMain": "",
|
||||||
"headingMain_button": "",
|
|
||||||
"clearCanvasMessage": "",
|
"clearCanvasMessage": "",
|
||||||
"clearCanvasMessage_button": "",
|
|
||||||
"clearCanvasCaveat": "",
|
"clearCanvasCaveat": "",
|
||||||
"trackedToSentry_pre": "",
|
"trackedToSentry": "",
|
||||||
"trackedToSentry_post": "",
|
"openIssueMessage": "<button></button> Kopiere og indsæt venligst oplysningerne nedenfor i et GitHub problem.",
|
||||||
"openIssueMessage_pre": "",
|
|
||||||
"openIssueMessage_button": "",
|
|
||||||
"openIssueMessage_post": " Kopiere og indsæt venligst oplysningerne nedenfor i et GitHub problem.",
|
|
||||||
"sceneContent": "Scene indhold:"
|
"sceneContent": "Scene indhold:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,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": "",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Keine Speicherung in der Backend-Datenbank möglich. Wenn die Probleme weiterhin bestehen, solltest Du Deine Datei lokal speichern, um sicherzustellen, dass Du Deine Arbeit nicht verlierst.",
|
"collabSaveFailed": "Keine Speicherung in der Backend-Datenbank möglich. Wenn die Probleme weiterhin bestehen, solltest Du Deine Datei lokal speichern, um sicherzustellen, dass Du Deine Arbeit nicht verlierst.",
|
||||||
"collabSaveFailed_sizeExceeded": "Keine Speicherung in der Backend-Datenbank möglich, die Zeichenfläche scheint zu groß zu sein. Du solltest Deine Datei lokal speichern, um sicherzustellen, dass Du Deine Arbeit nicht verlierst.",
|
"collabSaveFailed_sizeExceeded": "Keine Speicherung in der Backend-Datenbank möglich, die Zeichenfläche scheint zu groß zu sein. Du solltest Deine Datei lokal speichern, um sicherzustellen, dass Du Deine Arbeit nicht verlierst.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "Sieht so aus, als ob du den Brave Browser benutzt mit der",
|
"line1": "Sieht so aus, als ob Du den Brave-Browser verwendest und die <bold>aggressive Blockierung von Fingerabdrücken</bold> aktiviert hast.",
|
||||||
"aggressive_block_fingerprint": "\"Fingerprinting aggressiv blockieren\"",
|
"line2": "Dies könnte dazu führen, dass die <bold>Textelemente</bold> in Ihren Zeichnungen zerstört werden.",
|
||||||
"setting_enabled": "Einstellung aktiviert",
|
"line3": "Wir empfehlen dringend, diese Einstellung zu deaktivieren. Dazu kannst Du <link>diesen Schritten</link> folgen.",
|
||||||
"break": "Dies könnte zur inkorrekten Darstellung der",
|
"line4": "Wenn die Deaktivierung dieser Einstellung die fehlerhafte Anzeige von Textelementen nicht behebt, öffne bitte ein <issueLink>Ticket</issueLink> auf unserem GitHub oder schreibe uns auf <discordLink>Discord</discordLink>"
|
||||||
"text_elements": "Textelemente",
|
|
||||||
"in_your_drawings": "in deinen Zeichnungen führen",
|
|
||||||
"strongly_recommend": "Wir empfehlen dringend, diese Einstellung zu deaktivieren. Du kannst",
|
|
||||||
"steps": "diesen Schritten entsprechend",
|
|
||||||
"how": "folgen",
|
|
||||||
"disable_setting": " Wenn die Deaktivierung dieser Einstellung nicht zu einer korrekten Textdarstellung führt, öffne bitte einen",
|
|
||||||
"issue": "Issue",
|
|
||||||
"write": "auf GitHub, oder schreibe uns auf",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Tipp: Schiebe die am weitesten entfernten Elemente ein wenig näher zusammen."
|
"canvasTooBigTip": "Tipp: Schiebe die am weitesten entfernten Elemente ein wenig näher zusammen."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Es ist ein Fehler aufgetreten. Versuche ",
|
"headingMain": "Es ist ein Fehler aufgetreten. Versuche <button>die Seite neu zu laden.</button>",
|
||||||
"headingMain_button": "die Seite neu zu laden.",
|
"clearCanvasMessage": "Wenn das Neuladen nicht funktioniert, versuche <button>die Zeichenfläche zu löschen.</button>",
|
||||||
"clearCanvasMessage": "Wenn das Neuladen nicht funktioniert, versuche ",
|
|
||||||
"clearCanvasMessage_button": "die Zeichenfläche zu löschen.",
|
|
||||||
"clearCanvasCaveat": " Dies wird zum Verlust von Daten führen ",
|
"clearCanvasCaveat": " Dies wird zum Verlust von Daten führen ",
|
||||||
"trackedToSentry_pre": "Der Fehler mit der Kennung ",
|
"trackedToSentry": "Der Fehler mit der Kennung {{eventId}} wurde in unserem System registriert.",
|
||||||
"trackedToSentry_post": " 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_pre": "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 ",
|
|
||||||
"openIssueMessage_button": "Bug-Tracker.",
|
|
||||||
"openIssueMessage_post": " Bitte teile die unten stehenden Informationen mit uns im GitHub Issue (Kopieren und Einfügen).",
|
|
||||||
"sceneContent": "Zeichnungsinhalt:"
|
"sceneContent": "Zeichnungsinhalt:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Erforderlich",
|
"required": "Erforderlich",
|
||||||
"website": "Gültige URL eingeben"
|
"website": "Gültige URL eingeben"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Sende deine Bibliothek ein, um in die <link>öffentliche Bibliotheks-Repository aufgenommen zu werden</link>damit andere Nutzer sie in ihren Zeichnungen verwenden können.",
|
||||||
"pre": "Sende deine Bibliothek ein, um in die ",
|
"noteGuidelines": "Die Bibliothek muss zuerst manuell freigegeben werden. Bitte lies die <link>Richtlinien</link> vor dem Absenden. Du benötigst ein GitHub-Konto, um zu kommunizieren und Änderungen vorzunehmen, falls erforderlich, aber es ist nicht unbedingt erforderlich.",
|
||||||
"link": "öffentliche Bibliotheks-Repository aufgenommen zu werden",
|
"noteLicense": "Mit dem Absenden stimmst du zu, dass die Bibliothek unter der <link>MIT-Lizenz, </link>die zusammengefasst beinhaltet, dass jeder sie ohne Einschränkungen nutzen kann.",
|
||||||
"post": "damit andere Nutzer sie in ihren Zeichnungen verwenden können."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "Die Bibliothek muss zuerst manuell freigegeben werden. Bitte lies die ",
|
|
||||||
"link": "Richtlinien",
|
|
||||||
"post": " vor dem Absenden. Du benötigst ein GitHub-Konto, um zu kommunizieren und Änderungen vorzunehmen, falls erforderlich, aber es ist nicht unbedingt erforderlich."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Mit dem Absenden stimmst du zu, dass die Bibliothek unter der ",
|
|
||||||
"link": "MIT-Lizenz, ",
|
|
||||||
"post": "die zusammengefasst beinhaltet, dass jeder sie ohne Einschränkungen nutzen kann."
|
|
||||||
},
|
|
||||||
"noteItems": "Jedes Bibliothekselement muss einen eigenen Namen haben, damit es gefiltert werden kann. Die folgenden Bibliothekselemente werden hinzugefügt:",
|
"noteItems": "Jedes Bibliothekselement muss einen eigenen Namen haben, damit es gefiltert werden kann. Die folgenden Bibliothekselemente werden hinzugefügt:",
|
||||||
"atleastOneLibItem": "Bitte wähle mindestens ein Bibliothekselement aus, um zu beginnen",
|
"atleastOneLibItem": "Bitte wähle mindestens ein Bibliothekselement aus, um zu beginnen",
|
||||||
"republishWarning": "Hinweis: Einige der ausgewählten Elemente sind bereits als veröffentlicht/eingereicht markiert. Du solltest Elemente nur erneut einreichen, wenn Du eine existierende Bibliothek oder Einreichung aktualisierst."
|
"republishWarning": "Hinweis: Einige der ausgewählten Elemente sind bereits als veröffentlicht/eingereicht markiert. Du solltest Elemente nur erneut einreichen, wenn Du eine existierende Bibliothek oder Einreichung aktualisierst."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Bibliothek übermittelt",
|
"title": "Bibliothek übermittelt",
|
||||||
"content": "Vielen Dank {{authorName}}. Deine Bibliothek wurde zur Überprüfung eingereicht. Du kannst den Status verfolgen",
|
"content": "Vielen Dank {{authorName}}. Deine Bibliothek wurde zur Überprüfung eingereicht. Du kannst den Status verfolgen<link>hier</link>"
|
||||||
"link": "hier"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Bibliothek zurücksetzen",
|
"resetLibrary": "Bibliothek zurücksetzen",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή. Αν το προβλήματα παραμείνει, θα πρέπει να αποθηκεύσετε το αρχείο σας τοπικά για να βεβαιωθείτε ότι δεν χάνετε την εργασία σας.",
|
"collabSaveFailed": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή. Αν το προβλήματα παραμείνει, θα πρέπει να αποθηκεύσετε το αρχείο σας τοπικά για να βεβαιωθείτε ότι δεν χάνετε την εργασία σας.",
|
||||||
"collabSaveFailed_sizeExceeded": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή, ο καμβάς φαίνεται να είναι πολύ μεγάλος. Θα πρέπει να αποθηκεύσετε το αρχείο τοπικά για να βεβαιωθείτε ότι δεν θα χάσετε την εργασία σας.",
|
"collabSaveFailed_sizeExceeded": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή, ο καμβάς φαίνεται να είναι πολύ μεγάλος. Θα πρέπει να αποθηκεύσετε το αρχείο τοπικά για να βεβαιωθείτε ότι δεν θα χάσετε την εργασία σας.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "Φαίνεται ότι χρησιμοποιείτε το Brave browser με το",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "Αποκλεισμός \"Δακτυλικών Αποτυπωμάτων\"",
|
"line2": "",
|
||||||
"setting_enabled": "ρύθμιση ενεργοποιημένη",
|
"line3": "",
|
||||||
"break": "Αυτό θα μπορούσε να σπάσει το",
|
"line4": ""
|
||||||
"text_elements": "Στοιχεία Κειμένου",
|
|
||||||
"in_your_drawings": "στα σχέδιά σας",
|
|
||||||
"strongly_recommend": "Συνιστούμε να απενεργοποιήσετε αυτή τη ρύθμιση. Μπορείτε να ακολουθήσετε",
|
|
||||||
"steps": "αυτά τα βήματα",
|
|
||||||
"how": "για το πώς να το κάνετε",
|
|
||||||
"disable_setting": " Εάν η απενεργοποίηση αυτής της ρύθμισης δεν διορθώνει την εμφάνιση των στοιχείων κειμένου, παρακαλώ ανοίξτε ένα",
|
|
||||||
"issue": "πρόβλημα",
|
|
||||||
"write": "στο GitHub, ή γράψτε μας στο",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Συμβουλή: προσπαθήστε να μετακινήσετε τα πιο απομακρυσμένα στοιχεία λίγο πιο κοντά μαζί."
|
"canvasTooBigTip": "Συμβουλή: προσπαθήστε να μετακινήσετε τα πιο απομακρυσμένα στοιχεία λίγο πιο κοντά μαζί."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Συνέβη κάποιο σφάλμα. Προσπάθησε ",
|
"headingMain": "Συνέβη κάποιο σφάλμα. Προσπάθησε <button>φόρτωσε ξανά την σελίδα.</button>",
|
||||||
"headingMain_button": "φόρτωσε ξανά την σελίδα.",
|
"clearCanvasMessage": "Εάν το παραπάνω δεν δουλέψει, προσπάθησε <button>καθαρίσετε τον κανβά.</button>",
|
||||||
"clearCanvasMessage": "Εάν το παραπάνω δεν δουλέψει, προσπάθησε ",
|
|
||||||
"clearCanvasMessage_button": "καθαρίσετε τον κανβά.",
|
|
||||||
"clearCanvasCaveat": " Αυτό θα προκαλέσει απώλεια της δουλειάς σου ",
|
"clearCanvasCaveat": " Αυτό θα προκαλέσει απώλεια της δουλειάς σου ",
|
||||||
"trackedToSentry_pre": "Το σφάλμα με αναγνωριστικό ",
|
"trackedToSentry": "Το σφάλμα με αναγνωριστικό {{eventId}} παρακολουθήθηκε στο σύστημά μας.",
|
||||||
"trackedToSentry_post": " παρακολουθήθηκε στο σύστημά μας.",
|
"openIssueMessage": "Ήμασταν πολύ προσεκτικοί για να μην συμπεριλάβουμε τις πληροφορίες της σκηνής σου στο σφάλμα. Αν η σκηνή σου δεν είναι ιδιωτική, παρακαλώ σκέψου να ακολουθήσεις το δικό μας <button>ανιχνευτής σφαλμάτων.</button> Παρακαλώ να συμπεριλάβετε τις παρακάτω πληροφορίες, αντιγράφοντας και επικολλώντας το ζήτημα στο GitHub.",
|
||||||
"openIssueMessage_pre": "Ήμασταν πολύ προσεκτικοί για να μην συμπεριλάβουμε τις πληροφορίες της σκηνής σου στο σφάλμα. Αν η σκηνή σου δεν είναι ιδιωτική, παρακαλώ σκέψου να ακολουθήσεις το δικό μας ",
|
|
||||||
"openIssueMessage_button": "ανιχνευτής σφαλμάτων.",
|
|
||||||
"openIssueMessage_post": " Παρακαλώ να συμπεριλάβετε τις παρακάτω πληροφορίες, αντιγράφοντας και επικολλώντας το ζήτημα στο GitHub.",
|
|
||||||
"sceneContent": "Περιεχόμενο σκηνής:"
|
"sceneContent": "Περιεχόμενο σκηνής:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Απαιτείται",
|
"required": "Απαιτείται",
|
||||||
"website": "Εισάγετε μια έγκυρη διεύθυνση URL"
|
"website": "Εισάγετε μια έγκυρη διεύθυνση URL"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Υποβάλετε τη βιβλιοθήκη σας για να συμπεριληφθεί στο <link>δημόσιο αποθετήριο βιβλιοθήκης</link>ώστε να χρησιμοποιηθεί από άλλα άτομα στα σχέδιά τους.",
|
||||||
"pre": "Υποβάλετε τη βιβλιοθήκη σας για να συμπεριληφθεί στο ",
|
"noteGuidelines": "Η βιβλιοθήκη πρέπει πρώτα να εγκριθεί χειροκίνητα. Παρακαλώ διαβάστε τους <link>οδηγίες</link> πριν την υποβολή. Θα χρειαστείτε έναν λογαριασμό GitHub για την επικοινωνία και για να προβείτε σε αλλαγές εφ' όσον χρειαστεί, αλλά δεν είναι αυστηρή απαίτηση.",
|
||||||
"link": "δημόσιο αποθετήριο βιβλιοθήκης",
|
"noteLicense": "Με την υποβολή, συμφωνείτε ότι η βιβλιοθήκη θα δημοσιευθεί υπό την <link>Άδεια MIT, </link>που εν συντομία σημαίνει ότι ο καθένας μπορεί να τα χρησιμοποιήσει χωρίς περιορισμούς.",
|
||||||
"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>εδώ</link>"
|
||||||
"link": "εδώ"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Καθαρισμός βιβλιοθήκης",
|
"resetLibrary": "Καθαρισμός βιβλιοθήκης",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Couldn't save to the backend database. If problems persist, you should save your file locally to ensure you don't lose your work.",
|
"collabSaveFailed": "Couldn't save to the backend database. If problems persist, you should save your file locally to ensure you don't lose your work.",
|
||||||
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work.",
|
"collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "Looks like you are using Brave browser with the",
|
"line1": "Looks like you are using Brave browser with the <bold>Aggressively Block Fingerprinting</bold> setting enabled.",
|
||||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
"line2": "This could result in breaking the <bold>Text Elements</bold> in your drawings.",
|
||||||
"setting_enabled": "setting enabled",
|
"line3": "We strongly recommend disabling this setting. You can follow <link>these steps</link> on how to do so.",
|
||||||
"break": "This could result in breaking the",
|
"line4": "If disabling this setting doesn't fix the display of text elements, please open an <issueLink>issue</issueLink> on our GitHub, or write us on <discordLink>Discord</discordLink>"
|
||||||
"text_elements": "Text Elements",
|
|
||||||
"in_your_drawings": "in your drawings",
|
|
||||||
"strongly_recommend": "We strongly recommend disabling this setting. You can follow",
|
|
||||||
"steps": "these steps",
|
|
||||||
"how": "on how to do so",
|
|
||||||
"disable_setting": " If disabling this setting doesn't fix the display of text elements, please open an",
|
|
||||||
"issue": "issue",
|
|
||||||
"write": "on our GitHub, or write us on",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Tip: try moving the farthest elements a bit closer together."
|
"canvasTooBigTip": "Tip: try moving the farthest elements a bit closer together."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Encountered an error. Try ",
|
"headingMain": "Encountered an error. Try <button>reloading the page</button>.",
|
||||||
"headingMain_button": "reloading the page.",
|
"clearCanvasMessage": "If reloading doesn't work, try <button>clearing the canvas</button>.",
|
||||||
"clearCanvasMessage": "If reloading doesn't work, try ",
|
|
||||||
"clearCanvasMessage_button": "clearing the canvas.",
|
|
||||||
"clearCanvasCaveat": " This will result in loss of work ",
|
"clearCanvasCaveat": " This will result in loss of work ",
|
||||||
"trackedToSentry_pre": "The error with identifier ",
|
"trackedToSentry": "The error with identifier {{eventId}} was tracked on our system.",
|
||||||
"trackedToSentry_post": " was tracked on our system.",
|
"openIssueMessage": "We were very cautious not to include your scene information on the error. If your scene is not private, please consider following up on our <button>bug tracker</button>. Please include information below by copying and pasting into the GitHub issue.",
|
||||||
"openIssueMessage_pre": "We were very cautious not to include your scene information on the error. If your scene is not private, please consider following up on our ",
|
|
||||||
"openIssueMessage_button": "bug tracker.",
|
|
||||||
"openIssueMessage_post": " Please include information below by copying and pasting into the GitHub issue.",
|
|
||||||
"sceneContent": "Scene content:"
|
"sceneContent": "Scene content:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"website": "Enter a valid URL"
|
"website": "Enter a valid URL"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Submit your library to be included in the <link>public library repository</link> for other people to use in their drawings.",
|
||||||
"pre": "Submit your library to be included in the ",
|
"noteGuidelines": "The library needs to be manually approved first. Please read the <link>guidelines</link> before submitting. You will need a GitHub account to communicate and make changes if requested, but it is not strictly required.",
|
||||||
"link": "public library repository",
|
"noteLicense": "By submitting, you agree the library will be published under the <link>MIT License</link>, which in short means anyone can use them without restrictions.",
|
||||||
"post": "for other people to use in their drawings."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "The library needs to be manually approved first. Please read the ",
|
|
||||||
"link": "guidelines",
|
|
||||||
"post": " before submitting. You will need a GitHub account to communicate and make changes if requested, but it is not strictly required."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "By submitting, you agree the library will be published under the ",
|
|
||||||
"link": "MIT License, ",
|
|
||||||
"post": "which in short means anyone can use them without restrictions."
|
|
||||||
},
|
|
||||||
"noteItems": "Each library item must have its own name so it's filterable. The following library items will be included:",
|
"noteItems": "Each library item must have its own name so it's filterable. The following library items will be included:",
|
||||||
"atleastOneLibItem": "Please select at least one library item to get started",
|
"atleastOneLibItem": "Please select at least one library item to get started",
|
||||||
"republishWarning": "Note: some of the selected items are marked as already published/submitted. You should only resubmit items when updating an existing library or submission."
|
"republishWarning": "Note: some of the selected items are marked as already published/submitted. You should only resubmit items when updating an existing library or submission."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Library submitted",
|
"title": "Library submitted",
|
||||||
"content": "Thank you {{authorName}}. Your library has been submitted for review. You can track the status",
|
"content": "Thank you {{authorName}}. Your library has been submitted for review. You can track the status <link>here</link>"
|
||||||
"link": "here"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Reset library",
|
"resetLibrary": "Reset library",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "No se pudo guardar en la base de datos del backend. Si los problemas persisten, debería guardar su archivo localmente para asegurarse de que no pierde su trabajo.",
|
"collabSaveFailed": "No se pudo guardar en la base de datos del backend. Si los problemas persisten, debería guardar su archivo localmente para asegurarse de que no pierde su trabajo.",
|
||||||
"collabSaveFailed_sizeExceeded": "No se pudo guardar en la base de datos del backend, el lienzo parece ser demasiado grande. Debería guardar el archivo localmente para asegurarse de que no pierde su trabajo.",
|
"collabSaveFailed_sizeExceeded": "No se pudo guardar en la base de datos del backend, el lienzo parece ser demasiado grande. Debería guardar el archivo localmente para asegurarse de que no pierde su trabajo.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "Parece que estás usando el navegador Brave",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "Bloquear huellas dactilares agresivamente",
|
"line2": "",
|
||||||
"setting_enabled": "ajuste activado",
|
"line3": "",
|
||||||
"break": "Esto podría resultar en romper los",
|
"line4": ""
|
||||||
"text_elements": "Elementos de texto",
|
|
||||||
"in_your_drawings": "en tus dibujos",
|
|
||||||
"strongly_recommend": "Recomendamos desactivar esta configuración. Puedes seguir",
|
|
||||||
"steps": "estos pasos",
|
|
||||||
"how": "sobre cómo hacerlo",
|
|
||||||
"disable_setting": " Si deshabilitar esta opción no arregla la visualización de elementos de texto, por favor abre un",
|
|
||||||
"issue": "issue",
|
|
||||||
"write": "en GitHub, o escríbenos en",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Sugerencia: intenta acercar un poco más los elementos más lejanos."
|
"canvasTooBigTip": "Sugerencia: intenta acercar un poco más los elementos más lejanos."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Se encontró un error. Intente ",
|
"headingMain": "Se encontró un error. Intente <button>recargando la página.</button>",
|
||||||
"headingMain_button": "recargando la página.",
|
"clearCanvasMessage": "Si la recarga no funciona, intente <button>limpiando el lienzo.</button>",
|
||||||
"clearCanvasMessage": "Si la recarga no funciona, intente ",
|
|
||||||
"clearCanvasMessage_button": "limpiando el lienzo.",
|
|
||||||
"clearCanvasCaveat": " Esto provocará la pérdida de su trabajo ",
|
"clearCanvasCaveat": " Esto provocará la pérdida de su trabajo ",
|
||||||
"trackedToSentry_pre": "El error con el identificador ",
|
"trackedToSentry": "El error con el identificador {{eventId}} fue rastreado en nuestro sistema.",
|
||||||
"trackedToSentry_post": " fue rastreado en nuestro sistema.",
|
"openIssueMessage": "Fuimos muy cautelosos de no incluir la información de tu escena en el error. Si tu escena no es privada, por favor considera seguir nuestro <button>rastreador de errores.</button> Por favor, incluya la siguiente información copiándola y pegándola en el issue de GitHub.",
|
||||||
"openIssueMessage_pre": "Fuimos muy cautelosos de no incluir la información de tu escena en el error. Si tu escena no es privada, por favor considera seguir nuestro ",
|
|
||||||
"openIssueMessage_button": "rastreador de errores.",
|
|
||||||
"openIssueMessage_post": " Por favor, incluya la siguiente información copiándola y pegándola en el issue de GitHub.",
|
|
||||||
"sceneContent": "Contenido de la escena:"
|
"sceneContent": "Contenido de la escena:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Requerido",
|
"required": "Requerido",
|
||||||
"website": "Introduce una URL válida"
|
"website": "Introduce una URL válida"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Envía tu biblioteca para ser incluida en el <link>repositorio de librería pública</link>para que otras personas utilicen en sus dibujos.",
|
||||||
"pre": "Envía tu biblioteca para ser incluida en el ",
|
"noteGuidelines": "La biblioteca debe ser aprobada manualmente primero. Por favor, lea la <link>pautas</link> antes de enviar. Necesitará una cuenta de GitHub para comunicarse y hacer cambios si se solicita, pero no es estrictamente necesario.",
|
||||||
"link": "repositorio de librería pública",
|
"noteLicense": "Al enviar, usted acepta que la biblioteca se publicará bajo el <link>Licencia MIT </link>que en breve significa que cualquiera puede utilizarlos sin restricciones.",
|
||||||
"post": "para que otras personas utilicen en sus dibujos."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "La biblioteca debe ser aprobada manualmente primero. Por favor, lea la ",
|
|
||||||
"link": "pautas",
|
|
||||||
"post": " antes de enviar. Necesitará una cuenta de GitHub para comunicarse y hacer cambios si se solicita, pero no es estrictamente necesario."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Al enviar, usted acepta que la biblioteca se publicará bajo el ",
|
|
||||||
"link": "Licencia MIT ",
|
|
||||||
"post": "que en breve significa que cualquiera puede utilizarlos sin restricciones."
|
|
||||||
},
|
|
||||||
"noteItems": "Cada elemento de la biblioteca debe tener su propio nombre para que sea filtrable. Los siguientes elementos de la biblioteca serán incluidos:",
|
"noteItems": "Cada elemento de la biblioteca debe tener su propio nombre para que sea filtrable. Los siguientes elementos de la biblioteca serán incluidos:",
|
||||||
"atleastOneLibItem": "Por favor, seleccione al menos un elemento de la biblioteca para empezar",
|
"atleastOneLibItem": "Por favor, seleccione al menos un elemento de la biblioteca para empezar",
|
||||||
"republishWarning": "Nota: algunos de los elementos seleccionados están marcados como ya publicados/enviados. Sólo debería volver a enviar elementos cuando se actualice una biblioteca o envío."
|
"republishWarning": "Nota: algunos de los elementos seleccionados están marcados como ya publicados/enviados. Sólo debería volver a enviar elementos cuando se actualice una biblioteca o envío."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Biblioteca enviada",
|
"title": "Biblioteca enviada",
|
||||||
"content": "Gracias {{authorName}}. Su biblioteca ha sido enviada para ser revisada. Puede seguir el estado",
|
"content": "Gracias {{authorName}}. Su biblioteca ha sido enviada para ser revisada. Puede seguir el estado<link>aquí</link>"
|
||||||
"link": "aquí"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Reiniciar biblioteca",
|
"resetLibrary": "Reiniciar biblioteca",
|
||||||
|
|
|
@ -208,19 +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": {
|
||||||
"start": "Brave nabigatzailea erabiltzen ari zarela dirudi",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
"line2": "",
|
||||||
"setting_enabled": "ezarpena gaituta",
|
"line3": "",
|
||||||
"break": "Honek honen haustea eragin dezake",
|
"line4": ""
|
||||||
"text_elements": "Testu-elementuak",
|
|
||||||
"in_your_drawings": "zure marrazkietan",
|
|
||||||
"strongly_recommend": "Ezarpen hau desgaitzea gomendatzen dugu. Jarrai dezakezu",
|
|
||||||
"steps": "urrats hauek",
|
|
||||||
"how": "jakiteko nola egin",
|
|
||||||
"disable_setting": " Ezarpen hau desgaitzeak testu-elementuen bistaratzea konpontzen ez badu, ireki",
|
|
||||||
"issue": "eskaera (issue) bat",
|
|
||||||
"write": "gure Github-en edo idatz iezaguzu",
|
|
||||||
"discord": "Discord-en"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Aholkua: saiatu urrunen dauden elementuak pixka bat hurbiltzen."
|
"canvasTooBigTip": "Aholkua: saiatu urrunen dauden elementuak pixka bat hurbiltzen."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Errore bat aurkitu da. Saiatu ",
|
"headingMain": "Errore bat aurkitu da. Saiatu <button>orria birkargatzen.</button>",
|
||||||
"headingMain_button": "orria birkargatzen.",
|
"clearCanvasMessage": "Birkargatzea ez bada burutzen, saiatu <button>oihala garbitzen.</button>",
|
||||||
"clearCanvasMessage": "Birkargatzea ez bada burutzen, saiatu ",
|
|
||||||
"clearCanvasMessage_button": "oihala garbitzen.",
|
|
||||||
"clearCanvasCaveat": " Honen ondorioz lana galduko da ",
|
"clearCanvasCaveat": " Honen ondorioz lana galduko da ",
|
||||||
"trackedToSentry_pre": "Identifikatzailearen errorea ",
|
"trackedToSentry": "Identifikatzailearen errorea {{eventId}} gure sistemak behatu du.",
|
||||||
"trackedToSentry_post": " gure sistemak behatu du.",
|
"openIssueMessage": "Oso kontuz ibili gara zure eszenaren informazioa errorean ez sartzeko. Zure eszena pribatua ez bada, kontuan hartu gure <button>erroreen jarraipena egitea.</button> Sartu beheko informazioa kopiatu eta itsatsi bidez GitHub issue-n.",
|
||||||
"openIssueMessage_pre": "Oso kontuz ibili gara zure eszenaren informazioa errorean ez sartzeko. Zure eszena pribatua ez bada, kontuan hartu gure ",
|
|
||||||
"openIssueMessage_button": "erroreen jarraipena egitea.",
|
|
||||||
"openIssueMessage_post": " Sartu beheko informazioa kopiatu eta itsatsi bidez GitHub issue-n.",
|
|
||||||
"sceneContent": "Eszenaren edukia:"
|
"sceneContent": "Eszenaren edukia:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Beharrezkoa",
|
"required": "Beharrezkoa",
|
||||||
"website": "Sartu baliozko URL bat"
|
"website": "Sartu baliozko URL bat"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Bidali zure liburutegira sartu ahal izateko <link>zure liburutegiko biltegian</link>beste jendeak bere marrazkietan erabili ahal izateko.",
|
||||||
"pre": "Bidali zure liburutegira sartu ahal izateko ",
|
"noteGuidelines": "Liburutegia eskuz onartu behar da. Irakurri <link>gidalerroak</link> bidali aurretik. GitHub kontu bat edukitzea komeni da komunikatzeko eta aldaketak egin ahal izateko, baina ez da guztiz beharrezkoa.",
|
||||||
"link": "zure liburutegiko biltegian",
|
"noteLicense": "Bidaltzen baduzu, onartzen duzu liburutegia <link>MIT lizentziarekin argitaratuko dela, </link>zeinak, laburbilduz, esan nahi du edozeinek erabiltzen ahal duela murrizketarik gabe.",
|
||||||
"post": "beste jendeak bere marrazkietan erabili ahal izateko."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "Liburutegia eskuz onartu behar da. Irakurri ",
|
|
||||||
"link": "gidalerroak",
|
|
||||||
"post": " bidali aurretik. GitHub kontu bat edukitzea komeni da komunikatzeko eta aldaketak egin ahal izateko, baina ez da guztiz beharrezkoa."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Bidaltzen baduzu, onartzen duzu liburutegia ",
|
|
||||||
"link": "MIT lizentziarekin argitaratuko dela, ",
|
|
||||||
"post": "zeinak, laburbilduz, esan nahi du edozeinek erabiltzen ahal duela murrizketarik gabe."
|
|
||||||
},
|
|
||||||
"noteItems": "Liburutegiko elementu bakoitzak bere izena eduki behar du iragazi ahal izateko. Liburutegiko hurrengo elementuak barne daude:",
|
"noteItems": "Liburutegiko elementu bakoitzak bere izena eduki behar du iragazi ahal izateko. Liburutegiko hurrengo elementuak barne daude:",
|
||||||
"atleastOneLibItem": "Hautatu gutxienez liburutegiko elementu bat gutxienez hasi ahal izateko",
|
"atleastOneLibItem": "Hautatu gutxienez liburutegiko elementu bat gutxienez hasi ahal izateko",
|
||||||
"republishWarning": "Oharra: hautatutako elementu batzuk dagoeneko argitaratuta/bidalita bezala markatuta daude. Elementuak berriro bidali behar dituzu lehendik dagoen liburutegi edo bidalketa eguneratzen duzunean."
|
"republishWarning": "Oharra: hautatutako elementu batzuk dagoeneko argitaratuta/bidalita bezala markatuta daude. Elementuak berriro bidali behar dituzu lehendik dagoen liburutegi edo bidalketa eguneratzen duzunean."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Liburutegia bidali da",
|
"title": "Liburutegia bidali da",
|
||||||
"content": "Eskerrik asko {{authorName}}. Zure liburutegia bidali da berrikustera. Jarraitu dezakezu haren egoera",
|
"content": "Eskerrik asko {{authorName}}. Zure liburutegia bidali da berrikustera. Jarraitu dezakezu haren egoera<link>hemen</link>"
|
||||||
"link": "hemen"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Leheneratu liburutegia",
|
"resetLibrary": "Leheneratu liburutegia",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "جای گذاری",
|
"paste": "جای گذاری",
|
||||||
"pasteAsPlaintext": "",
|
"pasteAsPlaintext": "جایگذاری به عنوان متن ساده",
|
||||||
"pasteCharts": "قراردادن نمودارها",
|
"pasteCharts": "قراردادن نمودارها",
|
||||||
"selectAll": "انتخاب همه",
|
"selectAll": "انتخاب همه",
|
||||||
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید.",
|
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید.",
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
"veryLarge": "بسیار بزرگ",
|
"veryLarge": "بسیار بزرگ",
|
||||||
"solid": "توپر",
|
"solid": "توپر",
|
||||||
"hachure": "هاشور",
|
"hachure": "هاشور",
|
||||||
"zigzag": "",
|
"zigzag": "زیگزاگ",
|
||||||
"crossHatch": "هاشور متقاطع",
|
"crossHatch": "هاشور متقاطع",
|
||||||
"thin": "نازک",
|
"thin": "نازک",
|
||||||
"bold": "ضخیم",
|
"bold": "ضخیم",
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
"increaseFontSize": "افزایش دادن اندازه فونت",
|
"increaseFontSize": "افزایش دادن اندازه فونت",
|
||||||
"unbindText": "بازکردن نوشته",
|
"unbindText": "بازکردن نوشته",
|
||||||
"bindText": "بستن نوشته",
|
"bindText": "بستن نوشته",
|
||||||
"createContainerFromText": "",
|
"createContainerFromText": "متن را در یک جایگاه بپیچید",
|
||||||
"link": {
|
"link": {
|
||||||
"edit": "ویرایش لینک",
|
"edit": "ویرایش لینک",
|
||||||
"create": "ایجاد پیوند",
|
"create": "ایجاد پیوند",
|
||||||
|
@ -195,7 +195,7 @@
|
||||||
"resetLibrary": "ین کار کل صفحه را پاک میکند. آیا مطمئنید?",
|
"resetLibrary": "ین کار کل صفحه را پاک میکند. آیا مطمئنید?",
|
||||||
"removeItemsFromsLibrary": "حذف {{count}} آیتم(ها) از کتابخانه?",
|
"removeItemsFromsLibrary": "حذف {{count}} آیتم(ها) از کتابخانه?",
|
||||||
"invalidEncryptionKey": "کلید رمزگذاری باید 22 کاراکتر باشد. همکاری زنده غیرفعال است.",
|
"invalidEncryptionKey": "کلید رمزگذاری باید 22 کاراکتر باشد. همکاری زنده غیرفعال است.",
|
||||||
"collabOfflineWarning": ""
|
"collabOfflineWarning": "اتصال به اینترنت در دسترس نیست.\nتغییرات شما ذخیره نمی شود!"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"unsupportedFileType": "نوع فایل پشتیبانی نشده.",
|
"unsupportedFileType": "نوع فایل پشتیبانی نشده.",
|
||||||
|
@ -205,22 +205,13 @@
|
||||||
"invalidSVGString": "SVG نادرست.",
|
"invalidSVGString": "SVG نادرست.",
|
||||||
"cannotResolveCollabServer": "به سرور collab متصل نشد. لطفا صفحه را مجددا بارگذاری کنید و دوباره تلاش کنید.",
|
"cannotResolveCollabServer": "به سرور collab متصل نشد. لطفا صفحه را مجددا بارگذاری کنید و دوباره تلاش کنید.",
|
||||||
"importLibraryError": "دادهها بارگذاری نشدند",
|
"importLibraryError": "دادهها بارگذاری نشدند",
|
||||||
"collabSaveFailed": "",
|
"collabSaveFailed": "در پایگاه داده باطن ذخیره نشد. اگر مشکلات همچنان ادامه داشت، باید فایل خود را به صورت محلی ذخیره کنید تا مطمئن شوید کار خود را از دست نمی دهید.",
|
||||||
"collabSaveFailed_sizeExceeded": "",
|
"collabSaveFailed_sizeExceeded": "در پایگاه داده بکند ذخیره نشد. اگر مشکلات همچنان ادامه داشت، باید فایل خود را به صورت محلی ذخیره کنید تا مطمئن شوید کار خود را از دست نمی دهید.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "به نظر میرسد از مرورگر Brave با تنظیم <bold> مسدود کردن شدید اثرانگشت</bold> استفاده میکنید.",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "این می تواند منجر به شکستن <bold>عناصر متن</bold> در نقاشی های شما شود.",
|
||||||
"setting_enabled": "",
|
"line3": "اکیداً توصیه می کنیم این تنظیم را غیرفعال کنید. برای نحوه انجام این کار میتوانید <link>این مراحل</link> را دنبال کنید.",
|
||||||
"break": "",
|
"line4": "اگر غیرفعال کردن این تنظیم نمایش عناصر متنی را برطرف نکرد، لطفاً یک <issueLink>مشکل</issueLink> را در GitHub ما باز کنید یا برای ما در <discordLink>Discord</discordLink> بنویسید."
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -238,7 +229,7 @@
|
||||||
"penMode": "حالت قلم - جلوگیری از تماس",
|
"penMode": "حالت قلم - جلوگیری از تماس",
|
||||||
"link": "افزودن/بهروزرسانی پیوند برای شکل انتخابی",
|
"link": "افزودن/بهروزرسانی پیوند برای شکل انتخابی",
|
||||||
"eraser": "پاک کن",
|
"eraser": "پاک کن",
|
||||||
"hand": ""
|
"hand": "دست (ابزار پانینگ)"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "عملیات روی بوم",
|
"canvasActions": "عملیات روی بوم",
|
||||||
|
@ -246,7 +237,7 @@
|
||||||
"shapes": "شکلها"
|
"shapes": "شکلها"
|
||||||
},
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"canvasPanning": "",
|
"canvasPanning": "برای حرکت دادن بوم، چرخ ماوس یا فاصله را در حین کشیدن نگه دارید یا از ابزار دستی استفاده کنید",
|
||||||
"linearElement": "برای چند نقطه کلیک و برای یک خط بکشید",
|
"linearElement": "برای چند نقطه کلیک و برای یک خط بکشید",
|
||||||
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
|
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
|
||||||
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
|
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
|
||||||
|
@ -257,7 +248,7 @@
|
||||||
"resize": "می توانید با نگه داشتن SHIFT در هنگام تغییر اندازه، نسبت ها را محدود کنید،ALT را برای تغییر اندازه از مرکز نگه دارید",
|
"resize": "می توانید با نگه داشتن SHIFT در هنگام تغییر اندازه، نسبت ها را محدود کنید،ALT را برای تغییر اندازه از مرکز نگه دارید",
|
||||||
"resizeImage": "با نگه داشتن SHIFT می توانید آزادانه اندازه را تغییر دهید،\nبرای تغییر اندازه از مرکز، ALT را نگه دارید",
|
"resizeImage": "با نگه داشتن SHIFT می توانید آزادانه اندازه را تغییر دهید،\nبرای تغییر اندازه از مرکز، ALT را نگه دارید",
|
||||||
"rotate": "با نگه داشتن SHIFT هنگام چرخش می توانید زاویه ها را محدود کنید",
|
"rotate": "با نگه داشتن SHIFT هنگام چرخش می توانید زاویه ها را محدود کنید",
|
||||||
"lineEditor_info": "",
|
"lineEditor_info": "CtrlOrCmd را نگه دارید و دوبار کلیک کنید یا CtrlOrCmd + Enter را فشار دهید تا نقاط را ویرایش کنید.",
|
||||||
"lineEditor_pointSelected": "برای حذف نقطه Delete برای کپی زدن Ctrl یا Cmd+D را بزنید و یا برای جابجایی بکشید",
|
"lineEditor_pointSelected": "برای حذف نقطه Delete برای کپی زدن Ctrl یا Cmd+D را بزنید و یا برای جابجایی بکشید",
|
||||||
"lineEditor_nothingSelected": "یک نقطه را برای ویرایش انتخاب کنید (SHIFT را برای انتخاب چندگانه نگه دارید)،\nیا Alt را نگه دارید و برای افزودن نقاط جدید کلیک کنید",
|
"lineEditor_nothingSelected": "یک نقطه را برای ویرایش انتخاب کنید (SHIFT را برای انتخاب چندگانه نگه دارید)،\nیا Alt را نگه دارید و برای افزودن نقاط جدید کلیک کنید",
|
||||||
"placeImage": "برای قرار دادن تصویر کلیک کنید، یا کلیک کنید و بکشید تا اندازه آن به صورت دستی تنظیم شود",
|
"placeImage": "برای قرار دادن تصویر کلیک کنید، یا کلیک کنید و بکشید تا اندازه آن به صورت دستی تنظیم شود",
|
||||||
|
@ -265,7 +256,7 @@
|
||||||
"bindTextToElement": "برای افزودن اینتر را بزنید",
|
"bindTextToElement": "برای افزودن اینتر را بزنید",
|
||||||
"deepBoxSelect": "CtrlOrCmd را برای انتخاب عمیق و جلوگیری از کشیدن نگه دارید",
|
"deepBoxSelect": "CtrlOrCmd را برای انتخاب عمیق و جلوگیری از کشیدن نگه دارید",
|
||||||
"eraserRevert": "Alt را نگه دارید تا عناصر علامت گذاری شده برای حذف برگردند",
|
"eraserRevert": "Alt را نگه دارید تا عناصر علامت گذاری شده برای حذف برگردند",
|
||||||
"firefox_clipboard_write": ""
|
"firefox_clipboard_write": "احتمالاً میتوان این ویژگی را با تنظیم پرچم «dom.events.asyncClipboard.clipboardItem» روی «true» فعال کرد. برای تغییر پرچم های مرورگر در فایرفاکس، از صفحه \"about:config\" دیدن کنید."
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "پیش نمایش نشان داده نمی شود",
|
"cannotShowPreview": "پیش نمایش نشان داده نمی شود",
|
||||||
|
@ -320,8 +311,8 @@
|
||||||
"doubleClick": "دابل کلیک",
|
"doubleClick": "دابل کلیک",
|
||||||
"drag": "کشیدن",
|
"drag": "کشیدن",
|
||||||
"editor": "ویرایشگر",
|
"editor": "ویرایشگر",
|
||||||
"editLineArrowPoints": "",
|
"editLineArrowPoints": "نقاط خط/پیکان را ویرایش کنید",
|
||||||
"editText": "",
|
"editText": "ویرایش متن / افزودن برچسب",
|
||||||
"github": "اشکالی می بینید؟ گزارش دهید",
|
"github": "اشکالی می بینید؟ گزارش دهید",
|
||||||
"howto": "راهنمای ما را دنبال کنید",
|
"howto": "راهنمای ما را دنبال کنید",
|
||||||
"or": "یا",
|
"or": "یا",
|
||||||
|
@ -335,8 +326,8 @@
|
||||||
"zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
|
"zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
|
||||||
"zoomToSelection": "بزرگنمایی قسمت انتخاب شده",
|
"zoomToSelection": "بزرگنمایی قسمت انتخاب شده",
|
||||||
"toggleElementLock": "قفل/بازکردن انتخاب شده ها",
|
"toggleElementLock": "قفل/بازکردن انتخاب شده ها",
|
||||||
"movePageUpDown": "",
|
"movePageUpDown": "حرکت صفحه به بالا/پایین",
|
||||||
"movePageLeftRight": ""
|
"movePageLeftRight": "حرکت صفحه به چپ/راست"
|
||||||
},
|
},
|
||||||
"clearCanvasDialog": {
|
"clearCanvasDialog": {
|
||||||
"title": "پاک کردن بوم"
|
"title": "پاک کردن بوم"
|
||||||
|
@ -418,7 +409,7 @@
|
||||||
"fileSavedToFilename": "ذخیره در {filename}",
|
"fileSavedToFilename": "ذخیره در {filename}",
|
||||||
"canvas": "بوم",
|
"canvas": "بوم",
|
||||||
"selection": "انتخاب",
|
"selection": "انتخاب",
|
||||||
"pasteAsSingleElement": ""
|
"pasteAsSingleElement": "از {{shortcut}} برای چسباندن به عنوان یک عنصر استفاده کنید،\nیا در یک ویرایشگر متن موجود جایگذاری کنید"
|
||||||
},
|
},
|
||||||
"colors": {
|
"colors": {
|
||||||
"ffffff": "سفید",
|
"ffffff": "سفید",
|
||||||
|
@ -469,15 +460,15 @@
|
||||||
},
|
},
|
||||||
"welcomeScreen": {
|
"welcomeScreen": {
|
||||||
"app": {
|
"app": {
|
||||||
"center_heading": "",
|
"center_heading": "تمام داده های شما به صورت محلی در مرورگر شما ذخیره می شود.",
|
||||||
"center_heading_plus": "",
|
"center_heading_plus": "آیا میخواهید به جای آن به Excalidraw+ بروید؟",
|
||||||
"menuHint": ""
|
"menuHint": "خروجی، ترجیحات، زبان ها، ..."
|
||||||
},
|
},
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"menuHint": "",
|
"menuHint": "خروجی، ترجیحات، وبیشتر ...",
|
||||||
"center_heading": "",
|
"center_heading": "نمودارها .ساخته شده. ساده.",
|
||||||
"toolbarHint": "",
|
"toolbarHint": "ابزاری را انتخاب کنید و نقاشی را شروع کنید!",
|
||||||
"helpHint": ""
|
"helpHint": "میانبرها و راهنما"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi.",
|
"collabSaveFailed": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi.",
|
||||||
"collabSaveFailed_sizeExceeded": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi.",
|
"collabSaveFailed_sizeExceeded": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Vinkki: yritä siirtää kaukaisimpia elementtejä hieman lähemmäs toisiaan."
|
"canvasTooBigTip": "Vinkki: yritä siirtää kaukaisimpia elementtejä hieman lähemmäs toisiaan."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Tapahtui virhe. Yritä ",
|
"headingMain": "Tapahtui virhe. Yritä <button>sivun lataamista uudelleen.</button>",
|
||||||
"headingMain_button": "sivun lataamista uudelleen.",
|
"clearCanvasMessage": "Mikäli sivun lataaminen uudelleen ei auta, yritä <button>tyhjentää piirtoalue.</button>",
|
||||||
"clearCanvasMessage": "Mikäli sivun lataaminen uudelleen ei auta, yritä ",
|
|
||||||
"clearCanvasMessage_button": "tyhjentää piirtoalue.",
|
|
||||||
"clearCanvasCaveat": " Tämä johtaa työn menetykseen ",
|
"clearCanvasCaveat": " Tämä johtaa työn menetykseen ",
|
||||||
"trackedToSentry_pre": "Virhe tunnisteella ",
|
"trackedToSentry": "Virhe tunnisteella {{eventId}} tallennettiin järjestelmäämme.",
|
||||||
"trackedToSentry_post": " tallennettiin järjestelmäämme.",
|
"openIssueMessage": "Olimme varovaisia emmekä sisällyttäneet tietoa piirroksestasi virheeseen. Mikäli piirroksesi ei ole yksityinen, harkitsethan kertovasi meille <button>virheenseurantajärjestelmässämme.</button> Sisällytä alla olevat tiedot kopioimalla ne GitHub-ongelmaan.",
|
||||||
"openIssueMessage_pre": "Olimme varovaisia emmekä sisällyttäneet tietoa piirroksestasi virheeseen. Mikäli piirroksesi ei ole yksityinen, harkitsethan kertovasi meille ",
|
|
||||||
"openIssueMessage_button": "virheenseurantajärjestelmässämme.",
|
|
||||||
"openIssueMessage_post": " Sisällytä alla olevat tiedot kopioimalla ne GitHub-ongelmaan.",
|
|
||||||
"sceneContent": "Piirroksen tiedot:"
|
"sceneContent": "Piirroksen tiedot:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Pakollinen",
|
"required": "Pakollinen",
|
||||||
"website": "Syötä oikeamuotoinen URL-osoite"
|
"website": "Syötä oikeamuotoinen URL-osoite"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Lähetä kirjastosi, jotta se voidaan sisällyttää <link>julkisessa kirjastolistauksessa</link>muiden käyttöön omissa piirrustuksissaan.",
|
||||||
"pre": "Lähetä kirjastosi, jotta se voidaan sisällyttää ",
|
"noteGuidelines": "Kirjasto on ensin hyväksyttävä manuaalisesti. Ole hyvä ja lue <link>ohjeet</link> ennen lähettämistä. Tarvitset GitHub-tilin, jotta voit viestiä ja tehdä muutoksia pyydettäessä, mutta se ei ole ehdottoman välttämätöntä.",
|
||||||
"link": "julkisessa kirjastolistauksessa",
|
"noteLicense": "Lähettämällä hyväksyt että kirjasto julkaistaan <link>MIT-lisenssin </link>alla, mikä lyhyesti antaa muiden käyttää sitä ilman rajoituksia.",
|
||||||
"post": "muiden käyttöön omissa piirrustuksissaan."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "Kirjasto on ensin hyväksyttävä manuaalisesti. Ole hyvä ja lue ",
|
|
||||||
"link": "ohjeet",
|
|
||||||
"post": " ennen lähettämistä. Tarvitset GitHub-tilin, jotta voit viestiä ja tehdä muutoksia pyydettäessä, mutta se ei ole ehdottoman välttämätöntä."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Lähettämällä hyväksyt että kirjasto julkaistaan ",
|
|
||||||
"link": "MIT-lisenssin ",
|
|
||||||
"post": "alla, mikä lyhyesti antaa muiden käyttää sitä ilman rajoituksia."
|
|
||||||
},
|
|
||||||
"noteItems": "Jokaisella kirjaston kohteella on oltava oma nimensä suodatusta varten. Seuraavat kirjaston kohteet sisältyvät:",
|
"noteItems": "Jokaisella kirjaston kohteella on oltava oma nimensä suodatusta varten. Seuraavat kirjaston kohteet sisältyvät:",
|
||||||
"atleastOneLibItem": "Valitse vähintään yksi kirjaston kohde aloittaaksesi",
|
"atleastOneLibItem": "Valitse vähintään yksi kirjaston kohde aloittaaksesi",
|
||||||
"republishWarning": "Huom! Osa valituista kohteista on merkitty jo julkaistu/lähetetyiksi. Lähetä kohteita uudelleen vain päivitettäessä olemassa olevaa kirjastoa tai ehdotusta."
|
"republishWarning": "Huom! Osa valituista kohteista on merkitty jo julkaistu/lähetetyiksi. Lähetä kohteita uudelleen vain päivitettäessä olemassa olevaa kirjastoa tai ehdotusta."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Kirjasto lähetetty",
|
"title": "Kirjasto lähetetty",
|
||||||
"content": "Kiitos {{authorName}}. Kirjastosi on lähetetty tarkistettavaksi. Voit seurata sen tilaa",
|
"content": "Kiitos {{authorName}}. Kirjastosi on lähetetty tarkistettavaksi. Voit seurata sen tilaa<link>täällä</link>"
|
||||||
"link": "täällä"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Tyhjennä kirjasto",
|
"resetLibrary": "Tyhjennä kirjasto",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Impossible d'enregistrer dans la base de données en arrière-plan. Si des problèmes persistent, vous devriez enregistrer votre fichier localement pour vous assurer de ne pas perdre votre travail.",
|
"collabSaveFailed": "Impossible d'enregistrer dans la base de données en arrière-plan. Si des problèmes persistent, vous devriez enregistrer votre fichier localement pour vous assurer de ne pas perdre votre travail.",
|
||||||
"collabSaveFailed_sizeExceeded": "Impossible d'enregistrer dans la base de données en arrière-plan, le tableau semble trop grand. Vous devriez enregistrer le fichier localement pour vous assurer de ne pas perdre votre travail.",
|
"collabSaveFailed_sizeExceeded": "Impossible d'enregistrer dans la base de données en arrière-plan, le tableau semble trop grand. Vous devriez enregistrer le fichier localement pour vous assurer de ne pas perdre votre travail.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "Il semble que vous utilisiez le navigateur Brave avec le",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "blocage d'empreinte agressif",
|
"line2": "",
|
||||||
"setting_enabled": "activé",
|
"line3": "",
|
||||||
"break": "Ceci pourrait avoir pour conséquence de « casser » les",
|
"line4": ""
|
||||||
"text_elements": "éléments texte",
|
|
||||||
"in_your_drawings": "dans vos dessins",
|
|
||||||
"strongly_recommend": "Nous recommandons fortement de désactiver ce paramètre. Vous pouvez suivre",
|
|
||||||
"steps": "ces étapes",
|
|
||||||
"how": "sur la manière de procéder",
|
|
||||||
"disable_setting": " Si la désactivation de ce paramètre ne résout pas l'affichage des éléments texte, veuillez ouvrir un",
|
|
||||||
"issue": "ticket",
|
|
||||||
"write": "sur notre GitHub, ou nous écrire sur",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Astuce : essayez de rapprocher un peu les éléments les plus éloignés."
|
"canvasTooBigTip": "Astuce : essayez de rapprocher un peu les éléments les plus éloignés."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Une erreur est survenue. Essayez ",
|
"headingMain": "Une erreur est survenue. Essayez <button>de recharger la page.</button>",
|
||||||
"headingMain_button": "de recharger la page.",
|
"clearCanvasMessage": "Si le rechargement ne résout pas l'erreur, essayez <button>effacement du canevas.</button>",
|
||||||
"clearCanvasMessage": "Si le rechargement ne résout pas l'erreur, essayez ",
|
|
||||||
"clearCanvasMessage_button": "effacement du canevas.",
|
|
||||||
"clearCanvasCaveat": " Cela entraînera une perte du travail ",
|
"clearCanvasCaveat": " Cela entraînera une perte du travail ",
|
||||||
"trackedToSentry_pre": "L'erreur avec l'identifiant ",
|
"trackedToSentry": "L'erreur avec l'identifiant {{eventId}} a été enregistrée dans notre système.",
|
||||||
"trackedToSentry_post": " a été enregistrée dans notre système.",
|
"openIssueMessage": "Nous avons fait très attention à ne pas inclure les informations de votre scène dans l'erreur. Si votre scène n'est pas privée, veuillez envisager de poursuivre sur notre <button>outil de suivi des bugs.</button> Veuillez inclure les informations ci-dessous en les copiant-collant dans le ticket GitHub.",
|
||||||
"openIssueMessage_pre": "Nous avons fait très attention à ne pas inclure les informations de votre scène dans l'erreur. Si votre scène n'est pas privée, veuillez envisager de poursuivre sur notre ",
|
|
||||||
"openIssueMessage_button": "outil de suivi des bugs.",
|
|
||||||
"openIssueMessage_post": " Veuillez inclure les informations ci-dessous en les copiant-collant dans le ticket GitHub.",
|
|
||||||
"sceneContent": "Contenu de la scène :"
|
"sceneContent": "Contenu de la scène :"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Requis",
|
"required": "Requis",
|
||||||
"website": "Entrer une URL valide"
|
"website": "Entrer une URL valide"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Soumets ta bibliothèque pour l'inclure au <link>dépôt de bibliothèque publique</link>pour permettre son utilisation par autrui dans leurs dessins.",
|
||||||
"pre": "Soumets ta bibliothèque pour l'inclure au ",
|
"noteGuidelines": "La bibliothèque doit d'abord être approuvée manuellement. Veuillez lire les <link>lignes directrices</link> avant de la soumettre. Vous aurez besoin d'un compte GitHub pour communiquer et apporter des modifications si demandé, mais ce n'est pas obligatoire.",
|
||||||
"link": "dépôt de bibliothèque publique",
|
"noteLicense": "En soumettant, vous acceptez que la bibliothèque soit publiée sous la <link>Licence MIT, </link>ce qui en gros signifie que tout le monde peut l'utiliser sans restrictions.",
|
||||||
"post": "pour permettre son utilisation par autrui dans leurs dessins."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "La bibliothèque doit d'abord être approuvée manuellement. Veuillez lire les ",
|
|
||||||
"link": "lignes directrices",
|
|
||||||
"post": " avant de la soumettre. Vous aurez besoin d'un compte GitHub pour communiquer et apporter des modifications si demandé, mais ce n'est pas obligatoire."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "En soumettant, vous acceptez que la bibliothèque soit publiée sous la ",
|
|
||||||
"link": "Licence MIT, ",
|
|
||||||
"post": "ce qui en gros signifie que tout le monde peut l'utiliser sans restrictions."
|
|
||||||
},
|
|
||||||
"noteItems": "Chaque élément de la bibliothèque doit avoir son propre nom afin qu'il soit filtrable. Les éléments de bibliothèque suivants seront inclus :",
|
"noteItems": "Chaque élément de la bibliothèque doit avoir son propre nom afin qu'il soit filtrable. Les éléments de bibliothèque suivants seront inclus :",
|
||||||
"atleastOneLibItem": "Veuillez sélectionner au moins un élément de bibliothèque pour commencer",
|
"atleastOneLibItem": "Veuillez sélectionner au moins un élément de bibliothèque pour commencer",
|
||||||
"republishWarning": "Remarque : certains des éléments sélectionnés sont marqués comme étant déjà publiés/soumis. Vous devez uniquement resoumettre des éléments lors de la mise à jour d'une bibliothèque ou d'une soumission existante."
|
"republishWarning": "Remarque : certains des éléments sélectionnés sont marqués comme étant déjà publiés/soumis. Vous devez uniquement resoumettre des éléments lors de la mise à jour d'une bibliothèque ou d'une soumission existante."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Bibliothèque soumise",
|
"title": "Bibliothèque soumise",
|
||||||
"content": "Merci {{authorName}}. Votre bibliothèque a été soumise pour examen. Vous pouvez suivre le statut",
|
"content": "Merci {{authorName}}. Votre bibliothèque a été soumise pour examen. Vous pouvez suivre le statut<link>ici</link>"
|
||||||
"link": "ici"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Réinitialiser la bibliothèque",
|
"resetLibrary": "Réinitialiser la bibliothèque",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Non se puido gardar na base de datos. Se o problema persiste, deberías gardar o teu arquivo de maneira local para asegurarte de non perdelo teu traballo.",
|
"collabSaveFailed": "Non se puido gardar na base de datos. Se o problema persiste, deberías gardar o teu arquivo de maneira local para asegurarte de non perdelo teu traballo.",
|
||||||
"collabSaveFailed_sizeExceeded": "Non se puido gardar na base de datos, o lenzo semella demasiado grande. Deberías gardar o teu arquivo de maneira local para asegurarte de non perdelo teu traballo.",
|
"collabSaveFailed_sizeExceeded": "Non se puido gardar na base de datos, o lenzo semella demasiado grande. Deberías gardar o teu arquivo de maneira local para asegurarte de non perdelo teu traballo.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "Semella que estás usando o navegador Brave coa opción",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
"line2": "",
|
||||||
"setting_enabled": "activada",
|
"line3": "",
|
||||||
"break": "Isto podería provocar unha ruptura dos",
|
"line4": ""
|
||||||
"text_elements": "Elementos de Texto",
|
|
||||||
"in_your_drawings": "nos seus debuxos",
|
|
||||||
"strongly_recommend": "Recomendámoslle encarecidamente que desactive esa opción. Pode seguir",
|
|
||||||
"steps": "estes pasos",
|
|
||||||
"how": "sobre como facelo",
|
|
||||||
"disable_setting": " Se ao desactivar esta opción non se arranxa o problema ao mostrar os elementos de texto, por favor abra unha",
|
|
||||||
"issue": "issue",
|
|
||||||
"write": "no noso GitHub, ou escríbenos ao",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Consello: Probe a acercar un pouco os elementos máis afastados."
|
"canvasTooBigTip": "Consello: Probe a acercar un pouco os elementos máis afastados."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Atopouse un erro. Probe ",
|
"headingMain": "Atopouse un erro. Probe <button>recargando a páxina.</button>",
|
||||||
"headingMain_button": "recargando a páxina.",
|
"clearCanvasMessage": "Se recargar non funcionou, probe <button>limpando o lenzo.</button>",
|
||||||
"clearCanvasMessage": "Se recargar non funcionou, probe ",
|
|
||||||
"clearCanvasMessage_button": "limpando o lenzo.",
|
|
||||||
"clearCanvasCaveat": " Isto resultará nunha perda do seu traballo ",
|
"clearCanvasCaveat": " Isto resultará nunha perda do seu traballo ",
|
||||||
"trackedToSentry_pre": "O erro con identificador ",
|
"trackedToSentry": "O erro con identificador {{eventId}} foi rastrexado no noso sistema.",
|
||||||
"trackedToSentry_post": " foi rastrexado no noso sistema.",
|
"openIssueMessage": "Fomos moi cautelosos de non incluír a información da súa escena no erro. Se a súa escena non é privada, por favor, considere o seguimento do noso <button>rastrexador de erros.</button> Por favor inclúa a seguinte información copiándoa e pegándoa na issue de Github.",
|
||||||
"openIssueMessage_pre": "Fomos moi cautelosos de non incluír a información da súa escena no erro. Se a súa escena non é privada, por favor, considere o seguimento do noso ",
|
|
||||||
"openIssueMessage_button": "rastrexador de erros.",
|
|
||||||
"openIssueMessage_post": " Por favor inclúa a seguinte información copiándoa e pegándoa na issue de Github.",
|
|
||||||
"sceneContent": "Contido da escena:"
|
"sceneContent": "Contido da escena:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Obrigatorio",
|
"required": "Obrigatorio",
|
||||||
"website": "Introduza unha URL válida"
|
"website": "Introduza unha URL válida"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Envíe a súa biblioteca para que sexa incluída no <link>repositorio público de bibliotecas</link>para que outra xente a poida usar nos seus debuxos.",
|
||||||
"pre": "Envíe a súa biblioteca para que sexa incluída no ",
|
"noteGuidelines": "A biblioteca necesita ser aprobada manualmente primeiro. Por favor, lea as <link>normas</link> antes de ser enviado. Necesitarás unha conta de GitHub para comunicarte ou facer cambios se se solicitan, pero non é estritamente necesario.",
|
||||||
"link": "repositorio público de bibliotecas",
|
"noteLicense": "Ao enviar, estás de acordo con que a biblioteca sexa publicada baixo a <link>Licenza MIT, </link>o cal significa que, en resumo, calquera pode usalo sen restricións.",
|
||||||
"post": "para que outra xente a poida usar nos seus debuxos."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "A biblioteca necesita ser aprobada manualmente primeiro. Por favor, lea as ",
|
|
||||||
"link": "normas",
|
|
||||||
"post": " antes de ser enviado. Necesitarás unha conta de GitHub para comunicarte ou facer cambios se se solicitan, pero non é estritamente necesario."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Ao enviar, estás de acordo con que a biblioteca sexa publicada baixo a ",
|
|
||||||
"link": "Licenza MIT, ",
|
|
||||||
"post": "o cal significa que, en resumo, calquera pode usalo sen restricións."
|
|
||||||
},
|
|
||||||
"noteItems": "Cada elemento da biblioteca debe ter o seu nome propio para que se poida filtrar. Os seguintes elementos da biblioteca serán incluídos:",
|
"noteItems": "Cada elemento da biblioteca debe ter o seu nome propio para que se poida filtrar. Os seguintes elementos da biblioteca serán incluídos:",
|
||||||
"atleastOneLibItem": "Por favor seleccione polo menos un elemento da biblioteca para comezar",
|
"atleastOneLibItem": "Por favor seleccione polo menos un elemento da biblioteca para comezar",
|
||||||
"republishWarning": "Nota: algúns dos elementos seleccionados están marcados como xa publicados/enviados. Só deberías reenviar elementos cando se actualice unha biblioteca ou envío."
|
"republishWarning": "Nota: algúns dos elementos seleccionados están marcados como xa publicados/enviados. Só deberías reenviar elementos cando se actualice unha biblioteca ou envío."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Biblioteca enviada",
|
"title": "Biblioteca enviada",
|
||||||
"content": "Grazas {{authorName}}. A súa biblioteca foi enviada para ser revisada. Pode seguir o estado",
|
"content": "Grazas {{authorName}}. A súa biblioteca foi enviada para ser revisada. Pode seguir o estado<link>aquí</link>"
|
||||||
"link": "aquí"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Restablecer biblioteca",
|
"resetLibrary": "Restablecer biblioteca",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "לא הצלחתי להתחבר למסד הנתונים האחורי. אם הבעיה ממשיכה, כדאי שתשמור את הקובץ מקומית כדי לוודא שלא תאבד את העבודה שלך.",
|
"collabSaveFailed": "לא הצלחתי להתחבר למסד הנתונים האחורי. אם הבעיה ממשיכה, כדאי שתשמור את הקובץ מקומית כדי לוודא שלא תאבד את העבודה שלך.",
|
||||||
"collabSaveFailed_sizeExceeded": "לא הצלחתי לשמור למסד הנתונים האחורי, נראה שהקנבס שלך גדול מדי. כדאי שתשמור את הקובץ מקומית כדי לוודא שלא תאבד את העבודה שלך.",
|
"collabSaveFailed_sizeExceeded": "לא הצלחתי לשמור למסד הנתונים האחורי, נראה שהקנבס שלך גדול מדי. כדאי שתשמור את הקובץ מקומית כדי לוודא שלא תאבד את העבודה שלך.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "נראה שאתה משתמש בדפדפן Brave עם ה-",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "חסימת Fingerprinting אגרסיבית",
|
"line2": "",
|
||||||
"setting_enabled": "ההגדרה מופעלת",
|
"line3": "",
|
||||||
"break": "זה יכול לגרום לבעיה ב-",
|
"line4": ""
|
||||||
"text_elements": "רכיבי טקסט",
|
|
||||||
"in_your_drawings": "בציורים שלך",
|
|
||||||
"strongly_recommend": "אנו ממליצים בחום לנטרל את ההגדרה הזו. אתה יכול לעקוב",
|
|
||||||
"steps": "הצעדים הבאים",
|
|
||||||
"how": "כיצד לעשות את זה",
|
|
||||||
"disable_setting": " אם נטרול ההגדרה לא מתקן את תצוגת רכיבי הטקסט, אנא פתח",
|
|
||||||
"issue": "בעיה",
|
|
||||||
"write": "ב- GitHub שלנו, או כתוב לנו ב-",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "किसी कारण वश अंदरूनी डेटाबेस में सहेजा नहीं जा सका। यदि समस्या बनी रहती है, तो किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।",
|
"collabSaveFailed": "किसी कारण वश अंदरूनी डेटाबेस में सहेजा नहीं जा सका। यदि समस्या बनी रहती है, तो किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।",
|
||||||
"collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।",
|
"collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "ऐसा लगता है कि आप ब्रेव ब्राउज़र का उपयोग कर रहे है, उसके साथ",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "उग्रतापूर्वक उंगलियों के निशान रोकने की",
|
"line2": "",
|
||||||
"setting_enabled": "सेटिंग सक्रिय की गई है।",
|
"line3": "",
|
||||||
"break": "इसके परिणाम स्वरूम टूट सकता है यह",
|
"line4": ""
|
||||||
"text_elements": "मूल पाठ अवयव टूट सकते हैं",
|
|
||||||
"in_your_drawings": "आपके चित्र में उपस्थित",
|
|
||||||
"strongly_recommend": "हमारा शशक्त अनुरोध इस सेटिंग्स को अक्षम करने का है. आप",
|
|
||||||
"steps": "इन दिए हुए स्टेप्स को अनुकरीत करके देख सकते है कि",
|
|
||||||
"how": "ये कैसे करे",
|
|
||||||
"disable_setting": " यदि इन सेटिंग्स को निष्क्रिय करने पर भी, पाठ्य दिखना ठीक न हो तो",
|
|
||||||
"issue": "समस्या",
|
|
||||||
"write": "हमारे \"GitHub\" पर, अथवा",
|
|
||||||
"discord": "\"Discord\" पर लिख सकते हैं."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "कैनवास बहुत बड़ा टिप"
|
"canvasTooBigTip": "कैनवास बहुत बड़ा टिप"
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "एक त्रुटि का सामना करना पड़ा। प्रयत्न ",
|
"headingMain": "एक त्रुटि का सामना करना पड़ा। प्रयत्न <button>इस पृष्ठ को पुनः लोड करें</button>",
|
||||||
"headingMain_button": "इस पृष्ठ को पुनः लोड करें",
|
"clearCanvasMessage": "यदि पुनः लोड करना काम नहीं करता है, तो प्रयास करें <button>कैनवास साफ करना।</button>",
|
||||||
"clearCanvasMessage": "यदि पुनः लोड करना काम नहीं करता है, तो प्रयास करें ",
|
|
||||||
"clearCanvasMessage_button": "कैनवास साफ करना।",
|
|
||||||
"clearCanvasCaveat": " इससे काम का नुकसान होगा ",
|
"clearCanvasCaveat": " इससे काम का नुकसान होगा ",
|
||||||
"trackedToSentry_pre": "पहचानकर्ता के साथ त्रुटि ",
|
"trackedToSentry": "पहचानकर्ता के साथ त्रुटि {{eventId}} हमारे सिस्टम पर नज़र रखी गई थी।",
|
||||||
"trackedToSentry_post": " हमारे सिस्टम पर नज़र रखी गई थी।",
|
"openIssueMessage": "हम बहुत सतर्क थे कि त्रुटि पर आपकी दृश्य जानकारी शामिल न करें। यदि आपका दृश्य निजी नहीं है, तो कृपया हमारे बारे में विचार करें <button>बग ट्रैकर</button> कृपया GitHub मुद्दे को कॉपी और पेस्ट करके नीचे दी गई जानकारी शामिल करें।",
|
||||||
"openIssueMessage_pre": "हम बहुत सतर्क थे कि त्रुटि पर आपकी दृश्य जानकारी शामिल न करें। यदि आपका दृश्य निजी नहीं है, तो कृपया हमारे बारे में विचार करें ",
|
|
||||||
"openIssueMessage_button": "बग ट्रैकर",
|
|
||||||
"openIssueMessage_post": " कृपया GitHub मुद्दे को कॉपी और पेस्ट करके नीचे दी गई जानकारी शामिल करें।",
|
|
||||||
"sceneContent": "दृश्य सामग्री:"
|
"sceneContent": "दृश्य सामग्री:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "",
|
"required": "",
|
||||||
"website": "मान्य URL प्रविष्ट करें"
|
"website": "मान्य URL प्रविष्ट करें"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "संग्रह सम्मिलित करने हेतु प्रस्तुत करें <link>सार्वजनिक संग्रहालय</link>अन्य वक्तियों को उनके चित्रकारी में उपयोग के लिये",
|
||||||
"pre": "संग्रह सम्मिलित करने हेतु प्रस्तुत करें ",
|
"noteGuidelines": "संग्रह को पहले स्वीकृति आवश्यक कृपया यह पढ़ें <link>दिशा-निर्देश</link>",
|
||||||
"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": "",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "",
|
"collabSaveFailed": "",
|
||||||
"collabSaveFailed_sizeExceeded": "",
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Tipp: próbáld meg a legtávolabbi elemeket közelebb hozni egy máshoz."
|
"canvasTooBigTip": "Tipp: próbáld meg a legtávolabbi elemeket közelebb hozni egy máshoz."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Hiba történt. Próbáld ",
|
"headingMain": "Hiba történt. Próbáld <button>újratölteni az oldalt.</button>",
|
||||||
"headingMain_button": "újratölteni az oldalt.",
|
"clearCanvasMessage": "Ha az újratöltés nem működik, próbáld <button>letörölni a vászont.</button>",
|
||||||
"clearCanvasMessage": "Ha az újratöltés nem működik, próbáld ",
|
|
||||||
"clearCanvasMessage_button": "letörölni a vászont.",
|
|
||||||
"clearCanvasCaveat": " Ezzel az eddigi munka elveszik ",
|
"clearCanvasCaveat": " Ezzel az eddigi munka elveszik ",
|
||||||
"trackedToSentry_pre": "A hibakód azonosítóval ",
|
"trackedToSentry": "A hibakód azonosítóval {{eventId}} nyomon van követve a rendszerünkben.",
|
||||||
"trackedToSentry_post": " nyomon van követve a rendszerünkben.",
|
"openIssueMessage": "Vigyáztunk arra, hogy a jelenthez tartozó információ ne jelenjen meg a hibaüzenetben. Ha a jeleneted nem bizalmas, kérjük add hozzá a <button>hibakövető rendszerünkhöz.</button> Kérjük, másolja be az alábbi információkat a GitHub problémába.",
|
||||||
"openIssueMessage_pre": "Vigyáztunk arra, hogy a jelenthez tartozó információ ne jelenjen meg a hibaüzenetben. Ha a jeleneted nem bizalmas, kérjük add hozzá a ",
|
|
||||||
"openIssueMessage_button": "hibakövető rendszerünkhöz.",
|
|
||||||
"openIssueMessage_post": " Kérjük, másolja be az alábbi információkat a GitHub problémába.",
|
|
||||||
"sceneContent": "Jelenet tartalma:"
|
"sceneContent": "Jelenet tartalma:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Kötelező",
|
"required": "Kötelező",
|
||||||
"website": "Adj meg egy érvényes URL-t"
|
"website": "Adj meg egy érvényes URL-t"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Küld be könyvtáradat, hogy bekerüljön a <link>nyilvános könyvtár tárolóba</link>hogy mások is felhasználhassák a rajzaikban.",
|
||||||
"pre": "Küld be könyvtáradat, hogy bekerüljön a ",
|
"noteGuidelines": "A könyvtárat először manuálisan kell jóváhagyni. Kérjük, olvassa el a <link>segédletet</link> benyújtása előtt. Szüksége lesz egy GitHub-fiókra a kommunikációhoz és a módosításokhoz, ha kérik, de ez nem feltétlenül szükséges.",
|
||||||
"link": "nyilvános könyvtár tárolóba",
|
"noteLicense": "A beküldéssel elfogadja, hogy a könyvtár a következő alatt kerül közzétételre <link>MIT Licensz </link>ami röviden azt jelenti, hogy bárki korlátozás nélkül használhatja őket.",
|
||||||
"post": "hogy mások is felhasználhassák a rajzaikban."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "A könyvtárat először manuálisan kell jóváhagyni. Kérjük, olvassa el a ",
|
|
||||||
"link": "segédletet",
|
|
||||||
"post": " benyújtása előtt. Szüksége lesz egy GitHub-fiókra a kommunikációhoz és a módosításokhoz, ha kérik, de ez nem feltétlenül szükséges."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "A beküldéssel elfogadja, hogy a könyvtár a következő alatt kerül közzétételre ",
|
|
||||||
"link": "MIT Licensz ",
|
|
||||||
"post": "ami röviden azt jelenti, hogy bárki korlátozás nélkül használhatja őket."
|
|
||||||
},
|
|
||||||
"noteItems": "Minden könyvtárelemnek saját nevével kell rendelkeznie, hogy szűrhető legyen. A következő könyvtári tételek kerülnek bele:",
|
"noteItems": "Minden könyvtárelemnek saját nevével kell rendelkeznie, hogy szűrhető legyen. A következő könyvtári tételek kerülnek bele:",
|
||||||
"atleastOneLibItem": "A kezdéshez válassz ki legalább egy könyvtári elemet",
|
"atleastOneLibItem": "A kezdéshez válassz ki legalább egy könyvtári elemet",
|
||||||
"republishWarning": ""
|
"republishWarning": ""
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "A könyvtár beküldve",
|
"title": "A könyvtár beküldve",
|
||||||
"content": "Köszönjük {{authorName}}. Könyvtáradat elküldtük felülvizsgálatra. Nyomon követheted az állapotot",
|
"content": "Köszönjük {{authorName}}. Könyvtáradat elküldtük felülvizsgálatra. Nyomon követheted az állapotot<link>itt</link>"
|
||||||
"link": "itt"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Könyvtár alaphelyzetbe állítása",
|
"resetLibrary": "Könyvtár alaphelyzetbe állítása",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Tidak dapat menyimpan ke dalam basis data server. Jika masih berlanjut, Anda sebaiknya simpan berkas Anda secara lokal untuk memastikan pekerjaan Anda tidak hilang.",
|
"collabSaveFailed": "Tidak dapat menyimpan ke dalam basis data server. Jika masih berlanjut, Anda sebaiknya simpan berkas Anda secara lokal untuk memastikan pekerjaan Anda tidak hilang.",
|
||||||
"collabSaveFailed_sizeExceeded": "Tidak dapat menyimpan ke dalam basis data server, tampaknya ukuran kanvas terlalu besar. Anda sebaiknya simpan berkas Anda secara lokal untuk memastikan pekerjaan Anda tidak hilang.",
|
"collabSaveFailed_sizeExceeded": "Tidak dapat menyimpan ke dalam basis data server, tampaknya ukuran kanvas terlalu besar. Anda sebaiknya simpan berkas Anda secara lokal untuk memastikan pekerjaan Anda tidak hilang.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "Sepertinya kamu menggunakan browser Brave dengan",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "pengaturan diaktifkan",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "Elemen Teks",
|
|
||||||
"in_your_drawings": "dalam gambar anda",
|
|
||||||
"strongly_recommend": "Kami sangat menyarankan untuk mematikan pengaturan ini. Kamu dapat mengikuti",
|
|
||||||
"steps": "langkah-langkah ini",
|
|
||||||
"how": "dalam bagaimana melakukan itu",
|
|
||||||
"disable_setting": " Jika pengaturan ini dimatikan tidak mengatasi masalah tampilan dari elemen teks, silahkan buka",
|
|
||||||
"issue": "isu",
|
|
||||||
"write": "di GitHub kami, atau tulis kami di",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Tip: coba pindahkan elemen-terjauh lebih dekat bersama."
|
"canvasTooBigTip": "Tip: coba pindahkan elemen-terjauh lebih dekat bersama."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Mengalami sebuah kesalahan. Cobalah ",
|
"headingMain": "Mengalami sebuah kesalahan. Cobalah <button>muat ulang halaman.</button>",
|
||||||
"headingMain_button": "muat ulang halaman.",
|
"clearCanvasMessage": "Jika memuat ulang tidak bekerja, cobalah <button>bersihkan canvas.</button>",
|
||||||
"clearCanvasMessage": "Jika memuat ulang tidak bekerja, cobalah ",
|
|
||||||
"clearCanvasMessage_button": "bersihkan canvas.",
|
|
||||||
"clearCanvasCaveat": " Ini akan menghasilkan hilangnya pekerjaan ",
|
"clearCanvasCaveat": " Ini akan menghasilkan hilangnya pekerjaan ",
|
||||||
"trackedToSentry_pre": "Kesalahan dengan pengidentifikasi ",
|
"trackedToSentry": "Kesalahan dengan pengidentifikasi {{eventId}} dilacak di sistem kami.",
|
||||||
"trackedToSentry_post": " dilacak di sistem kami.",
|
"openIssueMessage": "Kami sangat berhati-hati untuk tidak menyertakan informasi pemandangan Anda pada kesalahan. Jika pemandangan Anda tidak bersifat pribadi, mohon pertimbangkan menindak lanjut pada <button>pelacak bug.</button> Mohon sertakan informasi dibawah ini dengan menyalin dan menempelkan di Github issue.",
|
||||||
"openIssueMessage_pre": "Kami sangat berhati-hati untuk tidak menyertakan informasi pemandangan Anda pada kesalahan. Jika pemandangan Anda tidak bersifat pribadi, mohon pertimbangkan menindak lanjut pada ",
|
|
||||||
"openIssueMessage_button": "pelacak bug.",
|
|
||||||
"openIssueMessage_post": " Mohon sertakan informasi dibawah ini dengan menyalin dan menempelkan di Github issue.",
|
|
||||||
"sceneContent": "Pemandangan konten:"
|
"sceneContent": "Pemandangan konten:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Dibutuhkan",
|
"required": "Dibutuhkan",
|
||||||
"website": "Masukkan URL valid"
|
"website": "Masukkan URL valid"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Kirimkan pustaka Anda untuk disertakan di <link>repositori pustaka publik</link>untuk orang lain menggunakannya dalam gambar mereka.",
|
||||||
"pre": "Kirimkan pustaka Anda untuk disertakan di ",
|
"noteGuidelines": "Pustaka butuh disetujui secara manual terlebih dahulu. Baca <link>pedoman</link> sebelum mengirim. Anda butuh akun GitHub untuk berkomunikasi dan membuat perubahan jika dibutuhkan, tetapi tidak wajib dibutukan.",
|
||||||
"link": "repositori pustaka publik",
|
"noteLicense": "Dengan mengkirimkannya, Anda setuju pustaka akan diterbitkan dibawah <link>Lisensi MIT, </link>yang artinya siapa pun dapat menggunakannya tanpa batasan.",
|
||||||
"post": "untuk orang lain menggunakannya dalam gambar mereka."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "Pustaka butuh disetujui secara manual terlebih dahulu. Baca ",
|
|
||||||
"link": "pedoman",
|
|
||||||
"post": " sebelum mengirim. Anda butuh akun GitHub untuk berkomunikasi dan membuat perubahan jika dibutuhkan, tetapi tidak wajib dibutukan."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Dengan mengkirimkannya, Anda setuju pustaka akan diterbitkan dibawah ",
|
|
||||||
"link": "Lisensi MIT, ",
|
|
||||||
"post": "yang artinya siapa pun dapat menggunakannya tanpa batasan."
|
|
||||||
},
|
|
||||||
"noteItems": "Setiap item pustaka harus memiliki nama, sehingga bisa disortir. Item pustaka di bawah ini akan dimasukan:",
|
"noteItems": "Setiap item pustaka harus memiliki nama, sehingga bisa disortir. Item pustaka di bawah ini akan dimasukan:",
|
||||||
"atleastOneLibItem": "Pilih setidaknya satu item pustaka untuk mulai",
|
"atleastOneLibItem": "Pilih setidaknya satu item pustaka untuk mulai",
|
||||||
"republishWarning": "Catatan: beberapa item yang dipilih telah ditandai sebagai sudah dipublikasikan/diserahkan. Anda hanya dapat menyerahkan kembali item-item ketika memperbarui pustaka atau pengumpulan."
|
"republishWarning": "Catatan: beberapa item yang dipilih telah ditandai sebagai sudah dipublikasikan/diserahkan. Anda hanya dapat menyerahkan kembali item-item ketika memperbarui pustaka atau pengumpulan."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Pustaka telah dikirm",
|
"title": "Pustaka telah dikirm",
|
||||||
"content": "Terima kasih {{authorName}}. pustaka Anda telah diserahkan untuk ditinjau ulang. Anda dapat cek statusnya",
|
"content": "Terima kasih {{authorName}}. pustaka Anda telah diserahkan untuk ditinjau ulang. Anda dapat cek statusnya<link>di sini</link>"
|
||||||
"link": "di sini"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Reset pustaka",
|
"resetLibrary": "Reset pustaka",
|
||||||
|
|
|
@ -208,19 +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": {
|
||||||
"start": "Sembra che tu stia usando il browser Brave con il",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "Blocco Aggressivamente Impronte Digitali",
|
"line2": "",
|
||||||
"setting_enabled": "impostazioni abilitate",
|
"line3": "",
|
||||||
"break": "Questo potrebbe portare a rompere gli",
|
"line4": ""
|
||||||
"text_elements": "Elementi Di Testo",
|
|
||||||
"in_your_drawings": "nei tuoi disegni",
|
|
||||||
"strongly_recommend": "Si consiglia vivamente di disabilitare questa impostazione. È possibile seguire",
|
|
||||||
"steps": "questi passaggi",
|
|
||||||
"how": "su come fare",
|
|
||||||
"disable_setting": " Se la disabilitazione di questa impostazione non corregge la visualizzazione degli elementi di testo, apri un",
|
|
||||||
"issue": "problema",
|
|
||||||
"write": "sul nostro GitHub, o scrivici su",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Suggerimento: prova a spostare gli elementi più lontani più vicini tra loro."
|
"canvasTooBigTip": "Suggerimento: prova a spostare gli elementi più lontani più vicini tra loro."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Si è verificato un errore. Provare ",
|
"headingMain": "Si è verificato un errore. Provare <button>ricaricando la pagina.</button>",
|
||||||
"headingMain_button": "ricaricando la pagina.",
|
"clearCanvasMessage": "Se ricaricare non funziona, prova <button>pulire la tela.</button>",
|
||||||
"clearCanvasMessage": "Se ricaricare non funziona, prova ",
|
|
||||||
"clearCanvasMessage_button": "pulire la tela.",
|
|
||||||
"clearCanvasCaveat": " Questo risulterà nella perdita del lavoro ",
|
"clearCanvasCaveat": " Questo risulterà nella perdita del lavoro ",
|
||||||
"trackedToSentry_pre": "L'errore con identificativo ",
|
"trackedToSentry": "L'errore con identificativo {{eventId}} è stato tracciato nel nostro sistema.",
|
||||||
"trackedToSentry_post": " è stato tracciato nel nostro sistema.",
|
"openIssueMessage": "Siamo stati molto cauti nel non includere informazioni della scena nell'errore. Se la tua scena non è privata, ti preghiamo di considerare la sua inclusione nel nostro <button>bug tracker.</button> Per favore includi le informazioni riportate qui sotto copiandole e incollandole nella issue di GitHub.",
|
||||||
"openIssueMessage_pre": "Siamo stati molto cauti nel non includere informazioni della scena nell'errore. Se la tua scena non è privata, ti preghiamo di considerare la sua inclusione nel nostro ",
|
|
||||||
"openIssueMessage_button": "bug tracker.",
|
|
||||||
"openIssueMessage_post": " Per favore includi le informazioni riportate qui sotto copiandole e incollandole nella issue di GitHub.",
|
|
||||||
"sceneContent": "Contenuto della scena:"
|
"sceneContent": "Contenuto della scena:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Obbligatorio",
|
"required": "Obbligatorio",
|
||||||
"website": "Inserisci un URL valido"
|
"website": "Inserisci un URL valido"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Invia la tua libreria da includere nella <link>repository della libreria pubblica</link>perché sia usata da altri nei loro disegni.",
|
||||||
"pre": "Invia la tua libreria da includere nella ",
|
"noteGuidelines": "La libreria dev'esser prima approvata manualmente. Sei pregato di leggere le <link>linee guida</link> prima di inviarla. Necessiterai di un profilo di GitHub per comunicare ed effettuare modifiche se richiesto, ma non è strettamente necessario.",
|
||||||
"link": "repository della libreria pubblica",
|
"noteLicense": "Inviando, acconsenti che la libreria sarà pubblicata sotto la <link>Licenza MIT, </link>che in breve significa che chiunque possa usarla senza restrizioni.",
|
||||||
"post": "perché sia usata da altri nei loro disegni."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "La libreria dev'esser prima approvata manualmente. Sei pregato di leggere le ",
|
|
||||||
"link": "linee guida",
|
|
||||||
"post": " prima di inviarla. Necessiterai di un profilo di GitHub per comunicare ed effettuare modifiche se richiesto, ma non è strettamente necessario."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Inviando, acconsenti che la libreria sarà pubblicata sotto la ",
|
|
||||||
"link": "Licenza MIT, ",
|
|
||||||
"post": "che in breve significa che chiunque possa usarla senza restrizioni."
|
|
||||||
},
|
|
||||||
"noteItems": "Ogni elemento della libreria deve avere il proprio nome, così che sia filtrabile. Gli elementi della seguente libreria saranno inclusi:",
|
"noteItems": "Ogni elemento della libreria deve avere il proprio nome, così che sia filtrabile. Gli elementi della seguente libreria saranno inclusi:",
|
||||||
"atleastOneLibItem": "Sei pregato di selezionare almeno un elemento della libreria per iniziare",
|
"atleastOneLibItem": "Sei pregato di selezionare almeno un elemento della libreria per iniziare",
|
||||||
"republishWarning": "Nota: alcuni degli elementi selezionati sono contrassegnati come già pubblicati/presentati. È necessario reinviare gli elementi solo quando si aggiorna una libreria o una presentazione esistente."
|
"republishWarning": "Nota: alcuni degli elementi selezionati sono contrassegnati come già pubblicati/presentati. È necessario reinviare gli elementi solo quando si aggiorna una libreria o una presentazione esistente."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Libreria inviata",
|
"title": "Libreria inviata",
|
||||||
"content": "Grazie {{authorName}}. La tua libreria è stata inviata per la revisione. Puoi monitorarne lo stato",
|
"content": "Grazie {{authorName}}. La tua libreria è stata inviata per la revisione. Puoi monitorarne lo stato<link>qui</link>"
|
||||||
"link": "qui"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Ripristina la libreria",
|
"resetLibrary": "Ripristina la libreria",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
|
"collabSaveFailed": "バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
|
||||||
"collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
|
"collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "設定が有効化されました",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "テキスト要素",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "ヒント: 最も遠い要素をもう少し近づけてみてください。"
|
"canvasTooBigTip": "ヒント: 最も遠い要素をもう少し近づけてみてください。"
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "エラーが発生しました。もう一度やり直してください。 ",
|
"headingMain": "エラーが発生しました。もう一度やり直してください。 <button>ページを再読み込みする。</button>",
|
||||||
"headingMain_button": "ページを再読み込みする。",
|
"clearCanvasMessage": "再読み込みがうまくいかない場合は、 <button>キャンバスを消去しています</button>",
|
||||||
"clearCanvasMessage": "再読み込みがうまくいかない場合は、 ",
|
|
||||||
"clearCanvasMessage_button": "キャンバスを消去しています",
|
|
||||||
"clearCanvasCaveat": " これにより作業が失われます ",
|
"clearCanvasCaveat": " これにより作業が失われます ",
|
||||||
"trackedToSentry_pre": "識別子のエラー ",
|
"trackedToSentry": "識別子のエラー {{eventId}} が我々のシステムで追跡されました。",
|
||||||
"trackedToSentry_post": " が我々のシステムで追跡されました。",
|
"openIssueMessage": "エラーに関するシーン情報を含めないように非常に慎重に設定しました。もしあなたのシーンがプライベートでない場合は、私たちのフォローアップを検討してください。 <button>バグ報告</button> GitHub のIssueに以下の情報をコピーして貼り付けてください。",
|
||||||
"openIssueMessage_pre": "エラーに関するシーン情報を含めないように非常に慎重に設定しました。もしあなたのシーンがプライベートでない場合は、私たちのフォローアップを検討してください。 ",
|
|
||||||
"openIssueMessage_button": "バグ報告",
|
|
||||||
"openIssueMessage_post": " GitHub のIssueに以下の情報をコピーして貼り付けてください。",
|
|
||||||
"sceneContent": "シーンの内容:"
|
"sceneContent": "シーンの内容:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "必須項目",
|
"required": "必須項目",
|
||||||
"website": "有効な URL を入力してください"
|
"website": "有効な URL を入力してください"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "以下に含めるライブラリを提出してください <link>公開ライブラリのリポジトリ</link>他の人が作図に使えるようにするためです",
|
||||||
"pre": "以下に含めるライブラリを提出してください ",
|
"noteGuidelines": "最初にライブラリを手動で承認する必要があります。次をお読みください <link>ガイドライン</link> 送信する前に、GitHubアカウントが必要になりますが、必須ではありません。",
|
||||||
"link": "公開ライブラリのリポジトリ",
|
"noteLicense": "提出することにより、ライブラリが次の下で公開されることに同意します: <link>MIT ライセンス </link>つまり誰でも制限なく使えるということです",
|
||||||
"post": "他の人が作図に使えるようにするためです"
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "最初にライブラリを手動で承認する必要があります。次をお読みください ",
|
|
||||||
"link": "ガイドライン",
|
|
||||||
"post": " 送信する前に、GitHubアカウントが必要になりますが、必須ではありません。"
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "提出することにより、ライブラリが次の下で公開されることに同意します: ",
|
|
||||||
"link": "MIT ライセンス ",
|
|
||||||
"post": "つまり誰でも制限なく使えるということです"
|
|
||||||
},
|
|
||||||
"noteItems": "各ライブラリ項目は、フィルタリングのために独自の名前を持つ必要があります。以下のライブラリアイテムが含まれます:",
|
"noteItems": "各ライブラリ項目は、フィルタリングのために独自の名前を持つ必要があります。以下のライブラリアイテムが含まれます:",
|
||||||
"atleastOneLibItem": "開始するには少なくとも1つのライブラリ項目を選択してください",
|
"atleastOneLibItem": "開始するには少なくとも1つのライブラリ項目を選択してください",
|
||||||
"republishWarning": "注意: 選択された項目の中には、すでに公開/投稿済みと表示されているものがあります。既存のライブラリや投稿を更新する場合のみ、アイテムを再投稿してください。"
|
"republishWarning": "注意: 選択された項目の中には、すでに公開/投稿済みと表示されているものがあります。既存のライブラリや投稿を更新する場合のみ、アイテムを再投稿してください。"
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "ライブラリを送信しました",
|
"title": "ライブラリを送信しました",
|
||||||
"content": "{{authorName}} さん、ありがとうございます。あなたのライブラリはレビューのために提出されました。状況を追跡できます。",
|
"content": "{{authorName}} さん、ありがとうございます。あなたのライブラリはレビューのために提出されました。状況を追跡できます。<link>こちら</link>"
|
||||||
"link": "こちら"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "ライブラリをリセット",
|
"resetLibrary": "ライブラリをリセット",
|
||||||
|
|
474
src/locales/kaa.json
Normal file
474
src/locales/kaa.json
Normal file
|
@ -0,0 +1,474 @@
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"paste": "",
|
||||||
|
"pasteAsPlaintext": "",
|
||||||
|
"pasteCharts": "",
|
||||||
|
"selectAll": "Barlıǵın tańlaw",
|
||||||
|
"multiSelect": "",
|
||||||
|
"moveCanvas": "",
|
||||||
|
"cut": "",
|
||||||
|
"copy": "Kóshirip alıw",
|
||||||
|
"copyAsPng": "",
|
||||||
|
"copyAsSvg": "",
|
||||||
|
"copyText": "",
|
||||||
|
"bringForward": "",
|
||||||
|
"sendToBack": "",
|
||||||
|
"bringToFront": "",
|
||||||
|
"sendBackward": "",
|
||||||
|
"delete": "Óshiriw",
|
||||||
|
"copyStyles": "",
|
||||||
|
"pasteStyles": "",
|
||||||
|
"stroke": "",
|
||||||
|
"background": "",
|
||||||
|
"fill": "",
|
||||||
|
"strokeWidth": "",
|
||||||
|
"strokeStyle": "",
|
||||||
|
"strokeStyle_solid": "",
|
||||||
|
"strokeStyle_dashed": "",
|
||||||
|
"strokeStyle_dotted": "",
|
||||||
|
"sloppiness": "",
|
||||||
|
"opacity": "",
|
||||||
|
"textAlign": "",
|
||||||
|
"edges": "",
|
||||||
|
"sharp": "",
|
||||||
|
"round": "",
|
||||||
|
"arrowheads": "",
|
||||||
|
"arrowhead_none": "",
|
||||||
|
"arrowhead_arrow": "",
|
||||||
|
"arrowhead_bar": "",
|
||||||
|
"arrowhead_dot": "",
|
||||||
|
"arrowhead_triangle": "",
|
||||||
|
"fontSize": "Shrift ólshemi",
|
||||||
|
"fontFamily": "",
|
||||||
|
"onlySelected": "",
|
||||||
|
"withBackground": "",
|
||||||
|
"exportEmbedScene": "",
|
||||||
|
"exportEmbedScene_details": "",
|
||||||
|
"addWatermark": "",
|
||||||
|
"handDrawn": "",
|
||||||
|
"normal": "",
|
||||||
|
"code": "",
|
||||||
|
"small": "",
|
||||||
|
"medium": "",
|
||||||
|
"large": "",
|
||||||
|
"veryLarge": "",
|
||||||
|
"solid": "",
|
||||||
|
"hachure": "",
|
||||||
|
"zigzag": "",
|
||||||
|
"crossHatch": "",
|
||||||
|
"thin": "",
|
||||||
|
"bold": "",
|
||||||
|
"left": "",
|
||||||
|
"center": "",
|
||||||
|
"right": "",
|
||||||
|
"extraBold": "",
|
||||||
|
"architect": "",
|
||||||
|
"artist": "",
|
||||||
|
"cartoonist": "",
|
||||||
|
"fileTitle": "Fayl ataması",
|
||||||
|
"colorPicker": "",
|
||||||
|
"canvasColors": "",
|
||||||
|
"canvasBackground": "",
|
||||||
|
"drawingCanvas": "",
|
||||||
|
"layers": "",
|
||||||
|
"actions": "Háreketler",
|
||||||
|
"language": "Til",
|
||||||
|
"liveCollaboration": "",
|
||||||
|
"duplicateSelection": "Nusqa",
|
||||||
|
"untitled": "",
|
||||||
|
"name": "Ataması",
|
||||||
|
"yourName": "Atıńız",
|
||||||
|
"madeWithExcalidraw": "",
|
||||||
|
"group": "",
|
||||||
|
"ungroup": "",
|
||||||
|
"collaborators": "",
|
||||||
|
"showGrid": "",
|
||||||
|
"addToLibrary": "Kitapxanaǵa qosıw",
|
||||||
|
"removeFromLibrary": "Kitapxanadan alıp taslaw",
|
||||||
|
"libraryLoadingMessage": "Kitapxana júklenbekte…",
|
||||||
|
"libraries": "Kitapxanalardı kóriw",
|
||||||
|
"loadingScene": "",
|
||||||
|
"align": "",
|
||||||
|
"alignTop": "",
|
||||||
|
"alignBottom": "",
|
||||||
|
"alignLeft": "",
|
||||||
|
"alignRight": "",
|
||||||
|
"centerVertically": "",
|
||||||
|
"centerHorizontally": "",
|
||||||
|
"distributeHorizontally": "",
|
||||||
|
"distributeVertically": "",
|
||||||
|
"flipHorizontal": "",
|
||||||
|
"flipVertical": "",
|
||||||
|
"viewMode": "Kóriw rejimi",
|
||||||
|
"toggleExportColorScheme": "",
|
||||||
|
"share": "Bólisiw",
|
||||||
|
"showStroke": "",
|
||||||
|
"showBackground": "",
|
||||||
|
"toggleTheme": "",
|
||||||
|
"personalLib": "",
|
||||||
|
"excalidrawLib": "",
|
||||||
|
"decreaseFontSize": "",
|
||||||
|
"increaseFontSize": "",
|
||||||
|
"unbindText": "",
|
||||||
|
"bindText": "",
|
||||||
|
"createContainerFromText": "",
|
||||||
|
"link": {
|
||||||
|
"edit": "Siltemeni ózgertiw",
|
||||||
|
"create": "Siltemeni jaratıw",
|
||||||
|
"label": "Silteme"
|
||||||
|
},
|
||||||
|
"lineEditor": {
|
||||||
|
"edit": "",
|
||||||
|
"exit": ""
|
||||||
|
},
|
||||||
|
"elementLock": {
|
||||||
|
"lock": "Qulıplaw",
|
||||||
|
"unlock": "Qulıptan shıǵarıw",
|
||||||
|
"lockAll": "Barlıǵın qulıplaw",
|
||||||
|
"unlockAll": "Barlıǵın qulıptan shıǵarıw"
|
||||||
|
},
|
||||||
|
"statusPublished": "",
|
||||||
|
"sidebarLock": ""
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"noItems": "",
|
||||||
|
"hint_emptyLibrary": "",
|
||||||
|
"hint_emptyPrivateLibrary": ""
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"clearReset": "",
|
||||||
|
"exportJSON": "",
|
||||||
|
"exportImage": "",
|
||||||
|
"export": "",
|
||||||
|
"exportToPng": "",
|
||||||
|
"exportToSvg": "",
|
||||||
|
"copyToClipboard": "Almasıw buferine kóshirip alındı",
|
||||||
|
"copyPngToClipboard": "",
|
||||||
|
"scale": "",
|
||||||
|
"save": "",
|
||||||
|
"saveAs": "",
|
||||||
|
"load": "Ashıw",
|
||||||
|
"getShareableLink": "",
|
||||||
|
"close": "Jabıw",
|
||||||
|
"selectLanguage": "Tildi tańlaw",
|
||||||
|
"scrollBackToContent": "",
|
||||||
|
"zoomIn": "",
|
||||||
|
"zoomOut": "",
|
||||||
|
"resetZoom": "",
|
||||||
|
"menu": "Menyu",
|
||||||
|
"done": "Tayın",
|
||||||
|
"edit": "Ózgertiw",
|
||||||
|
"undo": "",
|
||||||
|
"redo": "",
|
||||||
|
"resetLibrary": "",
|
||||||
|
"createNewRoom": "",
|
||||||
|
"fullScreen": "Tolıq ekran",
|
||||||
|
"darkMode": "Qarańǵı tema",
|
||||||
|
"lightMode": "Jaqtı tema",
|
||||||
|
"zenMode": "",
|
||||||
|
"exitZenMode": "",
|
||||||
|
"cancel": "Biykarlaw",
|
||||||
|
"clear": "Tazalaw",
|
||||||
|
"remove": "Óshiriw",
|
||||||
|
"publishLibrary": "",
|
||||||
|
"submit": "Jiberiw",
|
||||||
|
"confirm": "Tastıyıqlaw"
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"clearReset": "",
|
||||||
|
"couldNotCreateShareableLink": "",
|
||||||
|
"couldNotCreateShareableLinkTooBig": "",
|
||||||
|
"couldNotLoadInvalidFile": "",
|
||||||
|
"importBackendFailed": "",
|
||||||
|
"cannotExportEmptyCanvas": "",
|
||||||
|
"couldNotCopyToClipboard": "Almasıw buferine kóshirip alıw ámelge aspadı.",
|
||||||
|
"decryptFailed": "",
|
||||||
|
"uploadedSecurly": "",
|
||||||
|
"loadSceneOverridePrompt": "",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
|
"errorAddingToLibrary": "",
|
||||||
|
"errorRemovingFromLibrary": "",
|
||||||
|
"confirmAddLibrary": "",
|
||||||
|
"imageDoesNotContainScene": "",
|
||||||
|
"cannotRestoreFromImage": "",
|
||||||
|
"invalidSceneUrl": "",
|
||||||
|
"resetLibrary": "",
|
||||||
|
"removeItemsFromsLibrary": "",
|
||||||
|
"invalidEncryptionKey": "",
|
||||||
|
"collabOfflineWarning": ""
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"unsupportedFileType": "",
|
||||||
|
"imageInsertError": "",
|
||||||
|
"fileTooBig": "",
|
||||||
|
"svgImageInsertError": "",
|
||||||
|
"invalidSVGString": "Jaramsız SVG.",
|
||||||
|
"cannotResolveCollabServer": "",
|
||||||
|
"importLibraryError": "Kitapxananı júklew ámelge aspadı",
|
||||||
|
"collabSaveFailed": "",
|
||||||
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
|
"brave_measure_text_error": {
|
||||||
|
"line1": "",
|
||||||
|
"line2": "",
|
||||||
|
"line3": "",
|
||||||
|
"line4": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toolBar": {
|
||||||
|
"selection": "",
|
||||||
|
"image": "Súwret qoyıw",
|
||||||
|
"rectangle": "",
|
||||||
|
"diamond": "",
|
||||||
|
"ellipse": "",
|
||||||
|
"arrow": "",
|
||||||
|
"line": "Sızıq",
|
||||||
|
"freedraw": "Sızıw",
|
||||||
|
"text": "Tekst",
|
||||||
|
"library": "Kitapxana",
|
||||||
|
"lock": "",
|
||||||
|
"penMode": "",
|
||||||
|
"link": "",
|
||||||
|
"eraser": "Óshirgish",
|
||||||
|
"hand": ""
|
||||||
|
},
|
||||||
|
"headings": {
|
||||||
|
"canvasActions": "",
|
||||||
|
"selectedShapeActions": "",
|
||||||
|
"shapes": ""
|
||||||
|
},
|
||||||
|
"hints": {
|
||||||
|
"canvasPanning": "",
|
||||||
|
"linearElement": "",
|
||||||
|
"freeDraw": "",
|
||||||
|
"text": "",
|
||||||
|
"text_selected": "",
|
||||||
|
"text_editing": "",
|
||||||
|
"linearElementMulti": "",
|
||||||
|
"lockAngle": "",
|
||||||
|
"resize": "",
|
||||||
|
"resizeImage": "",
|
||||||
|
"rotate": "",
|
||||||
|
"lineEditor_info": "",
|
||||||
|
"lineEditor_pointSelected": "",
|
||||||
|
"lineEditor_nothingSelected": "",
|
||||||
|
"placeImage": "",
|
||||||
|
"publishLibrary": "",
|
||||||
|
"bindTextToElement": "",
|
||||||
|
"deepBoxSelect": "",
|
||||||
|
"eraserRevert": "",
|
||||||
|
"firefox_clipboard_write": ""
|
||||||
|
},
|
||||||
|
"canvasError": {
|
||||||
|
"cannotShowPreview": "",
|
||||||
|
"canvasTooBig": "",
|
||||||
|
"canvasTooBigTip": ""
|
||||||
|
},
|
||||||
|
"errorSplash": {
|
||||||
|
"headingMain_pre": "",
|
||||||
|
"headingMain_button": "",
|
||||||
|
"clearCanvasMessage": "",
|
||||||
|
"clearCanvasMessage_button": "",
|
||||||
|
"clearCanvasCaveat": "",
|
||||||
|
"trackedToSentry_pre": "",
|
||||||
|
"trackedToSentry_post": "",
|
||||||
|
"openIssueMessage_pre": "",
|
||||||
|
"openIssueMessage_button": "",
|
||||||
|
"openIssueMessage_post": "",
|
||||||
|
"sceneContent": ""
|
||||||
|
},
|
||||||
|
"roomDialog": {
|
||||||
|
"desc_intro": "",
|
||||||
|
"desc_privacy": "",
|
||||||
|
"button_startSession": "",
|
||||||
|
"button_stopSession": "",
|
||||||
|
"desc_inProgressIntro": "",
|
||||||
|
"desc_shareLink": "",
|
||||||
|
"desc_exitSession": "",
|
||||||
|
"shareTitle": ""
|
||||||
|
},
|
||||||
|
"errorDialog": {
|
||||||
|
"title": "Qátelik"
|
||||||
|
},
|
||||||
|
"exportDialog": {
|
||||||
|
"disk_title": "",
|
||||||
|
"disk_details": "",
|
||||||
|
"disk_button": "",
|
||||||
|
"link_title": "",
|
||||||
|
"link_details": "",
|
||||||
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "Eksportlaw",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
|
},
|
||||||
|
"helpDialog": {
|
||||||
|
"blog": "",
|
||||||
|
"click": "basıw",
|
||||||
|
"deepSelect": "",
|
||||||
|
"deepBoxSelect": "",
|
||||||
|
"curvedArrow": "",
|
||||||
|
"curvedLine": "",
|
||||||
|
"documentation": "Hújjetshilik",
|
||||||
|
"doubleClick": "",
|
||||||
|
"drag": "",
|
||||||
|
"editor": "",
|
||||||
|
"editLineArrowPoints": "",
|
||||||
|
"editText": "",
|
||||||
|
"github": "",
|
||||||
|
"howto": "",
|
||||||
|
"or": "yamasa",
|
||||||
|
"preventBinding": "",
|
||||||
|
"tools": "Ásbaplar",
|
||||||
|
"shortcuts": "",
|
||||||
|
"textFinish": "",
|
||||||
|
"textNewLine": "",
|
||||||
|
"title": "Járdem",
|
||||||
|
"view": "Kóriw",
|
||||||
|
"zoomToFit": "",
|
||||||
|
"zoomToSelection": "",
|
||||||
|
"toggleElementLock": "",
|
||||||
|
"movePageUpDown": "",
|
||||||
|
"movePageLeftRight": ""
|
||||||
|
},
|
||||||
|
"clearCanvasDialog": {
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"publishDialog": {
|
||||||
|
"title": "",
|
||||||
|
"itemName": "",
|
||||||
|
"authorName": "Avtor atı",
|
||||||
|
"githubUsername": "GitHub paydalanıwshı atı",
|
||||||
|
"twitterUsername": "Twitter paydalanıwshı atı",
|
||||||
|
"libraryName": "Kitapxana ataması",
|
||||||
|
"libraryDesc": "",
|
||||||
|
"website": "Veb-sayt",
|
||||||
|
"placeholder": {
|
||||||
|
"authorName": "",
|
||||||
|
"libraryName": "",
|
||||||
|
"libraryDesc": "",
|
||||||
|
"githubHandle": "",
|
||||||
|
"twitterHandle": "",
|
||||||
|
"website": ""
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"required": "",
|
||||||
|
"website": ""
|
||||||
|
},
|
||||||
|
"noteDescription": {
|
||||||
|
"pre": "",
|
||||||
|
"link": "",
|
||||||
|
"post": ""
|
||||||
|
},
|
||||||
|
"noteGuidelines": {
|
||||||
|
"pre": "",
|
||||||
|
"link": "",
|
||||||
|
"post": ""
|
||||||
|
},
|
||||||
|
"noteLicense": {
|
||||||
|
"pre": "",
|
||||||
|
"link": "",
|
||||||
|
"post": ""
|
||||||
|
},
|
||||||
|
"noteItems": "",
|
||||||
|
"atleastOneLibItem": "",
|
||||||
|
"republishWarning": ""
|
||||||
|
},
|
||||||
|
"publishSuccessDialog": {
|
||||||
|
"title": "",
|
||||||
|
"content": "",
|
||||||
|
"link": "usı jerde"
|
||||||
|
},
|
||||||
|
"confirmDialog": {
|
||||||
|
"resetLibrary": "",
|
||||||
|
"removeItemsFromLib": ""
|
||||||
|
},
|
||||||
|
"encrypted": {
|
||||||
|
"tooltip": "",
|
||||||
|
"link": ""
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"angle": "",
|
||||||
|
"element": "",
|
||||||
|
"elements": "",
|
||||||
|
"height": "",
|
||||||
|
"scene": "",
|
||||||
|
"selected": "Tańlandı",
|
||||||
|
"storage": "",
|
||||||
|
"title": "",
|
||||||
|
"total": "",
|
||||||
|
"version": "Versiya",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
|
"width": ""
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"addedToLibrary": "",
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "",
|
||||||
|
"fileSaved": "Fayl saqlandı.",
|
||||||
|
"fileSavedToFilename": "",
|
||||||
|
"canvas": "",
|
||||||
|
"selection": "",
|
||||||
|
"pasteAsSingleElement": ""
|
||||||
|
},
|
||||||
|
"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ızǵılt sarı 0",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "Qızıl 6",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "Kók 6",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "Jasıl 6",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "Sarı 6",
|
||||||
|
"fd7e14": "",
|
||||||
|
"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ızǵılt sarı 9"
|
||||||
|
},
|
||||||
|
"welcomeScreen": {
|
||||||
|
"app": {
|
||||||
|
"center_heading": "",
|
||||||
|
"center_heading_plus": "",
|
||||||
|
"menuHint": "Eksportlaw, sazlawlar, tiller, ..."
|
||||||
|
},
|
||||||
|
"defaults": {
|
||||||
|
"menuHint": "Eksportlaw, sazlawlar hám basqa...",
|
||||||
|
"center_heading": "",
|
||||||
|
"toolbarHint": "",
|
||||||
|
"helpHint": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Ulamek asekles deg uzadur n yisefka deg ugilal. Ma ikemmel wugur, isefk ad teskelseḍ afaylu s wudem adigan akken ad tetḥeqqeḍ ur tesruḥuyeḍ ara amahil-inek•inem.",
|
"collabSaveFailed": "Ulamek asekles deg uzadur n yisefka deg ugilal. Ma ikemmel wugur, 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.",
|
"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": {
|
||||||
"start": "Ittban-d am wakken tsseqdaceḍ iminig Brave akked",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "yermed",
|
"line3": "",
|
||||||
"break": "Ayagi yezmer ad d-iseglu s truẓi n",
|
"line4": ""
|
||||||
"text_elements": "Iferdisen iḍrisen",
|
|
||||||
"in_your_drawings": "deg wunuɣen-inek",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "isurifen-agi",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "di GitHub inek neɣ aru-yaɣ-d di",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Tixidest: eɛreḍ ad tesqerbeḍ ciṭ iferdisen yembaɛaden."
|
"canvasTooBigTip": "Tixidest: eɛreḍ ad tesqerbeḍ ciṭ iferdisen yembaɛaden."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Teḍra-d tuccḍa. Eɛreḍ ",
|
"headingMain": "Teḍra-d tuccḍa. Eɛreḍ <button>asali n usebter tikkelt-nniḍen.</button>",
|
||||||
"headingMain_button": "asali n usebter tikkelt-nniḍen.",
|
"clearCanvasMessage": "Ma yella tulsa n usali ur tefri ara ugur, eɛreḍ <button>asfaḍ n teɣzut n usuneɣ.</button>",
|
||||||
"clearCanvasMessage": "Ma yella tulsa n usali ur tefri ara ugur, eɛreḍ ",
|
|
||||||
"clearCanvasMessage_button": "asfaḍ n teɣzut n usuneɣ.",
|
|
||||||
"clearCanvasCaveat": " Ayagi ad d-iglu s usṛuḥu n umahil ",
|
"clearCanvasCaveat": " Ayagi ad d-iglu s usṛuḥu n umahil ",
|
||||||
"trackedToSentry_pre": "Tuccḍa akked umesmagi ",
|
"trackedToSentry": "Tuccḍa akked umesmagi {{eventId}} tettwasekles deg unagraw-nneɣ.",
|
||||||
"trackedToSentry_post": " tettwasekles deg unagraw-nneɣ.",
|
"openIssueMessage": "Nḥuder aṭas akken ur nseddu ara talɣut n usayes-inek (m) di tuccḍa. Ma yella asayes-inek (m) mačči d amaẓlay, ttxil-k (m) xemmem ad ḍefreḍ <button>afecku n weḍfar n yibugen.</button> Ma ulac uɣilif seddu talɣut ukessar-agi s wenɣal akked usenṭeḍ di GitHub issue.",
|
||||||
"openIssueMessage_pre": "Nḥuder aṭas akken ur nseddu ara talɣut n usayes-inek (m) di tuccḍa. Ma yella asayes-inek (m) mačči d amaẓlay, ttxil-k (m) xemmem ad ḍefreḍ ",
|
|
||||||
"openIssueMessage_button": "afecku n weḍfar n yibugen.",
|
|
||||||
"openIssueMessage_post": " Ma ulac uɣilif seddu talɣut ukessar-agi s wenɣal akked usenṭeḍ di GitHub issue.",
|
|
||||||
"sceneContent": "Agbur n usayes:"
|
"sceneContent": "Agbur n usayes:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Yettwasra",
|
"required": "Yettwasra",
|
||||||
"website": "Sekcem URL ameɣtu"
|
"website": "Sekcem URL ameɣtu"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Azen tamkarḍit-inek•inem akken ad teddu di <link>akaram azayez n temkarḍit</link>i yimdanen-nniḍen ara isqedcen deg wunuɣen-nnsen.",
|
||||||
"pre": "Azen tamkarḍit-inek•inem akken ad teddu di ",
|
"noteGuidelines": "Tamkarḍit teḥwaǧ ad tettwaqbel s ufus qbel. Ma ulac uɣilif ɣer <link>iwellihen</link> send ad tazneḍ. Tesriḍ amiḍan n GitHub akken ad tmmeslayeḍ yerna ad tgeḍ ibeddilen ma yelaq, maca mačči d ayen yettwaḥetmen.",
|
||||||
"link": "akaram azayez n temkarḍit",
|
"noteLicense": "Mi tuzneḍ ad tqebleḍ akken tamkarḍit ad d-teffeɣ s <link>Turagt MIT, </link>ayen yebɣan ad d-yini belli yal yiwen izmer ad ten-iseqdec war tilist.",
|
||||||
"post": "i yimdanen-nniḍen ara isqedcen deg wunuɣen-nnsen."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "Tamkarḍit teḥwaǧ ad tettwaqbel s ufus qbel. Ma ulac uɣilif ɣer ",
|
|
||||||
"link": "iwellihen",
|
|
||||||
"post": " send ad tazneḍ. Tesriḍ amiḍan n GitHub akken ad tmmeslayeḍ yerna ad tgeḍ ibeddilen ma yelaq, maca mačči d ayen yettwaḥetmen."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Mi tuzneḍ ad tqebleḍ akken tamkarḍit ad d-teffeɣ s ",
|
|
||||||
"link": "Turagt MIT, ",
|
|
||||||
"post": "ayen yebɣan ad d-yini belli yal yiwen izmer ad ten-iseqdec war tilist."
|
|
||||||
},
|
|
||||||
"noteItems": "Yal aferdis n temkarḍit isefk ad isɛu isem-is i yiman-is akken ad yili wamek ara yettusizdeg. Iferdisen-agi n temkarḍit ad ddun:",
|
"noteItems": "Yal aferdis n temkarḍit isefk ad isɛu isem-is i yiman-is akken ad yili wamek ara yettusizdeg. Iferdisen-agi n temkarḍit ad ddun:",
|
||||||
"atleastOneLibItem": "Ma ulac uɣilif fern ma drus yiwen n uferdis n temkarḍit akken ad tebduḍ",
|
"atleastOneLibItem": "Ma ulac uɣilif fern ma drus yiwen n uferdis n temkarḍit akken ad tebduḍ",
|
||||||
"republishWarning": "Tamawt: kra n yiferdisen yettwafernen ttwacerḍen ffeɣen-d/ttwaznen. Isefk ad talseḍ tuzzna n yiferdisen anagar mi ara tleqqemeḍ tamkarḍit neɣ tuzzna yellan."
|
"republishWarning": "Tamawt: kra n yiferdisen yettwafernen ttwacerḍen ffeɣen-d/ttwaznen. Isefk ad talseḍ tuzzna n yiferdisen anagar mi ara tleqqemeḍ tamkarḍit neɣ tuzzna yellan."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Tamkarḍit tettwazen",
|
"title": "Tamkarḍit tettwazen",
|
||||||
"content": "Tanemmirt-ik•im {{authorName}}. Tamkarḍit-inek•inem tettwazen i weselken. Tzemreḍ ad tḍefreḍ aẓayer",
|
"content": "Tanemmirt-ik•im {{authorName}}. Tamkarḍit-inek•inem tettwazen i weselken. Tzemreḍ ad tḍefreḍ aẓayer<link>dagi</link>"
|
||||||
"link": "dagi"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Ales awennez n temkarḍit",
|
"resetLibrary": "Ales awennez n temkarḍit",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "",
|
"collabSaveFailed": "",
|
||||||
"collabSaveFailed_sizeExceeded": "",
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,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": {
|
||||||
|
@ -362,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": "",
|
||||||
|
|
474
src/locales/km-KH.json
Normal file
474
src/locales/km-KH.json
Normal file
|
@ -0,0 +1,474 @@
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"paste": "",
|
||||||
|
"pasteAsPlaintext": "",
|
||||||
|
"pasteCharts": "",
|
||||||
|
"selectAll": "",
|
||||||
|
"multiSelect": "",
|
||||||
|
"moveCanvas": "",
|
||||||
|
"cut": "",
|
||||||
|
"copy": "",
|
||||||
|
"copyAsPng": "",
|
||||||
|
"copyAsSvg": "",
|
||||||
|
"copyText": "",
|
||||||
|
"bringForward": "",
|
||||||
|
"sendToBack": "",
|
||||||
|
"bringToFront": "",
|
||||||
|
"sendBackward": "",
|
||||||
|
"delete": "",
|
||||||
|
"copyStyles": "",
|
||||||
|
"pasteStyles": "",
|
||||||
|
"stroke": "",
|
||||||
|
"background": "",
|
||||||
|
"fill": "",
|
||||||
|
"strokeWidth": "",
|
||||||
|
"strokeStyle": "",
|
||||||
|
"strokeStyle_solid": "",
|
||||||
|
"strokeStyle_dashed": "",
|
||||||
|
"strokeStyle_dotted": "",
|
||||||
|
"sloppiness": "",
|
||||||
|
"opacity": "",
|
||||||
|
"textAlign": "",
|
||||||
|
"edges": "",
|
||||||
|
"sharp": "",
|
||||||
|
"round": "",
|
||||||
|
"arrowheads": "",
|
||||||
|
"arrowhead_none": "",
|
||||||
|
"arrowhead_arrow": "",
|
||||||
|
"arrowhead_bar": "",
|
||||||
|
"arrowhead_dot": "",
|
||||||
|
"arrowhead_triangle": "",
|
||||||
|
"fontSize": "",
|
||||||
|
"fontFamily": "",
|
||||||
|
"onlySelected": "",
|
||||||
|
"withBackground": "",
|
||||||
|
"exportEmbedScene": "",
|
||||||
|
"exportEmbedScene_details": "",
|
||||||
|
"addWatermark": "",
|
||||||
|
"handDrawn": "",
|
||||||
|
"normal": "",
|
||||||
|
"code": "",
|
||||||
|
"small": "",
|
||||||
|
"medium": "",
|
||||||
|
"large": "",
|
||||||
|
"veryLarge": "",
|
||||||
|
"solid": "",
|
||||||
|
"hachure": "",
|
||||||
|
"zigzag": "",
|
||||||
|
"crossHatch": "",
|
||||||
|
"thin": "",
|
||||||
|
"bold": "",
|
||||||
|
"left": "",
|
||||||
|
"center": "",
|
||||||
|
"right": "",
|
||||||
|
"extraBold": "",
|
||||||
|
"architect": "",
|
||||||
|
"artist": "",
|
||||||
|
"cartoonist": "",
|
||||||
|
"fileTitle": "",
|
||||||
|
"colorPicker": "",
|
||||||
|
"canvasColors": "",
|
||||||
|
"canvasBackground": "",
|
||||||
|
"drawingCanvas": "",
|
||||||
|
"layers": "",
|
||||||
|
"actions": "",
|
||||||
|
"language": "",
|
||||||
|
"liveCollaboration": "",
|
||||||
|
"duplicateSelection": "",
|
||||||
|
"untitled": "",
|
||||||
|
"name": "",
|
||||||
|
"yourName": "",
|
||||||
|
"madeWithExcalidraw": "",
|
||||||
|
"group": "",
|
||||||
|
"ungroup": "",
|
||||||
|
"collaborators": "",
|
||||||
|
"showGrid": "",
|
||||||
|
"addToLibrary": "",
|
||||||
|
"removeFromLibrary": "",
|
||||||
|
"libraryLoadingMessage": "",
|
||||||
|
"libraries": "",
|
||||||
|
"loadingScene": "",
|
||||||
|
"align": "",
|
||||||
|
"alignTop": "",
|
||||||
|
"alignBottom": "",
|
||||||
|
"alignLeft": "",
|
||||||
|
"alignRight": "",
|
||||||
|
"centerVertically": "",
|
||||||
|
"centerHorizontally": "",
|
||||||
|
"distributeHorizontally": "",
|
||||||
|
"distributeVertically": "",
|
||||||
|
"flipHorizontal": "",
|
||||||
|
"flipVertical": "",
|
||||||
|
"viewMode": "",
|
||||||
|
"toggleExportColorScheme": "",
|
||||||
|
"share": "",
|
||||||
|
"showStroke": "",
|
||||||
|
"showBackground": "",
|
||||||
|
"toggleTheme": "",
|
||||||
|
"personalLib": "",
|
||||||
|
"excalidrawLib": "",
|
||||||
|
"decreaseFontSize": "",
|
||||||
|
"increaseFontSize": "",
|
||||||
|
"unbindText": "",
|
||||||
|
"bindText": "",
|
||||||
|
"createContainerFromText": "",
|
||||||
|
"link": {
|
||||||
|
"edit": "",
|
||||||
|
"create": "",
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"lineEditor": {
|
||||||
|
"edit": "",
|
||||||
|
"exit": ""
|
||||||
|
},
|
||||||
|
"elementLock": {
|
||||||
|
"lock": "",
|
||||||
|
"unlock": "",
|
||||||
|
"lockAll": "",
|
||||||
|
"unlockAll": ""
|
||||||
|
},
|
||||||
|
"statusPublished": "",
|
||||||
|
"sidebarLock": ""
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"noItems": "",
|
||||||
|
"hint_emptyLibrary": "",
|
||||||
|
"hint_emptyPrivateLibrary": ""
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"clearReset": "",
|
||||||
|
"exportJSON": "",
|
||||||
|
"exportImage": "",
|
||||||
|
"export": "",
|
||||||
|
"exportToPng": "",
|
||||||
|
"exportToSvg": "",
|
||||||
|
"copyToClipboard": "",
|
||||||
|
"copyPngToClipboard": "",
|
||||||
|
"scale": "",
|
||||||
|
"save": "",
|
||||||
|
"saveAs": "",
|
||||||
|
"load": "",
|
||||||
|
"getShareableLink": "",
|
||||||
|
"close": "",
|
||||||
|
"selectLanguage": "",
|
||||||
|
"scrollBackToContent": "",
|
||||||
|
"zoomIn": "",
|
||||||
|
"zoomOut": "",
|
||||||
|
"resetZoom": "",
|
||||||
|
"menu": "",
|
||||||
|
"done": "",
|
||||||
|
"edit": "",
|
||||||
|
"undo": "",
|
||||||
|
"redo": "",
|
||||||
|
"resetLibrary": "",
|
||||||
|
"createNewRoom": "",
|
||||||
|
"fullScreen": "",
|
||||||
|
"darkMode": "",
|
||||||
|
"lightMode": "",
|
||||||
|
"zenMode": "",
|
||||||
|
"exitZenMode": "",
|
||||||
|
"cancel": "",
|
||||||
|
"clear": "",
|
||||||
|
"remove": "",
|
||||||
|
"publishLibrary": "",
|
||||||
|
"submit": "",
|
||||||
|
"confirm": ""
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"clearReset": "",
|
||||||
|
"couldNotCreateShareableLink": "",
|
||||||
|
"couldNotCreateShareableLinkTooBig": "",
|
||||||
|
"couldNotLoadInvalidFile": "",
|
||||||
|
"importBackendFailed": "",
|
||||||
|
"cannotExportEmptyCanvas": "",
|
||||||
|
"couldNotCopyToClipboard": "",
|
||||||
|
"decryptFailed": "",
|
||||||
|
"uploadedSecurly": "",
|
||||||
|
"loadSceneOverridePrompt": "",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
|
"errorAddingToLibrary": "",
|
||||||
|
"errorRemovingFromLibrary": "",
|
||||||
|
"confirmAddLibrary": "",
|
||||||
|
"imageDoesNotContainScene": "",
|
||||||
|
"cannotRestoreFromImage": "",
|
||||||
|
"invalidSceneUrl": "",
|
||||||
|
"resetLibrary": "",
|
||||||
|
"removeItemsFromsLibrary": "",
|
||||||
|
"invalidEncryptionKey": "",
|
||||||
|
"collabOfflineWarning": ""
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"unsupportedFileType": "",
|
||||||
|
"imageInsertError": "",
|
||||||
|
"fileTooBig": "",
|
||||||
|
"svgImageInsertError": "",
|
||||||
|
"invalidSVGString": "",
|
||||||
|
"cannotResolveCollabServer": "",
|
||||||
|
"importLibraryError": "",
|
||||||
|
"collabSaveFailed": "",
|
||||||
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
|
"brave_measure_text_error": {
|
||||||
|
"line1": "",
|
||||||
|
"line2": "",
|
||||||
|
"line3": "",
|
||||||
|
"line4": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toolBar": {
|
||||||
|
"selection": "",
|
||||||
|
"image": "",
|
||||||
|
"rectangle": "",
|
||||||
|
"diamond": "",
|
||||||
|
"ellipse": "",
|
||||||
|
"arrow": "",
|
||||||
|
"line": "",
|
||||||
|
"freedraw": "",
|
||||||
|
"text": "",
|
||||||
|
"library": "",
|
||||||
|
"lock": "",
|
||||||
|
"penMode": "",
|
||||||
|
"link": "",
|
||||||
|
"eraser": "",
|
||||||
|
"hand": ""
|
||||||
|
},
|
||||||
|
"headings": {
|
||||||
|
"canvasActions": "",
|
||||||
|
"selectedShapeActions": "",
|
||||||
|
"shapes": ""
|
||||||
|
},
|
||||||
|
"hints": {
|
||||||
|
"canvasPanning": "",
|
||||||
|
"linearElement": "",
|
||||||
|
"freeDraw": "",
|
||||||
|
"text": "",
|
||||||
|
"text_selected": "",
|
||||||
|
"text_editing": "",
|
||||||
|
"linearElementMulti": "",
|
||||||
|
"lockAngle": "",
|
||||||
|
"resize": "",
|
||||||
|
"resizeImage": "",
|
||||||
|
"rotate": "",
|
||||||
|
"lineEditor_info": "",
|
||||||
|
"lineEditor_pointSelected": "",
|
||||||
|
"lineEditor_nothingSelected": "",
|
||||||
|
"placeImage": "",
|
||||||
|
"publishLibrary": "",
|
||||||
|
"bindTextToElement": "",
|
||||||
|
"deepBoxSelect": "",
|
||||||
|
"eraserRevert": "",
|
||||||
|
"firefox_clipboard_write": ""
|
||||||
|
},
|
||||||
|
"canvasError": {
|
||||||
|
"cannotShowPreview": "",
|
||||||
|
"canvasTooBig": "",
|
||||||
|
"canvasTooBigTip": ""
|
||||||
|
},
|
||||||
|
"errorSplash": {
|
||||||
|
"headingMain_pre": "",
|
||||||
|
"headingMain_button": "",
|
||||||
|
"clearCanvasMessage": "",
|
||||||
|
"clearCanvasMessage_button": "",
|
||||||
|
"clearCanvasCaveat": "",
|
||||||
|
"trackedToSentry_pre": "",
|
||||||
|
"trackedToSentry_post": "",
|
||||||
|
"openIssueMessage_pre": "",
|
||||||
|
"openIssueMessage_button": "",
|
||||||
|
"openIssueMessage_post": "",
|
||||||
|
"sceneContent": ""
|
||||||
|
},
|
||||||
|
"roomDialog": {
|
||||||
|
"desc_intro": "",
|
||||||
|
"desc_privacy": "",
|
||||||
|
"button_startSession": "",
|
||||||
|
"button_stopSession": "",
|
||||||
|
"desc_inProgressIntro": "",
|
||||||
|
"desc_shareLink": "",
|
||||||
|
"desc_exitSession": "",
|
||||||
|
"shareTitle": ""
|
||||||
|
},
|
||||||
|
"errorDialog": {
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"exportDialog": {
|
||||||
|
"disk_title": "",
|
||||||
|
"disk_details": "",
|
||||||
|
"disk_button": "",
|
||||||
|
"link_title": "",
|
||||||
|
"link_details": "",
|
||||||
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
|
},
|
||||||
|
"helpDialog": {
|
||||||
|
"blog": "",
|
||||||
|
"click": "",
|
||||||
|
"deepSelect": "",
|
||||||
|
"deepBoxSelect": "",
|
||||||
|
"curvedArrow": "",
|
||||||
|
"curvedLine": "",
|
||||||
|
"documentation": "",
|
||||||
|
"doubleClick": "",
|
||||||
|
"drag": "",
|
||||||
|
"editor": "",
|
||||||
|
"editLineArrowPoints": "",
|
||||||
|
"editText": "",
|
||||||
|
"github": "",
|
||||||
|
"howto": "",
|
||||||
|
"or": "",
|
||||||
|
"preventBinding": "",
|
||||||
|
"tools": "",
|
||||||
|
"shortcuts": "",
|
||||||
|
"textFinish": "",
|
||||||
|
"textNewLine": "",
|
||||||
|
"title": "",
|
||||||
|
"view": "",
|
||||||
|
"zoomToFit": "",
|
||||||
|
"zoomToSelection": "",
|
||||||
|
"toggleElementLock": "",
|
||||||
|
"movePageUpDown": "",
|
||||||
|
"movePageLeftRight": ""
|
||||||
|
},
|
||||||
|
"clearCanvasDialog": {
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"publishDialog": {
|
||||||
|
"title": "",
|
||||||
|
"itemName": "",
|
||||||
|
"authorName": "",
|
||||||
|
"githubUsername": "",
|
||||||
|
"twitterUsername": "",
|
||||||
|
"libraryName": "",
|
||||||
|
"libraryDesc": "",
|
||||||
|
"website": "",
|
||||||
|
"placeholder": {
|
||||||
|
"authorName": "",
|
||||||
|
"libraryName": "",
|
||||||
|
"libraryDesc": "",
|
||||||
|
"githubHandle": "",
|
||||||
|
"twitterHandle": "",
|
||||||
|
"website": ""
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"required": "",
|
||||||
|
"website": ""
|
||||||
|
},
|
||||||
|
"noteDescription": {
|
||||||
|
"pre": "",
|
||||||
|
"link": "",
|
||||||
|
"post": ""
|
||||||
|
},
|
||||||
|
"noteGuidelines": {
|
||||||
|
"pre": "",
|
||||||
|
"link": "",
|
||||||
|
"post": ""
|
||||||
|
},
|
||||||
|
"noteLicense": {
|
||||||
|
"pre": "",
|
||||||
|
"link": "",
|
||||||
|
"post": ""
|
||||||
|
},
|
||||||
|
"noteItems": "",
|
||||||
|
"atleastOneLibItem": "",
|
||||||
|
"republishWarning": ""
|
||||||
|
},
|
||||||
|
"publishSuccessDialog": {
|
||||||
|
"title": "",
|
||||||
|
"content": "",
|
||||||
|
"link": ""
|
||||||
|
},
|
||||||
|
"confirmDialog": {
|
||||||
|
"resetLibrary": "",
|
||||||
|
"removeItemsFromLib": ""
|
||||||
|
},
|
||||||
|
"encrypted": {
|
||||||
|
"tooltip": "",
|
||||||
|
"link": ""
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"angle": "",
|
||||||
|
"element": "",
|
||||||
|
"elements": "",
|
||||||
|
"height": "",
|
||||||
|
"scene": "",
|
||||||
|
"selected": "",
|
||||||
|
"storage": "",
|
||||||
|
"title": "",
|
||||||
|
"total": "",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "",
|
||||||
|
"versionNotAvailable": "",
|
||||||
|
"width": ""
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"addedToLibrary": "",
|
||||||
|
"copyStyles": "",
|
||||||
|
"copyToClipboard": "",
|
||||||
|
"copyToClipboardAsPng": "",
|
||||||
|
"fileSaved": "",
|
||||||
|
"fileSavedToFilename": "",
|
||||||
|
"canvas": "",
|
||||||
|
"selection": "",
|
||||||
|
"pasteAsSingleElement": ""
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"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": {
|
||||||
|
"app": {
|
||||||
|
"center_heading": "",
|
||||||
|
"center_heading_plus": "",
|
||||||
|
"menuHint": ""
|
||||||
|
},
|
||||||
|
"defaults": {
|
||||||
|
"menuHint": "",
|
||||||
|
"center_heading": "",
|
||||||
|
"toolbarHint": "",
|
||||||
|
"helpHint": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "데이터베이스에 저장하지 못했습니다. 문제가 계속 된다면, 작업 내용을 잃지 않도록 로컬 저장소에 저장해 주세요.",
|
"collabSaveFailed": "데이터베이스에 저장하지 못했습니다. 문제가 계속 된다면, 작업 내용을 잃지 않도록 로컬 저장소에 저장해 주세요.",
|
||||||
"collabSaveFailed_sizeExceeded": "데이터베이스에 저장하지 못했습니다. 캔버스가 너무 큰 거 같습니다. 문제가 계속 된다면, 작업 내용을 잃지 않도록 로컬 저장소에 저장해 주세요.",
|
"collabSaveFailed_sizeExceeded": "데이터베이스에 저장하지 못했습니다. 캔버스가 너무 큰 거 같습니다. 문제가 계속 된다면, 작업 내용을 잃지 않도록 로컬 저장소에 저장해 주세요.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "귀하께서는",
|
"line1": "귀하께서는 <bold>강력한 지문 차단 설정</bold>이 활성화된 Brave browser를 사용하고 계신 것 같습니다.",
|
||||||
"aggressive_block_fingerprint": "강력한 지문 차단",
|
"line2": "이 기능으로 인해 화이트보드의 <bold>텍스트 요소들</bold>이 손상될 수 있습니다.",
|
||||||
"setting_enabled": "설정이 활성화된 Brave browser를 사용하고 계신 것 같습니다",
|
"line3": "저희는 해당 기능을 비활성화하는 것을 강력히 권장 드립니다. 비활성화 방법에 대해서는 <link>이 게시글</link>을 참고해주세요.",
|
||||||
"break": "이 기능으로 인해 화이트보드의",
|
"line4": "만약 이 설정을 껐음에도 텍스트 요소들이 올바르게 표시되지 않는다면, 저희 Github에 <issueLink>이슈</issueLink>를 올려주시거나 <discordLink>Discord</discordLink>로 알려주세요."
|
||||||
"text_elements": "텍스트 요소들이",
|
|
||||||
"in_your_drawings": "손상될 수 있습니다",
|
|
||||||
"strongly_recommend": "해당 기능을 설정에서 비활성화하는 것을 강력히 권장 드립니다. 비활성화 방법에 대해서는",
|
|
||||||
"steps": "이 게시글을",
|
|
||||||
"how": "참고해주세요.",
|
|
||||||
"disable_setting": " 만약 이 설정을 껐음에도 텍스트 요소들이 올바르게 표시되지 않는다면, 저희",
|
|
||||||
"issue": "Github에 이슈를",
|
|
||||||
"write": "올려주시거나",
|
|
||||||
"discord": "Discord에 제보해주세요"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "팁: 멀리 있는 요소들을 좀 더 가까이로 붙여 보세요."
|
"canvasTooBigTip": "팁: 멀리 있는 요소들을 좀 더 가까이로 붙여 보세요."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "오류가 발생했습니다. ",
|
"headingMain": "오류가 발생했습니다. <button>페이지 새로고침</button>",
|
||||||
"headingMain_button": "페이지 새로고침",
|
"clearCanvasMessage": "새로고침으로 해결되지 않을 경우, <button>캔버스 비우기</button>",
|
||||||
"clearCanvasMessage": "새로고침으로 해결되지 않을 경우, ",
|
|
||||||
"clearCanvasMessage_button": "캔버스 비우기",
|
|
||||||
"clearCanvasCaveat": " 작업 내용을 잃게 됩니다 ",
|
"clearCanvasCaveat": " 작업 내용을 잃게 됩니다 ",
|
||||||
"trackedToSentry_pre": "오류 ",
|
"trackedToSentry": "오류 {{eventId}} 가 시스템에서 발견되었습니다.",
|
||||||
"trackedToSentry_post": " 가 시스템에서 발견되었습니다.",
|
"openIssueMessage": "저희는 화면 정보를 오류에 포함하지 않도록 매우 주의하고 있습니다. 혹시 화면에 민감한 내용이 없다면 이곳에 업로드를 고려해주세요.<button>버그 트래커</button> 아래 정보를 GitHub 이슈에 복사 및 붙여넣기해 주세요.",
|
||||||
"openIssueMessage_pre": "저희는 화면 정보를 오류에 포함하지 않도록 매우 주의하고 있습니다. 혹시 화면에 민감한 내용이 없다면 이곳에 업로드를 고려해주세요.",
|
|
||||||
"openIssueMessage_button": "버그 트래커",
|
|
||||||
"openIssueMessage_post": " 아래 정보를 GitHub 이슈에 복사 및 붙여넣기해 주세요.",
|
|
||||||
"sceneContent": "화면 내용:"
|
"sceneContent": "화면 내용:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "필수사항",
|
"required": "필수사항",
|
||||||
"website": "유효한 URL을 입력하세요"
|
"website": "유효한 URL을 입력하세요"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "당신의 라이브러리를 제출하여 <link>공개 라이브러리 저장소</link>에서 다른 사람들의 그림에 사용할 수 있도록 하세요.",
|
||||||
"pre": "당신의 라이브러리를 제출하여 ",
|
"noteGuidelines": "라이브러리는 먼저 수동으로 승인되어야 합니다. 제출하기 전에 <link>가이드라인</link>을 먼저 읽어보세요. 의견을 공유하거나 변경사항을 만들기 위해선 GitHub 계정이 필요하지만, 반드시 필요하진 않습니다.",
|
||||||
"link": "공개 라이브러리 저장소",
|
"noteLicense": "제출함으로써, 당신은 라이브러리가 <link>MIT 라이선스 </link>하에 배포됨을, 즉 아무나 제약 없이 사용할 수 있음에 동의합니다.",
|
||||||
"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>여기에서 확인하실 수 있습니다.</link>"
|
||||||
"link": "여기에서 확인하실 수 있습니다."
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "라이브러리 리셋",
|
"resetLibrary": "라이브러리 리셋",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت. ئەگەر کێشەکان بەردەوام بوون، پێویستە فایلەکەت لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
"collabSaveFailed": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت. ئەگەر کێشەکان بەردەوام بوون، پێویستە فایلەکەت لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
||||||
"collabSaveFailed_sizeExceeded": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت، پێدەچێت تابلۆکە زۆر گەورە بێت. پێویستە فایلەکە لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
"collabSaveFailed_sizeExceeded": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت، پێدەچێت تابلۆکە زۆر گەورە بێت. پێویستە فایلەکە لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "پێدەچێت وێبگەڕی Brave بەکاربهێنیت لەگەڵ",
|
"line1": "وادیارە وێبگەڕی Brave بەکاردەهێنیت و ڕێکخستنی <bold>Aggressively Block Fingerprinting</bold> ـت چالاک کردووە.",
|
||||||
"aggressive_block_fingerprint": "بلۆککردنی Fingerprinting بەشێوەیەکی توندوتیژانە",
|
"line2": "ئەمە ئەکرێ ببێتە هۆی تێکدانی <bold>دانە دەقییەکان</bold> لە وێنەکێشانەکانتدا.",
|
||||||
"setting_enabled": "ڕێکخستن چالاک کراوە",
|
"line3": "ئێمە بە توندی پێشنیاری لەکارخستنی ئەم ڕێکخستنە دەکەین. بۆ لە کارخستنی دەتوانیت بەم <link>هەنگاوانە</link>دا بڕۆیت.",
|
||||||
"break": "ئەمە دەکرێت ببێتە هۆی تێکدانی",
|
"line4": "ئەگەر لەکارخستنی ئەم ڕێکخستنە نەبوە هۆی چاککردنەوەی پێشاندانی دانە دەقییەکان، تکایە <issueLink>کێشە</issueLink>یەک بکەرەوە لەسەر گیتهەبەکەمان، یان بۆمان بنوسە لەسەر <discordLink>دیسکۆرد</discordLink>"
|
||||||
"text_elements": "دانە دەقییەکان",
|
|
||||||
"in_your_drawings": "لە وێنەکێشانەکانتدا",
|
|
||||||
"strongly_recommend": "بە توندی پێشنیار دەکەین ئەم ڕێکخستنە لەکاربخەیت. دەتوانیت بڕۆیت بە دوای",
|
|
||||||
"steps": "ئەم هەنگاوانەدا",
|
|
||||||
"how": "بۆ ئەوەی ئەنجامی بدەیت",
|
|
||||||
"disable_setting": " ئەگەر لەکارخستنی ئەم ڕێکخستنە پیشاندانی توخمەکانی دەق چاک نەکاتەوە، تکایە هەڵبستە بە کردنەوەی",
|
|
||||||
"issue": "کێشەیەک",
|
|
||||||
"write": "لەسەر گیتهەبەکەمان، یان بۆمان بنوسە لە",
|
|
||||||
"discord": "دیسکۆرد"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "زانیاری: هەوڵ بدە دوورترین توخمەکان کەمێک لە یەکتر نزیک بکەوە."
|
"canvasTooBigTip": "زانیاری: هەوڵ بدە دوورترین توخمەکان کەمێک لە یەکتر نزیک بکەوە."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "تووشی هەڵەیەک بوو. هەوڵ بدە ",
|
"headingMain": "تووشی هەڵەیەک بوو. هەوڵ بدە <button>دووبارە بارکردنی لاپەڕەکە.</button>",
|
||||||
"headingMain_button": "دووبارە بارکردنی لاپەڕەکە.",
|
"clearCanvasMessage": "ئەگەر دووبارە بارکردنەوە کار ناکات، هەوڵبدە <button>خاوێنکردنەوەی تابلۆکە.</button>",
|
||||||
"clearCanvasMessage": "ئەگەر دووبارە بارکردنەوە کار ناکات، هەوڵبدە ",
|
|
||||||
"clearCanvasMessage_button": "خاوێنکردنەوەی تابلۆکە.",
|
|
||||||
"clearCanvasCaveat": " ئەمە دەبێتە هۆی لەدەستدانی ئەوەی کە کردوتە ",
|
"clearCanvasCaveat": " ئەمە دەبێتە هۆی لەدەستدانی ئەوەی کە کردوتە ",
|
||||||
"trackedToSentry_pre": "هەڵەکە لەگەڵ ناسێنەری ",
|
"trackedToSentry": "هەڵەکە لەگەڵ ناسێنەری {{eventId}} لەسەر سیستەمەکەمان بەدواداچوونی بۆ کرا.",
|
||||||
"trackedToSentry_post": " لەسەر سیستەمەکەمان بەدواداچوونی بۆ کرا.",
|
"openIssueMessage": "ئێمە زۆر وریا بووین کە زانیارییەکانی دیمەنەکەت لەسەر هەڵەکە نەخەینەڕوو. ئەگەر دیمەنەکەت تایبەت نییە، تکایە بیر لە بەدواداچوون بکەنەوە بۆ ئێمە <button>شوێنپێهەڵگری هەڵە.</button> تکایە ئەم زانیارییانەی خوارەوە کۆپی بکە و لە بەشی کێشەکانی Github دایبنێ.",
|
||||||
"openIssueMessage_pre": "ئێمە زۆر وریا بووین کە زانیارییەکانی دیمەنەکەت لەسەر هەڵەکە نەخەینەڕوو. ئەگەر دیمەنەکەت تایبەت نییە، تکایە بیر لە بەدواداچوون بکەنەوە بۆ ئێمە ",
|
|
||||||
"openIssueMessage_button": "شوێنپێهەڵگری هەڵە.",
|
|
||||||
"openIssueMessage_post": " تکایە ئەم زانیارییانەی خوارەوە کۆپی بکە و لە بەشی کێشەکانی Github دایبنێ.",
|
|
||||||
"sceneContent": "پێکهاتەی ناو دیمەنەکە:"
|
"sceneContent": "پێکهاتەی ناو دیمەنەکە:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "داواکراوە",
|
"required": "داواکراوە",
|
||||||
"website": "URLێکی دروست تێبنووسە"
|
"website": "URLێکی دروست تێبنووسە"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "کتێبخانەکەت بنێرە بۆ ئەوەی بخرێتە ناو <link>کۆگای کتێبخانەی گشتی</link>بۆ ئەوەی کەسانی تر لە وێنەکێشانەکانیاندا بەکاری بهێنن.",
|
||||||
"pre": "کتێبخانەکەت بنێرە بۆ ئەوەی بخرێتە ناو ",
|
"noteGuidelines": "کتێبخانەکە پێویستە سەرەتا بە دەست پەسەند بکرێت. تکایە بفەرمو بە خوێندنەوەی <link>ڕێنماییەکان</link> پێش پێشکەشکردن. پێویستت بە ئەژمێری GitHub دەبێت بۆ پەیوەندیکردن و گۆڕانکاری ئەگەر داوای لێکرا، بەڵام بە توندی پێویست نییە.",
|
||||||
"link": "کۆگای کتێبخانەی گشتی",
|
"noteLicense": "بە پێشکەشکردن، تۆ ڕەزامەندیت لەسەر بڵاوکردنەوەی کتێبخانەکە بەپێی <link>مۆڵەتی MIT، </link>کە بە کورتی مانای ئەوەیە کە هەرکەسێک دەتوانێت بە بێ سنوور بەکاری بهێنێت",
|
||||||
"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>لێرە</link>"
|
||||||
"link": "لێرە"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "ڕێکخستنەوەی کتێبخانە",
|
"resetLibrary": "ڕێکخستنەوەی کتێبخانە",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "",
|
"collabSaveFailed": "",
|
||||||
"collabSaveFailed_sizeExceeded": "",
|
"collabSaveFailed_sizeExceeded": "",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "",
|
"line2": "",
|
||||||
"setting_enabled": "",
|
"line3": "",
|
||||||
"break": "",
|
"line4": ""
|
||||||
"text_elements": "",
|
|
||||||
"in_your_drawings": "",
|
|
||||||
"strongly_recommend": "",
|
|
||||||
"steps": "",
|
|
||||||
"how": "",
|
|
||||||
"disable_setting": "",
|
|
||||||
"issue": "",
|
|
||||||
"write": "",
|
|
||||||
"discord": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,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": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Privalomas",
|
"required": "Privalomas",
|
||||||
"website": "Įveskite teisingą nuorodą (URL)"
|
"website": "Įveskite teisingą nuorodą (URL)"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Pateik savo biblioteką, jog ji galėtų būti įtraukta į <link></link>jog kiti žmonės galėtų tai naudoti savo piešiniuose.",
|
||||||
"pre": "Pateik savo biblioteką, jog ji galėtų būti įtraukta į ",
|
"noteGuidelines": "Visų pirma, biblioteka turi būti rankiniu būdu patvirtinta. Prašome paskaityti <link>gairės</link>",
|
||||||
"link": "",
|
"noteLicense": "<link>MIT licencija, </link>",
|
||||||
"post": "jog kiti žmonės galėtų tai naudoti savo piešiniuose."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "Visų pirma, biblioteka turi būti rankiniu būdu patvirtinta. Prašome paskaityti ",
|
|
||||||
"link": "gairės",
|
|
||||||
"post": ""
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "",
|
|
||||||
"link": "MIT licencija, ",
|
|
||||||
"post": ""
|
|
||||||
},
|
|
||||||
"noteItems": "",
|
"noteItems": "",
|
||||||
"atleastOneLibItem": "",
|
"atleastOneLibItem": "",
|
||||||
"republishWarning": ""
|
"republishWarning": ""
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Biblioteka pateikta",
|
"title": "Biblioteka pateikta",
|
||||||
"content": "Ačiū {{authorName}}. Tavo biblioteka buvo pateikta peržiūrai. Gali sekti būseną",
|
"content": "Ačiū {{authorName}}. Tavo biblioteka buvo pateikta peržiūrai. Gali sekti būseną<link>čia</link>"
|
||||||
"link": "čia"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Atstatyti biblioteką",
|
"resetLibrary": "Atstatyti biblioteką",
|
||||||
|
|
|
@ -208,19 +208,10 @@
|
||||||
"collabSaveFailed": "Darbs nav saglabāts datubāzē. Ja problēma turpinās, saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.",
|
"collabSaveFailed": "Darbs nav saglabāts datubāzē. Ja problēma turpinās, saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.",
|
||||||
"collabSaveFailed_sizeExceeded": "Darbs nav saglabāts datubāzē, šķiet, ka tāfele ir pārāk liela. Saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.",
|
"collabSaveFailed_sizeExceeded": "Darbs nav saglabāts datubāzē, šķiet, ka tāfele ir pārāk liela. Saglabājiet datni lokālajā krātuvē, lai nodrošinātos pret darba pazaudēšanu.",
|
||||||
"brave_measure_text_error": {
|
"brave_measure_text_error": {
|
||||||
"start": "Izskatās, ka izmanto Brave interneta plārlūku ar ieslēgtu",
|
"line1": "",
|
||||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
"line2": "",
|
||||||
"setting_enabled": "ieslēgtu iestatījumu",
|
"line3": "",
|
||||||
"break": "Tas var salauzt",
|
"line4": ""
|
||||||
"text_elements": "Teksta elementus",
|
|
||||||
"in_your_drawings": "tavos zīmējumos",
|
|
||||||
"strongly_recommend": "Mēs iesakām izslēgt šo iestatījumu. Tu vari sekot",
|
|
||||||
"steps": "šiem soļiem",
|
|
||||||
"how": "kā to izdarīt",
|
|
||||||
"disable_setting": " Ja šī iestatījuma izslēgšana neatrisina teksta elementu attēlošanu, tad, lūdzu, atver",
|
|
||||||
"issue": "problēmu",
|
|
||||||
"write": "mūsu GitHub vai raksti mums",
|
|
||||||
"discord": "Discord"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
|
@ -273,16 +264,11 @@
|
||||||
"canvasTooBigTip": "Ieteikums: mēģiniet satuvināt pašus tālākos elementus."
|
"canvasTooBigTip": "Ieteikums: mēģiniet satuvināt pašus tālākos elementus."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "Notikusi kļūda. Mēģiniet ",
|
"headingMain": "Notikusi kļūda. Mēģiniet <button>pārlādēt lapu.</button>",
|
||||||
"headingMain_button": "pārlādēt lapu.",
|
"clearCanvasMessage": "Ja pārlādēšana nestrādā, mēģiniet <button>notīrot tāfeli.</button>",
|
||||||
"clearCanvasMessage": "Ja pārlādēšana nestrādā, mēģiniet ",
|
|
||||||
"clearCanvasMessage_button": "notīrot tāfeli.",
|
|
||||||
"clearCanvasCaveat": " Tas novedīs pie darba zaudēšanas ",
|
"clearCanvasCaveat": " Tas novedīs pie darba zaudēšanas ",
|
||||||
"trackedToSentry_pre": "Kļūda ar kodu ",
|
"trackedToSentry": "Kļūda ar kodu {{eventId}} tika noteikta mūsu sistēmā.",
|
||||||
"trackedToSentry_post": " tika noteikta mūsu sistēmā.",
|
"openIssueMessage": "Mēs uzmanījāmies, lai neiekļautu jūsu ainas informāciju šajā kļūdā. Ja jūsu aina nav privāta, lūdzu ziņojiet par šo kļūdu mūsu <button>kļūdu uzskaitē.</button> Lūdzu, miniet sekojošo informāciju to kopējot un ielīmējot jūsu ziņojumā platformā GitHub.",
|
||||||
"openIssueMessage_pre": "Mēs uzmanījāmies, lai neiekļautu jūsu ainas informāciju šajā kļūdā. Ja jūsu aina nav privāta, lūdzu ziņojiet par šo kļūdu mūsu ",
|
|
||||||
"openIssueMessage_button": "kļūdu uzskaitē.",
|
|
||||||
"openIssueMessage_post": " Lūdzu, miniet sekojošo informāciju to kopējot un ielīmējot jūsu ziņojumā platformā GitHub.",
|
|
||||||
"sceneContent": "Ainas saturs:"
|
"sceneContent": "Ainas saturs:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
|
@ -362,29 +348,16 @@
|
||||||
"required": "Obligāts",
|
"required": "Obligāts",
|
||||||
"website": "Ievadiet derīgu URL"
|
"website": "Ievadiet derīgu URL"
|
||||||
},
|
},
|
||||||
"noteDescription": {
|
"noteDescription": "Iesniegt savu bibliotēku iekļaušanai <link>publiskajā bibliotēku datubāzē</link>, lai citi to varētu izmantot savos zīmējumos.",
|
||||||
"pre": "Iesniegt savu bibliotēku iekļaušanai ",
|
"noteGuidelines": "Šai bibliotēkai vispirms jātiek manuāli apstiprinātai. Lūdzu, izlasiet <link>norādījumus</link> pirms iesniegšanas. Jums vajadzēs GitHub kontu, lai sazinātos un veiktu izmaiņas, ja tādas būs pieprasītas, bet tas nav absolūti nepieciešams.",
|
||||||
"link": "publiskajā bibliotēku datubāzē",
|
"noteLicense": "Iesniedzot bibliotēku, jūs piekrītat tās publicēšanai saskaņā ar <link>MIT Licenci, </link>kas īsumā nozīmē, ka jebkurš to varēs izmantot bez ierobežojumiem.",
|
||||||
"post": ", lai citi to varētu izmantot savos zīmējumos."
|
|
||||||
},
|
|
||||||
"noteGuidelines": {
|
|
||||||
"pre": "Šai bibliotēkai vispirms jātiek manuāli apstiprinātai. Lūdzu, izlasiet ",
|
|
||||||
"link": "norādījumus",
|
|
||||||
"post": " pirms iesniegšanas. Jums vajadzēs GitHub kontu, lai sazinātos un veiktu izmaiņas, ja tādas būs pieprasītas, bet tas nav absolūti nepieciešams."
|
|
||||||
},
|
|
||||||
"noteLicense": {
|
|
||||||
"pre": "Iesniedzot bibliotēku, jūs piekrītat tās publicēšanai saskaņā ar ",
|
|
||||||
"link": "MIT Licenci, ",
|
|
||||||
"post": "kas īsumā nozīmē, ka jebkurš to varēs izmantot bez ierobežojumiem."
|
|
||||||
},
|
|
||||||
"noteItems": "Katram bibliotēkas vienumam jābūt savam nosaukumam, lai to varētu atrast filtrējot. Tiks iekļauti sekojošie bibliotēkas vienumi:",
|
"noteItems": "Katram bibliotēkas vienumam jābūt savam nosaukumam, lai to varētu atrast filtrējot. Tiks iekļauti sekojošie bibliotēkas vienumi:",
|
||||||
"atleastOneLibItem": "Lūdzu, atlasiet vismaz vienu bibliotēkas vienumu, lai sāktu darbu",
|
"atleastOneLibItem": "Lūdzu, atlasiet vismaz vienu bibliotēkas vienumu, lai sāktu darbu",
|
||||||
"republishWarning": "Ievēro: daži no atzīmētajiem objektiem jau atzīmēti kā publicēti vai iesniegti publicēšanai. Tos vajadzētu atkārtoti iesniegt tikai tad, ja vēlies labot esošo bibliotēku."
|
"republishWarning": "Ievēro: daži no atzīmētajiem objektiem jau atzīmēti kā publicēti vai iesniegti publicēšanai. Tos vajadzētu atkārtoti iesniegt tikai tad, ja vēlies labot esošo bibliotēku."
|
||||||
},
|
},
|
||||||
"publishSuccessDialog": {
|
"publishSuccessDialog": {
|
||||||
"title": "Bibliotēka iesniegta",
|
"title": "Bibliotēka iesniegta",
|
||||||
"content": "Paldies, {{authorName}}! Jūsu bibliotēka iesniegta izskatīšanai. Jūs varat izsekot iesnieguma statusam",
|
"content": "Paldies, {{authorName}}! Jūsu bibliotēka iesniegta izskatīšanai. Jūs varat izsekot iesnieguma statusam<link>šeit</link>"
|
||||||
"link": "šeit"
|
|
||||||
},
|
},
|
||||||
"confirmDialog": {
|
"confirmDialog": {
|
||||||
"resetLibrary": "Atiestatīt bibliotēku",
|
"resetLibrary": "Atiestatīt bibliotēku",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue