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
|
@ -58,7 +58,7 @@ export const getDefaultAppState = (): Omit<
|
|||
fileHandle: null,
|
||||
gridSize: null,
|
||||
isBindingEnabled: true,
|
||||
isSidebarDocked: false,
|
||||
defaultSidebarDockedPreference: false,
|
||||
isLoading: false,
|
||||
isResizing: false,
|
||||
isRotating: false,
|
||||
|
@ -150,7 +150,11 @@ const APP_STATE_STORAGE_CONF = (<
|
|||
gridSize: { browser: true, export: true, server: true },
|
||||
height: { 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 },
|
||||
isResizing: { browser: false, export: false, server: false },
|
||||
isRotating: { browser: false, export: false, server: false },
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
hasText,
|
||||
} from "../scene";
|
||||
import { SHAPES } from "../shapes";
|
||||
import { AppState, Zoom } from "../types";
|
||||
import { UIAppState, Zoom } from "../types";
|
||||
import {
|
||||
capitalizeString,
|
||||
isTransparent,
|
||||
|
@ -28,19 +28,20 @@ import { trackEvent } from "../analytics";
|
|||
import { hasBoundTextElement } from "../element/typeChecks";
|
||||
import clsx from "clsx";
|
||||
import { actionToggleZenMode } from "../actions";
|
||||
import "./Actions.scss";
|
||||
import { Tooltip } from "./Tooltip";
|
||||
import {
|
||||
shouldAllowVerticalAlign,
|
||||
suppportsHorizontalAlign,
|
||||
} from "../element/textElement";
|
||||
|
||||
import "./Actions.scss";
|
||||
|
||||
export const SelectedShapeActions = ({
|
||||
appState,
|
||||
elements,
|
||||
renderAction,
|
||||
}: {
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
elements: readonly ExcalidrawElement[];
|
||||
renderAction: ActionManager["renderAction"];
|
||||
}) => {
|
||||
|
@ -215,10 +216,10 @@ export const ShapesSwitcher = ({
|
|||
appState,
|
||||
}: {
|
||||
canvas: HTMLCanvasElement | null;
|
||||
activeTool: AppState["activeTool"];
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
activeTool: UIAppState["activeTool"];
|
||||
setAppState: React.Component<any, UIAppState>["setState"];
|
||||
onImageAction: (data: { pointerType: PointerType | null }) => void;
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
}) => (
|
||||
<>
|
||||
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
||||
|
|
|
@ -211,6 +211,8 @@ import {
|
|||
PointerDownState,
|
||||
SceneData,
|
||||
Device,
|
||||
SidebarName,
|
||||
SidebarTabName,
|
||||
} from "../types";
|
||||
import {
|
||||
debounce,
|
||||
|
@ -300,6 +302,9 @@ import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
|||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||
|
||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||
|
||||
const deviceContextInitialValue = {
|
||||
isSmScreen: false,
|
||||
isMobile: false,
|
||||
|
@ -341,6 +346,8 @@ const ExcalidrawActionManagerContext = React.createContext<ActionManager>(
|
|||
);
|
||||
ExcalidrawActionManagerContext.displayName = "ExcalidrawActionManagerContext";
|
||||
|
||||
export const useApp = () => useContext(AppContext);
|
||||
export const useAppProps = () => useContext(AppPropsContext);
|
||||
export const useDevice = () => useContext<Device>(DeviceContext);
|
||||
export const useExcalidrawContainer = () =>
|
||||
useContext(ExcalidrawContainerContext);
|
||||
|
@ -401,7 +408,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
private nearestScrollableContainer: HTMLElement | Document | undefined;
|
||||
public library: AppClassProperties["library"];
|
||||
public libraryItemsFromStorage: LibraryItems | undefined;
|
||||
private id: string;
|
||||
public id: string;
|
||||
private history: History;
|
||||
private excalidrawContainerValue: {
|
||||
container: HTMLDivElement | null;
|
||||
|
@ -439,7 +446,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
showHyperlinkPopup: false,
|
||||
isSidebarDocked: false,
|
||||
defaultSidebarDockedPreference: false,
|
||||
};
|
||||
|
||||
this.id = nanoid();
|
||||
|
@ -470,7 +477,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
setActiveTool: this.setActiveTool,
|
||||
setCursor: this.setCursor,
|
||||
resetCursor: this.resetCursor,
|
||||
toggleMenu: this.toggleMenu,
|
||||
toggleSidebar: this.toggleSidebar,
|
||||
} as const;
|
||||
if (typeof excalidrawRef === "function") {
|
||||
excalidrawRef(api);
|
||||
|
@ -578,101 +585,91 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.props.handleKeyboardGlobally ? undefined : this.onKeyDown
|
||||
}
|
||||
>
|
||||
<ExcalidrawContainerContext.Provider
|
||||
value={this.excalidrawContainerValue}
|
||||
>
|
||||
<DeviceContext.Provider value={this.device}>
|
||||
<ExcalidrawSetAppStateContext.Provider value={this.setAppState}>
|
||||
<ExcalidrawAppStateContext.Provider value={this.state}>
|
||||
<ExcalidrawElementsContext.Provider
|
||||
value={this.scene.getNonDeletedElements()}
|
||||
>
|
||||
<ExcalidrawActionManagerContext.Provider
|
||||
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
|
||||
}
|
||||
<AppContext.Provider value={this}>
|
||||
<AppPropsContext.Provider value={this.props}>
|
||||
<ExcalidrawContainerContext.Provider
|
||||
value={this.excalidrawContainerValue}
|
||||
>
|
||||
<DeviceContext.Provider value={this.device}>
|
||||
<ExcalidrawSetAppStateContext.Provider value={this.setAppState}>
|
||||
<ExcalidrawAppStateContext.Provider value={this.state}>
|
||||
<ExcalidrawElementsContext.Provider
|
||||
value={this.scene.getNonDeletedElements()}
|
||||
>
|
||||
{this.props.children}
|
||||
</LayerUI>
|
||||
<div className="excalidraw-textEditorContainer" />
|
||||
<div className="excalidraw-contextMenuContainer" />
|
||||
{selectedElement.length === 1 &&
|
||||
!this.state.contextMenu &&
|
||||
this.state.showHyperlinkPopup && (
|
||||
<Hyperlink
|
||||
key={selectedElement[0].id}
|
||||
element={selectedElement[0]}
|
||||
<ExcalidrawActionManagerContext.Provider
|
||||
value={this.actionManager}
|
||||
>
|
||||
<LayerUI
|
||||
canvas={this.canvas}
|
||||
appState={this.state}
|
||||
files={this.files}
|
||||
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>
|
||||
actionManager={this.actionManager}
|
||||
elements={this.scene.getNonDeletedElements()}
|
||||
onLockToggle={this.toggleLock}
|
||||
onPenModeToggle={this.togglePenMode}
|
||||
onHandToolToggle={this.onHandToolToggle}
|
||||
langCode={getLanguage().code}
|
||||
renderTopRightUI={renderTopRightUI}
|
||||
renderCustomStats={renderCustomStats}
|
||||
showExitZenModeBtn={
|
||||
typeof this.props?.zenModeEnabled === "undefined" &&
|
||||
this.state.zenModeEnabled
|
||||
}
|
||||
UIOptions={this.props.UIOptions}
|
||||
onImageAction={this.onImageAction}
|
||||
renderWelcomeScreen={
|
||||
!this.state.isLoading &&
|
||||
this.state.showWelcomeScreen &&
|
||||
this.state.activeTool.type === "selection" &&
|
||||
!this.scene.getElementsIncludingDeleted().length
|
||||
}
|
||||
>
|
||||
{this.props.children}
|
||||
</LayerUI>
|
||||
<div className="excalidraw-textEditorContainer" />
|
||||
<div className="excalidraw-contextMenuContainer" />
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
public focusContainer: AppClassProperties["focusContainer"] = () => {
|
||||
if (this.props.autoFocus) {
|
||||
this.excalidrawContainerRef.current?.focus();
|
||||
}
|
||||
this.excalidrawContainerRef.current?.focus();
|
||||
};
|
||||
|
||||
public getSceneElementsIncludingDeleted = () => {
|
||||
|
@ -683,6 +680,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||
return this.scene.getNonDeletedElements();
|
||||
};
|
||||
|
||||
public onInsertElements = (elements: readonly ExcalidrawElement[]) => {
|
||||
this.addElementsFromPasteOrLibrary({
|
||||
elements,
|
||||
position: "center",
|
||||
files: null,
|
||||
});
|
||||
};
|
||||
|
||||
private syncActionResult = withBatchedUpdates(
|
||||
(actionResult: ActionResult) => {
|
||||
if (this.unmounted || actionResult === false) {
|
||||
|
@ -952,7 +957,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.scene.addCallback(this.onSceneUpdated);
|
||||
this.addEventListeners();
|
||||
|
||||
if (this.excalidrawContainerRef.current) {
|
||||
if (this.props.autoFocus && this.excalidrawContainerRef.current) {
|
||||
this.focusContainer();
|
||||
}
|
||||
|
||||
|
@ -1680,7 +1685,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
openSidebar:
|
||||
this.state.openSidebar &&
|
||||
this.device.canDeviceFitSidebar &&
|
||||
this.state.isSidebarDocked
|
||||
this.state.defaultSidebarDockedPreference
|
||||
? this.state.openSidebar
|
||||
: null,
|
||||
selectedElementIds: newElements.reduce(
|
||||
|
@ -2020,30 +2025,24 @@ class App extends React.Component<AppProps, AppState> {
|
|||
/**
|
||||
* @returns whether the menu was toggled on or off
|
||||
*/
|
||||
public toggleMenu = (
|
||||
type: "library" | "customSidebar",
|
||||
force?: boolean,
|
||||
): boolean => {
|
||||
if (type === "customSidebar" && !this.props.renderSidebar) {
|
||||
console.warn(
|
||||
`attempting to toggle "customSidebar", but no "props.renderSidebar" is defined`,
|
||||
);
|
||||
return false;
|
||||
public toggleSidebar = ({
|
||||
name,
|
||||
tab,
|
||||
force,
|
||||
}: {
|
||||
name: SidebarName;
|
||||
tab?: SidebarTabName;
|
||||
force?: boolean;
|
||||
}): 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") {
|
||||
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;
|
||||
return !!nextName;
|
||||
};
|
||||
|
||||
private updateCurrentCursorPosition = withBatchedUpdates(
|
||||
|
|
|
@ -1,39 +1,40 @@
|
|||
import { t } from "../i18n";
|
||||
import Trans from "./Trans";
|
||||
|
||||
const BraveMeasureTextError = () => {
|
||||
return (
|
||||
<div data-testid="brave-measure-text-error">
|
||||
<p>
|
||||
{t("errors.brave_measure_text_error.start")}
|
||||
<span style={{ fontWeight: 600 }}>
|
||||
{t("errors.brave_measure_text_error.aggressive_block_fingerprint")}
|
||||
</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")}.
|
||||
<Trans
|
||||
i18nKey="errors.brave_measure_text_error.line1"
|
||||
bold={(el) => <span style={{ fontWeight: 600 }}>{el}</span>}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{t("errors.brave_measure_text_error.strongly_recommend")}{" "}
|
||||
<a href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser">
|
||||
{" "}
|
||||
{t("errors.brave_measure_text_error.steps")}
|
||||
</a>{" "}
|
||||
{t("errors.brave_measure_text_error.how")}.
|
||||
<Trans
|
||||
i18nKey="errors.brave_measure_text_error.line2"
|
||||
bold={(el) => <span style={{ fontWeight: 600 }}>{el}</span>}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{t("errors.brave_measure_text_error.disable_setting")}{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/issues/new">
|
||||
{t("errors.brave_measure_text_error.issue")}
|
||||
</a>{" "}
|
||||
{t("errors.brave_measure_text_error.write")}{" "}
|
||||
<a href="https://discord.gg/UexuTaE">
|
||||
{t("errors.brave_measure_text_error.discord")}
|
||||
</a>
|
||||
.
|
||||
<Trans
|
||||
i18nKey="errors.brave_measure_text_error.line3"
|
||||
link={(el) => (
|
||||
<a href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser">
|
||||
{el}
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import clsx from "clsx";
|
||||
import { composeEventHandlers } from "../utils";
|
||||
import "./Button.scss";
|
||||
|
||||
interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
type?: "button" | "submit" | "reset";
|
||||
onSelect: () => any;
|
||||
/** whether button is in active state */
|
||||
selected?: boolean;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
@ -15,18 +19,18 @@ interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
|||
export const Button = ({
|
||||
type = "button",
|
||||
onSelect,
|
||||
selected,
|
||||
children,
|
||||
className = "",
|
||||
...rest
|
||||
}: ButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
onClick={(event) => {
|
||||
onClick={composeEventHandlers(rest.onClick, (event) => {
|
||||
onSelect();
|
||||
rest.onClick?.(event);
|
||||
}}
|
||||
})}
|
||||
type={type}
|
||||
className={`excalidraw-button ${className}`}
|
||||
className={clsx("excalidraw-button", className, { selected })}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -4,8 +4,8 @@ import { Dialog, DialogProps } from "./Dialog";
|
|||
import "./ConfirmDialog.scss";
|
||||
import DialogActionButton from "./DialogActionButton";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { isLibraryMenuOpenAtom } from "./LibraryMenuHeaderContent";
|
||||
import { useExcalidrawSetAppState } from "./App";
|
||||
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||
import { useExcalidrawContainer, useExcalidrawSetAppState } from "./App";
|
||||
import { jotaiScope } from "../jotai";
|
||||
|
||||
interface Props extends Omit<DialogProps, "onCloseRequest"> {
|
||||
|
@ -26,6 +26,7 @@ const ConfirmDialog = (props: Props) => {
|
|||
} = props;
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope);
|
||||
const { container } = useExcalidrawContainer();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
@ -42,6 +43,7 @@ const ConfirmDialog = (props: Props) => {
|
|||
setAppState({ openMenu: null });
|
||||
setIsLibraryMenuOpen(false);
|
||||
onCancel();
|
||||
container?.focus();
|
||||
}}
|
||||
/>
|
||||
<DialogActionButton
|
||||
|
@ -50,6 +52,7 @@ const ConfirmDialog = (props: Props) => {
|
|||
setAppState({ openMenu: null });
|
||||
setIsLibraryMenuOpen(false);
|
||||
onConfirm();
|
||||
container?.focus();
|
||||
}}
|
||||
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 { queryFocusableElements } from "../utils";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { isLibraryMenuOpenAtom } from "./LibraryMenuHeaderContent";
|
||||
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||
import { jotaiScope } from "../jotai";
|
||||
|
||||
export interface DialogProps {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { t } from "../i18n";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { getSelectedElements } from "../scene";
|
||||
|
||||
import "./HintViewer.scss";
|
||||
import { AppState, Device } from "../types";
|
||||
import { Device, UIAppState } from "../types";
|
||||
import {
|
||||
isImageElement,
|
||||
isLinearElement,
|
||||
|
@ -13,8 +11,10 @@ import {
|
|||
import { getShortcutKey } from "../utils";
|
||||
import { isEraserActive } from "../appState";
|
||||
|
||||
import "./HintViewer.scss";
|
||||
|
||||
interface HintViewerProps {
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
isMobile: boolean;
|
||||
device: Device;
|
||||
|
@ -29,7 +29,7 @@ const getHints = ({
|
|||
const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
|
||||
const multiMode = appState.multiElement !== null;
|
||||
|
||||
if (appState.openSidebar === "library" && !device.canDeviceFitSidebar) {
|
||||
if (appState.openSidebar && !device.canDeviceFitSidebar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,11 +4,10 @@ import { canvasToBlob } from "../data/blob";
|
|||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { AppState, BinaryFiles } from "../types";
|
||||
import { BinaryFiles, UIAppState } from "../types";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { clipboard } from "./icons";
|
||||
import Stack from "./Stack";
|
||||
import "./ExportDialog.scss";
|
||||
import OpenColor from "open-color";
|
||||
import { CheckboxItem } from "./CheckboxItem";
|
||||
import { DEFAULT_EXPORT_PADDING, isFirefox } from "../constants";
|
||||
|
@ -16,6 +15,8 @@ import { nativeFileSystemSupported } from "../data/filesystem";
|
|||
import { ActionManager } from "../actions/manager";
|
||||
import { exportToCanvas } from "../packages/utils";
|
||||
|
||||
import "./ExportDialog.scss";
|
||||
|
||||
const supportsContextFilters =
|
||||
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||
|
||||
|
@ -70,7 +71,7 @@ const ImageExportModal = ({
|
|||
onExportToSvg,
|
||||
onExportToClipboard,
|
||||
}: {
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
files: BinaryFiles;
|
||||
exportPadding?: number;
|
||||
|
@ -216,8 +217,8 @@ export const ImageExportDialog = ({
|
|||
onExportToSvg,
|
||||
onExportToClipboard,
|
||||
}: {
|
||||
appState: AppState;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
appState: UIAppState;
|
||||
setAppState: React.Component<any, UIAppState>["setState"];
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
files: BinaryFiles;
|
||||
exportPadding?: number;
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from "react";
|
|||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
|
||||
import { AppState, ExportOpts, BinaryFiles } from "../types";
|
||||
import { ExportOpts, BinaryFiles, UIAppState } from "../types";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { exportToFileIcon, LinkIcon } from "./icons";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
|
@ -28,7 +28,7 @@ const JSONExportModal = ({
|
|||
exportOpts,
|
||||
canvas,
|
||||
}: {
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
files: BinaryFiles;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
actionManager: ActionManager;
|
||||
|
@ -96,12 +96,12 @@ export const JSONExportDialog = ({
|
|||
setAppState,
|
||||
}: {
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
files: BinaryFiles;
|
||||
actionManager: ActionManager;
|
||||
exportOpts: ExportOpts;
|
||||
canvas: HTMLCanvasElement | null;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
setAppState: React.Component<any, UIAppState>["setState"];
|
||||
}) => {
|
||||
const handleClose = React.useCallback(() => {
|
||||
setAppState({ openDialog: null });
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
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 { isTextElement, showSelectedShapeActions } from "../element";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { Language, t } from "../i18n";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
import { ExportType } from "../scene/types";
|
||||
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
||||
import { isShallowEqual, muteFSAbortError } from "../utils";
|
||||
import {
|
||||
AppProps,
|
||||
AppState,
|
||||
ExcalidrawProps,
|
||||
BinaryFiles,
|
||||
UIAppState,
|
||||
} from "../types";
|
||||
import { capitalizeString, isShallowEqual, muteFSAbortError } from "../utils";
|
||||
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||
import { ErrorDialog } from "./ErrorDialog";
|
||||
import { ExportCB, ImageExportDialog } from "./ImageExportDialog";
|
||||
|
@ -24,32 +30,32 @@ import { Section } from "./Section";
|
|||
import { HelpDialog } from "./HelpDialog";
|
||||
import Stack from "./Stack";
|
||||
import { UserList } from "./UserList";
|
||||
import Library from "../data/library";
|
||||
import { JSONExportDialog } from "./JSONExportDialog";
|
||||
import { LibraryButton } from "./LibraryButton";
|
||||
import { isImageFileHandle } from "../data/blob";
|
||||
import { LibraryMenu } from "./LibraryMenu";
|
||||
|
||||
import "./LayerUI.scss";
|
||||
import "./Toolbar.scss";
|
||||
import { PenModeButton } from "./PenModeButton";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { useDevice } from "../components/App";
|
||||
import { Stats } from "./Stats";
|
||||
import { actionToggleStats } from "../actions/actionToggleStats";
|
||||
import Footer from "./footer/Footer";
|
||||
import { hostSidebarCountersAtom } from "./Sidebar/Sidebar";
|
||||
import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { Provider, useAtom } from "jotai";
|
||||
import { Provider, useAtomValue } from "jotai";
|
||||
import MainMenu from "./main-menu/MainMenu";
|
||||
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
||||
import { HandButton } from "./HandButton";
|
||||
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 {
|
||||
actionManager: ActionManager;
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
files: BinaryFiles;
|
||||
canvas: HTMLCanvasElement | null;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
|
@ -57,17 +63,11 @@ interface LayerUIProps {
|
|||
onLockToggle: () => void;
|
||||
onHandToolToggle: () => void;
|
||||
onPenModeToggle: () => void;
|
||||
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
||||
showExitZenModeBtn: boolean;
|
||||
langCode: Language["code"];
|
||||
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
|
||||
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
||||
renderCustomSidebar?: ExcalidrawProps["renderSidebar"];
|
||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||
UIOptions: AppProps["UIOptions"];
|
||||
focusContainer: () => void;
|
||||
library: Library;
|
||||
id: string;
|
||||
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
||||
renderWelcomeScreen: boolean;
|
||||
children?: React.ReactNode;
|
||||
|
@ -109,16 +109,10 @@ const LayerUI = ({
|
|||
onLockToggle,
|
||||
onHandToolToggle,
|
||||
onPenModeToggle,
|
||||
onInsertElements,
|
||||
showExitZenModeBtn,
|
||||
renderTopRightUI,
|
||||
renderCustomStats,
|
||||
renderCustomSidebar,
|
||||
libraryReturnUrl,
|
||||
UIOptions,
|
||||
focusContainer,
|
||||
library,
|
||||
id,
|
||||
onImageAction,
|
||||
renderWelcomeScreen,
|
||||
children,
|
||||
|
@ -156,7 +150,8 @@ const LayerUI = ({
|
|||
const fileHandle = await exportCanvas(
|
||||
type,
|
||||
exportedElements,
|
||||
appState,
|
||||
// FIXME once we split UI canvas from element canvas
|
||||
appState as AppState,
|
||||
files,
|
||||
{
|
||||
exportBackground: appState.exportBackground,
|
||||
|
@ -197,8 +192,8 @@ const LayerUI = ({
|
|||
<div style={{ position: "relative" }}>
|
||||
{/* wrapping to Fragment stops React from occasionally complaining
|
||||
about identical Keys */}
|
||||
<tunnels.mainMenuTunnel.Out />
|
||||
{renderWelcomeScreen && <tunnels.welcomeScreenMenuHintTunnel.Out />}
|
||||
<tunnels.MainMenuTunnel.Out />
|
||||
{renderWelcomeScreen && <tunnels.WelcomeScreenMenuHintTunnel.Out />}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -250,7 +245,7 @@ const LayerUI = ({
|
|||
{(heading: React.ReactNode) => (
|
||||
<div style={{ position: "relative" }}>
|
||||
{renderWelcomeScreen && (
|
||||
<tunnels.welcomeScreenToolbarHintTunnel.Out />
|
||||
<tunnels.WelcomeScreenToolbarHintTunnel.Out />
|
||||
)}
|
||||
<Stack.Col gap={4} align="start">
|
||||
<Stack.Row
|
||||
|
@ -324,9 +319,12 @@ const LayerUI = ({
|
|||
>
|
||||
<UserList collaborators={appState.collaborators} />
|
||||
{renderTopRightUI?.(device.isMobile, appState)}
|
||||
{!appState.viewModeEnabled && (
|
||||
<LibraryButton appState={appState} setAppState={setAppState} />
|
||||
)}
|
||||
{!appState.viewModeEnabled &&
|
||||
// hide button when sidebar docked
|
||||
(!isSidebarDocked ||
|
||||
appState.openSidebar?.name !== DEFAULT_SIDEBAR.name) && (
|
||||
<tunnels.DefaultSidebarTriggerTunnel.Out />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</FixedSideContainer>
|
||||
|
@ -334,21 +332,21 @@ const LayerUI = ({
|
|||
};
|
||||
|
||||
const renderSidebars = () => {
|
||||
return appState.openSidebar === "customSidebar" ? (
|
||||
renderCustomSidebar?.() || null
|
||||
) : appState.openSidebar === "library" ? (
|
||||
<LibraryMenu
|
||||
appState={appState}
|
||||
onInsertElements={onInsertElements}
|
||||
libraryReturnUrl={libraryReturnUrl}
|
||||
focusContainer={focusContainer}
|
||||
library={library}
|
||||
id={id}
|
||||
return (
|
||||
<DefaultSidebar
|
||||
__fallback
|
||||
onDock={(docked) => {
|
||||
trackEvent(
|
||||
"sidebar",
|
||||
`toggleDock (${docked ? "dock" : "undock"})`,
|
||||
`(${device.isMobile ? "mobile" : "desktop"})`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
);
|
||||
};
|
||||
|
||||
const [hostSidebarCounters] = useAtom(hostSidebarCountersAtom, jotaiScope);
|
||||
const isSidebarDocked = useAtomValue(isSidebarDockedAtom, jotaiScope);
|
||||
|
||||
const layerUIJSX = (
|
||||
<>
|
||||
|
@ -358,8 +356,25 @@ const LayerUI = ({
|
|||
{children}
|
||||
{/* render component fallbacks. Can be rendered anywhere as they'll be
|
||||
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} />
|
||||
<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} />}
|
||||
|
@ -382,7 +397,6 @@ const LayerUI = ({
|
|||
<PasteChartDialog
|
||||
setAppState={setAppState}
|
||||
appState={appState}
|
||||
onInsertChart={onInsertElements}
|
||||
onClose={() =>
|
||||
setAppState({
|
||||
pasteDialog: { shown: false, data: null },
|
||||
|
@ -410,7 +424,6 @@ const LayerUI = ({
|
|||
renderWelcomeScreen={renderWelcomeScreen}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!device.isMobile && (
|
||||
<>
|
||||
<div
|
||||
|
@ -422,15 +435,14 @@ const LayerUI = ({
|
|||
!isTextElement(appState.editingElement)),
|
||||
})}
|
||||
style={
|
||||
((appState.openSidebar === "library" &&
|
||||
appState.isSidebarDocked) ||
|
||||
hostSidebarCounters.docked) &&
|
||||
appState.openSidebar &&
|
||||
isSidebarDocked &&
|
||||
device.canDeviceFitSidebar
|
||||
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{renderWelcomeScreen && <tunnels.welcomeScreenCenterTunnel.Out />}
|
||||
{renderWelcomeScreen && <tunnels.WelcomeScreenCenterTunnel.Out />}
|
||||
{renderFixedSideContainer()}
|
||||
<Footer
|
||||
appState={appState}
|
||||
|
@ -453,9 +465,9 @@ const LayerUI = ({
|
|||
<button
|
||||
className="scroll-back-to-content"
|
||||
onClick={() => {
|
||||
setAppState({
|
||||
setAppState((appState) => ({
|
||||
...calculateScrollCenter(elements, appState, canvas),
|
||||
});
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{t("buttons.scrollBackToContent")}
|
||||
|
@ -469,19 +481,25 @@ const LayerUI = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<Provider scope={tunnels.jotaiScope}>
|
||||
<TunnelsContext.Provider value={tunnels}>
|
||||
{layerUIJSX}
|
||||
</TunnelsContext.Provider>
|
||||
</Provider>
|
||||
<UIAppStateContext.Provider value={appState}>
|
||||
<Provider scope={tunnels.jotaiScope}>
|
||||
<TunnelsContext.Provider value={tunnels}>
|
||||
{layerUIJSX}
|
||||
</TunnelsContext.Provider>
|
||||
</Provider>
|
||||
</UIAppStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const stripIrrelevantAppStateProps = (
|
||||
appState: AppState,
|
||||
): Partial<AppState> => {
|
||||
const { suggestedBindings, startBoundElement, cursorButton, ...ret } =
|
||||
appState;
|
||||
const stripIrrelevantAppStateProps = (appState: AppState): UIAppState => {
|
||||
const {
|
||||
suggestedBindings,
|
||||
startBoundElement,
|
||||
cursorButton,
|
||||
scrollX,
|
||||
scrollY,
|
||||
...ret
|
||||
} = appState;
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
@ -491,24 +509,19 @@ const areEqual = (prevProps: LayerUIProps, nextProps: LayerUIProps) => {
|
|||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
canvas: _prevCanvas,
|
||||
// not stable, but shouldn't matter in our case
|
||||
onInsertElements: _prevOnInsertElements,
|
||||
appState: prevAppState,
|
||||
...prev
|
||||
} = prevProps;
|
||||
const {
|
||||
canvas: _nextCanvas,
|
||||
onInsertElements: _nextOnInsertElements,
|
||||
appState: nextAppState,
|
||||
...next
|
||||
} = nextProps;
|
||||
const { canvas: _prevCanvas, appState: prevAppState, ...prev } = prevProps;
|
||||
const { canvas: _nextCanvas, appState: nextAppState, ...next } = nextProps;
|
||||
|
||||
return (
|
||||
isShallowEqual(
|
||||
stripIrrelevantAppStateProps(prevAppState),
|
||||
stripIrrelevantAppStateProps(nextAppState),
|
||||
// asserting AppState because we're being passed the whole AppState
|
||||
// but resolve to only the UI-relevant props
|
||||
stripIrrelevantAppStateProps(prevAppState as AppState),
|
||||
stripIrrelevantAppStateProps(nextAppState as AppState),
|
||||
{
|
||||
selectedElementIds: isShallowEqual,
|
||||
selectedGroupIds: isShallowEqual,
|
||||
},
|
||||
) && 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";
|
||||
|
||||
.excalidraw {
|
||||
.layer-ui__library-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.library-menu-items-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layer-ui__library {
|
||||
|
@ -11,28 +11,6 @@
|
|||
flex-direction: column;
|
||||
|
||||
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 {
|
||||
|
@ -87,10 +65,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.library-menu-browse-button {
|
||||
margin: 1rem auto;
|
||||
.library-menu-control-buttons {
|
||||
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;
|
||||
align-items: center;
|
||||
|
@ -122,30 +107,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
.library-menu-browse-button--mobile {
|
||||
min-height: 22px;
|
||||
margin-left: auto;
|
||||
a {
|
||||
padding-right: 0;
|
||||
}
|
||||
&.excalidraw--mobile .library-menu-browse-button {
|
||||
height: var(--default-button-size);
|
||||
}
|
||||
|
||||
.layer-ui__sidebar__header .dropdown-menu {
|
||||
&.dropdown-menu--mobile {
|
||||
top: 100%;
|
||||
}
|
||||
.layer-ui__library .dropdown-menu {
|
||||
width: auto;
|
||||
top: initial;
|
||||
right: 0;
|
||||
left: initial;
|
||||
bottom: 100%;
|
||||
margin-bottom: 0.625rem;
|
||||
|
||||
.dropdown-menu-container {
|
||||
--gap: 0;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
width: 196px;
|
||||
box-shadow: var(--library-dropdown-shadow);
|
||||
border-radius: var(--border-radius-lg);
|
||||
|
|
|
@ -1,77 +1,39 @@
|
|||
import {
|
||||
useRef,
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
RefObject,
|
||||
forwardRef,
|
||||
} from "react";
|
||||
import React, { useState, useCallback } from "react";
|
||||
import Library, {
|
||||
distributeLibraryItemsOnSquareGrid,
|
||||
libraryItemsAtom,
|
||||
} from "../data/library";
|
||||
import { t } from "../i18n";
|
||||
import { randomId } from "../random";
|
||||
import { LibraryItems, LibraryItem, AppState, ExcalidrawProps } from "../types";
|
||||
|
||||
import "./LibraryMenu.scss";
|
||||
import {
|
||||
LibraryItems,
|
||||
LibraryItem,
|
||||
ExcalidrawProps,
|
||||
UIAppState,
|
||||
} from "../types";
|
||||
import LibraryMenuItems from "./LibraryMenuItems";
|
||||
import { EVENT } from "../constants";
|
||||
import { KEYS } from "../keys";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { useAtom } from "jotai";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import Spinner from "./Spinner";
|
||||
import {
|
||||
useDevice,
|
||||
useApp,
|
||||
useAppProps,
|
||||
useExcalidrawElements,
|
||||
useExcalidrawSetAppState,
|
||||
} from "./App";
|
||||
import { Sidebar } from "./Sidebar/Sidebar";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { LibraryMenuHeader } from "./LibraryMenuHeaderContent";
|
||||
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
|
||||
import { useUIAppState } from "../context/ui-appState";
|
||||
|
||||
const useOnClickOutside = (
|
||||
ref: RefObject<HTMLElement>,
|
||||
cb: (event: MouseEvent) => void,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
const listener = (event: MouseEvent) => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
import "./LibraryMenu.scss";
|
||||
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
||||
|
||||
if (
|
||||
event.target instanceof Element &&
|
||||
(ref.current.contains(event.target) ||
|
||||
!document.body.contains(event.target))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
export const isLibraryMenuOpenAtom = atom(false);
|
||||
|
||||
cb(event);
|
||||
};
|
||||
document.addEventListener("pointerdown", listener, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", listener);
|
||||
};
|
||||
}, [ref, cb]);
|
||||
const LibraryMenuWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className="layer-ui__library">{children}</div>;
|
||||
};
|
||||
|
||||
const LibraryMenuWrapper = forwardRef<
|
||||
HTMLDivElement,
|
||||
{ children: React.ReactNode }
|
||||
>(({ children }, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="layer-ui__library">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const LibraryMenuContent = ({
|
||||
onInsertLibraryItems,
|
||||
pendingElements,
|
||||
|
@ -87,11 +49,11 @@ export const LibraryMenuContent = ({
|
|||
pendingElements: LibraryItem["elements"];
|
||||
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
||||
onAddToLibrary: () => void;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
setAppState: React.Component<any, UIAppState>["setState"];
|
||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||
library: Library;
|
||||
id: string;
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
selectedItems: LibraryItem["id"][];
|
||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||
}) => {
|
||||
|
@ -158,81 +120,31 @@ export const LibraryMenuContent = ({
|
|||
theme={appState.theme}
|
||||
/>
|
||||
{showBtn && (
|
||||
<LibraryMenuBrowseButton
|
||||
<LibraryMenuControlButtons
|
||||
style={{ padding: "16px 12px 0 12px" }}
|
||||
id={id}
|
||||
libraryReturnUrl={libraryReturnUrl}
|
||||
theme={appState.theme}
|
||||
selectedItems={selectedItems}
|
||||
onSelectItems={onSelectItems}
|
||||
/>
|
||||
)}
|
||||
</LibraryMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const LibraryMenu: React.FC<{
|
||||
appState: AppState;
|
||||
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||
focusContainer: () => void;
|
||||
library: Library;
|
||||
id: string;
|
||||
}> = ({
|
||||
appState,
|
||||
onInsertElements,
|
||||
libraryReturnUrl,
|
||||
focusContainer,
|
||||
library,
|
||||
id,
|
||||
}) => {
|
||||
/**
|
||||
* This component is meant to be rendered inside <Sidebar.Tab/> inside our
|
||||
* <DefaultSidebar/> or host apps Sidebar components.
|
||||
*/
|
||||
export const LibraryMenu = () => {
|
||||
const { library, id, onInsertElements } = useApp();
|
||||
const appProps = useAppProps();
|
||||
const appState = useUIAppState();
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
const elements = useExcalidrawElements();
|
||||
const device = useDevice();
|
||||
|
||||
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(() => {
|
||||
setAppState({
|
||||
|
@ -241,69 +153,20 @@ export const LibraryMenu: React.FC<{
|
|||
});
|
||||
}, [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 (
|
||||
<Sidebar
|
||||
__isInternal
|
||||
// necessary to remount when switching between internal
|
||||
// and custom (host app) sidebar, so that the `props.onClose`
|
||||
// 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"})`,
|
||||
);
|
||||
<LibraryMenuContent
|
||||
pendingElements={getSelectedElements(elements, appState, true)}
|
||||
onInsertLibraryItems={(libraryItems) => {
|
||||
onInsertElements(distributeLibraryItemsOnSquareGrid(libraryItems));
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<Sidebar.Header className="layer-ui__library-header">
|
||||
<LibraryMenuHeader
|
||||
appState={appState}
|
||||
setAppState={setAppState}
|
||||
selectedItems={selectedItems}
|
||||
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>
|
||||
onAddToLibrary={deselectItems}
|
||||
setAppState={setAppState}
|
||||
libraryReturnUrl={appProps.libraryReturnUrl}
|
||||
library={library}
|
||||
id={id}
|
||||
appState={appState}
|
||||
selectedItems={selectedItems}
|
||||
onSelectItems={setSelectedItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { VERSIONS } from "../constants";
|
||||
import { t } from "../i18n";
|
||||
import { AppState, ExcalidrawProps } from "../types";
|
||||
import { ExcalidrawProps, UIAppState } from "../types";
|
||||
|
||||
const LibraryMenuBrowseButton = ({
|
||||
theme,
|
||||
|
@ -8,7 +8,7 @@ const LibraryMenuBrowseButton = ({
|
|||
libraryReturnUrl,
|
||||
}: {
|
||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||
theme: AppState["theme"];
|
||||
theme: UIAppState["theme"];
|
||||
id: string;
|
||||
}) => {
|
||||
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 Library, { libraryItemsAtom } from "../data/library";
|
||||
import { t } from "../i18n";
|
||||
import { AppState, LibraryItem, LibraryItems } from "../types";
|
||||
import {
|
||||
DotsIcon,
|
||||
ExportIcon,
|
||||
|
@ -13,29 +16,27 @@ import {
|
|||
import { ToolButton } from "./ToolButton";
|
||||
import { fileOpen } from "../data/filesystem";
|
||||
import { muteFSAbortError } from "../utils";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { useAtom } from "jotai";
|
||||
import ConfirmDialog from "./ConfirmDialog";
|
||||
import PublishLibrary from "./PublishLibrary";
|
||||
import { Dialog } from "./Dialog";
|
||||
|
||||
import DropdownMenu from "./dropdownMenu/DropdownMenu";
|
||||
|
||||
export const isLibraryMenuOpenAtom = atom(false);
|
||||
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||
import { useUIAppState } from "../context/ui-appState";
|
||||
|
||||
const getSelectedItems = (
|
||||
libraryItems: LibraryItems,
|
||||
selectedItems: LibraryItem["id"][],
|
||||
) => libraryItems.filter((item) => selectedItems.includes(item.id));
|
||||
|
||||
export const LibraryMenuHeader: React.FC<{
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
export const LibraryDropdownMenuButton: React.FC<{
|
||||
setAppState: React.Component<any, UIAppState>["setState"];
|
||||
selectedItems: LibraryItem["id"][];
|
||||
library: Library;
|
||||
onRemoveFromLibrary: () => void;
|
||||
resetLibrary: () => void;
|
||||
onSelectItems: (items: LibraryItem["id"][]) => void;
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
}> = ({
|
||||
setAppState,
|
||||
selectedItems,
|
||||
|
@ -50,6 +51,7 @@ export const LibraryMenuHeader: React.FC<{
|
|||
isLibraryMenuOpenAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
|
||||
const renderRemoveLibAlert = useCallback(() => {
|
||||
const content = selectedItems.length
|
||||
? t("alerts.removeItemsFromsLibrary", { count: selectedItems.length })
|
||||
|
@ -104,16 +106,19 @@ export const LibraryMenuHeader: React.FC<{
|
|||
small={true}
|
||||
>
|
||||
<p>
|
||||
{t("publishSuccessDialog.content", {
|
||||
authorName: publishLibSuccess!.authorName,
|
||||
})}{" "}
|
||||
<a
|
||||
href={publishLibSuccess?.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t("publishSuccessDialog.link")}
|
||||
</a>
|
||||
<Trans
|
||||
i18nKey="publishSuccessDialog.content"
|
||||
authorName={publishLibSuccess!.authorName}
|
||||
link={(el) => (
|
||||
<a
|
||||
href={publishLibSuccess?.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{el}
|
||||
</a>
|
||||
)}
|
||||
/>
|
||||
</p>
|
||||
<ToolButton
|
||||
type="button"
|
||||
|
@ -181,7 +186,6 @@ export const LibraryMenuHeader: React.FC<{
|
|||
return (
|
||||
<DropdownMenu open={isLibraryMenuOpen}>
|
||||
<DropdownMenu.Trigger
|
||||
className="Sidebar__dropdown-btn"
|
||||
onToggle={() => setIsLibraryMenuOpen(!isLibraryMenuOpen)}
|
||||
>
|
||||
{DotsIcon}
|
||||
|
@ -230,6 +234,7 @@ export const LibraryMenuHeader: React.FC<{
|
|||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
{renderLibraryMenu()}
|
||||
|
@ -261,3 +266,48 @@ export const LibraryMenuHeader: React.FC<{
|
|||
</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 {
|
||||
row-gap: 0.5rem;
|
||||
padding: var(--container-padding-y) var(--container-padding-x);
|
||||
padding: var(--container-padding-y) 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
@ -61,7 +61,7 @@
|
|||
margin-bottom: 0.75rem;
|
||||
|
||||
&--excal {
|
||||
margin-top: 2.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,17 +2,21 @@ import React, { useState } from "react";
|
|||
import { serializeLibraryAsJSON } from "../data/json";
|
||||
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { AppState, ExcalidrawProps, LibraryItem, LibraryItems } from "../types";
|
||||
import {
|
||||
ExcalidrawProps,
|
||||
LibraryItem,
|
||||
LibraryItems,
|
||||
UIAppState,
|
||||
} from "../types";
|
||||
import { arrayToMap, chunk } from "../utils";
|
||||
import { LibraryUnit } from "./LibraryUnit";
|
||||
import Stack from "./Stack";
|
||||
|
||||
import "./LibraryMenuItems.scss";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import Spinner from "./Spinner";
|
||||
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
|
||||
import clsx from "clsx";
|
||||
import { duplicateElements } from "../element/newElement";
|
||||
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
||||
|
||||
import "./LibraryMenuItems.scss";
|
||||
|
||||
const CELLS_PER_ROW = 4;
|
||||
|
||||
|
@ -36,7 +40,7 @@ const LibraryMenuItems = ({
|
|||
selectedItems: LibraryItem["id"][];
|
||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||
theme: AppState["theme"];
|
||||
theme: UIAppState["theme"];
|
||||
id: string;
|
||||
}) => {
|
||||
const [lastSelectedItem, setLastSelectedItem] = useState<
|
||||
|
@ -201,11 +205,7 @@ const LibraryMenuItems = ({
|
|||
(item) => item.status === "published",
|
||||
);
|
||||
|
||||
const showBtn =
|
||||
!libraryItems.length &&
|
||||
!unpublishedItems.length &&
|
||||
!publishedItems.length &&
|
||||
!pendingElements.length;
|
||||
const showBtn = !libraryItems.length && !pendingElements.length;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -215,7 +215,7 @@ const LibraryMenuItems = ({
|
|||
unpublishedItems.length ||
|
||||
publishedItems.length
|
||||
? { justifyContent: "flex-start" }
|
||||
: {}
|
||||
: { borderBottom: 0 }
|
||||
}
|
||||
>
|
||||
<Stack.Col
|
||||
|
@ -251,11 +251,7 @@ const LibraryMenuItems = ({
|
|||
</div>
|
||||
{!pendingElements.length && !unpublishedItems.length ? (
|
||||
<div className="library-menu-items__no-items">
|
||||
<div
|
||||
className={clsx({
|
||||
"library-menu-items__no-items__label": showBtn,
|
||||
})}
|
||||
>
|
||||
<div className="library-menu-items__no-items__label">
|
||||
{t("library.noItems")}
|
||||
</div>
|
||||
<div className="library-menu-items__no-items__hint">
|
||||
|
@ -303,10 +299,13 @@ const LibraryMenuItems = ({
|
|||
</>
|
||||
|
||||
{showBtn && (
|
||||
<LibraryMenuBrowseButton
|
||||
<LibraryMenuControlButtons
|
||||
style={{ padding: "16px 0", width: "100%" }}
|
||||
id={id}
|
||||
libraryReturnUrl={libraryReturnUrl}
|
||||
theme={theme}
|
||||
selectedItems={selectedItems}
|
||||
onSelectItems={onSelectItems}
|
||||
/>
|
||||
)}
|
||||
</Stack.Col>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { AppState, Device, ExcalidrawProps } from "../types";
|
||||
import { AppState, Device, ExcalidrawProps, UIAppState } from "../types";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { t } from "../i18n";
|
||||
import Stack from "./Stack";
|
||||
|
@ -13,16 +13,15 @@ import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
|||
import { Section } from "./Section";
|
||||
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
||||
import { LockButton } from "./LockButton";
|
||||
import { LibraryButton } from "./LibraryButton";
|
||||
import { PenModeButton } from "./PenModeButton";
|
||||
import { Stats } from "./Stats";
|
||||
import { actionToggleStats } from "../actions";
|
||||
import { HandButton } from "./HandButton";
|
||||
import { isHandToolActive } from "../appState";
|
||||
import { useTunnels } from "./context/tunnels";
|
||||
import { useTunnels } from "../context/tunnels";
|
||||
|
||||
type MobileMenuProps = {
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
actionManager: ActionManager;
|
||||
renderJSONExportDialog: () => React.ReactNode;
|
||||
renderImageExportDialog: () => React.ReactNode;
|
||||
|
@ -36,7 +35,7 @@ type MobileMenuProps = {
|
|||
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
||||
renderTopRightUI?: (
|
||||
isMobile: boolean,
|
||||
appState: AppState,
|
||||
appState: UIAppState,
|
||||
) => JSX.Element | null;
|
||||
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
||||
renderSidebars: () => JSX.Element | null;
|
||||
|
@ -60,11 +59,15 @@ export const MobileMenu = ({
|
|||
device,
|
||||
renderWelcomeScreen,
|
||||
}: MobileMenuProps) => {
|
||||
const { welcomeScreenCenterTunnel, mainMenuTunnel } = useTunnels();
|
||||
const {
|
||||
WelcomeScreenCenterTunnel,
|
||||
MainMenuTunnel,
|
||||
DefaultSidebarTriggerTunnel,
|
||||
} = useTunnels();
|
||||
const renderToolbar = () => {
|
||||
return (
|
||||
<FixedSideContainer side="top" className="App-top-bar">
|
||||
{renderWelcomeScreen && <welcomeScreenCenterTunnel.Out />}
|
||||
{renderWelcomeScreen && <WelcomeScreenCenterTunnel.Out />}
|
||||
<Section heading="shapes">
|
||||
{(heading: React.ReactNode) => (
|
||||
<Stack.Col gap={4} align="center">
|
||||
|
@ -88,11 +91,7 @@ export const MobileMenu = ({
|
|||
{renderTopRightUI && renderTopRightUI(true, appState)}
|
||||
<div className="mobile-misc-tools-container">
|
||||
{!appState.viewModeEnabled && (
|
||||
<LibraryButton
|
||||
appState={appState}
|
||||
setAppState={setAppState}
|
||||
isMobile
|
||||
/>
|
||||
<DefaultSidebarTriggerTunnel.Out />
|
||||
)}
|
||||
<PenModeButton
|
||||
checked={appState.penMode}
|
||||
|
@ -132,14 +131,14 @@ export const MobileMenu = ({
|
|||
if (appState.viewModeEnabled) {
|
||||
return (
|
||||
<div className="App-toolbar-content">
|
||||
<mainMenuTunnel.Out />
|
||||
<MainMenuTunnel.Out />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App-toolbar-content">
|
||||
<mainMenuTunnel.Out />
|
||||
<MainMenuTunnel.Out />
|
||||
{actionManager.renderAction("toggleEditMenu")}
|
||||
{actionManager.renderAction("undo")}
|
||||
{actionManager.renderAction("redo")}
|
||||
|
@ -190,13 +189,13 @@ export const MobileMenu = ({
|
|||
{renderAppToolbar()}
|
||||
{appState.scrolledOutside &&
|
||||
!appState.openMenu &&
|
||||
appState.openSidebar !== "library" && (
|
||||
!appState.openSidebar && (
|
||||
<button
|
||||
className="scroll-back-to-content"
|
||||
onClick={() => {
|
||||
setAppState({
|
||||
setAppState((appState) => ({
|
||||
...calculateScrollCenter(elements, appState, canvas),
|
||||
});
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{t("buttons.scrollBackToContent")}
|
||||
|
|
|
@ -5,8 +5,10 @@ import { ChartElements, renderSpreadsheet, Spreadsheet } from "../charts";
|
|||
import { ChartType } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { exportToSvg } from "../scene/export";
|
||||
import { AppState, LibraryItem } from "../types";
|
||||
import { UIAppState } from "../types";
|
||||
import { useApp } from "./App";
|
||||
import { Dialog } from "./Dialog";
|
||||
|
||||
import "./PasteChartDialog.scss";
|
||||
|
||||
type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void;
|
||||
|
@ -78,13 +80,12 @@ export const PasteChartDialog = ({
|
|||
setAppState,
|
||||
appState,
|
||||
onClose,
|
||||
onInsertChart,
|
||||
}: {
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
onClose: () => void;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
onInsertChart: (elements: LibraryItem["elements"]) => void;
|
||||
setAppState: React.Component<any, UIAppState>["setState"];
|
||||
}) => {
|
||||
const { onInsertElements } = useApp();
|
||||
const handleClose = React.useCallback(() => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
|
@ -92,7 +93,7 @@ export const PasteChartDialog = ({
|
|||
}, [onClose]);
|
||||
|
||||
const handleChartClick = (chartType: ChartType, elements: ChartElements) => {
|
||||
onInsertChart(elements);
|
||||
onInsertElements(elements);
|
||||
trackEvent("magic", "chart", chartType);
|
||||
setAppState({
|
||||
currentChartType: chartType,
|
||||
|
|
|
@ -3,8 +3,9 @@ import OpenColor from "open-color";
|
|||
|
||||
import { Dialog } from "./Dialog";
|
||||
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 {
|
||||
EXPORT_DATA_TYPES,
|
||||
|
@ -135,7 +136,7 @@ const SingleLibraryItem = ({
|
|||
onRemove,
|
||||
}: {
|
||||
libItem: LibraryItem;
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
index: number;
|
||||
onChange: (val: string, index: number) => void;
|
||||
onRemove: (id: string) => void;
|
||||
|
@ -231,7 +232,7 @@ const PublishLibrary = ({
|
|||
}: {
|
||||
onClose: () => void;
|
||||
libraryItems: LibraryItems;
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
onSuccess: (data: {
|
||||
url: string;
|
||||
authorName: string;
|
||||
|
@ -402,26 +403,32 @@ const PublishLibrary = ({
|
|||
{shouldRenderForm ? (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="publish-library-note">
|
||||
{t("publishDialog.noteDescription.pre")}
|
||||
<a
|
||||
href="https://libraries.excalidraw.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t("publishDialog.noteDescription.link")}
|
||||
</a>{" "}
|
||||
{t("publishDialog.noteDescription.post")}
|
||||
<Trans
|
||||
i18nKey="publishDialog.noteDescription"
|
||||
link={(el) => (
|
||||
<a
|
||||
href="https://libraries.excalidraw.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{el}
|
||||
</a>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<span className="publish-library-note">
|
||||
{t("publishDialog.noteGuidelines.pre")}
|
||||
<a
|
||||
href="https://github.com/excalidraw/excalidraw-libraries#guidelines"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t("publishDialog.noteGuidelines.link")}
|
||||
</a>
|
||||
{t("publishDialog.noteGuidelines.post")}
|
||||
<Trans
|
||||
i18nKey="publishDialog.noteGuidelines"
|
||||
link={(el) => (
|
||||
<a
|
||||
href="https://github.com/excalidraw/excalidraw-libraries#guidelines"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{el}
|
||||
</a>
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<div className="publish-library-note">
|
||||
|
@ -515,15 +522,18 @@ const PublishLibrary = ({
|
|||
/>
|
||||
</label>
|
||||
<span className="publish-library-note">
|
||||
{t("publishDialog.noteLicense.pre")}
|
||||
<a
|
||||
href="https://github.com/excalidraw/excalidraw-libraries/blob/main/LICENSE"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t("publishDialog.noteLicense.link")}
|
||||
</a>
|
||||
{t("publishDialog.noteLicense.post")}
|
||||
<Trans
|
||||
i18nKey="publishDialog.noteLicense"
|
||||
link={(el) => (
|
||||
<a
|
||||
href="https://github.com/excalidraw/excalidraw-libraries/blob/main/LICENSE"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{el}
|
||||
</a>
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="publish-library__buttons">
|
||||
|
|
|
@ -2,67 +2,26 @@
|
|||
@import "../../css/variables.module";
|
||||
|
||||
.excalidraw {
|
||||
.Sidebar {
|
||||
&__close-btn,
|
||||
&__pin-btn,
|
||||
&__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 {
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
background-color: var(--sidebar-bg-color);
|
||||
box-shadow: var(--sidebar-shadow);
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
background-color: var(--sidebar-bg-color);
|
||||
|
||||
box-shadow: var(--sidebar-shadow);
|
||||
|
||||
&--docked {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
@ -77,52 +36,134 @@
|
|||
border-right: 1px solid var(--sidebar-border-color);
|
||||
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;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--sidebar-border-color);
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.layer-ui__sidebar__header__buttons {
|
||||
.sidebar__header__buttons {
|
||||
gap: 0;
|
||||
display: flex;
|
||||
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 { DEFAULT_SIDEBAR } from "../../constants";
|
||||
import { Excalidraw, Sidebar } from "../../packages/excalidraw/index";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
GlobalTestState,
|
||||
queryAllByTestId,
|
||||
queryByTestId,
|
||||
render,
|
||||
|
@ -10,346 +11,321 @@ import {
|
|||
withExcalidrawDimensions,
|
||||
} 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", () => {
|
||||
it("should render custom sidebar", async () => {
|
||||
const { container } = await render(
|
||||
<Excalidraw
|
||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
||||
renderSidebar={() => (
|
||||
<Sidebar>
|
||||
describe("General behavior", () => {
|
||||
it("should render custom sidebar", async () => {
|
||||
const { container } = await render(
|
||||
<Excalidraw
|
||||
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||
>
|
||||
<Sidebar name="customSidebar">
|
||||
<div id="test-sidebar-content">42</div>
|
||||
</Sidebar>
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
</Excalidraw>,
|
||||
);
|
||||
|
||||
const node = container.querySelector("#test-sidebar-content");
|
||||
expect(node).not.toBe(null);
|
||||
const node = container.querySelector("#test-sidebar-content");
|
||||
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 () => {
|
||||
const { container } = await render(
|
||||
<Excalidraw
|
||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
||||
renderSidebar={() => (
|
||||
<Sidebar>
|
||||
describe("<Sidebar.Header/>", () => {
|
||||
it("should render custom sidebar header", async () => {
|
||||
const { container } = await render(
|
||||
<Excalidraw
|
||||
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||
>
|
||||
<Sidebar name="customSidebar">
|
||||
<Sidebar.Header>
|
||||
<div id="test-sidebar-header-content">42</div>
|
||||
</Sidebar.Header>
|
||||
</Sidebar>
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
</Excalidraw>,
|
||||
);
|
||||
|
||||
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");
|
||||
const node = container.querySelector("#test-sidebar-header-content");
|
||||
expect(node).not.toBe(null);
|
||||
|
||||
// make sure only one sidebar is rendered
|
||||
const sidebars = container.querySelectorAll(".layer-ui__sidebar");
|
||||
expect(sidebars.length).toBe(1);
|
||||
// make sure we don't render the default fallback header,
|
||||
// just the custom one
|
||||
expect(queryAllByTestId(container, "sidebar-header").length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("should always render custom sidebar with close button & close on click", async () => {
|
||||
const onClose = jest.fn();
|
||||
const CustomExcalidraw = () => {
|
||||
return (
|
||||
<Excalidraw
|
||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
||||
renderSidebar={() => (
|
||||
<Sidebar className="test-sidebar" onClose={onClose}>
|
||||
it("should not render <Sidebar.Header> for custom sidebars by default", async () => {
|
||||
const CustomExcalidraw = () => {
|
||||
return (
|
||||
<Excalidraw
|
||||
initialData={{
|
||||
appState: { openSidebar: { name: "customSidebar" } },
|
||||
}}
|
||||
>
|
||||
<Sidebar name="customSidebar" className="test-sidebar">
|
||||
hello
|
||||
</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");
|
||||
expect(sidebar).not.toBe(null);
|
||||
const closeButton = queryByTestId(sidebar!, "sidebar-dock");
|
||||
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");
|
||||
const closeButton = queryByTestId(sidebar!, "sidebar-close");
|
||||
expect(closeButton).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it("should support controlled docking", async () => {
|
||||
let _setDockable: (dockable: boolean) => void = null!;
|
||||
|
||||
const CustomExcalidraw = () => {
|
||||
const [dockable, setDockable] = React.useState(false);
|
||||
_setDockable = setDockable;
|
||||
return (
|
||||
<Excalidraw
|
||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
||||
renderSidebar={() => (
|
||||
it("<Sidebar.Header> should render close button", async () => {
|
||||
const onStateChange = jest.fn();
|
||||
const CustomExcalidraw = () => {
|
||||
return (
|
||||
<Excalidraw
|
||||
initialData={{
|
||||
appState: { openSidebar: { name: "customSidebar" } },
|
||||
}}
|
||||
>
|
||||
<Sidebar
|
||||
name="customSidebar"
|
||||
className="test-sidebar"
|
||||
docked={false}
|
||||
dockable={dockable}
|
||||
onStateChange={onStateChange}
|
||||
>
|
||||
hello
|
||||
<Sidebar.Header />
|
||||
</Sidebar>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
</Excalidraw>
|
||||
);
|
||||
};
|
||||
|
||||
const { container } = await render(<CustomExcalidraw />);
|
||||
const { container } = await render(<CustomExcalidraw />);
|
||||
|
||||
await withExcalidrawDimensions({ width: 1920, height: 1080 }, async () => {
|
||||
// should not show dock button when `dockable` is `false`
|
||||
// -------------------------------------------------------------------------
|
||||
// initial open
|
||||
expect(onStateChange).toHaveBeenCalledWith({ name: "customSidebar" });
|
||||
|
||||
act(() => {
|
||||
_setDockable(false);
|
||||
});
|
||||
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(() => {
|
||||
const sidebar = container.querySelector<HTMLElement>(".test-sidebar");
|
||||
expect(sidebar).not.toBe(null);
|
||||
const closeButton = queryByTestId(sidebar!, "sidebar-dock");
|
||||
expect(closeButton).toBe(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);
|
||||
expect(container.querySelector<HTMLElement>(".test-sidebar")).toBe(
|
||||
null,
|
||||
);
|
||||
expect(onStateChange).toHaveBeenCalledWith(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should support controlled docking", async () => {
|
||||
let _setDocked: (docked?: boolean) => void = null!;
|
||||
describe("Docking behavior", () => {
|
||||
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 = () => {
|
||||
const [docked, setDocked] = React.useState<boolean | undefined>();
|
||||
_setDocked = setDocked;
|
||||
return (
|
||||
it("shouldn't be user-dockable if `onDock` not supplied & `docked={true}`", async () => {
|
||||
await assertExcalidrawWithSidebar(
|
||||
<Sidebar name="customSidebar" docked={true}>
|
||||
<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
|
||||
initialData={{ appState: { openSidebar: "customSidebar" } }}
|
||||
renderSidebar={() => (
|
||||
<Sidebar className="test-sidebar" docked={docked}>
|
||||
hello
|
||||
</Sidebar>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||
>
|
||||
<Sidebar
|
||||
name="customSidebar"
|
||||
className="test-sidebar"
|
||||
onDock={() => {}}
|
||||
docked
|
||||
>
|
||||
<Sidebar.Header />
|
||||
</Sidebar>
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
</Excalidraw>,
|
||||
);
|
||||
|
||||
// sidebar isn't rendered initially
|
||||
// -------------------------------------------------------------------------
|
||||
await waitFor(() => {
|
||||
const node = container.querySelector("#test-sidebar-content");
|
||||
expect(node).toBe(null);
|
||||
await withExcalidrawDimensions(
|
||||
{ width: 1920, height: 1080 },
|
||||
async () => {
|
||||
await assertSidebarDockButton(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// toggle sidebar on
|
||||
// -------------------------------------------------------------------------
|
||||
expect(window.h.app.toggleMenu("customSidebar")).toBe(true);
|
||||
it("shouldn't be user-dockable when only `onDock` supplied w/o `docked`", async () => {
|
||||
await render(
|
||||
<Excalidraw
|
||||
initialData={{ appState: { openSidebar: { name: "customSidebar" } } }}
|
||||
>
|
||||
<Sidebar
|
||||
name="customSidebar"
|
||||
className="test-sidebar"
|
||||
onDock={() => {}}
|
||||
>
|
||||
<Sidebar.Header />
|
||||
</Sidebar>
|
||||
</Excalidraw>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const node = container.querySelector("#test-sidebar-content");
|
||||
expect(node).not.toBe(null);
|
||||
});
|
||||
|
||||
// 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);
|
||||
await withExcalidrawDimensions(
|
||||
{ width: 1920, height: 1080 },
|
||||
async () => {
|
||||
await assertSidebarDockButton(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,151 +1,246 @@
|
|||
import {
|
||||
import React, {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useCallback,
|
||||
RefObject,
|
||||
} from "react";
|
||||
import { Island } from ".././Island";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { atom, useSetAtom } from "jotai";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import {
|
||||
SidebarPropsContext,
|
||||
SidebarProps,
|
||||
SidebarPropsContextValue,
|
||||
} from "./common";
|
||||
|
||||
import { SidebarHeaderComponents } from "./SidebarHeader";
|
||||
import { SidebarHeader } 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 clsx from "clsx";
|
||||
import { useExcalidrawSetAppState } from "../App";
|
||||
import { updateObject } from "../../utils";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
|
||||
/** using a counter instead of boolean to handle race conditions where
|
||||
* the host app may render (mount/unmount) multiple different sidebar */
|
||||
export const hostSidebarCountersAtom = atom({ rendered: 0, docked: 0 });
|
||||
|
||||
export const Sidebar = Object.assign(
|
||||
forwardRef(
|
||||
(
|
||||
{
|
||||
children,
|
||||
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;
|
||||
// FIXME replace this with the implem from ColorPicker once it's merged
|
||||
const useOnClickOutside = (
|
||||
ref: RefObject<HTMLElement>,
|
||||
cb: (event: MouseEvent) => void,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
const listener = (event: MouseEvent) => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Island
|
||||
className={clsx(
|
||||
"layer-ui__sidebar",
|
||||
{ "layer-ui__sidebar--docked": isDockedFallback },
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
<SidebarPropsContext.Provider value={headerPropsRef.current}>
|
||||
<SidebarHeaderComponents.Context>
|
||||
<SidebarHeaderComponents.Component __isFallback />
|
||||
{children}
|
||||
</SidebarHeaderComponents.Context>
|
||||
</SidebarPropsContext.Provider>
|
||||
</Island>
|
||||
if (
|
||||
event.target instanceof Element &&
|
||||
(ref.current.contains(event.target) ||
|
||||
!document.body.contains(event.target))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
cb(event);
|
||||
};
|
||||
document.addEventListener("pointerdown", listener, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", listener);
|
||||
};
|
||||
}, [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`",
|
||||
);
|
||||
},
|
||||
),
|
||||
{
|
||||
Header: SidebarHeaderComponents.Component,
|
||||
}
|
||||
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
|
||||
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 { SidebarPropsContext } from "./common";
|
||||
import { CloseIcon, PinIcon } from "../icons";
|
||||
import { withUpstreamOverride } from "../hoc/withUpstreamOverride";
|
||||
import { Tooltip } from "../Tooltip";
|
||||
import { Button } from "../Button";
|
||||
|
||||
export const SidebarDockButton = (props: {
|
||||
checked: boolean;
|
||||
onChange?(): void;
|
||||
}) => {
|
||||
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<{
|
||||
export const SidebarHeader = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}> = ({ children, className }) => {
|
||||
}) => {
|
||||
const device = useDevice();
|
||||
const props = useContext(SidebarPropsContext);
|
||||
|
||||
const renderDockButton = !!(device.canDeviceFitSidebar && props.dockable);
|
||||
const renderCloseButton = !!props.onClose;
|
||||
const renderDockButton = !!(
|
||||
device.canDeviceFitSidebar && props.shouldRenderDockButton
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("layer-ui__sidebar__header", className)}
|
||||
className={clsx("sidebar__header", className)}
|
||||
data-testid="sidebar-header"
|
||||
>
|
||||
{children}
|
||||
{(renderDockButton || renderCloseButton) && (
|
||||
<div className="layer-ui__sidebar__header__buttons">
|
||||
{renderDockButton && (
|
||||
<SidebarDockButton
|
||||
checked={!!props.docked}
|
||||
onChange={() => {
|
||||
props.onDock?.(!props.docked);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{renderCloseButton && (
|
||||
<button
|
||||
data-testid="sidebar-close"
|
||||
className="Sidebar__close-btn"
|
||||
onClick={props.onClose}
|
||||
aria-label={t("buttons.close")}
|
||||
<div className="sidebar__header__buttons">
|
||||
{renderDockButton && (
|
||||
<Tooltip label={t("labels.sidebarLock")}>
|
||||
<Button
|
||||
onSelect={() => props.onDock?.(!props.docked)}
|
||||
selected={!!props.docked}
|
||||
className="sidebar__dock"
|
||||
data-testid="sidebar-dock"
|
||||
aria-label={t("labels.sidebarLock")}
|
||||
>
|
||||
{CloseIcon}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{PinIcon}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Button
|
||||
data-testid="sidebar-close"
|
||||
className="sidebar__close"
|
||||
onSelect={props.onCloseRequest}
|
||||
aria-label={t("buttons.close")}
|
||||
>
|
||||
{CloseIcon}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const [Context, Component] = withUpstreamOverride(_SidebarHeader);
|
||||
|
||||
/** @private */
|
||||
export const SidebarHeaderComponents = { Context, Component };
|
||||
SidebarHeader.displayName = "SidebarHeader";
|
||||
|
|
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 { 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 = {}> = {
|
||||
name: SidebarName;
|
||||
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;
|
||||
docked?: boolean;
|
||||
initialDockedState?: boolean;
|
||||
dockable?: boolean;
|
||||
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;
|
||||
|
||||
export type SidebarPropsContextValue = Pick<
|
||||
SidebarProps,
|
||||
"onClose" | "onDock" | "docked" | "dockable"
|
||||
>;
|
||||
"onDock" | "docked"
|
||||
> & { onCloseRequest: () => void; shouldRenderDockButton: boolean };
|
||||
|
||||
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 { t } from "../i18n";
|
||||
import { getTargetElements } from "../scene";
|
||||
import { AppState, ExcalidrawProps } from "../types";
|
||||
import { ExcalidrawProps, UIAppState } from "../types";
|
||||
import { CloseIcon } from "./icons";
|
||||
import { Island } from "./Island";
|
||||
import "./Stats.scss";
|
||||
|
||||
export const Stats = (props: {
|
||||
appState: AppState;
|
||||
setAppState: React.Component<any, AppState>["setState"];
|
||||
appState: UIAppState;
|
||||
setAppState: React.Component<any, UIAppState>["setState"];
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
onClose: () => void;
|
||||
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import { t } from "../i18n";
|
||||
import Trans from "./Trans";
|
||||
|
||||
interface TopErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
|
@ -74,25 +75,31 @@ export class TopErrorBoundary extends React.Component<
|
|||
<div className="ErrorSplash excalidraw">
|
||||
<div className="ErrorSplash-messageContainer">
|
||||
<div className="ErrorSplash-paragraph bigger align-center">
|
||||
{t("errorSplash.headingMain_pre")}
|
||||
<button onClick={() => window.location.reload()}>
|
||||
{t("errorSplash.headingMain_button")}
|
||||
</button>
|
||||
<Trans
|
||||
i18nKey="errorSplash.headingMain"
|
||||
button={(el) => (
|
||||
<button onClick={() => window.location.reload()}>{el}</button>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ErrorSplash-paragraph align-center">
|
||||
{t("errorSplash.clearCanvasMessage")}
|
||||
<button
|
||||
onClick={() => {
|
||||
try {
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("errorSplash.clearCanvasMessage_button")}
|
||||
</button>
|
||||
<Trans
|
||||
i18nKey="errorSplash.clearCanvasMessage"
|
||||
button={(el) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
try {
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{el}
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<br />
|
||||
<div className="smaller">
|
||||
<span role="img" aria-label="warning">
|
||||
|
@ -106,16 +113,17 @@ export class TopErrorBoundary extends React.Component<
|
|||
</div>
|
||||
<div>
|
||||
<div className="ErrorSplash-paragraph">
|
||||
{t("errorSplash.trackedToSentry_pre")}
|
||||
{this.state.sentryEventId}
|
||||
{t("errorSplash.trackedToSentry_post")}
|
||||
{t("errorSplash.trackedToSentry", {
|
||||
eventId: this.state.sentryEventId,
|
||||
})}
|
||||
</div>
|
||||
<div className="ErrorSplash-paragraph">
|
||||
{t("errorSplash.openIssueMessage_pre")}
|
||||
<button onClick={() => this.createGithubIssue()}>
|
||||
{t("errorSplash.openIssueMessage_button")}
|
||||
</button>
|
||||
{t("errorSplash.openIssueMessage_post")}
|
||||
<Trans
|
||||
i18nKey="errorSplash.openIssueMessage"
|
||||
button={(el) => (
|
||||
<button onClick={() => this.createGithubIssue()}>{el}</button>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ErrorSplash-paragraph">
|
||||
<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"
|
||||
>
|
||||
<p>
|
||||
Looks like you are using Brave browser with the
|
||||
|
||||
Looks like you are using Brave browser with the
|
||||
<span
|
||||
style="font-weight: 600;"
|
||||
>
|
||||
Aggressively Block Fingerprinting
|
||||
</span>
|
||||
|
||||
setting enabled
|
||||
.
|
||||
<br />
|
||||
<br />
|
||||
This could result in breaking the
|
||||
|
||||
setting enabled.
|
||||
</p>
|
||||
<p>
|
||||
This could result in breaking the
|
||||
<span
|
||||
style="font-weight: 600;"
|
||||
>
|
||||
Text Elements
|
||||
</span>
|
||||
|
||||
in your drawings
|
||||
.
|
||||
in your drawings.
|
||||
</p>
|
||||
<p>
|
||||
We strongly recommend disabling this setting. You can follow
|
||||
|
||||
We strongly recommend disabling this setting. You can follow
|
||||
<a
|
||||
href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser"
|
||||
>
|
||||
|
||||
these steps
|
||||
</a>
|
||||
|
||||
on how to do so
|
||||
.
|
||||
on how to do so.
|
||||
</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
|
||||
href="https://github.com/excalidraw/excalidraw/issues/new"
|
||||
>
|
||||
issue
|
||||
</a>
|
||||
|
||||
on our GitHub, or write us on
|
||||
|
||||
on our GitHub, or write us on
|
||||
<a
|
||||
href="https://discord.gg/UexuTaE"
|
||||
>
|
||||
Discord
|
||||
.
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</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 { useDevice } from "../App";
|
||||
|
@ -24,7 +24,7 @@ const MenuContent = ({
|
|||
style?: React.CSSProperties;
|
||||
}) => {
|
||||
const device = useDevice();
|
||||
const menuRef = useOutsideClickHook(() => {
|
||||
const menuRef = useOutsideClick(() => {
|
||||
onClickOutside?.();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import clsx from "clsx";
|
||||
import { useDevice, useExcalidrawAppState } from "../App";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
import { useDevice } from "../App";
|
||||
|
||||
const MenuTrigger = ({
|
||||
className = "",
|
||||
|
@ -10,7 +11,7 @@ const MenuTrigger = ({
|
|||
children: React.ReactNode;
|
||||
onToggle: () => void;
|
||||
}) => {
|
||||
const appState = useExcalidrawAppState();
|
||||
const appState = useUIAppState();
|
||||
const device = useDevice();
|
||||
const classNames = clsx(
|
||||
`dropdown-menu-button ${className}`,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import clsx from "clsx";
|
||||
import { actionShortcuts } from "../../actions";
|
||||
import { ActionManager } from "../../actions/manager";
|
||||
import { AppState } from "../../types";
|
||||
import {
|
||||
ExitZenModeAction,
|
||||
FinalizeAction,
|
||||
|
@ -9,10 +8,11 @@ import {
|
|||
ZoomActions,
|
||||
} from "../Actions";
|
||||
import { useDevice } from "../App";
|
||||
import { useTunnels } from "../context/tunnels";
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import { HelpButton } from "../HelpButton";
|
||||
import { Section } from "../Section";
|
||||
import Stack from "../Stack";
|
||||
import { UIAppState } from "../../types";
|
||||
|
||||
const Footer = ({
|
||||
appState,
|
||||
|
@ -20,12 +20,12 @@ const Footer = ({
|
|||
showExitZenModeBtn,
|
||||
renderWelcomeScreen,
|
||||
}: {
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
actionManager: ActionManager;
|
||||
showExitZenModeBtn: boolean;
|
||||
renderWelcomeScreen: boolean;
|
||||
}) => {
|
||||
const { footerCenterTunnel, welcomeScreenHelpHintTunnel } = useTunnels();
|
||||
const { FooterCenterTunnel, WelcomeScreenHelpHintTunnel } = useTunnels();
|
||||
|
||||
const device = useDevice();
|
||||
const showFinalize =
|
||||
|
@ -70,14 +70,14 @@ const Footer = ({
|
|||
</Section>
|
||||
</Stack.Col>
|
||||
</div>
|
||||
<footerCenterTunnel.Out />
|
||||
<FooterCenterTunnel.Out />
|
||||
<div
|
||||
className={clsx("layer-ui__wrapper__footer-right zen-mode-transition", {
|
||||
"transition-right disable-pointerEvents": appState.zenModeEnabled,
|
||||
})}
|
||||
>
|
||||
<div style={{ position: "relative" }}>
|
||||
{renderWelcomeScreen && <welcomeScreenHelpHintTunnel.Out />}
|
||||
{renderWelcomeScreen && <WelcomeScreenHelpHintTunnel.Out />}
|
||||
<HelpButton
|
||||
onClick={() => actionManager.executeAction(actionShortcuts)}
|
||||
/>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import clsx from "clsx";
|
||||
import { useExcalidrawAppState } from "../App";
|
||||
import { useTunnels } from "../context/tunnels";
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import "./FooterCenter.scss";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
|
||||
const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
|
||||
const { footerCenterTunnel } = useTunnels();
|
||||
const appState = useExcalidrawAppState();
|
||||
const { FooterCenterTunnel } = useTunnels();
|
||||
const appState = useUIAppState();
|
||||
return (
|
||||
<footerCenterTunnel.In>
|
||||
<FooterCenterTunnel.In>
|
||||
<div
|
||||
className={clsx("footer-center zen-mode-transition", {
|
||||
"layer-ui__wrapper__footer-left--transition-bottom":
|
||||
|
@ -16,7 +16,7 @@ const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
|
|||
>
|
||||
{children}
|
||||
</div>
|
||||
</footerCenterTunnel.In>
|
||||
</FooterCenterTunnel.In>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,32 +1,46 @@
|
|||
import { atom, useAtom } from "jotai";
|
||||
import React, { useLayoutEffect } from "react";
|
||||
import { useTunnels } from "../context/tunnels";
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
|
||||
export const withInternalFallback = <P,>(
|
||||
componentName: string,
|
||||
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
|
||||
// render until mount counter are initialized. This is because the counter
|
||||
// is initialized in an effect, and thus we could end rendering both
|
||||
// components at the same time until counter is initialized.
|
||||
let preferHost = false;
|
||||
|
||||
let counter = 0;
|
||||
|
||||
const WrapperComponent: React.FC<
|
||||
P & {
|
||||
__fallback?: boolean;
|
||||
}
|
||||
> = (props) => {
|
||||
const { jotaiScope } = useTunnels();
|
||||
const [counter, setCounter] = useAtom(counterAtom, jotaiScope);
|
||||
const [, setRender] = useAtom(renderAtom, jotaiScope);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setCounter((counter) => counter + 1);
|
||||
setRender((c) => {
|
||||
const next = c + 1;
|
||||
counter = next;
|
||||
|
||||
return next;
|
||||
});
|
||||
return () => {
|
||||
setCounter((counter) => counter - 1);
|
||||
setRender((c) => {
|
||||
const next = c - 1;
|
||||
counter = next;
|
||||
if (!next) {
|
||||
preferHost = false;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
}, [setCounter]);
|
||||
}, [setRender]);
|
||||
|
||||
if (!props.__fallback) {
|
||||
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 clsx from "clsx";
|
||||
import { useExcalidrawAppState } from "../App";
|
||||
|
||||
import "./LiveCollaborationTrigger.scss";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
|
||||
const LiveCollaborationTrigger = ({
|
||||
isCollaborating,
|
||||
|
@ -15,7 +15,7 @@ const LiveCollaborationTrigger = ({
|
|||
isCollaborating: boolean;
|
||||
onSelect: () => void;
|
||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||
const appState = useExcalidrawAppState();
|
||||
const appState = useUIAppState();
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||
import { useI18n } from "../../i18n";
|
||||
import {
|
||||
useExcalidrawAppState,
|
||||
useExcalidrawSetAppState,
|
||||
useExcalidrawActionManager,
|
||||
} from "../App";
|
||||
import { useExcalidrawSetAppState, useExcalidrawActionManager } from "../App";
|
||||
import {
|
||||
ExportIcon,
|
||||
ExportImageIcon,
|
||||
|
@ -32,6 +28,7 @@ import clsx from "clsx";
|
|||
import { useSetAtom } from "jotai";
|
||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
|
||||
export const LoadScene = () => {
|
||||
const { t } = useI18n();
|
||||
|
@ -139,7 +136,7 @@ ClearCanvas.displayName = "ClearCanvas";
|
|||
|
||||
export const ToggleTheme = () => {
|
||||
const { t } = useI18n();
|
||||
const appState = useExcalidrawAppState();
|
||||
const appState = useUIAppState();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
|
||||
if (!actionManager.isActionEnabled(actionToggleTheme)) {
|
||||
|
@ -172,7 +169,7 @@ ToggleTheme.displayName = "ToggleTheme";
|
|||
|
||||
export const ChangeCanvasBackground = () => {
|
||||
const { t } = useI18n();
|
||||
const appState = useExcalidrawAppState();
|
||||
const appState = useUIAppState();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
|
||||
if (appState.viewModeEnabled) {
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import React from "react";
|
||||
import {
|
||||
useDevice,
|
||||
useExcalidrawAppState,
|
||||
useExcalidrawSetAppState,
|
||||
} from "../App";
|
||||
import { useDevice, useExcalidrawSetAppState } from "../App";
|
||||
import DropdownMenu from "../dropdownMenu/DropdownMenu";
|
||||
|
||||
import * as DefaultItems from "./DefaultItems";
|
||||
|
@ -13,7 +9,8 @@ import { t } from "../../i18n";
|
|||
import { HamburgerMenuIcon } from "../icons";
|
||||
import { withInternalFallback } from "../hoc/withInternalFallback";
|
||||
import { composeEventHandlers } from "../../utils";
|
||||
import { useTunnels } from "../context/tunnels";
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
|
||||
const MainMenu = Object.assign(
|
||||
withInternalFallback(
|
||||
|
@ -28,16 +25,16 @@ const MainMenu = Object.assign(
|
|||
*/
|
||||
onSelect?: (event: Event) => void;
|
||||
}) => {
|
||||
const { mainMenuTunnel } = useTunnels();
|
||||
const { MainMenuTunnel } = useTunnels();
|
||||
const device = useDevice();
|
||||
const appState = useExcalidrawAppState();
|
||||
const appState = useUIAppState();
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
const onClickOutside = device.isMobile
|
||||
? undefined
|
||||
: () => setAppState({ openMenu: null });
|
||||
|
||||
return (
|
||||
<mainMenuTunnel.In>
|
||||
<MainMenuTunnel.In>
|
||||
<DropdownMenu open={appState.openMenu === "canvas"}>
|
||||
<DropdownMenu.Trigger
|
||||
onToggle={() => {
|
||||
|
@ -66,7 +63,7 @@ const MainMenu = Object.assign(
|
|||
)}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
</mainMenuTunnel.In>
|
||||
</MainMenuTunnel.In>
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { actionLoadScene, actionShortcuts } from "../../actions";
|
||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||
import { t, useI18n } from "../../i18n";
|
||||
import {
|
||||
useDevice,
|
||||
useExcalidrawActionManager,
|
||||
useExcalidrawAppState,
|
||||
} from "../App";
|
||||
import { useTunnels } from "../context/tunnels";
|
||||
import { useDevice, useExcalidrawActionManager } from "../App";
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
|
||||
const WelcomeScreenMenuItemContent = ({
|
||||
icon,
|
||||
|
@ -89,9 +86,9 @@ const WelcomeScreenMenuItemLink = ({
|
|||
WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink";
|
||||
|
||||
const Center = ({ children }: { children?: React.ReactNode }) => {
|
||||
const { welcomeScreenCenterTunnel } = useTunnels();
|
||||
const { WelcomeScreenCenterTunnel } = useTunnels();
|
||||
return (
|
||||
<welcomeScreenCenterTunnel.In>
|
||||
<WelcomeScreenCenterTunnel.In>
|
||||
<div className="welcome-screen-center">
|
||||
{children || (
|
||||
<>
|
||||
|
@ -104,7 +101,7 @@ const Center = ({ children }: { children?: React.ReactNode }) => {
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
</welcomeScreenCenterTunnel.In>
|
||||
</WelcomeScreenCenterTunnel.In>
|
||||
);
|
||||
};
|
||||
Center.displayName = "Center";
|
||||
|
@ -148,7 +145,7 @@ const MenuItemHelp = () => {
|
|||
MenuItemHelp.displayName = "MenuItemHelp";
|
||||
|
||||
const MenuItemLoadScene = () => {
|
||||
const appState = useExcalidrawAppState();
|
||||
const appState = useUIAppState();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
|
||||
if (appState.viewModeEnabled) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { t } from "../../i18n";
|
||||
import { useTunnels } from "../context/tunnels";
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import {
|
||||
WelcomeScreenHelpArrow,
|
||||
WelcomeScreenMenuArrow,
|
||||
|
@ -7,44 +7,44 @@ import {
|
|||
} from "../icons";
|
||||
|
||||
const MenuHint = ({ children }: { children?: React.ReactNode }) => {
|
||||
const { welcomeScreenMenuHintTunnel } = useTunnels();
|
||||
const { WelcomeScreenMenuHintTunnel } = useTunnels();
|
||||
return (
|
||||
<welcomeScreenMenuHintTunnel.In>
|
||||
<WelcomeScreenMenuHintTunnel.In>
|
||||
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu">
|
||||
{WelcomeScreenMenuArrow}
|
||||
<div className="welcome-screen-decor-hint__label">
|
||||
{children || t("welcomeScreen.defaults.menuHint")}
|
||||
</div>
|
||||
</div>
|
||||
</welcomeScreenMenuHintTunnel.In>
|
||||
</WelcomeScreenMenuHintTunnel.In>
|
||||
);
|
||||
};
|
||||
MenuHint.displayName = "MenuHint";
|
||||
|
||||
const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
|
||||
const { welcomeScreenToolbarHintTunnel } = useTunnels();
|
||||
const { WelcomeScreenToolbarHintTunnel } = useTunnels();
|
||||
return (
|
||||
<welcomeScreenToolbarHintTunnel.In>
|
||||
<WelcomeScreenToolbarHintTunnel.In>
|
||||
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar">
|
||||
<div className="welcome-screen-decor-hint__label">
|
||||
{children || t("welcomeScreen.defaults.toolbarHint")}
|
||||
</div>
|
||||
{WelcomeScreenTopToolbarArrow}
|
||||
</div>
|
||||
</welcomeScreenToolbarHintTunnel.In>
|
||||
</WelcomeScreenToolbarHintTunnel.In>
|
||||
);
|
||||
};
|
||||
ToolbarHint.displayName = "ToolbarHint";
|
||||
|
||||
const HelpHint = ({ children }: { children?: React.ReactNode }) => {
|
||||
const { welcomeScreenHelpHintTunnel } = useTunnels();
|
||||
const { WelcomeScreenHelpHintTunnel } = useTunnels();
|
||||
return (
|
||||
<welcomeScreenHelpHintTunnel.In>
|
||||
<WelcomeScreenHelpHintTunnel.In>
|
||||
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">
|
||||
<div>{children || t("welcomeScreen.defaults.helpHint")}</div>
|
||||
{WelcomeScreenHelpArrow}
|
||||
</div>
|
||||
</welcomeScreenHelpHintTunnel.In>
|
||||
</WelcomeScreenHelpHintTunnel.In>
|
||||
);
|
||||
};
|
||||
HelpHint.displayName = "HelpHint";
|
||||
|
|
|
@ -275,3 +275,10 @@ export const DEFAULT_ELEMENT_PROPS: {
|
|||
opacity: 100,
|
||||
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;
|
||||
}
|
||||
|
||||
.library-button {
|
||||
.default-sidebar-trigger {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,10 +78,13 @@
|
|||
|
||||
--color-selection: #6965db;
|
||||
|
||||
--color-icon-white: #{$oc-white};
|
||||
|
||||
--color-primary: #6965db;
|
||||
--color-primary-darker: #5b57d1;
|
||||
--color-primary-darkest: #4a47b1;
|
||||
--color-primary-light: #e3e2fe;
|
||||
--color-primary-light-darker: #d7d5ff;
|
||||
|
||||
--color-gray-10: #f5f5f5;
|
||||
--color-gray-20: #ebebeb;
|
||||
|
@ -161,10 +164,13 @@
|
|||
// will be inverted to a lighter color.
|
||||
--color-selection: #3530c4;
|
||||
|
||||
--color-icon-white: var(--color-gray-90);
|
||||
|
||||
--color-primary: #a8a5ff;
|
||||
--color-primary-darker: #b2aeff;
|
||||
--color-primary-darkest: #beb9ff;
|
||||
--color-primary-light: #4f4d6f;
|
||||
--color-primary-light-darker: #43415e;
|
||||
|
||||
--color-text-warning: var(--color-gray-80);
|
||||
|
||||
|
|
|
@ -72,7 +72,14 @@
|
|||
|
||||
&:hover {
|
||||
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 {
|
||||
|
@ -81,11 +88,14 @@
|
|||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-primary-light);
|
||||
border-color: var(--color-primary-light);
|
||||
background-color: var(--button-selected-bg, var(--color-primary-light));
|
||||
border-color: var(--button-selected-border, var(--color-primary-light));
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-light);
|
||||
background-color: var(
|
||||
--button-selected-hover-bg,
|
||||
var(--color-primary-light)
|
||||
);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
|
|
@ -14,7 +14,14 @@ import { getCommonBoundingBox } from "../element/bounds";
|
|||
import { AbortError } from "../errors";
|
||||
import { t } from "../i18n";
|
||||
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<{
|
||||
status: "loading" | "loaded";
|
||||
|
@ -148,7 +155,9 @@ class Library {
|
|||
defaultStatus?: "unpublished" | "published";
|
||||
}): Promise<LibraryItems> => {
|
||||
if (openLibraryMenu) {
|
||||
this.app.setState({ openSidebar: "library" });
|
||||
this.app.setState({
|
||||
openSidebar: { name: DEFAULT_SIDEBAR.name, tab: LIBRARY_SIDEBAR_TAB },
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
resolve(mergeLibraryItems(this.lastLibraryItems, nextItems));
|
||||
} else {
|
||||
|
@ -186,8 +202,6 @@ class Library {
|
|||
reject(error);
|
||||
}
|
||||
});
|
||||
}).finally(() => {
|
||||
this.app.focusContainer();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
PRECEDING_ELEMENT_KEY,
|
||||
FONT_FAMILY,
|
||||
ROUNDNESS,
|
||||
DEFAULT_SIDEBAR,
|
||||
} from "../constants";
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
@ -440,21 +441,15 @@ const LegacyAppStateMigrations: {
|
|||
defaultAppState: ReturnType<typeof getDefaultAppState>,
|
||||
) => [LegacyAppState[K][1], AppState[LegacyAppState[K][1]]];
|
||||
} = {
|
||||
isLibraryOpen: (appState, defaultAppState) => {
|
||||
isSidebarDocked: (appState, defaultAppState) => {
|
||||
return [
|
||||
"openSidebar",
|
||||
"isLibraryOpen" in appState
|
||||
? appState.isLibraryOpen
|
||||
? "library"
|
||||
: null
|
||||
: coalesceAppStateValue("openSidebar", appState, defaultAppState),
|
||||
];
|
||||
},
|
||||
isLibraryMenuDocked: (appState, defaultAppState) => {
|
||||
return [
|
||||
"isSidebarDocked",
|
||||
appState.isLibraryMenuDocked ??
|
||||
coalesceAppStateValue("isSidebarDocked", appState, defaultAppState),
|
||||
"defaultSidebarDockedPreference",
|
||||
appState.isSidebarDocked ??
|
||||
coalesceAppStateValue(
|
||||
"defaultSidebarDockedPreference",
|
||||
appState,
|
||||
defaultAppState,
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
|
@ -526,13 +521,10 @@ export const restoreAppState = (
|
|||
: appState.zoom?.value
|
||||
? appState.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:
|
||||
nextAppState.openSidebar === "library"
|
||||
? nextAppState.isSidebarDocked
|
||||
? "library"
|
||||
: null
|
||||
// string (legacy)
|
||||
typeof (appState.openSidebar as any as string) === "string"
|
||||
? { name: DEFAULT_SIDEBAR.name }
|
||||
: nextAppState.openSidebar,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -27,10 +27,8 @@ export interface ExportedDataState {
|
|||
* Don't consume on its own.
|
||||
*/
|
||||
export type LegacyAppState = {
|
||||
/** @deprecated #5663 TODO remove 22-12-15 */
|
||||
isLibraryOpen: [boolean, "openSidebar"];
|
||||
/** @deprecated #5663 TODO remove 22-12-15 */
|
||||
isLibraryMenuDocked: [boolean, "isSidebarDocked"];
|
||||
/** @deprecated #6213 TODO remove 23-06-01 */
|
||||
isSidebarDocked: [boolean, "defaultSidebarDockedPreference"];
|
||||
};
|
||||
|
||||
export interface ImportedDataState {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AppState, ExcalidrawProps, Point } from "../types";
|
||||
import { AppState, ExcalidrawProps, Point, UIAppState } from "../types";
|
||||
import {
|
||||
getShortcutKey,
|
||||
sceneCoordsToViewportCoords,
|
||||
|
@ -297,10 +297,11 @@ export const getContextMenuLabel = (
|
|||
: "labels.link.create";
|
||||
return label;
|
||||
};
|
||||
|
||||
export const getLinkHandleFromCoords = (
|
||||
[x1, y1, x2, y2]: Bounds,
|
||||
angle: number,
|
||||
appState: AppState,
|
||||
appState: UIAppState,
|
||||
): [x: number, y: number, width: number, height: number] => {
|
||||
const size = DEFAULT_LINK_SIZE;
|
||||
const linkWidth = size / appState.zoom.value;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { AppState } from "../types";
|
||||
import { NonDeletedExcalidrawElement } from "./types";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { UIAppState } from "../types";
|
||||
|
||||
export const showSelectedShapeActions = (
|
||||
appState: AppState,
|
||||
appState: UIAppState,
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
) =>
|
||||
Boolean(
|
||||
|
|
|
@ -7,8 +7,9 @@ import {
|
|||
import { DEFAULT_VERSION } from "../constants";
|
||||
import { t } from "../i18n";
|
||||
import { copyTextToSystemClipboard } from "../clipboard";
|
||||
import { AppState } from "../types";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { UIAppState } from "../types";
|
||||
|
||||
type StorageSizes = { scene: number; total: number };
|
||||
|
||||
const STORAGE_SIZE_TIMEOUT = 500;
|
||||
|
@ -23,7 +24,7 @@ const getStorageSizes = debounce((cb: (sizes: StorageSizes) => void) => {
|
|||
type Props = {
|
||||
setToast: (message: string) => void;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
appState: AppState;
|
||||
appState: UIAppState;
|
||||
};
|
||||
const CustomStats = (props: Props) => {
|
||||
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
|
||||
|
|
|
@ -18,7 +18,7 @@ import { getFrame } from "../../utils";
|
|||
|
||||
const exportToExcalidrawPlus = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
appState: AppState,
|
||||
appState: Partial<AppState>,
|
||||
files: BinaryFiles,
|
||||
) => {
|
||||
const firebase = await loadFirebaseStorage();
|
||||
|
@ -75,7 +75,7 @@ const exportToExcalidrawPlus = async (
|
|||
|
||||
export const ExportToExcalidrawPlus: React.FC<{
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
appState: AppState;
|
||||
appState: Partial<AppState>;
|
||||
files: BinaryFiles;
|
||||
onError: (error: Error) => void;
|
||||
}> = ({ elements, appState, files, onError }) => {
|
||||
|
|
|
@ -285,7 +285,7 @@ export const loadScene = async (
|
|||
|
||||
export const exportToBackend = async (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
appState: Partial<AppState>,
|
||||
files: BinaryFiles,
|
||||
) => {
|
||||
const encryptionKey = await generateEncryptionKey("string");
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
ExcalidrawImperativeAPI,
|
||||
BinaryFiles,
|
||||
ExcalidrawInitialDataState,
|
||||
UIAppState,
|
||||
} from "../types";
|
||||
import {
|
||||
debounce,
|
||||
|
@ -552,7 +553,7 @@ const ExcalidrawWrapper = () => {
|
|||
|
||||
const onExportToBackend = async (
|
||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||
appState: AppState,
|
||||
appState: Partial<AppState>,
|
||||
files: BinaryFiles,
|
||||
canvas: HTMLCanvasElement | null,
|
||||
) => {
|
||||
|
@ -583,7 +584,7 @@ const ExcalidrawWrapper = () => {
|
|||
|
||||
const renderCustomStats = (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
appState: AppState,
|
||||
appState: UIAppState,
|
||||
) => {
|
||||
return (
|
||||
<CustomStats
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
|
||||
export const useOutsideClickHook = (handler: (event: Event) => void) => {
|
||||
export const useOutsideClick = (handler: (event: Event) => void) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "تعذر الحفظ في قاعدة البيانات. إذا استمرت المشاكل، يفضل أن تحفظ ملفك محليا كي لا تفقد عملك.",
|
||||
"collabSaveFailed_sizeExceeded": "تعذر الحفظ في قاعدة البيانات، يبدو أن القماش كبير للغاية، يفضّل حفظ الملف محليا كي لا تفقد عملك.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Подсказка: пробвайте да приближите далечните елементи по-близко."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Среща грешка. Опитайте ",
|
||||
"headingMain_button": "презареждане на страницата.",
|
||||
"clearCanvasMessage": "Ако презареждането не работи, опитайте ",
|
||||
"clearCanvasMessage_button": "изчистване на платното.",
|
||||
"headingMain": "Среща грешка. Опитайте <button>презареждане на страницата.</button>",
|
||||
"clearCanvasMessage": "Ако презареждането не работи, опитайте <button>изчистване на платното.</button>",
|
||||
"clearCanvasCaveat": " Това ще доведе до загуба на работа ",
|
||||
"trackedToSentry_pre": "Грешката с идентификатор ",
|
||||
"trackedToSentry_post": " беше проследен в нашата система.",
|
||||
"openIssueMessage_pre": "Бяхме много предпазливи да не включите информацията за вашата сцена при грешката. Ако сцената ви не е частна, моля, помислете за последващи действия на нашата ",
|
||||
"openIssueMessage_button": "тракер за грешки.",
|
||||
"openIssueMessage_post": " Моля, включете информация по-долу, като я копирате и добавите в GitHub.",
|
||||
"trackedToSentry": "Грешката с идентификатор {{eventId}} беше проследен в нашата система.",
|
||||
"openIssueMessage": "Бяхме много предпазливи да не включите информацията за вашата сцена при грешката. Ако сцената ви не е частна, моля, помислете за последващи действия на нашата <button>тракер за грешки.</button> Моля, включете информация по-долу, като я копирате и добавите в GitHub.",
|
||||
"sceneContent": "Съдържание на сцената:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "",
|
||||
"website": ""
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteDescription": "",
|
||||
"noteGuidelines": "",
|
||||
"noteLicense": "",
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "",
|
||||
"content": "",
|
||||
"link": ""
|
||||
"content": ""
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"labels": {
|
||||
"paste": "পেস্ট করুন",
|
||||
"pasteAsPlaintext": "",
|
||||
"pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন",
|
||||
"pasteCharts": "চার্ট পেস্ট করুন",
|
||||
"selectAll": "সবটা সিলেক্ট করুন",
|
||||
"multiSelect": "একাধিক সিলেক্ট করুন",
|
||||
|
@ -54,7 +54,7 @@
|
|||
"veryLarge": "অনেক বড়",
|
||||
"solid": "দৃঢ়",
|
||||
"hachure": "ভ্রুলেখা",
|
||||
"zigzag": "",
|
||||
"zigzag": "আঁকাবাঁকা",
|
||||
"crossHatch": "ক্রস হ্যাচ",
|
||||
"thin": "পাতলা",
|
||||
"bold": "পুরু",
|
||||
|
@ -73,7 +73,7 @@
|
|||
"layers": "মাত্রা",
|
||||
"actions": "ক্রিয়া",
|
||||
"language": "ভাষা",
|
||||
"liveCollaboration": "",
|
||||
"liveCollaboration": "সরাসরি পারস্পরিক সহযোগিতা...",
|
||||
"duplicateSelection": "সদৃশ সিলেক্ট",
|
||||
"untitled": "অনামী",
|
||||
"name": "নাম",
|
||||
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "বিশেষ্য: দূরতম উপাদানগুলোকে একটু কাছাকাছি নিয়ে যাওয়ার চেষ্টা করুন।"
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "একটি ত্রুটির সম্মুখীন হয়েছে৷ চেষ্টা করুন ",
|
||||
"headingMain_button": "পৃষ্ঠাটি পুনরায় লোড করার।",
|
||||
"clearCanvasMessage": "যদি পুনরায় লোড করা কাজ না করে, চেষ্টা করুন ",
|
||||
"clearCanvasMessage_button": "ক্যানভাস পরিষ্কার করার।",
|
||||
"headingMain": "একটি ত্রুটির সম্মুখীন হয়েছে৷ চেষ্টা করুন <button>পৃষ্ঠাটি পুনরায় লোড করার।</button>",
|
||||
"clearCanvasMessage": "যদি পুনরায় লোড করা কাজ না করে, চেষ্টা করুন <button>ক্যানভাস পরিষ্কার করার।</button>",
|
||||
"clearCanvasCaveat": " এর ফলে কাজের ক্ষতি হবে ",
|
||||
"trackedToSentry_pre": "ত্রুটি ",
|
||||
"trackedToSentry_post": " আমাদের সিস্টেমে ট্র্যাক করা হয়েছিল।",
|
||||
"openIssueMessage_pre": "আমরা ত্রুটিতে আপনার দৃশ্যের তথ্য অন্তর্ভুক্ত না করার জন্য খুব সতর্ক ছিলাম। আপনার দৃশ্য ব্যক্তিগত না হলে, আমাদের অনুসরণ করার কথা বিবেচনা করুন ",
|
||||
"openIssueMessage_button": "ত্রুটি ইতিবৃত্ত।",
|
||||
"openIssueMessage_post": " অনুগ্রহ করে GitHub ইস্যুতে অনুলিপি এবং পেস্ট করে নীচের তথ্য অন্তর্ভুক্ত করুন।",
|
||||
"trackedToSentry": "ত্রুটি {{eventId}} আমাদের সিস্টেমে ট্র্যাক করা হয়েছিল।",
|
||||
"openIssueMessage": "আমরা ত্রুটিতে আপনার দৃশ্যের তথ্য অন্তর্ভুক্ত না করার জন্য খুব সতর্ক ছিলাম। আপনার দৃশ্য ব্যক্তিগত না হলে, আমাদের অনুসরণ করার কথা বিবেচনা করুন <button>ত্রুটি ইতিবৃত্ত।</button> অনুগ্রহ করে GitHub ইস্যুতে অনুলিপি এবং পেস্ট করে নীচের তথ্য অন্তর্ভুক্ত করুন।",
|
||||
"sceneContent": "দৃশ্য বিষয়বস্তু:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "",
|
||||
"website": ""
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteDescription": "",
|
||||
"noteGuidelines": "",
|
||||
"noteLicense": "",
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "",
|
||||
"content": "",
|
||||
"link": ""
|
||||
"content": ""
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Consell: proveu d’acostar una mica els elements més allunyats."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "S'ha produït un error. Proveu ",
|
||||
"headingMain_button": "recarregar la pàgina.",
|
||||
"clearCanvasMessage": "Si la recàrrega no funciona, proveu ",
|
||||
"clearCanvasMessage_button": "esborrar el llenç.",
|
||||
"headingMain": "S'ha produït un error. Proveu <button>recarregar la pàgina.</button>",
|
||||
"clearCanvasMessage": "Si la recàrrega no funciona, proveu <button>esborrar el llenç.</button>",
|
||||
"clearCanvasCaveat": " Això resultarà en la pèrdua de feina ",
|
||||
"trackedToSentry_pre": "L'error amb l'identificador ",
|
||||
"trackedToSentry_post": " s'ha rastrejat en el nostre sistema.",
|
||||
"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.",
|
||||
"trackedToSentry": "L'error amb l'identificador {{eventId}} 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.",
|
||||
"sceneContent": "Contingut de l'escena:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Requerit",
|
||||
"website": "Introduïu una URL vàlida"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Envieu la vostra biblioteca perquè sigui inclosa al ",
|
||||
"link": "repositori públic",
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"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": {
|
||||
"title": "Biblioteca enviada",
|
||||
"content": "Gràcies, {{authorName}}. La vostra biblioteca ha estat enviada per a ser revisada. Podeu comprovar-ne l'estat",
|
||||
"link": "aquí"
|
||||
"content": "Gràcies, {{authorName}}. La vostra biblioteca ha estat enviada per a ser revisada. Podeu comprovar-ne l'estat<link>aquí</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "Restableix la biblioteca",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": ""
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "",
|
||||
"headingMain_button": "",
|
||||
"headingMain": "",
|
||||
"clearCanvasMessage": "",
|
||||
"clearCanvasMessage_button": "",
|
||||
"clearCanvasCaveat": "",
|
||||
"trackedToSentry_pre": "Chyba identifikátoru ",
|
||||
"trackedToSentry_post": " byl zaznamenán v našem systému.",
|
||||
"openIssueMessage_pre": "",
|
||||
"openIssueMessage_button": "",
|
||||
"openIssueMessage_post": "",
|
||||
"trackedToSentry": "Chyba identifikátoru {{eventId}} byl zaznamenán v našem systému.",
|
||||
"openIssueMessage": "",
|
||||
"sceneContent": ""
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Povinné",
|
||||
"website": "Zadejte platnou URL adresu"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Odešlete svou knihovnu, pro zařazení do ",
|
||||
"link": "veřejného úložiště knihoven",
|
||||
"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": ""
|
||||
},
|
||||
"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é.",
|
||||
"noteGuidelines": "Knihovna musí být nejdříve ručně schválena. Přečtěte si prosím <link>pokyny</link>",
|
||||
"noteLicense": "",
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "Knihovna byla odeslána",
|
||||
"content": "",
|
||||
"link": ""
|
||||
"content": ""
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": ""
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "",
|
||||
"headingMain_button": "",
|
||||
"headingMain": "",
|
||||
"clearCanvasMessage": "",
|
||||
"clearCanvasMessage_button": "",
|
||||
"clearCanvasCaveat": "",
|
||||
"trackedToSentry_pre": "",
|
||||
"trackedToSentry_post": "",
|
||||
"openIssueMessage_pre": "",
|
||||
"openIssueMessage_button": "",
|
||||
"openIssueMessage_post": " Kopiere og indsæt venligst oplysningerne nedenfor i et GitHub problem.",
|
||||
"trackedToSentry": "",
|
||||
"openIssueMessage": "<button></button> Kopiere og indsæt venligst oplysningerne nedenfor i et GitHub problem.",
|
||||
"sceneContent": "Scene indhold:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "",
|
||||
"website": ""
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteDescription": "",
|
||||
"noteGuidelines": "",
|
||||
"noteLicense": "",
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "",
|
||||
"content": "",
|
||||
"link": ""
|
||||
"content": ""
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Sieht so aus, als ob du den Brave Browser benutzt mit der",
|
||||
"aggressive_block_fingerprint": "\"Fingerprinting aggressiv blockieren\"",
|
||||
"setting_enabled": "Einstellung aktiviert",
|
||||
"break": "Dies könnte zur inkorrekten Darstellung der",
|
||||
"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"
|
||||
"line1": "Sieht so aus, als ob Du den Brave-Browser verwendest und die <bold>aggressive Blockierung von Fingerabdrücken</bold> aktiviert hast.",
|
||||
"line2": "Dies könnte dazu führen, dass die <bold>Textelemente</bold> in Ihren Zeichnungen zerstört werden.",
|
||||
"line3": "Wir empfehlen dringend, diese Einstellung zu deaktivieren. Dazu kannst Du <link>diesen Schritten</link> folgen.",
|
||||
"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>"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Tipp: Schiebe die am weitesten entfernten Elemente ein wenig näher zusammen."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Es ist ein Fehler aufgetreten. Versuche ",
|
||||
"headingMain_button": "die Seite neu zu laden.",
|
||||
"clearCanvasMessage": "Wenn das Neuladen nicht funktioniert, versuche ",
|
||||
"clearCanvasMessage_button": "die Zeichenfläche zu löschen.",
|
||||
"headingMain": "Es ist ein Fehler aufgetreten. Versuche <button>die Seite neu zu laden.</button>",
|
||||
"clearCanvasMessage": "Wenn das Neuladen nicht funktioniert, versuche <button>die Zeichenfläche zu löschen.</button>",
|
||||
"clearCanvasCaveat": " Dies wird zum Verlust von Daten führen ",
|
||||
"trackedToSentry_pre": "Der Fehler mit der Kennung ",
|
||||
"trackedToSentry_post": " wurde in unserem System registriert.",
|
||||
"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).",
|
||||
"trackedToSentry": "Der Fehler mit der Kennung {{eventId}} wurde in unserem System registriert.",
|
||||
"openIssueMessage": "Wir waren sehr vorsichtig und haben deine Zeichnungsinformationen nicht in die Fehlerinformationen aufgenommen. Wenn deine Zeichnung nicht privat ist, unterstütze uns bitte über unseren <button>Bug-Tracker.</button> Bitte teile die unten stehenden Informationen mit uns im GitHub Issue (Kopieren und Einfügen).",
|
||||
"sceneContent": "Zeichnungsinhalt:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Erforderlich",
|
||||
"website": "Gültige URL eingeben"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Sende deine Bibliothek ein, um in die ",
|
||||
"link": "öffentliche Bibliotheks-Repository aufgenommen zu werden",
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"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": {
|
||||
"title": "Bibliothek übermittelt",
|
||||
"content": "Vielen Dank {{authorName}}. Deine Bibliothek wurde zur Überprüfung eingereicht. Du kannst den Status verfolgen",
|
||||
"link": "hier"
|
||||
"content": "Vielen Dank {{authorName}}. Deine Bibliothek wurde zur Überprüfung eingereicht. Du kannst den Status verfolgen<link>hier</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "Bibliothek zurücksetzen",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή. Αν το προβλήματα παραμείνει, θα πρέπει να αποθηκεύσετε το αρχείο σας τοπικά για να βεβαιωθείτε ότι δεν χάνετε την εργασία σας.",
|
||||
"collabSaveFailed_sizeExceeded": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή, ο καμβάς φαίνεται να είναι πολύ μεγάλος. Θα πρέπει να αποθηκεύσετε το αρχείο τοπικά για να βεβαιωθείτε ότι δεν θα χάσετε την εργασία σας.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Φαίνεται ότι χρησιμοποιείτε το Brave browser με το",
|
||||
"aggressive_block_fingerprint": "Αποκλεισμός \"Δακτυλικών Αποτυπωμάτων\"",
|
||||
"setting_enabled": "ρύθμιση ενεργοποιημένη",
|
||||
"break": "Αυτό θα μπορούσε να σπάσει το",
|
||||
"text_elements": "Στοιχεία Κειμένου",
|
||||
"in_your_drawings": "στα σχέδιά σας",
|
||||
"strongly_recommend": "Συνιστούμε να απενεργοποιήσετε αυτή τη ρύθμιση. Μπορείτε να ακολουθήσετε",
|
||||
"steps": "αυτά τα βήματα",
|
||||
"how": "για το πώς να το κάνετε",
|
||||
"disable_setting": " Εάν η απενεργοποίηση αυτής της ρύθμισης δεν διορθώνει την εμφάνιση των στοιχείων κειμένου, παρακαλώ ανοίξτε ένα",
|
||||
"issue": "πρόβλημα",
|
||||
"write": "στο GitHub, ή γράψτε μας στο",
|
||||
"discord": "Discord"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Συμβουλή: προσπαθήστε να μετακινήσετε τα πιο απομακρυσμένα στοιχεία λίγο πιο κοντά μαζί."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Συνέβη κάποιο σφάλμα. Προσπάθησε ",
|
||||
"headingMain_button": "φόρτωσε ξανά την σελίδα.",
|
||||
"clearCanvasMessage": "Εάν το παραπάνω δεν δουλέψει, προσπάθησε ",
|
||||
"clearCanvasMessage_button": "καθαρίσετε τον κανβά.",
|
||||
"headingMain": "Συνέβη κάποιο σφάλμα. Προσπάθησε <button>φόρτωσε ξανά την σελίδα.</button>",
|
||||
"clearCanvasMessage": "Εάν το παραπάνω δεν δουλέψει, προσπάθησε <button>καθαρίσετε τον κανβά.</button>",
|
||||
"clearCanvasCaveat": " Αυτό θα προκαλέσει απώλεια της δουλειάς σου ",
|
||||
"trackedToSentry_pre": "Το σφάλμα με αναγνωριστικό ",
|
||||
"trackedToSentry_post": " παρακολουθήθηκε στο σύστημά μας.",
|
||||
"openIssueMessage_pre": "Ήμασταν πολύ προσεκτικοί για να μην συμπεριλάβουμε τις πληροφορίες της σκηνής σου στο σφάλμα. Αν η σκηνή σου δεν είναι ιδιωτική, παρακαλώ σκέψου να ακολουθήσεις το δικό μας ",
|
||||
"openIssueMessage_button": "ανιχνευτής σφαλμάτων.",
|
||||
"openIssueMessage_post": " Παρακαλώ να συμπεριλάβετε τις παρακάτω πληροφορίες, αντιγράφοντας και επικολλώντας το ζήτημα στο GitHub.",
|
||||
"trackedToSentry": "Το σφάλμα με αναγνωριστικό {{eventId}} παρακολουθήθηκε στο σύστημά μας.",
|
||||
"openIssueMessage": "Ήμασταν πολύ προσεκτικοί για να μην συμπεριλάβουμε τις πληροφορίες της σκηνής σου στο σφάλμα. Αν η σκηνή σου δεν είναι ιδιωτική, παρακαλώ σκέψου να ακολουθήσεις το δικό μας <button>ανιχνευτής σφαλμάτων.</button> Παρακαλώ να συμπεριλάβετε τις παρακάτω πληροφορίες, αντιγράφοντας και επικολλώντας το ζήτημα στο GitHub.",
|
||||
"sceneContent": "Περιεχόμενο σκηνής:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Απαιτείται",
|
||||
"website": "Εισάγετε μια έγκυρη διεύθυνση URL"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Υποβάλετε τη βιβλιοθήκη σας για να συμπεριληφθεί στο ",
|
||||
"link": "δημόσιο αποθετήριο βιβλιοθήκης",
|
||||
"post": "ώστε να χρησιμοποιηθεί από άλλα άτομα στα σχέδιά τους."
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "Η βιβλιοθήκη πρέπει πρώτα να εγκριθεί χειροκίνητα. Παρακαλώ διαβάστε τους ",
|
||||
"link": "οδηγίες",
|
||||
"post": " πριν την υποβολή. Θα χρειαστείτε έναν λογαριασμό GitHub για την επικοινωνία και για να προβείτε σε αλλαγές εφ' όσον χρειαστεί, αλλά δεν είναι αυστηρή απαίτηση."
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "Με την υποβολή, συμφωνείτε ότι η βιβλιοθήκη θα δημοσιευθεί υπό την ",
|
||||
"link": "Άδεια MIT, ",
|
||||
"post": "που εν συντομία σημαίνει ότι ο καθένας μπορεί να τα χρησιμοποιήσει χωρίς περιορισμούς."
|
||||
},
|
||||
"noteDescription": "Υποβάλετε τη βιβλιοθήκη σας για να συμπεριληφθεί στο <link>δημόσιο αποθετήριο βιβλιοθήκης</link>ώστε να χρησιμοποιηθεί από άλλα άτομα στα σχέδιά τους.",
|
||||
"noteGuidelines": "Η βιβλιοθήκη πρέπει πρώτα να εγκριθεί χειροκίνητα. Παρακαλώ διαβάστε τους <link>οδηγίες</link> πριν την υποβολή. Θα χρειαστείτε έναν λογαριασμό GitHub για την επικοινωνία και για να προβείτε σε αλλαγές εφ' όσον χρειαστεί, αλλά δεν είναι αυστηρή απαίτηση.",
|
||||
"noteLicense": "Με την υποβολή, συμφωνείτε ότι η βιβλιοθήκη θα δημοσιευθεί υπό την <link>Άδεια MIT, </link>που εν συντομία σημαίνει ότι ο καθένας μπορεί να τα χρησιμοποιήσει χωρίς περιορισμούς.",
|
||||
"noteItems": "Κάθε αντικείμενο της βιβλιοθήκης πρέπει να έχει το δικό του όνομα ώστε να μπορεί να φιλτραριστεί. Θα συμπεριληφθούν τα ακόλουθα αντικείμενα βιβλιοθήκης:",
|
||||
"atleastOneLibItem": "Παρακαλώ επιλέξτε τουλάχιστον ένα αντικείμενο βιβλιοθήκης για να ξεκινήσετε",
|
||||
"republishWarning": "Σημείωση: μερικά από τα επιλεγμένα αντικέιμενα έχουν ήδη επισημανθεί ως δημοσιευμένα/υποβεβλημένα. Θα πρέπει να υποβάλετε αντικείμενα εκ νέου μόνο για να ενημερώσετε μία ήδη υπάρχουσα βιβλιοθήκη ή υποβολή."
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "Η βιβλιοθήκη υποβλήθηκε",
|
||||
"content": "Ευχαριστούμε {{authorName}}. Η βιβλιοθήκη σας έχει υποβληθεί για αξιολόγηση. Μπορείτε να παρακολουθείτε τη διαδικασία",
|
||||
"link": "εδώ"
|
||||
"content": "Ευχαριστούμε {{authorName}}. Η βιβλιοθήκη σας έχει υποβληθεί για αξιολόγηση. Μπορείτε να παρακολουθείτε τη διαδικασία<link>εδώ</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Looks like you are using Brave browser with the",
|
||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
||||
"setting_enabled": "setting enabled",
|
||||
"break": "This could result in breaking the",
|
||||
"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"
|
||||
"line1": "Looks like you are using Brave browser with the <bold>Aggressively Block Fingerprinting</bold> setting enabled.",
|
||||
"line2": "This could result in breaking the <bold>Text Elements</bold> in your drawings.",
|
||||
"line3": "We strongly recommend disabling this setting. You can follow <link>these steps</link> on how to do so.",
|
||||
"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>"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Tip: try moving the farthest elements a bit closer together."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Encountered an error. Try ",
|
||||
"headingMain_button": "reloading the page.",
|
||||
"clearCanvasMessage": "If reloading doesn't work, try ",
|
||||
"clearCanvasMessage_button": "clearing the canvas.",
|
||||
"headingMain": "Encountered an error. Try <button>reloading the page</button>.",
|
||||
"clearCanvasMessage": "If reloading doesn't work, try <button>clearing the canvas</button>.",
|
||||
"clearCanvasCaveat": " This will result in loss of work ",
|
||||
"trackedToSentry_pre": "The error with identifier ",
|
||||
"trackedToSentry_post": " was tracked on our system.",
|
||||
"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.",
|
||||
"trackedToSentry": "The error with identifier {{eventId}} 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.",
|
||||
"sceneContent": "Scene content:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Required",
|
||||
"website": "Enter a valid URL"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Submit your library to be included in the ",
|
||||
"link": "public library repository",
|
||||
"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."
|
||||
},
|
||||
"noteDescription": "Submit your library to be included in the <link>public library repository</link> for other people to use in their drawings.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"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": {
|
||||
"title": "Library submitted",
|
||||
"content": "Thank you {{authorName}}. Your library has been submitted for review. You can track the status",
|
||||
"link": "here"
|
||||
"content": "Thank you {{authorName}}. Your library has been submitted for review. You can track the status <link>here</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Parece que estás usando el navegador Brave",
|
||||
"aggressive_block_fingerprint": "Bloquear huellas dactilares agresivamente",
|
||||
"setting_enabled": "ajuste activado",
|
||||
"break": "Esto podría resultar en romper los",
|
||||
"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"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Sugerencia: intenta acercar un poco más los elementos más lejanos."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Se encontró un error. Intente ",
|
||||
"headingMain_button": "recargando la página.",
|
||||
"clearCanvasMessage": "Si la recarga no funciona, intente ",
|
||||
"clearCanvasMessage_button": "limpiando el lienzo.",
|
||||
"headingMain": "Se encontró un error. Intente <button>recargando la página.</button>",
|
||||
"clearCanvasMessage": "Si la recarga no funciona, intente <button>limpiando el lienzo.</button>",
|
||||
"clearCanvasCaveat": " Esto provocará la pérdida de su trabajo ",
|
||||
"trackedToSentry_pre": "El error con el identificador ",
|
||||
"trackedToSentry_post": " fue rastreado en nuestro sistema.",
|
||||
"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.",
|
||||
"trackedToSentry": "El error con el identificador {{eventId}} 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.",
|
||||
"sceneContent": "Contenido de la escena:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Requerido",
|
||||
"website": "Introduce una URL válida"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Envía tu biblioteca para ser incluida en el ",
|
||||
"link": "repositorio de librería pública",
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"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": {
|
||||
"title": "Biblioteca enviada",
|
||||
"content": "Gracias {{authorName}}. Su biblioteca ha sido enviada para ser revisada. Puede seguir el estado",
|
||||
"link": "aquí"
|
||||
"content": "Gracias {{authorName}}. Su biblioteca ha sido enviada para ser revisada. Puede seguir el estado<link>aquí</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Brave nabigatzailea erabiltzen ari zarela dirudi",
|
||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
||||
"setting_enabled": "ezarpena gaituta",
|
||||
"break": "Honek honen haustea eragin dezake",
|
||||
"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"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Aholkua: saiatu urrunen dauden elementuak pixka bat hurbiltzen."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Errore bat aurkitu da. Saiatu ",
|
||||
"headingMain_button": "orria birkargatzen.",
|
||||
"clearCanvasMessage": "Birkargatzea ez bada burutzen, saiatu ",
|
||||
"clearCanvasMessage_button": "oihala garbitzen.",
|
||||
"headingMain": "Errore bat aurkitu da. Saiatu <button>orria birkargatzen.</button>",
|
||||
"clearCanvasMessage": "Birkargatzea ez bada burutzen, saiatu <button>oihala garbitzen.</button>",
|
||||
"clearCanvasCaveat": " Honen ondorioz lana galduko da ",
|
||||
"trackedToSentry_pre": "Identifikatzailearen errorea ",
|
||||
"trackedToSentry_post": " gure sistemak behatu du.",
|
||||
"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.",
|
||||
"trackedToSentry": "Identifikatzailearen errorea {{eventId}} 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.",
|
||||
"sceneContent": "Eszenaren edukia:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Beharrezkoa",
|
||||
"website": "Sartu baliozko URL bat"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Bidali zure liburutegira sartu ahal izateko ",
|
||||
"link": "zure liburutegiko biltegian",
|
||||
"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."
|
||||
},
|
||||
"noteDescription": "Bidali zure liburutegira sartu ahal izateko <link>zure liburutegiko biltegian</link>beste jendeak bere marrazkietan erabili 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.",
|
||||
"noteLicense": "Bidaltzen baduzu, onartzen duzu liburutegia <link>MIT lizentziarekin argitaratuko dela, </link>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:",
|
||||
"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."
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "Liburutegia bidali da",
|
||||
"content": "Eskerrik asko {{authorName}}. Zure liburutegia bidali da berrikustera. Jarraitu dezakezu haren egoera",
|
||||
"link": "hemen"
|
||||
"content": "Eskerrik asko {{authorName}}. Zure liburutegia bidali da berrikustera. Jarraitu dezakezu haren egoera<link>hemen</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "Leheneratu liburutegia",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"labels": {
|
||||
"paste": "جای گذاری",
|
||||
"pasteAsPlaintext": "",
|
||||
"pasteAsPlaintext": "جایگذاری به عنوان متن ساده",
|
||||
"pasteCharts": "قراردادن نمودارها",
|
||||
"selectAll": "انتخاب همه",
|
||||
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید.",
|
||||
|
@ -54,7 +54,7 @@
|
|||
"veryLarge": "بسیار بزرگ",
|
||||
"solid": "توپر",
|
||||
"hachure": "هاشور",
|
||||
"zigzag": "",
|
||||
"zigzag": "زیگزاگ",
|
||||
"crossHatch": "هاشور متقاطع",
|
||||
"thin": "نازک",
|
||||
"bold": "ضخیم",
|
||||
|
@ -111,7 +111,7 @@
|
|||
"increaseFontSize": "افزایش دادن اندازه فونت",
|
||||
"unbindText": "بازکردن نوشته",
|
||||
"bindText": "بستن نوشته",
|
||||
"createContainerFromText": "",
|
||||
"createContainerFromText": "متن را در یک جایگاه بپیچید",
|
||||
"link": {
|
||||
"edit": "ویرایش لینک",
|
||||
"create": "ایجاد پیوند",
|
||||
|
@ -195,7 +195,7 @@
|
|||
"resetLibrary": "ین کار کل صفحه را پاک میکند. آیا مطمئنید?",
|
||||
"removeItemsFromsLibrary": "حذف {{count}} آیتم(ها) از کتابخانه?",
|
||||
"invalidEncryptionKey": "کلید رمزگذاری باید 22 کاراکتر باشد. همکاری زنده غیرفعال است.",
|
||||
"collabOfflineWarning": ""
|
||||
"collabOfflineWarning": "اتصال به اینترنت در دسترس نیست.\nتغییرات شما ذخیره نمی شود!"
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "نوع فایل پشتیبانی نشده.",
|
||||
|
@ -205,22 +205,13 @@
|
|||
"invalidSVGString": "SVG نادرست.",
|
||||
"cannotResolveCollabServer": "به سرور collab متصل نشد. لطفا صفحه را مجددا بارگذاری کنید و دوباره تلاش کنید.",
|
||||
"importLibraryError": "دادهها بارگذاری نشدند",
|
||||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"collabSaveFailed": "در پایگاه داده باطن ذخیره نشد. اگر مشکلات همچنان ادامه داشت، باید فایل خود را به صورت محلی ذخیره کنید تا مطمئن شوید کار خود را از دست نمی دهید.",
|
||||
"collabSaveFailed_sizeExceeded": "در پایگاه داده بکند ذخیره نشد. اگر مشکلات همچنان ادامه داشت، باید فایل خود را به صورت محلی ذخیره کنید تا مطمئن شوید کار خود را از دست نمی دهید.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "به نظر میرسد از مرورگر Brave با تنظیم <bold> مسدود کردن شدید اثرانگشت</bold> استفاده میکنید.",
|
||||
"line2": "این می تواند منجر به شکستن <bold>عناصر متن</bold> در نقاشی های شما شود.",
|
||||
"line3": "اکیداً توصیه می کنیم این تنظیم را غیرفعال کنید. برای نحوه انجام این کار میتوانید <link>این مراحل</link> را دنبال کنید.",
|
||||
"line4": "اگر غیرفعال کردن این تنظیم نمایش عناصر متنی را برطرف نکرد، لطفاً یک <issueLink>مشکل</issueLink> را در GitHub ما باز کنید یا برای ما در <discordLink>Discord</discordLink> بنویسید."
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -238,7 +229,7 @@
|
|||
"penMode": "حالت قلم - جلوگیری از تماس",
|
||||
"link": "افزودن/بهروزرسانی پیوند برای شکل انتخابی",
|
||||
"eraser": "پاک کن",
|
||||
"hand": ""
|
||||
"hand": "دست (ابزار پانینگ)"
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "عملیات روی بوم",
|
||||
|
@ -246,7 +237,7 @@
|
|||
"shapes": "شکلها"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "",
|
||||
"canvasPanning": "برای حرکت دادن بوم، چرخ ماوس یا فاصله را در حین کشیدن نگه دارید یا از ابزار دستی استفاده کنید",
|
||||
"linearElement": "برای چند نقطه کلیک و برای یک خط بکشید",
|
||||
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
|
||||
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
|
||||
|
@ -257,7 +248,7 @@
|
|||
"resize": "می توانید با نگه داشتن SHIFT در هنگام تغییر اندازه، نسبت ها را محدود کنید،ALT را برای تغییر اندازه از مرکز نگه دارید",
|
||||
"resizeImage": "با نگه داشتن SHIFT می توانید آزادانه اندازه را تغییر دهید،\nبرای تغییر اندازه از مرکز، ALT را نگه دارید",
|
||||
"rotate": "با نگه داشتن SHIFT هنگام چرخش می توانید زاویه ها را محدود کنید",
|
||||
"lineEditor_info": "",
|
||||
"lineEditor_info": "CtrlOrCmd را نگه دارید و دوبار کلیک کنید یا CtrlOrCmd + Enter را فشار دهید تا نقاط را ویرایش کنید.",
|
||||
"lineEditor_pointSelected": "برای حذف نقطه Delete برای کپی زدن Ctrl یا Cmd+D را بزنید و یا برای جابجایی بکشید",
|
||||
"lineEditor_nothingSelected": "یک نقطه را برای ویرایش انتخاب کنید (SHIFT را برای انتخاب چندگانه نگه دارید)،\nیا Alt را نگه دارید و برای افزودن نقاط جدید کلیک کنید",
|
||||
"placeImage": "برای قرار دادن تصویر کلیک کنید، یا کلیک کنید و بکشید تا اندازه آن به صورت دستی تنظیم شود",
|
||||
|
@ -265,7 +256,7 @@
|
|||
"bindTextToElement": "برای افزودن اینتر را بزنید",
|
||||
"deepBoxSelect": "CtrlOrCmd را برای انتخاب عمیق و جلوگیری از کشیدن نگه دارید",
|
||||
"eraserRevert": "Alt را نگه دارید تا عناصر علامت گذاری شده برای حذف برگردند",
|
||||
"firefox_clipboard_write": ""
|
||||
"firefox_clipboard_write": "احتمالاً میتوان این ویژگی را با تنظیم پرچم «dom.events.asyncClipboard.clipboardItem» روی «true» فعال کرد. برای تغییر پرچم های مرورگر در فایرفاکس، از صفحه \"about:config\" دیدن کنید."
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "پیش نمایش نشان داده نمی شود",
|
||||
|
@ -320,8 +311,8 @@
|
|||
"doubleClick": "دابل کلیک",
|
||||
"drag": "کشیدن",
|
||||
"editor": "ویرایشگر",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "نقاط خط/پیکان را ویرایش کنید",
|
||||
"editText": "ویرایش متن / افزودن برچسب",
|
||||
"github": "اشکالی می بینید؟ گزارش دهید",
|
||||
"howto": "راهنمای ما را دنبال کنید",
|
||||
"or": "یا",
|
||||
|
@ -335,8 +326,8 @@
|
|||
"zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
|
||||
"zoomToSelection": "بزرگنمایی قسمت انتخاب شده",
|
||||
"toggleElementLock": "قفل/بازکردن انتخاب شده ها",
|
||||
"movePageUpDown": "",
|
||||
"movePageLeftRight": ""
|
||||
"movePageUpDown": "حرکت صفحه به بالا/پایین",
|
||||
"movePageLeftRight": "حرکت صفحه به چپ/راست"
|
||||
},
|
||||
"clearCanvasDialog": {
|
||||
"title": "پاک کردن بوم"
|
||||
|
@ -418,7 +409,7 @@
|
|||
"fileSavedToFilename": "ذخیره در {filename}",
|
||||
"canvas": "بوم",
|
||||
"selection": "انتخاب",
|
||||
"pasteAsSingleElement": ""
|
||||
"pasteAsSingleElement": "از {{shortcut}} برای چسباندن به عنوان یک عنصر استفاده کنید،\nیا در یک ویرایشگر متن موجود جایگذاری کنید"
|
||||
},
|
||||
"colors": {
|
||||
"ffffff": "سفید",
|
||||
|
@ -469,15 +460,15 @@
|
|||
},
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
"center_heading": "",
|
||||
"center_heading_plus": "",
|
||||
"menuHint": ""
|
||||
"center_heading": "تمام داده های شما به صورت محلی در مرورگر شما ذخیره می شود.",
|
||||
"center_heading_plus": "آیا میخواهید به جای آن به Excalidraw+ بروید؟",
|
||||
"menuHint": "خروجی، ترجیحات، زبان ها، ..."
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
"menuHint": "خروجی، ترجیحات، وبیشتر ...",
|
||||
"center_heading": "نمودارها .ساخته شده. ساده.",
|
||||
"toolbarHint": "ابزاری را انتخاب کنید و نقاشی را شروع کنید!",
|
||||
"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_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": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Vinkki: yritä siirtää kaukaisimpia elementtejä hieman lähemmäs toisiaan."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Tapahtui virhe. Yritä ",
|
||||
"headingMain_button": "sivun lataamista uudelleen.",
|
||||
"clearCanvasMessage": "Mikäli sivun lataaminen uudelleen ei auta, yritä ",
|
||||
"clearCanvasMessage_button": "tyhjentää piirtoalue.",
|
||||
"headingMain": "Tapahtui virhe. Yritä <button>sivun lataamista uudelleen.</button>",
|
||||
"clearCanvasMessage": "Mikäli sivun lataaminen uudelleen ei auta, yritä <button>tyhjentää piirtoalue.</button>",
|
||||
"clearCanvasCaveat": " Tämä johtaa työn menetykseen ",
|
||||
"trackedToSentry_pre": "Virhe tunnisteella ",
|
||||
"trackedToSentry_post": " tallennettiin järjestelmäämme.",
|
||||
"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.",
|
||||
"trackedToSentry": "Virhe tunnisteella {{eventId}} 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.",
|
||||
"sceneContent": "Piirroksen tiedot:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Pakollinen",
|
||||
"website": "Syötä oikeamuotoinen URL-osoite"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Lähetä kirjastosi, jotta se voidaan sisällyttää ",
|
||||
"link": "julkisessa kirjastolistauksessa",
|
||||
"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."
|
||||
},
|
||||
"noteDescription": "Lähetä kirjastosi, jotta se voidaan sisällyttää <link>julkisessa kirjastolistauksessa</link>muiden käyttöön omissa piirrustuksissaan.",
|
||||
"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ä.",
|
||||
"noteLicense": "Lähettämällä hyväksyt että kirjasto julkaistaan <link>MIT-lisenssin </link>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:",
|
||||
"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."
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "Kirjasto lähetetty",
|
||||
"content": "Kiitos {{authorName}}. Kirjastosi on lähetetty tarkistettavaksi. Voit seurata sen tilaa",
|
||||
"link": "täällä"
|
||||
"content": "Kiitos {{authorName}}. Kirjastosi on lähetetty tarkistettavaksi. Voit seurata sen tilaa<link>täällä</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Il semble que vous utilisiez le navigateur Brave avec le",
|
||||
"aggressive_block_fingerprint": "blocage d'empreinte agressif",
|
||||
"setting_enabled": "activé",
|
||||
"break": "Ceci pourrait avoir pour conséquence de « casser » les",
|
||||
"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"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Astuce : essayez de rapprocher un peu les éléments les plus éloignés."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Une erreur est survenue. Essayez ",
|
||||
"headingMain_button": "de recharger la page.",
|
||||
"clearCanvasMessage": "Si le rechargement ne résout pas l'erreur, essayez ",
|
||||
"clearCanvasMessage_button": "effacement du canevas.",
|
||||
"headingMain": "Une erreur est survenue. Essayez <button>de recharger la page.</button>",
|
||||
"clearCanvasMessage": "Si le rechargement ne résout pas l'erreur, essayez <button>effacement du canevas.</button>",
|
||||
"clearCanvasCaveat": " Cela entraînera une perte du travail ",
|
||||
"trackedToSentry_pre": "L'erreur avec l'identifiant ",
|
||||
"trackedToSentry_post": " a été enregistrée dans notre système.",
|
||||
"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.",
|
||||
"trackedToSentry": "L'erreur avec l'identifiant {{eventId}} 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.",
|
||||
"sceneContent": "Contenu de la scène :"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Requis",
|
||||
"website": "Entrer une URL valide"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Soumets ta bibliothèque pour l'inclure au ",
|
||||
"link": "dépôt de bibliothèque publique",
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"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": {
|
||||
"title": "Bibliothèque soumise",
|
||||
"content": "Merci {{authorName}}. Votre bibliothèque a été soumise pour examen. Vous pouvez suivre le statut",
|
||||
"link": "ici"
|
||||
"content": "Merci {{authorName}}. Votre bibliothèque a été soumise pour examen. Vous pouvez suivre le statut<link>ici</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Semella que estás usando o navegador Brave coa opción",
|
||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
||||
"setting_enabled": "activada",
|
||||
"break": "Isto podería provocar unha ruptura dos",
|
||||
"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"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Consello: Probe a acercar un pouco os elementos máis afastados."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Atopouse un erro. Probe ",
|
||||
"headingMain_button": "recargando a páxina.",
|
||||
"clearCanvasMessage": "Se recargar non funcionou, probe ",
|
||||
"clearCanvasMessage_button": "limpando o lenzo.",
|
||||
"headingMain": "Atopouse un erro. Probe <button>recargando a páxina.</button>",
|
||||
"clearCanvasMessage": "Se recargar non funcionou, probe <button>limpando o lenzo.</button>",
|
||||
"clearCanvasCaveat": " Isto resultará nunha perda do seu traballo ",
|
||||
"trackedToSentry_pre": "O erro con identificador ",
|
||||
"trackedToSentry_post": " foi rastrexado no noso sistema.",
|
||||
"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.",
|
||||
"trackedToSentry": "O erro con identificador {{eventId}} 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.",
|
||||
"sceneContent": "Contido da escena:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Obrigatorio",
|
||||
"website": "Introduza unha URL válida"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Envíe a súa biblioteca para que sexa incluída no ",
|
||||
"link": "repositorio público de bibliotecas",
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"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": {
|
||||
"title": "Biblioteca enviada",
|
||||
"content": "Grazas {{authorName}}. A súa biblioteca foi enviada para ser revisada. Pode seguir o estado",
|
||||
"link": "aquí"
|
||||
"content": "Grazas {{authorName}}. A súa biblioteca foi enviada para ser revisada. Pode seguir o estado<link>aquí</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "Restablecer biblioteca",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "לא הצלחתי להתחבר למסד הנתונים האחורי. אם הבעיה ממשיכה, כדאי שתשמור את הקובץ מקומית כדי לוודא שלא תאבד את העבודה שלך.",
|
||||
"collabSaveFailed_sizeExceeded": "לא הצלחתי לשמור למסד הנתונים האחורי, נראה שהקנבס שלך גדול מדי. כדאי שתשמור את הקובץ מקומית כדי לוודא שלא תאבד את העבודה שלך.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "נראה שאתה משתמש בדפדפן Brave עם ה-",
|
||||
"aggressive_block_fingerprint": "חסימת Fingerprinting אגרסיבית",
|
||||
"setting_enabled": "ההגדרה מופעלת",
|
||||
"break": "זה יכול לגרום לבעיה ב-",
|
||||
"text_elements": "רכיבי טקסט",
|
||||
"in_your_drawings": "בציורים שלך",
|
||||
"strongly_recommend": "אנו ממליצים בחום לנטרל את ההגדרה הזו. אתה יכול לעקוב",
|
||||
"steps": "הצעדים הבאים",
|
||||
"how": "כיצד לעשות את זה",
|
||||
"disable_setting": " אם נטרול ההגדרה לא מתקן את תצוגת רכיבי הטקסט, אנא פתח",
|
||||
"issue": "בעיה",
|
||||
"write": "ב- GitHub שלנו, או כתוב לנו ב-",
|
||||
"discord": "Discord"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "किसी कारण वश अंदरूनी डेटाबेस में सहेजा नहीं जा सका। यदि समस्या बनी रहती है, तो किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।",
|
||||
"collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।",
|
||||
"brave_measure_text_error": {
|
||||
"start": "ऐसा लगता है कि आप ब्रेव ब्राउज़र का उपयोग कर रहे है, उसके साथ",
|
||||
"aggressive_block_fingerprint": "उग्रतापूर्वक उंगलियों के निशान रोकने की",
|
||||
"setting_enabled": "सेटिंग सक्रिय की गई है।",
|
||||
"break": "इसके परिणाम स्वरूम टूट सकता है यह",
|
||||
"text_elements": "मूल पाठ अवयव टूट सकते हैं",
|
||||
"in_your_drawings": "आपके चित्र में उपस्थित",
|
||||
"strongly_recommend": "हमारा शशक्त अनुरोध इस सेटिंग्स को अक्षम करने का है. आप",
|
||||
"steps": "इन दिए हुए स्टेप्स को अनुकरीत करके देख सकते है कि",
|
||||
"how": "ये कैसे करे",
|
||||
"disable_setting": " यदि इन सेटिंग्स को निष्क्रिय करने पर भी, पाठ्य दिखना ठीक न हो तो",
|
||||
"issue": "समस्या",
|
||||
"write": "हमारे \"GitHub\" पर, अथवा",
|
||||
"discord": "\"Discord\" पर लिख सकते हैं."
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "कैनवास बहुत बड़ा टिप"
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "एक त्रुटि का सामना करना पड़ा। प्रयत्न ",
|
||||
"headingMain_button": "इस पृष्ठ को पुनः लोड करें",
|
||||
"clearCanvasMessage": "यदि पुनः लोड करना काम नहीं करता है, तो प्रयास करें ",
|
||||
"clearCanvasMessage_button": "कैनवास साफ करना।",
|
||||
"headingMain": "एक त्रुटि का सामना करना पड़ा। प्रयत्न <button>इस पृष्ठ को पुनः लोड करें</button>",
|
||||
"clearCanvasMessage": "यदि पुनः लोड करना काम नहीं करता है, तो प्रयास करें <button>कैनवास साफ करना।</button>",
|
||||
"clearCanvasCaveat": " इससे काम का नुकसान होगा ",
|
||||
"trackedToSentry_pre": "पहचानकर्ता के साथ त्रुटि ",
|
||||
"trackedToSentry_post": " हमारे सिस्टम पर नज़र रखी गई थी।",
|
||||
"openIssueMessage_pre": "हम बहुत सतर्क थे कि त्रुटि पर आपकी दृश्य जानकारी शामिल न करें। यदि आपका दृश्य निजी नहीं है, तो कृपया हमारे बारे में विचार करें ",
|
||||
"openIssueMessage_button": "बग ट्रैकर",
|
||||
"openIssueMessage_post": " कृपया GitHub मुद्दे को कॉपी और पेस्ट करके नीचे दी गई जानकारी शामिल करें।",
|
||||
"trackedToSentry": "पहचानकर्ता के साथ त्रुटि {{eventId}} हमारे सिस्टम पर नज़र रखी गई थी।",
|
||||
"openIssueMessage": "हम बहुत सतर्क थे कि त्रुटि पर आपकी दृश्य जानकारी शामिल न करें। यदि आपका दृश्य निजी नहीं है, तो कृपया हमारे बारे में विचार करें <button>बग ट्रैकर</button> कृपया GitHub मुद्दे को कॉपी और पेस्ट करके नीचे दी गई जानकारी शामिल करें।",
|
||||
"sceneContent": "दृश्य सामग्री:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "",
|
||||
"website": "मान्य URL प्रविष्ट करें"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "संग्रह सम्मिलित करने हेतु प्रस्तुत करें ",
|
||||
"link": "सार्वजनिक संग्रहालय",
|
||||
"post": "अन्य वक्तियों को उनके चित्रकारी में उपयोग के लिये"
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "संग्रह को पहले स्वीकृति आवश्यक कृपया यह पढ़ें ",
|
||||
"link": "दिशा-निर्देश",
|
||||
"post": ""
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteDescription": "संग्रह सम्मिलित करने हेतु प्रस्तुत करें <link>सार्वजनिक संग्रहालय</link>अन्य वक्तियों को उनके चित्रकारी में उपयोग के लिये",
|
||||
"noteGuidelines": "संग्रह को पहले स्वीकृति आवश्यक कृपया यह पढ़ें <link>दिशा-निर्देश</link>",
|
||||
"noteLicense": "",
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": "टिप्पणी: कुछ चुने हुवे आइटम पहले ही प्रकाशित/प्रस्तुत किए जा चुके हैं। किसी प्रकाशित संग्रह को अद्यतन करते समय या पहले से प्रस्तुत आइटम को पुन्हा प्रस्तुत करते समय, आप बस उसे केवल अद्यतन करें ।"
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "",
|
||||
"content": "",
|
||||
"link": ""
|
||||
"content": ""
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Tipp: próbáld meg a legtávolabbi elemeket közelebb hozni egy máshoz."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Hiba történt. Próbáld ",
|
||||
"headingMain_button": "újratölteni az oldalt.",
|
||||
"clearCanvasMessage": "Ha az újratöltés nem működik, próbáld ",
|
||||
"clearCanvasMessage_button": "letörölni a vászont.",
|
||||
"headingMain": "Hiba történt. Próbáld <button>újratölteni az oldalt.</button>",
|
||||
"clearCanvasMessage": "Ha az újratöltés nem működik, próbáld <button>letörölni a vászont.</button>",
|
||||
"clearCanvasCaveat": " Ezzel az eddigi munka elveszik ",
|
||||
"trackedToSentry_pre": "A hibakód azonosítóval ",
|
||||
"trackedToSentry_post": " nyomon van követve a rendszerünkben.",
|
||||
"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.",
|
||||
"trackedToSentry": "A hibakód azonosítóval {{eventId}} 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.",
|
||||
"sceneContent": "Jelenet tartalma:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Kötelező",
|
||||
"website": "Adj meg egy érvényes URL-t"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Küld be könyvtáradat, hogy bekerüljön a ",
|
||||
"link": "nyilvános könyvtár tárolóba",
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"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",
|
||||
"link": "itt"
|
||||
"content": "Köszönjük {{authorName}}. Könyvtáradat elküldtük felülvizsgálatra. Nyomon követheted az állapotot<link>itt</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Sepertinya kamu menggunakan browser Brave dengan",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "pengaturan diaktifkan",
|
||||
"break": "",
|
||||
"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"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Tip: coba pindahkan elemen-terjauh lebih dekat bersama."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Mengalami sebuah kesalahan. Cobalah ",
|
||||
"headingMain_button": "muat ulang halaman.",
|
||||
"clearCanvasMessage": "Jika memuat ulang tidak bekerja, cobalah ",
|
||||
"clearCanvasMessage_button": "bersihkan canvas.",
|
||||
"headingMain": "Mengalami sebuah kesalahan. Cobalah <button>muat ulang halaman.</button>",
|
||||
"clearCanvasMessage": "Jika memuat ulang tidak bekerja, cobalah <button>bersihkan canvas.</button>",
|
||||
"clearCanvasCaveat": " Ini akan menghasilkan hilangnya pekerjaan ",
|
||||
"trackedToSentry_pre": "Kesalahan dengan pengidentifikasi ",
|
||||
"trackedToSentry_post": " dilacak di sistem kami.",
|
||||
"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.",
|
||||
"trackedToSentry": "Kesalahan dengan pengidentifikasi {{eventId}} 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.",
|
||||
"sceneContent": "Pemandangan konten:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Dibutuhkan",
|
||||
"website": "Masukkan URL valid"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Kirimkan pustaka Anda untuk disertakan di ",
|
||||
"link": "repositori pustaka publik",
|
||||
"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."
|
||||
},
|
||||
"noteDescription": "Kirimkan pustaka Anda untuk disertakan di <link>repositori pustaka publik</link>untuk orang lain menggunakannya dalam gambar mereka.",
|
||||
"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.",
|
||||
"noteLicense": "Dengan mengkirimkannya, Anda setuju pustaka akan diterbitkan dibawah <link>Lisensi MIT, </link>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:",
|
||||
"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."
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "Pustaka telah dikirm",
|
||||
"content": "Terima kasih {{authorName}}. pustaka Anda telah diserahkan untuk ditinjau ulang. Anda dapat cek statusnya",
|
||||
"link": "di sini"
|
||||
"content": "Terima kasih {{authorName}}. pustaka Anda telah diserahkan untuk ditinjau ulang. Anda dapat cek statusnya<link>di sini</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Sembra che tu stia usando il browser Brave con il",
|
||||
"aggressive_block_fingerprint": "Blocco Aggressivamente Impronte Digitali",
|
||||
"setting_enabled": "impostazioni abilitate",
|
||||
"break": "Questo potrebbe portare a rompere gli",
|
||||
"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"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Suggerimento: prova a spostare gli elementi più lontani più vicini tra loro."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Si è verificato un errore. Provare ",
|
||||
"headingMain_button": "ricaricando la pagina.",
|
||||
"clearCanvasMessage": "Se ricaricare non funziona, prova ",
|
||||
"clearCanvasMessage_button": "pulire la tela.",
|
||||
"headingMain": "Si è verificato un errore. Provare <button>ricaricando la pagina.</button>",
|
||||
"clearCanvasMessage": "Se ricaricare non funziona, prova <button>pulire la tela.</button>",
|
||||
"clearCanvasCaveat": " Questo risulterà nella perdita del lavoro ",
|
||||
"trackedToSentry_pre": "L'errore con identificativo ",
|
||||
"trackedToSentry_post": " è stato tracciato nel nostro sistema.",
|
||||
"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.",
|
||||
"trackedToSentry": "L'errore con identificativo {{eventId}} è 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.",
|
||||
"sceneContent": "Contenuto della scena:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Obbligatorio",
|
||||
"website": "Inserisci un URL valido"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Invia la tua libreria da includere nella ",
|
||||
"link": "repository della libreria pubblica",
|
||||
"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."
|
||||
},
|
||||
"noteDescription": "Invia la tua libreria da includere nella <link>repository della libreria pubblica</link>perché sia usata da altri nei loro disegni.",
|
||||
"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.",
|
||||
"noteLicense": "Inviando, acconsenti che la libreria sarà pubblicata sotto la <link>Licenza MIT, </link>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:",
|
||||
"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."
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "Libreria inviata",
|
||||
"content": "Grazie {{authorName}}. La tua libreria è stata inviata per la revisione. Puoi monitorarne lo stato",
|
||||
"link": "qui"
|
||||
"content": "Grazie {{authorName}}. La tua libreria è stata inviata per la revisione. Puoi monitorarne lo stato<link>qui</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "Ripristina la libreria",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
|
||||
"collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "設定が有効化されました",
|
||||
"break": "",
|
||||
"text_elements": "テキスト要素",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": "Discord"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "ヒント: 最も遠い要素をもう少し近づけてみてください。"
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "エラーが発生しました。もう一度やり直してください。 ",
|
||||
"headingMain_button": "ページを再読み込みする。",
|
||||
"clearCanvasMessage": "再読み込みがうまくいかない場合は、 ",
|
||||
"clearCanvasMessage_button": "キャンバスを消去しています",
|
||||
"headingMain": "エラーが発生しました。もう一度やり直してください。 <button>ページを再読み込みする。</button>",
|
||||
"clearCanvasMessage": "再読み込みがうまくいかない場合は、 <button>キャンバスを消去しています</button>",
|
||||
"clearCanvasCaveat": " これにより作業が失われます ",
|
||||
"trackedToSentry_pre": "識別子のエラー ",
|
||||
"trackedToSentry_post": " が我々のシステムで追跡されました。",
|
||||
"openIssueMessage_pre": "エラーに関するシーン情報を含めないように非常に慎重に設定しました。もしあなたのシーンがプライベートでない場合は、私たちのフォローアップを検討してください。 ",
|
||||
"openIssueMessage_button": "バグ報告",
|
||||
"openIssueMessage_post": " GitHub のIssueに以下の情報をコピーして貼り付けてください。",
|
||||
"trackedToSentry": "識別子のエラー {{eventId}} が我々のシステムで追跡されました。",
|
||||
"openIssueMessage": "エラーに関するシーン情報を含めないように非常に慎重に設定しました。もしあなたのシーンがプライベートでない場合は、私たちのフォローアップを検討してください。 <button>バグ報告</button> GitHub のIssueに以下の情報をコピーして貼り付けてください。",
|
||||
"sceneContent": "シーンの内容:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "必須項目",
|
||||
"website": "有効な URL を入力してください"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "以下に含めるライブラリを提出してください ",
|
||||
"link": "公開ライブラリのリポジトリ",
|
||||
"post": "他の人が作図に使えるようにするためです"
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "最初にライブラリを手動で承認する必要があります。次をお読みください ",
|
||||
"link": "ガイドライン",
|
||||
"post": " 送信する前に、GitHubアカウントが必要になりますが、必須ではありません。"
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "提出することにより、ライブラリが次の下で公開されることに同意します: ",
|
||||
"link": "MIT ライセンス ",
|
||||
"post": "つまり誰でも制限なく使えるということです"
|
||||
},
|
||||
"noteDescription": "以下に含めるライブラリを提出してください <link>公開ライブラリのリポジトリ</link>他の人が作図に使えるようにするためです",
|
||||
"noteGuidelines": "最初にライブラリを手動で承認する必要があります。次をお読みください <link>ガイドライン</link> 送信する前に、GitHubアカウントが必要になりますが、必須ではありません。",
|
||||
"noteLicense": "提出することにより、ライブラリが次の下で公開されることに同意します: <link>MIT ライセンス </link>つまり誰でも制限なく使えるということです",
|
||||
"noteItems": "各ライブラリ項目は、フィルタリングのために独自の名前を持つ必要があります。以下のライブラリアイテムが含まれます:",
|
||||
"atleastOneLibItem": "開始するには少なくとも1つのライブラリ項目を選択してください",
|
||||
"republishWarning": "注意: 選択された項目の中には、すでに公開/投稿済みと表示されているものがあります。既存のライブラリや投稿を更新する場合のみ、アイテムを再投稿してください。"
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "ライブラリを送信しました",
|
||||
"content": "{{authorName}} さん、ありがとうございます。あなたのライブラリはレビューのために提出されました。状況を追跡できます。",
|
||||
"link": "こちら"
|
||||
"content": "{{authorName}} さん、ありがとうございます。あなたのライブラリはレビューのために提出されました。状況を追跡できます。<link>こちら</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Ittban-d am wakken tsseqdaceḍ iminig Brave akked",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "yermed",
|
||||
"break": "Ayagi yezmer ad d-iseglu s truẓi n",
|
||||
"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": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Tixidest: eɛreḍ ad tesqerbeḍ ciṭ iferdisen yembaɛaden."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Teḍra-d tuccḍa. Eɛreḍ ",
|
||||
"headingMain_button": "asali n usebter tikkelt-nniḍen.",
|
||||
"clearCanvasMessage": "Ma yella tulsa n usali ur tefri ara ugur, eɛreḍ ",
|
||||
"clearCanvasMessage_button": "asfaḍ n teɣzut n usuneɣ.",
|
||||
"headingMain": "Teḍra-d tuccḍa. Eɛreḍ <button>asali n usebter tikkelt-nniḍen.</button>",
|
||||
"clearCanvasMessage": "Ma yella tulsa n usali ur tefri ara ugur, eɛreḍ <button>asfaḍ n teɣzut n usuneɣ.</button>",
|
||||
"clearCanvasCaveat": " Ayagi ad d-iglu s usṛuḥu n umahil ",
|
||||
"trackedToSentry_pre": "Tuccḍa akked umesmagi ",
|
||||
"trackedToSentry_post": " tettwasekles deg unagraw-nneɣ.",
|
||||
"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.",
|
||||
"trackedToSentry": "Tuccḍa akked umesmagi {{eventId}} 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.",
|
||||
"sceneContent": "Agbur n usayes:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Yettwasra",
|
||||
"website": "Sekcem URL ameɣtu"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Azen tamkarḍit-inek•inem akken ad teddu di ",
|
||||
"link": "akaram azayez n temkarḍit",
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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ḍ",
|
||||
"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": {
|
||||
"title": "Tamkarḍit tettwazen",
|
||||
"content": "Tanemmirt-ik•im {{authorName}}. Tamkarḍit-inek•inem tettwazen i weselken. Tzemreḍ ad tḍefreḍ aẓayer",
|
||||
"link": "dagi"
|
||||
"content": "Tanemmirt-ik•im {{authorName}}. Tamkarḍit-inek•inem tettwazen i weselken. Tzemreḍ ad tḍefreḍ aẓayer<link>dagi</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "Ales awennez n temkarḍit",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": ""
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "",
|
||||
"headingMain_button": "",
|
||||
"headingMain": "",
|
||||
"clearCanvasMessage": "",
|
||||
"clearCanvasMessage_button": "",
|
||||
"clearCanvasCaveat": "",
|
||||
"trackedToSentry_pre": "",
|
||||
"trackedToSentry_post": "",
|
||||
"openIssueMessage_pre": "",
|
||||
"openIssueMessage_button": "",
|
||||
"openIssueMessage_post": "",
|
||||
"trackedToSentry": "",
|
||||
"openIssueMessage": "",
|
||||
"sceneContent": ""
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "",
|
||||
"website": ""
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteDescription": "",
|
||||
"noteGuidelines": "",
|
||||
"noteLicense": "",
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "",
|
||||
"content": "",
|
||||
"link": ""
|
||||
"content": ""
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_sizeExceeded": "데이터베이스에 저장하지 못했습니다. 캔버스가 너무 큰 거 같습니다. 문제가 계속 된다면, 작업 내용을 잃지 않도록 로컬 저장소에 저장해 주세요.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "귀하께서는",
|
||||
"aggressive_block_fingerprint": "강력한 지문 차단",
|
||||
"setting_enabled": "설정이 활성화된 Brave browser를 사용하고 계신 것 같습니다",
|
||||
"break": "이 기능으로 인해 화이트보드의",
|
||||
"text_elements": "텍스트 요소들이",
|
||||
"in_your_drawings": "손상될 수 있습니다",
|
||||
"strongly_recommend": "해당 기능을 설정에서 비활성화하는 것을 강력히 권장 드립니다. 비활성화 방법에 대해서는",
|
||||
"steps": "이 게시글을",
|
||||
"how": "참고해주세요.",
|
||||
"disable_setting": " 만약 이 설정을 껐음에도 텍스트 요소들이 올바르게 표시되지 않는다면, 저희",
|
||||
"issue": "Github에 이슈를",
|
||||
"write": "올려주시거나",
|
||||
"discord": "Discord에 제보해주세요"
|
||||
"line1": "귀하께서는 <bold>강력한 지문 차단 설정</bold>이 활성화된 Brave browser를 사용하고 계신 것 같습니다.",
|
||||
"line2": "이 기능으로 인해 화이트보드의 <bold>텍스트 요소들</bold>이 손상될 수 있습니다.",
|
||||
"line3": "저희는 해당 기능을 비활성화하는 것을 강력히 권장 드립니다. 비활성화 방법에 대해서는 <link>이 게시글</link>을 참고해주세요.",
|
||||
"line4": "만약 이 설정을 껐음에도 텍스트 요소들이 올바르게 표시되지 않는다면, 저희 Github에 <issueLink>이슈</issueLink>를 올려주시거나 <discordLink>Discord</discordLink>로 알려주세요."
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "팁: 멀리 있는 요소들을 좀 더 가까이로 붙여 보세요."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "오류가 발생했습니다. ",
|
||||
"headingMain_button": "페이지 새로고침",
|
||||
"clearCanvasMessage": "새로고침으로 해결되지 않을 경우, ",
|
||||
"clearCanvasMessage_button": "캔버스 비우기",
|
||||
"headingMain": "오류가 발생했습니다. <button>페이지 새로고침</button>",
|
||||
"clearCanvasMessage": "새로고침으로 해결되지 않을 경우, <button>캔버스 비우기</button>",
|
||||
"clearCanvasCaveat": " 작업 내용을 잃게 됩니다 ",
|
||||
"trackedToSentry_pre": "오류 ",
|
||||
"trackedToSentry_post": " 가 시스템에서 발견되었습니다.",
|
||||
"openIssueMessage_pre": "저희는 화면 정보를 오류에 포함하지 않도록 매우 주의하고 있습니다. 혹시 화면에 민감한 내용이 없다면 이곳에 업로드를 고려해주세요.",
|
||||
"openIssueMessage_button": "버그 트래커",
|
||||
"openIssueMessage_post": " 아래 정보를 GitHub 이슈에 복사 및 붙여넣기해 주세요.",
|
||||
"trackedToSentry": "오류 {{eventId}} 가 시스템에서 발견되었습니다.",
|
||||
"openIssueMessage": "저희는 화면 정보를 오류에 포함하지 않도록 매우 주의하고 있습니다. 혹시 화면에 민감한 내용이 없다면 이곳에 업로드를 고려해주세요.<button>버그 트래커</button> 아래 정보를 GitHub 이슈에 복사 및 붙여넣기해 주세요.",
|
||||
"sceneContent": "화면 내용:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "필수사항",
|
||||
"website": "유효한 URL을 입력하세요"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "당신의 라이브러리를 제출하여 ",
|
||||
"link": "공개 라이브러리 저장소",
|
||||
"post": "에서 다른 사람들의 그림에 사용할 수 있도록 하세요."
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "라이브러리는 먼저 수동으로 승인되어야 합니다. 제출하기 전에 ",
|
||||
"link": "가이드라인",
|
||||
"post": "을 먼저 읽어보세요. 의견을 공유하거나 변경사항을 만들기 위해선 GitHub 계정이 필요하지만, 반드시 필요하진 않습니다."
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "제출함으로써, 당신은 라이브러리가 ",
|
||||
"link": "MIT 라이선스 ",
|
||||
"post": "하에 배포됨을, 즉 아무나 제약 없이 사용할 수 있음에 동의합니다."
|
||||
},
|
||||
"noteDescription": "당신의 라이브러리를 제출하여 <link>공개 라이브러리 저장소</link>에서 다른 사람들의 그림에 사용할 수 있도록 하세요.",
|
||||
"noteGuidelines": "라이브러리는 먼저 수동으로 승인되어야 합니다. 제출하기 전에 <link>가이드라인</link>을 먼저 읽어보세요. 의견을 공유하거나 변경사항을 만들기 위해선 GitHub 계정이 필요하지만, 반드시 필요하진 않습니다.",
|
||||
"noteLicense": "제출함으로써, 당신은 라이브러리가 <link>MIT 라이선스 </link>하에 배포됨을, 즉 아무나 제약 없이 사용할 수 있음에 동의합니다.",
|
||||
"noteItems": "각각의 라이브러리는 분류할 수 있도록 고유한 이름을 가져야 합니다. 다음의 라이브러리 항목이 포함됩니다:",
|
||||
"atleastOneLibItem": "최소한 하나의 라이브러리를 선택해주세요",
|
||||
"republishWarning": "참고: 선택된 항목의 일부는 이미 제출/게시되었습니다. 기존의 라이브러리나 제출물을 업데이트하는 경우에만 제출하세요."
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "라이브러리 제출됨",
|
||||
"content": "{{authorName}}님 감사합니다. 당신의 라이브러리가 심사를 위해 제출되었습니다. 진행 상황을",
|
||||
"link": "여기에서 확인하실 수 있습니다."
|
||||
"content": "{{authorName}}님 감사합니다. 당신의 라이브러리가 심사를 위해 제출되었습니다. 진행 상황을<link>여기에서 확인하실 수 있습니다.</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "라이브러리 리셋",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت. ئەگەر کێشەکان بەردەوام بوون، پێویستە فایلەکەت لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
||||
"collabSaveFailed_sizeExceeded": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت، پێدەچێت تابلۆکە زۆر گەورە بێت. پێویستە فایلەکە لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "پێدەچێت وێبگەڕی Brave بەکاربهێنیت لەگەڵ",
|
||||
"aggressive_block_fingerprint": "بلۆککردنی Fingerprinting بەشێوەیەکی توندوتیژانە",
|
||||
"setting_enabled": "ڕێکخستن چالاک کراوە",
|
||||
"break": "ئەمە دەکرێت ببێتە هۆی تێکدانی",
|
||||
"text_elements": "دانە دەقییەکان",
|
||||
"in_your_drawings": "لە وێنەکێشانەکانتدا",
|
||||
"strongly_recommend": "بە توندی پێشنیار دەکەین ئەم ڕێکخستنە لەکاربخەیت. دەتوانیت بڕۆیت بە دوای",
|
||||
"steps": "ئەم هەنگاوانەدا",
|
||||
"how": "بۆ ئەوەی ئەنجامی بدەیت",
|
||||
"disable_setting": " ئەگەر لەکارخستنی ئەم ڕێکخستنە پیشاندانی توخمەکانی دەق چاک نەکاتەوە، تکایە هەڵبستە بە کردنەوەی",
|
||||
"issue": "کێشەیەک",
|
||||
"write": "لەسەر گیتهەبەکەمان، یان بۆمان بنوسە لە",
|
||||
"discord": "دیسکۆرد"
|
||||
"line1": "وادیارە وێبگەڕی Brave بەکاردەهێنیت و ڕێکخستنی <bold>Aggressively Block Fingerprinting</bold> ـت چالاک کردووە.",
|
||||
"line2": "ئەمە ئەکرێ ببێتە هۆی تێکدانی <bold>دانە دەقییەکان</bold> لە وێنەکێشانەکانتدا.",
|
||||
"line3": "ئێمە بە توندی پێشنیاری لەکارخستنی ئەم ڕێکخستنە دەکەین. بۆ لە کارخستنی دەتوانیت بەم <link>هەنگاوانە</link>دا بڕۆیت.",
|
||||
"line4": "ئەگەر لەکارخستنی ئەم ڕێکخستنە نەبوە هۆی چاککردنەوەی پێشاندانی دانە دەقییەکان، تکایە <issueLink>کێشە</issueLink>یەک بکەرەوە لەسەر گیتهەبەکەمان، یان بۆمان بنوسە لەسەر <discordLink>دیسکۆرد</discordLink>"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "زانیاری: هەوڵ بدە دوورترین توخمەکان کەمێک لە یەکتر نزیک بکەوە."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "تووشی هەڵەیەک بوو. هەوڵ بدە ",
|
||||
"headingMain_button": "دووبارە بارکردنی لاپەڕەکە.",
|
||||
"clearCanvasMessage": "ئەگەر دووبارە بارکردنەوە کار ناکات، هەوڵبدە ",
|
||||
"clearCanvasMessage_button": "خاوێنکردنەوەی تابلۆکە.",
|
||||
"headingMain": "تووشی هەڵەیەک بوو. هەوڵ بدە <button>دووبارە بارکردنی لاپەڕەکە.</button>",
|
||||
"clearCanvasMessage": "ئەگەر دووبارە بارکردنەوە کار ناکات، هەوڵبدە <button>خاوێنکردنەوەی تابلۆکە.</button>",
|
||||
"clearCanvasCaveat": " ئەمە دەبێتە هۆی لەدەستدانی ئەوەی کە کردوتە ",
|
||||
"trackedToSentry_pre": "هەڵەکە لەگەڵ ناسێنەری ",
|
||||
"trackedToSentry_post": " لەسەر سیستەمەکەمان بەدواداچوونی بۆ کرا.",
|
||||
"openIssueMessage_pre": "ئێمە زۆر وریا بووین کە زانیارییەکانی دیمەنەکەت لەسەر هەڵەکە نەخەینەڕوو. ئەگەر دیمەنەکەت تایبەت نییە، تکایە بیر لە بەدواداچوون بکەنەوە بۆ ئێمە ",
|
||||
"openIssueMessage_button": "شوێنپێهەڵگری هەڵە.",
|
||||
"openIssueMessage_post": " تکایە ئەم زانیارییانەی خوارەوە کۆپی بکە و لە بەشی کێشەکانی Github دایبنێ.",
|
||||
"trackedToSentry": "هەڵەکە لەگەڵ ناسێنەری {{eventId}} لەسەر سیستەمەکەمان بەدواداچوونی بۆ کرا.",
|
||||
"openIssueMessage": "ئێمە زۆر وریا بووین کە زانیارییەکانی دیمەنەکەت لەسەر هەڵەکە نەخەینەڕوو. ئەگەر دیمەنەکەت تایبەت نییە، تکایە بیر لە بەدواداچوون بکەنەوە بۆ ئێمە <button>شوێنپێهەڵگری هەڵە.</button> تکایە ئەم زانیارییانەی خوارەوە کۆپی بکە و لە بەشی کێشەکانی Github دایبنێ.",
|
||||
"sceneContent": "پێکهاتەی ناو دیمەنەکە:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "داواکراوە",
|
||||
"website": "URLێکی دروست تێبنووسە"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "کتێبخانەکەت بنێرە بۆ ئەوەی بخرێتە ناو ",
|
||||
"link": "کۆگای کتێبخانەی گشتی",
|
||||
"post": "بۆ ئەوەی کەسانی تر لە وێنەکێشانەکانیاندا بەکاری بهێنن."
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "کتێبخانەکە پێویستە سەرەتا بە دەست پەسەند بکرێت. تکایە بفەرمو بە خوێندنەوەی ",
|
||||
"link": "ڕێنماییەکان",
|
||||
"post": " پێش پێشکەشکردن. پێویستت بە ئەژمێری GitHub دەبێت بۆ پەیوەندیکردن و گۆڕانکاری ئەگەر داوای لێکرا، بەڵام بە توندی پێویست نییە."
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "بە پێشکەشکردن، تۆ ڕەزامەندیت لەسەر بڵاوکردنەوەی کتێبخانەکە بەپێی ",
|
||||
"link": "مۆڵەتی MIT، ",
|
||||
"post": "کە بە کورتی مانای ئەوەیە کە هەرکەسێک دەتوانێت بە بێ سنوور بەکاری بهێنێت"
|
||||
},
|
||||
"noteDescription": "کتێبخانەکەت بنێرە بۆ ئەوەی بخرێتە ناو <link>کۆگای کتێبخانەی گشتی</link>بۆ ئەوەی کەسانی تر لە وێنەکێشانەکانیاندا بەکاری بهێنن.",
|
||||
"noteGuidelines": "کتێبخانەکە پێویستە سەرەتا بە دەست پەسەند بکرێت. تکایە بفەرمو بە خوێندنەوەی <link>ڕێنماییەکان</link> پێش پێشکەشکردن. پێویستت بە ئەژمێری GitHub دەبێت بۆ پەیوەندیکردن و گۆڕانکاری ئەگەر داوای لێکرا، بەڵام بە توندی پێویست نییە.",
|
||||
"noteLicense": "بە پێشکەشکردن، تۆ ڕەزامەندیت لەسەر بڵاوکردنەوەی کتێبخانەکە بەپێی <link>مۆڵەتی MIT، </link>کە بە کورتی مانای ئەوەیە کە هەرکەسێک دەتوانێت بە بێ سنوور بەکاری بهێنێت",
|
||||
"noteItems": "هەر شتێکی کتێبخانە دەبێت ناوی تایبەتی خۆی هەبێت بۆ ئەوەی بتوانرێت فلتەر بکرێت. ئەم بابەتانەی کتێبخانانەی خوارەوە لەخۆدەگرێت:",
|
||||
"atleastOneLibItem": "تکایە بەلایەنی کەمەوە یەک بڕگەی کتێبخانە دیاریبکە بۆ دەستپێکردن",
|
||||
"republishWarning": "تێبینی: هەندێک لە ئایتمە دیاریکراوەکان نیشانکراون وەک ئەوەی پێشتر بڵاوکراونەتەوە/نێردراون. تەنها پێویستە شتەکان دووبارە پێشکەش بکەیتەوە لە کاتی نوێکردنەوەی کتێبخانەیەکی هەبوو یان پێشکەشکردن."
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "کتێبخانە پێشکەش کرا",
|
||||
"content": "سوپاس {{authorName}}. کتێبخانەکەت پێشکەش کراوە بۆ پێداچوونەوە. دەتوانیت بەدواداچوون بۆ دۆخەکە بکەیت",
|
||||
"link": "لێرە"
|
||||
"content": "سوپاس {{authorName}}. کتێبخانەکەت پێشکەش کراوە بۆ پێداچوونەوە. دەتوانیت بەدواداچوون بۆ دۆخەکە بکەیت<link>لێرە</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "ڕێکخستنەوەی کتێبخانە",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": ""
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "",
|
||||
"headingMain_button": "",
|
||||
"headingMain": "",
|
||||
"clearCanvasMessage": "",
|
||||
"clearCanvasMessage_button": "",
|
||||
"clearCanvasCaveat": "",
|
||||
"trackedToSentry_pre": "",
|
||||
"trackedToSentry_post": "",
|
||||
"openIssueMessage_pre": "",
|
||||
"openIssueMessage_button": "",
|
||||
"openIssueMessage_post": "",
|
||||
"trackedToSentry": "",
|
||||
"openIssueMessage": "",
|
||||
"sceneContent": ""
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Privalomas",
|
||||
"website": "Įveskite teisingą nuorodą (URL)"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Pateik savo biblioteką, jog ji galėtų būti įtraukta į ",
|
||||
"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": ""
|
||||
},
|
||||
"noteDescription": "Pateik savo biblioteką, jog ji galėtų būti įtraukta į <link></link>jog kiti žmonės galėtų tai naudoti savo piešiniuose.",
|
||||
"noteGuidelines": "Visų pirma, biblioteka turi būti rankiniu būdu patvirtinta. Prašome paskaityti <link>gairės</link>",
|
||||
"noteLicense": "<link>MIT licencija, </link>",
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "Biblioteka pateikta",
|
||||
"content": "Ačiū {{authorName}}. Tavo biblioteka buvo pateikta peržiūrai. Gali sekti būseną",
|
||||
"link": "čia"
|
||||
"content": "Ačiū {{authorName}}. Tavo biblioteka buvo pateikta peržiūrai. Gali sekti būseną<link>čia</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"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_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": {
|
||||
"start": "Izskatās, ka izmanto Brave interneta plārlūku ar ieslēgtu",
|
||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
||||
"setting_enabled": "ieslēgtu iestatījumu",
|
||||
"break": "Tas var salauzt",
|
||||
"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"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "Ieteikums: mēģiniet satuvināt pašus tālākos elementus."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "Notikusi kļūda. Mēģiniet ",
|
||||
"headingMain_button": "pārlādēt lapu.",
|
||||
"clearCanvasMessage": "Ja pārlādēšana nestrādā, mēģiniet ",
|
||||
"clearCanvasMessage_button": "notīrot tāfeli.",
|
||||
"headingMain": "Notikusi kļūda. Mēģiniet <button>pārlādēt lapu.</button>",
|
||||
"clearCanvasMessage": "Ja pārlādēšana nestrādā, mēģiniet <button>notīrot tāfeli.</button>",
|
||||
"clearCanvasCaveat": " Tas novedīs pie darba zaudēšanas ",
|
||||
"trackedToSentry_pre": "Kļūda ar kodu ",
|
||||
"trackedToSentry_post": " tika noteikta mūsu sistēmā.",
|
||||
"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.",
|
||||
"trackedToSentry": "Kļūda ar kodu {{eventId}} 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.",
|
||||
"sceneContent": "Ainas saturs:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "Obligāts",
|
||||
"website": "Ievadiet derīgu URL"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "Iesniegt savu bibliotēku iekļaušanai ",
|
||||
"link": "publiskajā bibliotēku datubāzē",
|
||||
"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."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"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": {
|
||||
"title": "Bibliotēka iesniegta",
|
||||
"content": "Paldies, {{authorName}}! Jūsu bibliotēka iesniegta izskatīšanai. Jūs varat izsekot iesnieguma statusam",
|
||||
"link": "šeit"
|
||||
"content": "Paldies, {{authorName}}! Jūsu bibliotēka iesniegta izskatīšanai. Jūs varat izsekot iesnieguma statusam<link>šeit</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "Atiestatīt bibliotēku",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "काही कारणा निमित्त आतल्या डेटाबेसमध्ये जतन करू शकत नाही। समस्या तशिस राहिल्यास, तुम्ही तुमचे काम गमावणार नाही याची खात्री करण्यासाठी तुम्ही तुमची फाइल स्थानिक जतन करावी.",
|
||||
"collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।\n\nबॅकएंड डेटाबेसमध्ये जतन करू शकत नाही, कॅनव्हास खूप मोठा असल्याचे दिसते. तुम्ही तुमचे काम गमावणार नाही याची खात्री करण्यासाठी तुम्ही फाइल स्थानिक पातळीवर जतन करावी.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "असं वाटते की तुम्हीं \"Brave\" \"Browser\" वापरत आहात, त्या बरोबार",
|
||||
"aggressive_block_fingerprint": "बोटांचे ठसे उग्रतेने थाम्बवाचे",
|
||||
"setting_enabled": "सेटिंग्स सक्रिय केले आहेत",
|
||||
"break": "ह्या कारणानिं",
|
||||
"text_elements": "पाठ अवयव तुटु शकतात",
|
||||
"in_your_drawings": "तुमच्या चित्रिकराणतले",
|
||||
"strongly_recommend": "आमचा ज़ोरदार सल्ला असा कि सेटिंग्स निष्क्रिय करावे. तुम्हीं",
|
||||
"steps": "ह्या स्टेप्स",
|
||||
"how": "घेउ शकतात",
|
||||
"disable_setting": " जर सेटिंग्स निष्क्रिय करून पाठ्य दिसणे ठीक नसेल होत तर",
|
||||
"issue": "मुद्दा",
|
||||
"write": "आमच्या Github वर, किव्हा आम्हाला",
|
||||
"discord": "\"Discord\" वर लिहां"
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "टीप: दूर चा तत्व थोडं जवळ आणण्याचा प्रयत्न करावा."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "त्रुटि आली. परत प्रयत्न करा ",
|
||||
"headingMain_button": "ह्या पानाला पुनः लोड करा.",
|
||||
"clearCanvasMessage": "रीलोडिंग होत नसल्यास, परत प्रयत्न करा ",
|
||||
"clearCanvasMessage_button": "पटल स्वच्छ करित आहे.",
|
||||
"headingMain": "त्रुटि आली. परत प्रयत्न करा <button>ह्या पानाला पुनः लोड करा.</button>",
|
||||
"clearCanvasMessage": "रीलोडिंग होत नसल्यास, परत प्रयत्न करा <button>पटल स्वच्छ करित आहे.</button>",
|
||||
"clearCanvasCaveat": " त्यामुळे केलेल्या कामाचे नुकसान होईल ",
|
||||
"trackedToSentry_pre": "त्रुटि क्रमांक के साथ त्रुटि ",
|
||||
"trackedToSentry_post": " आमच्या प्रणाली नी निरीक्षण केले होते.",
|
||||
"openIssueMessage_pre": "त्रुटीत तुमची दृश्य माहिती समाविष्ट न करण्यासाठी आम्ही खूप सावध होतो. तुमचा सीन खाजगी नसल्यास, कृपया आम्हाला पुढ च्या कारवाई साठी सम्पर्क साधा ",
|
||||
"openIssueMessage_button": "त्रुटि व्यवस्थापन.",
|
||||
"openIssueMessage_post": " कृपया गिटहब समस्येमध्ये कॉपी आणि पेस्ट करून खालिल माहिती समाविष्ट करा.",
|
||||
"trackedToSentry": "त्रुटि क्रमांक के साथ त्रुटि {{eventId}} आमच्या प्रणाली नी निरीक्षण केले होते.",
|
||||
"openIssueMessage": "त्रुटीत तुमची दृश्य माहिती समाविष्ट न करण्यासाठी आम्ही खूप सावध होतो. तुमचा सीन खाजगी नसल्यास, कृपया आम्हाला पुढ च्या कारवाई साठी सम्पर्क साधा <button>त्रुटि व्यवस्थापन.</button> कृपया गिटहब समस्येमध्ये कॉपी आणि पेस्ट करून खालिल माहिती समाविष्ट करा.",
|
||||
"sceneContent": "दृश्य विषय:"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "आवश्यक आहे",
|
||||
"website": "वैध यू-आर-एल द्या"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "समाविष्ट करण्या साठी तुमचा संग्रह ह्याचात जमा करा ",
|
||||
"link": "सार्वजनिक संग्रहाचे कोठार",
|
||||
"post": "इतर लोकांना त्यांच्या रेखाचित्रांमधे वापरण्यासाठी."
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "संग्रहाला आधी स्वहस्ते स्वीकृती मिळणे आवश्यक आहे. कृपया हे वाचा ",
|
||||
"link": "मार्गदर्शक तत्त्वे",
|
||||
"post": " जमा करण्या पूर्वी, तुमच्या जवळ एक गिटहब खाते असणे आवश्यक आहे जे संवादा साठी आणिक बदल करण्या साठी लागेल, तरी हे सर्व अगदी आवश्यक नाही आहे."
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "जमा करताना तुम्हीं सहमति दाखवतात आहे की संग्रह ह्याचा खाली प्रकाशित होईल ",
|
||||
"link": "एम-आइ-टी परवाना, ",
|
||||
"post": "ज्याचा थोडक्यात अर्थ कोणीही निर्बंधांशिवाय वापरू शकतो."
|
||||
},
|
||||
"noteDescription": "समाविष्ट करण्या साठी तुमचा संग्रह ह्याचात जमा करा <link>सार्वजनिक संग्रहाचे कोठार</link>इतर लोकांना त्यांच्या रेखाचित्रांमधे वापरण्यासाठी.",
|
||||
"noteGuidelines": "संग्रहाला आधी स्वहस्ते स्वीकृती मिळणे आवश्यक आहे. कृपया हे वाचा <link>मार्गदर्शक तत्त्वे</link> जमा करण्या पूर्वी, तुमच्या जवळ एक गिटहब खाते असणे आवश्यक आहे जे संवादा साठी आणिक बदल करण्या साठी लागेल, तरी हे सर्व अगदी आवश्यक नाही आहे.",
|
||||
"noteLicense": "जमा करताना तुम्हीं सहमति दाखवतात आहे की संग्रह ह्याचा खाली प्रकाशित होईल <link>एम-आइ-टी परवाना, </link>ज्याचा थोडक्यात अर्थ कोणीही निर्बंधांशिवाय वापरू शकतो.",
|
||||
"noteItems": "प्रतैक संग्रहाचे नाव, नीट शोधनासाठी, असणे आवश्यक आहे. खाली दिलेल्या वस्तु समाविष्ट केल्या जातील:",
|
||||
"atleastOneLibItem": "सुरु करण्यासाठी, कृपया करून, कमित कमी एक वस्तु तरी निवडा",
|
||||
"republishWarning": "टीप: काही निवडक आयटम आधीच प्रकाशित/प्रस्तुत केलेले आहेत. विद्यमान लायब्ररी किंवा प्रस्तुतित आयटम अद्यावित करताना तुम्ही फक्त तो पुन्हा प्रस्तुत करा."
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "संग्रह जमा केला",
|
||||
"content": "धन्यवाद {{authorName}}. आपला संग्रह पुनरावलोकना साठी जमा झाला आहे. तुम्हीं स्थिति सारखी तपासू सकता",
|
||||
"link": "इकडे"
|
||||
"content": "धन्यवाद {{authorName}}. आपला संग्रह पुनरावलोकना साठी जमा झाला आहे. तुम्हीं स्थिति सारखी तपासू सकता<link>इकडे</link>"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "संग्रह पुनर्स्थित करा",
|
||||
|
|
|
@ -208,19 +208,10 @@
|
|||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"line1": "",
|
||||
"line2": "",
|
||||
"line3": "",
|
||||
"line4": ""
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -273,16 +264,11 @@
|
|||
"canvasTooBigTip": "မှတ်ချက်။ ။ဝေးကွာနေသော ပုံများ၊ စာများအား ပိုမိုနီးကပ်အောင်ရွှေ့ကြည့်ပါ။"
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "ချို့ယွင်းမှုဖြစ်ပေါ်ခဲ့သဖြင့် ထပ်မံကြိုးစားကြည့်ရန် ",
|
||||
"headingMain_button": "စာမျက်နှာအား အသစ်ပြန်လည်ရယူပါ။",
|
||||
"clearCanvasMessage": "အသစ်ပြန်လည်မရယူနိုင်ပါက ထပ်မံကြိုးစားကြည့်ရန်",
|
||||
"clearCanvasMessage_button": "ကားချပ်အား ရှင်းလင်းပါ။",
|
||||
"headingMain": "ချို့ယွင်းမှုဖြစ်ပေါ်ခဲ့သဖြင့် ထပ်မံကြိုးစားကြည့်ရန် <button>စာမျက်နှာအား အသစ်ပြန်လည်ရယူပါ။</button>",
|
||||
"clearCanvasMessage": "အသစ်ပြန်လည်မရယူနိုင်ပါက ထပ်မံကြိုးစားကြည့်ရန်<button>ကားချပ်အား ရှင်းလင်းပါ။</button>",
|
||||
"clearCanvasCaveat": " ရေးဆွဲထားသည်များ ဆုံးရှုံးနိုင်သည် ",
|
||||
"trackedToSentry_pre": "ချို့ယွင်းမှုသတ်မှတ်ချက် ",
|
||||
"trackedToSentry_post": " အားစနစ်အတွင်းခြေရာကောက်ပြီးပါပြီ။",
|
||||
"openIssueMessage_pre": "ချို့ယွင်းမှုမှတ်တမ်းတွင် အရေးကြီးအချက်အလက်များပါဝင်မှုမရှိစေရန်အထူးသတိပြုပါသည်။ မပါဝင်ပါက ဆက်လက်ဆောင်ရွက်ရန် ",
|
||||
"openIssueMessage_button": "ချို့ယွင်းမှုအားခြေရာကောက်ပါ။",
|
||||
"openIssueMessage_post": " အောက်ပါအချက်အလက်များအား Github တွင် Issue အနေဖြင့်ဖြည့်သွင်းဖော်ပြပေးပါ။",
|
||||
"trackedToSentry": "ချို့ယွင်းမှုသတ်မှတ်ချက် {{eventId}} အားစနစ်အတွင်းခြေရာကောက်ပြီးပါပြီ။",
|
||||
"openIssueMessage": "ချို့ယွင်းမှုမှတ်တမ်းတွင် အရေးကြီးအချက်အလက်များပါဝင်မှုမရှိစေရန်အထူးသတိပြုပါသည်။ မပါဝင်ပါက ဆက်လက်ဆောင်ရွက်ရန် <button>ချို့ယွင်းမှုအားခြေရာကောက်ပါ။</button> အောက်ပါအချက်အလက်များအား Github တွင် Issue အနေဖြင့်ဖြည့်သွင်းဖော်ပြပေးပါ။",
|
||||
"sceneContent": "မြင်ကွင်းပါအချက်အလက်။ ။"
|
||||
},
|
||||
"roomDialog": {
|
||||
|
@ -362,29 +348,16 @@
|
|||
"required": "",
|
||||
"website": ""
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "",
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteDescription": "",
|
||||
"noteGuidelines": "",
|
||||
"noteLicense": "",
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "",
|
||||
"content": "",
|
||||
"link": ""
|
||||
"content": ""
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "",
|
||||
|
|
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