Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 993294ac08 | |||
| f584416c9a | |||
| 87b0c7a679 | |||
| ee8fff8e8b | |||
| b799490ece | |||
| fd18896293 | |||
| e900cb0b64 | |||
| 54bf3d9092 | |||
| 15f19835fe | |||
| 96c4cff805 | |||
| 1ac580136d | |||
| 8c89fdfa51 | |||
| 0e54994187 | |||
| 91f6e87317 | |||
| a05db6864e | |||
| eacee9a158 | |||
| 7722de4ef2 | |||
| 0a295e523b | |||
| 60deddb0e2 |
@@ -23,11 +23,6 @@ REACT_APP_DEV_DISABLE_LIVE_RELOAD=
|
||||
|
||||
FAST_REFRESH=false
|
||||
|
||||
# MATOMO
|
||||
REACT_APP_MATOMO_URL=
|
||||
REACT_APP_CDN_MATOMO_TRACKER_URL=
|
||||
REACT_APP_MATOMO_SITE_ID=
|
||||
|
||||
#Debug flags
|
||||
|
||||
# To enable bounding box for text containers
|
||||
|
||||
@@ -12,13 +12,6 @@ REACT_APP_WS_SERVER_URL=
|
||||
REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
|
||||
|
||||
# production-only vars
|
||||
# GOOGLE ANALYTICS
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=UA-387204-13
|
||||
# MATOMO
|
||||
REACT_APP_MATOMO_URL=https://excalidraw.matomo.cloud/
|
||||
REACT_APP_CDN_MATOMO_TRACKER_URL=//cdn.matomo.cloud/excalidraw.matomo.cloud/matomo.js
|
||||
REACT_APP_MATOMO_SITE_ID=1
|
||||
|
||||
|
||||
|
||||
REACT_APP_PLUS_APP=https://app.excalidraw.com
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
An open source virtual hand-drawn style whiteboard. </br>
|
||||
Collaborative and end-to-end encrypted. </br>
|
||||
<br />
|
||||
</h2>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
# ref
|
||||
|
||||
<pre>
|
||||
<a href="https://reactjs.org/docs/refs-and-the-dom.html#creating-refs">
|
||||
createRef
|
||||
</a>{" "}
|
||||
|{" "}
|
||||
<a href="https://reactjs.org/docs/hooks-reference.html#useref">useRef</a>{" "}
|
||||
|{" "}
|
||||
<a href="https://reactjs.org/docs/refs-and-the-dom.html#callback-refs">
|
||||
callbackRef
|
||||
</a>{" "}
|
||||
| <br />
|
||||
{ current: { readyPromise: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">
|
||||
resolvablePromise
|
||||
</a> } }
|
||||
<a href="https://reactjs.org/docs/refs-and-the-dom.html#creating-refs">createRef</a> | <a href="https://reactjs.org/docs/hooks-reference.html#useref">useRef</a> | <a href="https://reactjs.org/docs/refs-and-the-dom.html#callback-refs">callbackRef</a> | <br/>{ current: { readyPromise: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L460">resolvablePromise</a> } }
|
||||
</pre>
|
||||
|
||||
You can pass a `ref` when you want to access some excalidraw APIs. We expose the below APIs:
|
||||
@@ -152,9 +139,7 @@ function App() {
|
||||
return (
|
||||
<div style={{ height: "500px" }}>
|
||||
<p style={{ fontSize: "16px" }}> Click to update the scene</p>
|
||||
<button className="custom-button" onClick={updateScene}>
|
||||
Update Scene
|
||||
</button>
|
||||
<button className="custom-button" onClick={updateScene}>Update Scene</button>
|
||||
<Excalidraw ref={(api) => setExcalidrawAPI(api)} />
|
||||
</div>
|
||||
);
|
||||
@@ -202,8 +187,7 @@ function App() {
|
||||
return (
|
||||
<div style={{ height: "500px" }}>
|
||||
<p style={{ fontSize: "16px" }}> Click to update the library items</p>
|
||||
<button
|
||||
className="custom-button"
|
||||
<button className="custom-button"
|
||||
onClick={() => {
|
||||
const libraryItems = [
|
||||
{
|
||||
@@ -221,8 +205,10 @@ function App() {
|
||||
];
|
||||
excalidrawAPI.updateLibrary({
|
||||
libraryItems,
|
||||
openLibraryMenu: true,
|
||||
openLibraryMenu: true
|
||||
|
||||
});
|
||||
|
||||
}}
|
||||
>
|
||||
Update Library
|
||||
@@ -264,7 +250,7 @@ Resets the scene. If `resetLoadingState` is passed as true then it will also for
|
||||
|
||||
<pre>
|
||||
() =>{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
|
||||
ExcalidrawElement[]
|
||||
</a>
|
||||
</pre>
|
||||
@@ -275,7 +261,7 @@ Returns all the elements including the deleted in the scene.
|
||||
|
||||
<pre>
|
||||
() => NonDeleted<
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
|
||||
ExcalidrawElement
|
||||
</a>
|
||||
[]>
|
||||
@@ -307,31 +293,18 @@ This is the history API. history.clear() will clear the history.
|
||||
## scrollToContent
|
||||
|
||||
<pre>
|
||||
(<br />
|
||||
{" "}
|
||||
target?:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
|
||||
(target?:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
|
||||
ExcalidrawElement
|
||||
</a>{" "}
|
||||
|{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
|
||||
ExcalidrawElement
|
||||
</a>
|
||||
[],
|
||||
<br />
|
||||
{" "}opts?: { fitToContent?: boolean; animate?: boolean; duration?: number
|
||||
}
|
||||
<br />) => void
|
||||
[]) => void
|
||||
</pre>
|
||||
|
||||
Scroll the nearest element out of the elements supplied to the center of the viewport. Defaults to the elements on the scene.
|
||||
|
||||
| Attribute | type | default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| target | <code>ExcalidrawElement | ExcalidrawElement[]</code> | All scene elements | The element(s) to scroll to. |
|
||||
| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. |
|
||||
| opts.animate | boolean | false | Whether to animate between starting and ending position. Note that for larger scenes the animation may not be smooth due to performance issues. |
|
||||
| opts.duration | number | 500 | Duration of the animation if `opts.animate` is `true`. |
|
||||
Scroll the nearest element out of the elements supplied to the center. Defaults to the elements on the scene.
|
||||
|
||||
## refresh
|
||||
|
||||
@@ -350,7 +323,7 @@ For any other cases if the position of excalidraw is updated (example due to scr
|
||||
This API can be used to show the toast with custom message.
|
||||
|
||||
```tsx
|
||||
({ message: string, closable?:boolean,duration?:number
|
||||
({ message: string, closable?:boolean,duration?:number
|
||||
} | null) => void
|
||||
```
|
||||
|
||||
@@ -385,18 +358,15 @@ This API can be used to get the files present in the scene. It may contain files
|
||||
|
||||
This API has the below signature. It sets the `tool` passed in param as the active tool.
|
||||
|
||||
|
||||
<pre>
|
||||
(tool: <br /> { type:{" "}
|
||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/shapes.tsx#L15">
|
||||
SHAPES
|
||||
</a>
|
||||
[number]["value"]| "eraser" } |
|
||||
<br /> { type: "custom"; customType: string }) => void
|
||||
(tool: <br/> { type: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/shapes.tsx#L15">SHAPES</a>[number]["value"]| "eraser" } |<br/> { type: "custom"; customType: string }) => void
|
||||
</pre>
|
||||
|
||||
## setCursor
|
||||
|
||||
This API can be used to customise the mouse cursor on the canvas and has the below signature. It sets the mouse cursor to the cursor passed in param.
|
||||
This API can be used to customise the mouse cursor on the canvas and has the below signature.
|
||||
It sets the mouse cursor to the cursor passed in param.
|
||||
|
||||
```tsx
|
||||
(cursor: string) => void
|
||||
|
||||
+2
-33
@@ -79,7 +79,6 @@
|
||||
</style>
|
||||
<!------------------------------------------------------------------------->
|
||||
|
||||
<% if (process.env.NODE_ENV === "production") { %>
|
||||
<script>
|
||||
// Redirect Excalidraw+ users which have auto-redirect enabled.
|
||||
//
|
||||
@@ -98,7 +97,6 @@
|
||||
window.location.href = "https://app.excalidraw.com";
|
||||
}
|
||||
</script>
|
||||
<% } %>
|
||||
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
|
||||
|
||||
@@ -148,10 +146,8 @@
|
||||
// setting this so that libraries installation reuses this window tab.
|
||||
window.name = "_excalidraw";
|
||||
</script>
|
||||
<% if (process.env.REACT_APP_DISABLE_TRACKING !== 'true') { %>
|
||||
|
||||
<!-- LEGACY GOOGLE ANALYTICS -->
|
||||
<% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
|
||||
<% if (process.env.REACT_APP_DISABLE_TRACKING !== 'true' &&
|
||||
process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=%REACT_APP_GOOGLE_ANALYTICS_ID%"
|
||||
@@ -164,33 +160,6 @@
|
||||
gtag("js", new Date());
|
||||
gtag("config", "%REACT_APP_GOOGLE_ANALYTICS_ID%");
|
||||
</script>
|
||||
<% } %>
|
||||
<!-- end LEGACY GOOGLE ANALYTICS -->
|
||||
|
||||
<!-- Matomo -->
|
||||
<% if (process.env.REACT_APP_MATOMO_URL &&
|
||||
process.env.REACT_APP_MATOMO_SITE_ID &&
|
||||
process.env.REACT_APP_CDN_MATOMO_TRACKER_URL) { %>
|
||||
<script>
|
||||
var _paq = (window._paq = window._paq || []);
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(["trackPageView"]);
|
||||
_paq.push(["enableLinkTracking"]);
|
||||
(function () {
|
||||
var u = "%REACT_APP_MATOMO_URL%";
|
||||
_paq.push(["setTrackerUrl", u + "matomo.php"]);
|
||||
_paq.push(["setSiteId", "%REACT_APP_MATOMO_SITE_ID%"]);
|
||||
var d = document,
|
||||
g = d.createElement("script"),
|
||||
s = d.getElementsByTagName("script")[0];
|
||||
g.async = true;
|
||||
g.src = "%REACT_APP_CDN_MATOMO_TRACKER_URL%";
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})();
|
||||
</script>
|
||||
<% } %>
|
||||
<!-- end Matomo analytics -->
|
||||
|
||||
<% } %>
|
||||
|
||||
<!-- FIXME: remove this when we update CRA (fix SW caching) -->
|
||||
|
||||
+92
-113
@@ -1,22 +1,15 @@
|
||||
import {
|
||||
BOUND_TEXT_PADDING,
|
||||
ROUNDNESS,
|
||||
VERTICAL_ALIGN,
|
||||
TEXT_ALIGN,
|
||||
} from "../constants";
|
||||
import { BOUND_TEXT_PADDING, ROUNDNESS, VERTICAL_ALIGN } from "../constants";
|
||||
import { getNonDeletedElements, isTextElement, newElement } from "../element";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import {
|
||||
computeBoundTextPosition,
|
||||
computeContainerDimensionForBoundText,
|
||||
getBoundTextElement,
|
||||
measureText,
|
||||
redrawTextBoundingBox,
|
||||
} from "../element/textElement";
|
||||
import { measureText } from "../element/textMeasurements";
|
||||
import {
|
||||
getOriginalContainerHeightFromCache,
|
||||
resetOriginalContainerCache,
|
||||
updateOriginalContainerCache,
|
||||
} from "../element/textWysiwyg";
|
||||
import {
|
||||
hasBoundTextElement,
|
||||
@@ -30,7 +23,6 @@ import {
|
||||
ExcalidrawTextElement,
|
||||
} from "../element/types";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
import { getFontString } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
@@ -40,7 +32,6 @@ export const actionUnbindText = register({
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
|
||||
return selectedElements.some((element) => hasBoundTextElement(element));
|
||||
},
|
||||
perform: (elements, appState) => {
|
||||
@@ -51,7 +42,7 @@ export const actionUnbindText = register({
|
||||
selectedElements.forEach((element) => {
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
if (boundTextElement) {
|
||||
const { width, height, baseline } = measureText(
|
||||
const { width, height } = measureText(
|
||||
boundTextElement.originalText,
|
||||
getFontString(boundTextElement),
|
||||
boundTextElement.lineHeight,
|
||||
@@ -60,15 +51,12 @@ export const actionUnbindText = register({
|
||||
element.id,
|
||||
);
|
||||
resetOriginalContainerCache(element.id);
|
||||
const { x, y } = computeBoundTextPosition(element, boundTextElement);
|
||||
|
||||
mutateElement(boundTextElement as ExcalidrawTextElement, {
|
||||
containerId: null,
|
||||
width,
|
||||
height,
|
||||
baseline,
|
||||
text: boundTextElement.originalText,
|
||||
x,
|
||||
y,
|
||||
});
|
||||
mutateElement(element, {
|
||||
boundElements: element.boundElements?.filter(
|
||||
@@ -138,7 +126,6 @@ export const actionBindText = register({
|
||||
mutateElement(textElement, {
|
||||
containerId: container.id,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
});
|
||||
mutateElement(container, {
|
||||
boundElements: (container.boundElements || []).concat({
|
||||
@@ -146,11 +133,7 @@ export const actionBindText = register({
|
||||
id: textElement.id,
|
||||
}),
|
||||
});
|
||||
const originalContainerHeight = container.height;
|
||||
redrawTextBoundingBox(textElement, container);
|
||||
// overwritting the cache with original container height so
|
||||
// it can be restored when unbind
|
||||
updateOriginalContainerCache(container.id, originalContainerHeight);
|
||||
|
||||
return {
|
||||
elements: pushTextAboveContainer(elements, container, textElement),
|
||||
@@ -196,117 +179,113 @@ const pushContainerBelowText = (
|
||||
return updatedElements;
|
||||
};
|
||||
|
||||
export const actionWrapTextInContainer = register({
|
||||
name: "wrapTextInContainer",
|
||||
export const actionCreateContainerFromText = register({
|
||||
name: "createContainerFromText",
|
||||
contextItemLabel: "labels.createContainerFromText",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const areTextElements = selectedElements.every((el) => isTextElement(el));
|
||||
return selectedElements.length > 0 && areTextElements;
|
||||
return selectedElements.length === 1 && isTextElement(selectedElements[0]);
|
||||
},
|
||||
perform: (elements, appState) => {
|
||||
const selectedElements = getSelectedElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
);
|
||||
let updatedElements: readonly ExcalidrawElement[] = elements.slice();
|
||||
const containerIds: AppState["selectedElementIds"] = {};
|
||||
const updatedElements = elements.slice();
|
||||
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
|
||||
const textElement = selectedElements[0];
|
||||
const container = newElement({
|
||||
type: "rectangle",
|
||||
backgroundColor: appState.currentItemBackgroundColor,
|
||||
boundElements: [
|
||||
...(textElement.boundElements || []),
|
||||
{ id: textElement.id, type: "text" },
|
||||
],
|
||||
angle: textElement.angle,
|
||||
fillStyle: appState.currentItemFillStyle,
|
||||
strokeColor: appState.currentItemStrokeColor,
|
||||
roughness: appState.currentItemRoughness,
|
||||
strokeWidth: appState.currentItemStrokeWidth,
|
||||
strokeStyle: appState.currentItemStrokeStyle,
|
||||
roundness:
|
||||
appState.currentItemRoundness === "round"
|
||||
? {
|
||||
type: isUsingAdaptiveRadius("rectangle")
|
||||
? ROUNDNESS.ADAPTIVE_RADIUS
|
||||
: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||
}
|
||||
: null,
|
||||
opacity: 100,
|
||||
locked: false,
|
||||
x: textElement.x - BOUND_TEXT_PADDING,
|
||||
y: textElement.y - BOUND_TEXT_PADDING,
|
||||
width: computeContainerDimensionForBoundText(
|
||||
textElement.width,
|
||||
"rectangle",
|
||||
),
|
||||
height: computeContainerDimensionForBoundText(
|
||||
textElement.height,
|
||||
"rectangle",
|
||||
),
|
||||
groupIds: textElement.groupIds,
|
||||
});
|
||||
|
||||
for (const textElement of selectedElements) {
|
||||
if (isTextElement(textElement)) {
|
||||
const container = newElement({
|
||||
type: "rectangle",
|
||||
backgroundColor: appState.currentItemBackgroundColor,
|
||||
boundElements: [
|
||||
...(textElement.boundElements || []),
|
||||
{ id: textElement.id, type: "text" },
|
||||
],
|
||||
angle: textElement.angle,
|
||||
fillStyle: appState.currentItemFillStyle,
|
||||
strokeColor: appState.currentItemStrokeColor,
|
||||
roughness: appState.currentItemRoughness,
|
||||
strokeWidth: appState.currentItemStrokeWidth,
|
||||
strokeStyle: appState.currentItemStrokeStyle,
|
||||
roundness:
|
||||
appState.currentItemRoundness === "round"
|
||||
? {
|
||||
type: isUsingAdaptiveRadius("rectangle")
|
||||
? ROUNDNESS.ADAPTIVE_RADIUS
|
||||
: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||
}
|
||||
: null,
|
||||
opacity: 100,
|
||||
locked: false,
|
||||
x: textElement.x - BOUND_TEXT_PADDING,
|
||||
y: textElement.y - BOUND_TEXT_PADDING,
|
||||
width: computeContainerDimensionForBoundText(
|
||||
textElement.width,
|
||||
"rectangle",
|
||||
),
|
||||
height: computeContainerDimensionForBoundText(
|
||||
textElement.height,
|
||||
"rectangle",
|
||||
),
|
||||
groupIds: textElement.groupIds,
|
||||
// update bindings
|
||||
if (textElement.boundElements?.length) {
|
||||
const linearElementIds = textElement.boundElements
|
||||
.filter((ele) => ele.type === "arrow")
|
||||
.map((el) => el.id);
|
||||
const linearElements = updatedElements.filter((ele) =>
|
||||
linearElementIds.includes(ele.id),
|
||||
) as ExcalidrawLinearElement[];
|
||||
linearElements.forEach((ele) => {
|
||||
let startBinding = ele.startBinding;
|
||||
let endBinding = ele.endBinding;
|
||||
|
||||
if (startBinding?.elementId === textElement.id) {
|
||||
startBinding = {
|
||||
...startBinding,
|
||||
elementId: container.id,
|
||||
};
|
||||
}
|
||||
|
||||
if (endBinding?.elementId === textElement.id) {
|
||||
endBinding = { ...endBinding, elementId: container.id };
|
||||
}
|
||||
|
||||
if (startBinding || endBinding) {
|
||||
mutateElement(ele, { startBinding, endBinding });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// update bindings
|
||||
if (textElement.boundElements?.length) {
|
||||
const linearElementIds = textElement.boundElements
|
||||
.filter((ele) => ele.type === "arrow")
|
||||
.map((el) => el.id);
|
||||
const linearElements = updatedElements.filter((ele) =>
|
||||
linearElementIds.includes(ele.id),
|
||||
) as ExcalidrawLinearElement[];
|
||||
linearElements.forEach((ele) => {
|
||||
let startBinding = ele.startBinding;
|
||||
let endBinding = ele.endBinding;
|
||||
mutateElement(textElement, {
|
||||
containerId: container.id,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
boundElements: null,
|
||||
});
|
||||
redrawTextBoundingBox(textElement, container);
|
||||
|
||||
if (startBinding?.elementId === textElement.id) {
|
||||
startBinding = {
|
||||
...startBinding,
|
||||
elementId: container.id,
|
||||
};
|
||||
}
|
||||
|
||||
if (endBinding?.elementId === textElement.id) {
|
||||
endBinding = { ...endBinding, elementId: container.id };
|
||||
}
|
||||
|
||||
if (startBinding || endBinding) {
|
||||
mutateElement(ele, { startBinding, endBinding }, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mutateElement(
|
||||
textElement,
|
||||
{
|
||||
containerId: container.id,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
boundElements: null,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
},
|
||||
false,
|
||||
);
|
||||
redrawTextBoundingBox(textElement, container);
|
||||
|
||||
updatedElements = pushContainerBelowText(
|
||||
[...updatedElements, container],
|
||||
return {
|
||||
elements: pushContainerBelowText(
|
||||
[...elements, container],
|
||||
container,
|
||||
textElement,
|
||||
);
|
||||
containerIds[container.id] = true;
|
||||
}
|
||||
),
|
||||
appState: {
|
||||
...appState,
|
||||
selectedElementIds: {
|
||||
[container.id]: true,
|
||||
[textElement.id]: false,
|
||||
},
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
elements: updatedElements,
|
||||
appState: {
|
||||
...appState,
|
||||
selectedElementIds: containerIds,
|
||||
},
|
||||
appState,
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -226,7 +226,7 @@ const zoomValueToFitBoundsOnViewport = (
|
||||
return clampedZoomValueToFitElements as NormalizedZoomValue;
|
||||
};
|
||||
|
||||
export const zoomToFitElements = (
|
||||
const zoomToFitElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: Readonly<AppState>,
|
||||
zoomToSelection: boolean,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AppState } from "../../src/types";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { ButtonIconSelect } from "../components/ButtonIconSelect";
|
||||
import { ColorPicker } from "../components/ColorPicker";
|
||||
import { IconPicker } from "../components/IconPicker";
|
||||
@@ -38,7 +37,6 @@ import {
|
||||
TextAlignLeftIcon,
|
||||
TextAlignCenterIcon,
|
||||
TextAlignRightIcon,
|
||||
FillZigZagIcon,
|
||||
} from "../components/icons";
|
||||
import {
|
||||
DEFAULT_FONT_FAMILY,
|
||||
@@ -56,8 +54,8 @@ import { mutateElement, newElementWith } from "../element/mutateElement";
|
||||
import {
|
||||
getBoundTextElement,
|
||||
getContainerElement,
|
||||
getDefaultLineHeight,
|
||||
} from "../element/textElement";
|
||||
import { getDefaultLineHeight } from "../element/textMeasurements";
|
||||
import {
|
||||
isBoundToContainer,
|
||||
isLinearElement,
|
||||
@@ -296,12 +294,7 @@ export const actionChangeBackgroundColor = register({
|
||||
export const actionChangeFillStyle = register({
|
||||
name: "changeFillStyle",
|
||||
trackEvent: false,
|
||||
perform: (elements, appState, value, app) => {
|
||||
trackEvent(
|
||||
"element",
|
||||
"changeFillStyle",
|
||||
`${value} (${app.device.isMobile ? "mobile" : "desktop"})`,
|
||||
);
|
||||
perform: (elements, appState, value) => {
|
||||
return {
|
||||
elements: changeProperty(elements, appState, (el) =>
|
||||
newElementWith(el, {
|
||||
@@ -312,55 +305,40 @@ export const actionChangeFillStyle = register({
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const allElementsZigZag = selectedElements.every(
|
||||
(el) => el.fillStyle === "zigzag",
|
||||
);
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fill")}</legend>
|
||||
<ButtonIconSelect
|
||||
type="button"
|
||||
options={[
|
||||
{
|
||||
value: "hachure",
|
||||
text: t("labels.hachure"),
|
||||
icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
|
||||
active: allElementsZigZag ? true : undefined,
|
||||
},
|
||||
{
|
||||
value: "cross-hatch",
|
||||
text: t("labels.crossHatch"),
|
||||
icon: FillCrossHatchIcon,
|
||||
},
|
||||
{
|
||||
value: "solid",
|
||||
text: t("labels.solid"),
|
||||
icon: FillSolidIcon,
|
||||
},
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => element.fillStyle,
|
||||
appState.currentItemFillStyle,
|
||||
)}
|
||||
onClick={(value, event) => {
|
||||
const nextValue =
|
||||
event.altKey &&
|
||||
value === "hachure" &&
|
||||
selectedElements.every((el) => el.fillStyle === "hachure")
|
||||
? "zigzag"
|
||||
: value;
|
||||
|
||||
updateData(nextValue);
|
||||
}}
|
||||
/>
|
||||
</fieldset>
|
||||
);
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fill")}</legend>
|
||||
<ButtonIconSelect
|
||||
options={[
|
||||
{
|
||||
value: "hachure",
|
||||
text: t("labels.hachure"),
|
||||
icon: FillHachureIcon,
|
||||
},
|
||||
{
|
||||
value: "cross-hatch",
|
||||
text: t("labels.crossHatch"),
|
||||
icon: FillCrossHatchIcon,
|
||||
},
|
||||
{
|
||||
value: "solid",
|
||||
text: t("labels.solid"),
|
||||
icon: FillSolidIcon,
|
||||
},
|
||||
]}
|
||||
group="fill"
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
(element) => element.fillStyle,
|
||||
appState.currentItemFillStyle,
|
||||
)}
|
||||
onChange={(value) => {
|
||||
updateData(value);
|
||||
}}
|
||||
/>
|
||||
</fieldset>
|
||||
),
|
||||
});
|
||||
|
||||
export const actionChangeStrokeWidth = register({
|
||||
|
||||
@@ -12,16 +12,14 @@ import {
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
} from "../constants";
|
||||
import {
|
||||
getBoundTextElement,
|
||||
getDefaultLineHeight,
|
||||
} from "../element/textElement";
|
||||
import { getBoundTextElement } from "../element/textElement";
|
||||
import {
|
||||
hasBoundTextElement,
|
||||
canApplyRoundnessTypeToElement,
|
||||
getDefaultRoundnessTypeForElement,
|
||||
} from "../element/typeChecks";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { getDefaultLineHeight } from "../element/textMeasurements";
|
||||
|
||||
// `copiedStyles` is exported only for tests.
|
||||
export let copiedStyles: string = "{}";
|
||||
|
||||
@@ -115,7 +115,7 @@ export type ActionName =
|
||||
| "toggleLinearEditor"
|
||||
| "toggleEraserTool"
|
||||
| "toggleHandTool"
|
||||
| "wrapTextInContainer";
|
||||
| "createContainerFromText";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
||||
+22
-30
@@ -1,30 +1,22 @@
|
||||
export const trackEvent = (
|
||||
category: string,
|
||||
action: string,
|
||||
label?: string,
|
||||
value?: number,
|
||||
) => {
|
||||
try {
|
||||
// Uncomment the next line to track locally
|
||||
// console.log("Track Event", { category, action, label, value });
|
||||
|
||||
if (typeof window === "undefined" || process.env.JEST_WORKER_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID && window.gtag) {
|
||||
window.gtag("event", action, {
|
||||
event_category: category,
|
||||
event_label: label,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
// MATOMO event tracking _paq must be same as the one in index.html
|
||||
if (window._paq) {
|
||||
window._paq.push(["trackEvent", category, action, label, value]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("error during analytics", error);
|
||||
}
|
||||
};
|
||||
export const trackEvent =
|
||||
typeof process !== "undefined" &&
|
||||
process.env?.REACT_APP_GOOGLE_ANALYTICS_ID &&
|
||||
typeof window !== "undefined" &&
|
||||
window.gtag
|
||||
? (category: string, action: string, label?: string, value?: number) => {
|
||||
try {
|
||||
window.gtag("event", action, {
|
||||
event_category: category,
|
||||
event_label: label,
|
||||
value,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error logging to ga", error);
|
||||
}
|
||||
}
|
||||
: typeof process !== "undefined" && process.env?.JEST_WORKER_ID
|
||||
? (category: string, action: string, label?: string, value?: number) => {}
|
||||
: (category: string, action: string, label?: string, value?: number) => {
|
||||
// Uncomment the next line to track locally
|
||||
// console.log("Track Event", { category, action, label, value });
|
||||
};
|
||||
|
||||
+8
-9
@@ -1,6 +1,5 @@
|
||||
import oc from "open-color";
|
||||
import {
|
||||
DEFAULT_ELEMENT_PROPS,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
@@ -24,18 +23,18 @@ export const getDefaultAppState = (): Omit<
|
||||
theme: THEME.LIGHT,
|
||||
collaborators: new Map(),
|
||||
currentChartType: "bar",
|
||||
currentItemBackgroundColor: DEFAULT_ELEMENT_PROPS.backgroundColor,
|
||||
currentItemBackgroundColor: "transparent",
|
||||
currentItemEndArrowhead: "arrow",
|
||||
currentItemFillStyle: DEFAULT_ELEMENT_PROPS.fillStyle,
|
||||
currentItemFillStyle: "hachure",
|
||||
currentItemFontFamily: DEFAULT_FONT_FAMILY,
|
||||
currentItemFontSize: DEFAULT_FONT_SIZE,
|
||||
currentItemOpacity: DEFAULT_ELEMENT_PROPS.opacity,
|
||||
currentItemRoughness: DEFAULT_ELEMENT_PROPS.roughness,
|
||||
currentItemOpacity: 100,
|
||||
currentItemRoughness: 1,
|
||||
currentItemStartArrowhead: null,
|
||||
currentItemStrokeColor: DEFAULT_ELEMENT_PROPS.strokeColor,
|
||||
currentItemStrokeColor: oc.black,
|
||||
currentItemRoundness: "round",
|
||||
currentItemStrokeStyle: DEFAULT_ELEMENT_PROPS.strokeStyle,
|
||||
currentItemStrokeWidth: DEFAULT_ELEMENT_PROPS.strokeWidth,
|
||||
currentItemStrokeStyle: "solid",
|
||||
currentItemStrokeWidth: 1,
|
||||
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
|
||||
cursorButton: "up",
|
||||
draggingElement: null,
|
||||
@@ -45,7 +44,7 @@ export const getDefaultAppState = (): Omit<
|
||||
activeTool: {
|
||||
type: "selection",
|
||||
customType: null,
|
||||
locked: DEFAULT_ELEMENT_PROPS.locked,
|
||||
locked: false,
|
||||
lastActiveTool: null,
|
||||
},
|
||||
penMode: false,
|
||||
|
||||
+17
-1
@@ -1,5 +1,10 @@
|
||||
import colors from "./colors";
|
||||
import { DEFAULT_FONT_SIZE, ENV } from "./constants";
|
||||
import {
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
ENV,
|
||||
VERTICAL_ALIGN,
|
||||
} from "./constants";
|
||||
import { newElement, newLinearElement, newTextElement } from "./element";
|
||||
import { NonDeletedExcalidrawElement } from "./element/types";
|
||||
import { randomId } from "./random";
|
||||
@@ -161,7 +166,17 @@ const bgColors = colors.elementBackground.slice(
|
||||
// Put all the common properties here so when the whole chart is selected
|
||||
// the properties dialog shows the correct selected values
|
||||
const commonProps = {
|
||||
fillStyle: "hachure",
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
opacity: 100,
|
||||
roughness: 1,
|
||||
strokeColor: colors.elementStroke[0],
|
||||
roundness: null,
|
||||
strokeStyle: "solid",
|
||||
strokeWidth: 1,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
locked: false,
|
||||
} as const;
|
||||
|
||||
const getChartDimentions = (spreadsheet: Spreadsheet) => {
|
||||
@@ -308,6 +323,7 @@ const chartBaseElements = (
|
||||
x: x + chartWidth / 2,
|
||||
y: y - BAR_HEIGHT - BAR_GAP * 2 - DEFAULT_FONT_SIZE,
|
||||
roundness: null,
|
||||
strokeStyle: "solid",
|
||||
textAlign: "center",
|
||||
})
|
||||
: null;
|
||||
|
||||
+53
-158
@@ -127,11 +127,7 @@ import {
|
||||
} from "../element/binding";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { mutateElement, newElementWith } from "../element/mutateElement";
|
||||
import {
|
||||
deepCopyElement,
|
||||
duplicateElements,
|
||||
newFreeDrawElement,
|
||||
} from "../element/newElement";
|
||||
import { deepCopyElement, newFreeDrawElement } from "../element/newElement";
|
||||
import {
|
||||
hasBoundTextElement,
|
||||
isArrowElement,
|
||||
@@ -233,7 +229,6 @@ import {
|
||||
updateActiveTool,
|
||||
getShortcutKey,
|
||||
isTransparent,
|
||||
easeToValuesRAF,
|
||||
} from "../utils";
|
||||
import {
|
||||
ContextMenu,
|
||||
@@ -265,18 +260,20 @@ import throttle from "lodash.throttle";
|
||||
import { fileOpen, FileSystemHandle } from "../data/filesystem";
|
||||
import {
|
||||
bindTextToShapeAfterDuplication,
|
||||
getApproxMinLineHeight,
|
||||
getApproxMinLineWidth,
|
||||
getBoundTextElement,
|
||||
getContainerCenter,
|
||||
getContainerDims,
|
||||
getContainerElement,
|
||||
getDefaultLineHeight,
|
||||
getLineHeightInPx,
|
||||
getTextBindableContainerAtPosition,
|
||||
isMeasureTextSupported,
|
||||
isValidTextContainer,
|
||||
} from "../element/textElement";
|
||||
import {
|
||||
getApproxMinContainerHeight,
|
||||
getApproxMinContainerWidth,
|
||||
isMeasureTextSupported,
|
||||
getLineHeightInPx,
|
||||
getDefaultLineHeight,
|
||||
} from "../element/textMeasurements";
|
||||
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
||||
import {
|
||||
normalizeLink,
|
||||
@@ -289,13 +286,10 @@ import {
|
||||
import { shouldShowBoundingBox } from "../element/transformHandles";
|
||||
import { Fonts } from "../scene/Fonts";
|
||||
import { actionPaste } from "../actions/actionClipboard";
|
||||
import {
|
||||
actionToggleHandTool,
|
||||
zoomToFitElements,
|
||||
} from "../actions/actionCanvas";
|
||||
import { actionToggleHandTool } from "../actions/actionCanvas";
|
||||
import { jotaiStore } from "../jotai";
|
||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||
|
||||
const deviceContextInitialValue = {
|
||||
@@ -1629,22 +1623,35 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
const dx = x - elementsCenterX;
|
||||
const dy = y - elementsCenterY;
|
||||
const groupIdMap = new Map();
|
||||
|
||||
const [gridX, gridY] = getGridPoint(dx, dy, this.state.gridSize);
|
||||
|
||||
const newElements = duplicateElements(
|
||||
elements.map((element) => {
|
||||
return newElementWith(element, {
|
||||
const oldIdToDuplicatedId = new Map();
|
||||
const newElements = elements.map((element) => {
|
||||
const newElement = duplicateElement(
|
||||
this.state.editingGroupId,
|
||||
groupIdMap,
|
||||
element,
|
||||
{
|
||||
x: element.x + gridX - minX,
|
||||
y: element.y + gridY - minY,
|
||||
});
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
oldIdToDuplicatedId.set(element.id, newElement.id);
|
||||
return newElement;
|
||||
});
|
||||
|
||||
bindTextToShapeAfterDuplication(newElements, elements, oldIdToDuplicatedId);
|
||||
const nextElements = [
|
||||
...this.scene.getElementsIncludingDeleted(),
|
||||
...newElements,
|
||||
];
|
||||
fixBindingsAfterDuplication(nextElements, elements, oldIdToDuplicatedId);
|
||||
|
||||
if (opts.files) {
|
||||
this.files = { ...this.files, ...opts.files };
|
||||
}
|
||||
|
||||
this.scene.replaceAllElements(nextElements);
|
||||
|
||||
@@ -1655,10 +1662,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.files) {
|
||||
this.files = { ...this.files, ...opts.files };
|
||||
}
|
||||
|
||||
this.history.resumeRecording();
|
||||
|
||||
this.setState(
|
||||
@@ -1842,89 +1845,18 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.actionManager.executeAction(actionToggleHandTool);
|
||||
};
|
||||
|
||||
/**
|
||||
* Zooms on canvas viewport center
|
||||
*/
|
||||
zoomCanvas = (
|
||||
/** decimal fraction between 0.1 (10% zoom) and 30 (3000% zoom) */
|
||||
value: number,
|
||||
) => {
|
||||
this.setState({
|
||||
...getStateForZoom(
|
||||
{
|
||||
viewportX: this.state.width / 2 + this.state.offsetLeft,
|
||||
viewportY: this.state.height / 2 + this.state.offsetTop,
|
||||
nextZoom: getNormalizedZoom(value),
|
||||
},
|
||||
this.state,
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
private cancelInProgresAnimation: (() => void) | null = null;
|
||||
|
||||
scrollToContent = (
|
||||
target:
|
||||
| ExcalidrawElement
|
||||
| readonly ExcalidrawElement[] = this.scene.getNonDeletedElements(),
|
||||
opts?: { fitToContent?: boolean; animate?: boolean; duration?: number },
|
||||
) => {
|
||||
this.cancelInProgresAnimation?.();
|
||||
|
||||
// convert provided target into ExcalidrawElement[] if necessary
|
||||
const targets = Array.isArray(target) ? target : [target];
|
||||
|
||||
let zoom = this.state.zoom;
|
||||
let scrollX = this.state.scrollX;
|
||||
let scrollY = this.state.scrollY;
|
||||
|
||||
if (opts?.fitToContent) {
|
||||
// compute an appropriate viewport location (scroll X, Y) and zoom level
|
||||
// that fit the target elements on the scene
|
||||
const { appState } = zoomToFitElements(targets, this.state, false);
|
||||
zoom = appState.zoom;
|
||||
scrollX = appState.scrollX;
|
||||
scrollY = appState.scrollY;
|
||||
} else {
|
||||
// compute only the viewport location, without any zoom adjustment
|
||||
const scroll = calculateScrollCenter(targets, this.state, this.canvas);
|
||||
scrollX = scroll.scrollX;
|
||||
scrollY = scroll.scrollY;
|
||||
}
|
||||
|
||||
// when animating, we use RequestAnimationFrame to prevent the animation
|
||||
// from slowing down other processes
|
||||
if (opts?.animate) {
|
||||
const origScrollX = this.state.scrollX;
|
||||
const origScrollY = this.state.scrollY;
|
||||
|
||||
// zoom animation could become problematic on scenes with large number
|
||||
// of elements, setting it to its final value to improve user experience.
|
||||
//
|
||||
// using zoomCanvas() to zoom on current viewport center
|
||||
this.zoomCanvas(zoom.value);
|
||||
|
||||
const cancel = easeToValuesRAF(
|
||||
[origScrollX, origScrollY],
|
||||
[scrollX, scrollY],
|
||||
(scrollX, scrollY) => this.setState({ scrollX, scrollY }),
|
||||
{ duration: opts?.duration ?? 500 },
|
||||
);
|
||||
this.cancelInProgresAnimation = () => {
|
||||
cancel();
|
||||
this.cancelInProgresAnimation = null;
|
||||
};
|
||||
} else {
|
||||
this.setState({ scrollX, scrollY, zoom });
|
||||
}
|
||||
};
|
||||
|
||||
/** use when changing scrollX/scrollY/zoom based on user interaction */
|
||||
private translateCanvas: React.Component<any, AppState>["setState"] = (
|
||||
state,
|
||||
) => {
|
||||
this.cancelInProgresAnimation?.();
|
||||
this.setState(state);
|
||||
this.setState({
|
||||
...calculateScrollCenter(
|
||||
Array.isArray(target) ? target : [target],
|
||||
this.state,
|
||||
this.canvas,
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
setToast = (
|
||||
@@ -2125,13 +2057,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||
offset = -offset;
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
this.translateCanvas((state) => ({
|
||||
scrollX: state.scrollX + offset,
|
||||
}));
|
||||
this.setState((state) => ({ scrollX: state.scrollX + offset }));
|
||||
} else {
|
||||
this.translateCanvas((state) => ({
|
||||
scrollY: state.scrollY + offset,
|
||||
}));
|
||||
this.setState((state) => ({ scrollY: state.scrollY + offset }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2696,11 +2624,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
fontSize,
|
||||
fontFamily,
|
||||
};
|
||||
const minWidth = getApproxMinLineWidth(
|
||||
const minWidth = getApproxMinContainerWidth(
|
||||
getFontString(fontString),
|
||||
lineHeight,
|
||||
);
|
||||
const minHeight = getApproxMinLineHeight(fontSize, lineHeight);
|
||||
const minHeight = getApproxMinContainerHeight(fontSize, lineHeight);
|
||||
const containerDims = getContainerDims(container);
|
||||
const newHeight = Math.max(containerDims.height, minHeight);
|
||||
const newWidth = Math.max(containerDims.width, minWidth);
|
||||
@@ -2732,6 +2660,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
strokeStyle: this.state.currentItemStrokeStyle,
|
||||
roughness: this.state.currentItemRoughness,
|
||||
opacity: this.state.currentItemOpacity,
|
||||
roundness: null,
|
||||
text: "",
|
||||
fontSize,
|
||||
fontFamily,
|
||||
@@ -2743,6 +2672,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
: DEFAULT_VERTICAL_ALIGN,
|
||||
containerId: shouldBindToContainer ? container?.id : undefined,
|
||||
groupIds: container?.groupIds ?? [],
|
||||
locked: false,
|
||||
lineHeight,
|
||||
});
|
||||
|
||||
@@ -3010,12 +2940,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
state,
|
||||
);
|
||||
|
||||
this.translateCanvas({
|
||||
return {
|
||||
zoom: zoomState.zoom,
|
||||
scrollX: zoomState.scrollX + deltaX / nextZoom,
|
||||
scrollY: zoomState.scrollY + deltaY / nextZoom,
|
||||
shouldCacheIgnoreZoom: true,
|
||||
});
|
||||
};
|
||||
});
|
||||
this.resetShouldCacheIgnoreZoomDebounced();
|
||||
} else {
|
||||
@@ -3493,43 +3423,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.setState({ contextMenu: null });
|
||||
}
|
||||
|
||||
this.updateGestureOnPointerDown(event);
|
||||
|
||||
// if dragging element is freedraw and another pointerdown event occurs
|
||||
// a second finger is on the screen
|
||||
// discard the freedraw element if it is very short because it is likely
|
||||
// just a spike, otherwise finalize the freedraw element when the second
|
||||
// finger is lifted
|
||||
if (
|
||||
event.pointerType === "touch" &&
|
||||
this.state.draggingElement &&
|
||||
this.state.draggingElement.type === "freedraw"
|
||||
) {
|
||||
const element = this.state.draggingElement as ExcalidrawFreeDrawElement;
|
||||
this.updateScene({
|
||||
...(element.points.length < 10
|
||||
? {
|
||||
elements: this.scene
|
||||
.getElementsIncludingDeleted()
|
||||
.filter((el) => el.id !== element.id),
|
||||
}
|
||||
: {}),
|
||||
appState: {
|
||||
draggingElement: null,
|
||||
editingElement: null,
|
||||
startBoundElement: null,
|
||||
suggestedBindings: [],
|
||||
selectedElementIds: Object.keys(this.state.selectedElementIds)
|
||||
.filter((key) => key !== element.id)
|
||||
.reduce((obj: { [id: string]: boolean }, key) => {
|
||||
obj[key] = this.state.selectedElementIds[key];
|
||||
return obj;
|
||||
}, {}),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// remove any active selection when we start to interact with canvas
|
||||
// (mainly, we care about removing selection outside the component which
|
||||
// would prevent our copy handling otherwise)
|
||||
@@ -3569,6 +3462,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
this.savePointer(event.clientX, event.clientY, "down");
|
||||
|
||||
this.updateGestureOnPointerDown(event);
|
||||
|
||||
if (this.handleCanvasPanUsingWheelOrSpaceDrag(event)) {
|
||||
return;
|
||||
}
|
||||
@@ -3826,7 +3721,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
window.addEventListener(EVENT.POINTER_UP, enableNextPaste);
|
||||
}
|
||||
|
||||
this.translateCanvas({
|
||||
this.setState({
|
||||
scrollX: this.state.scrollX - deltaX / this.state.zoom.value,
|
||||
scrollY: this.state.scrollY - deltaY / this.state.zoom.value,
|
||||
});
|
||||
@@ -4972,7 +4867,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (pointerDownState.scrollbars.isOverHorizontal) {
|
||||
const x = event.clientX;
|
||||
const dx = x - pointerDownState.lastCoords.x;
|
||||
this.translateCanvas({
|
||||
this.setState({
|
||||
scrollX: this.state.scrollX - dx / this.state.zoom.value,
|
||||
});
|
||||
pointerDownState.lastCoords.x = x;
|
||||
@@ -4982,7 +4877,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (pointerDownState.scrollbars.isOverVertical) {
|
||||
const y = event.clientY;
|
||||
const dy = y - pointerDownState.lastCoords.y;
|
||||
this.translateCanvas({
|
||||
this.setState({
|
||||
scrollY: this.state.scrollY - dy / this.state.zoom.value,
|
||||
});
|
||||
pointerDownState.lastCoords.y = y;
|
||||
@@ -6364,7 +6259,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
actionGroup,
|
||||
actionUnbindText,
|
||||
actionBindText,
|
||||
actionWrapTextInContainer,
|
||||
actionCreateContainerFromText,
|
||||
actionUngroup,
|
||||
CONTEXT_MENU_SEPARATOR,
|
||||
actionAddToLibrary,
|
||||
@@ -6411,7 +6306,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
// reduced amplification for small deltas (small movements on a trackpad)
|
||||
Math.min(1, absDelta / 20);
|
||||
|
||||
this.translateCanvas((state) => ({
|
||||
this.setState((state) => ({
|
||||
...getStateForZoom(
|
||||
{
|
||||
viewportX: cursorX,
|
||||
@@ -6428,14 +6323,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
// scroll horizontally when shift pressed
|
||||
if (event.shiftKey) {
|
||||
this.translateCanvas(({ zoom, scrollX }) => ({
|
||||
this.setState(({ zoom, scrollX }) => ({
|
||||
// on Mac, shift+wheel tends to result in deltaX
|
||||
scrollX: scrollX - (deltaY || deltaX) / zoom.value,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
this.translateCanvas(({ zoom, scrollX, scrollY }) => ({
|
||||
this.setState(({ zoom, scrollX, scrollY }) => ({
|
||||
scrollX: scrollX - deltaX / zoom.value,
|
||||
scrollY: scrollY - deltaY / zoom.value,
|
||||
}));
|
||||
|
||||
@@ -1,59 +1,33 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
// TODO: It might be "clever" to add option.icon to the existing component <ButtonSelect />
|
||||
export const ButtonIconSelect = <T extends Object>(
|
||||
props: {
|
||||
options: {
|
||||
value: T;
|
||||
text: string;
|
||||
icon: JSX.Element;
|
||||
testId?: string;
|
||||
/** if not supplied, defaults to value identity check */
|
||||
active?: boolean;
|
||||
}[];
|
||||
value: T | null;
|
||||
type?: "radio" | "button";
|
||||
} & (
|
||||
| { type?: "radio"; group: string; onChange: (value: T) => void }
|
||||
| {
|
||||
type: "button";
|
||||
onClick: (
|
||||
value: T,
|
||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
|
||||
) => void;
|
||||
}
|
||||
),
|
||||
) => (
|
||||
export const ButtonIconSelect = <T extends Object>({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
group,
|
||||
}: {
|
||||
options: { value: T; text: string; icon: JSX.Element; testId?: string }[];
|
||||
value: T | null;
|
||||
onChange: (value: T) => void;
|
||||
group: string;
|
||||
}) => (
|
||||
<div className="buttonList buttonListIcon">
|
||||
{props.options.map((option) =>
|
||||
props.type === "button" ? (
|
||||
<button
|
||||
key={option.text}
|
||||
onClick={(event) => props.onClick(option.value, event)}
|
||||
className={clsx({
|
||||
active: option.active ?? props.value === option.value,
|
||||
})}
|
||||
{options.map((option) => (
|
||||
<label
|
||||
key={option.text}
|
||||
className={clsx({ active: value === option.value })}
|
||||
title={option.text}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={group}
|
||||
onChange={() => onChange(option.value)}
|
||||
checked={value === option.value}
|
||||
data-testid={option.testId}
|
||||
title={option.text}
|
||||
>
|
||||
{option.icon}
|
||||
</button>
|
||||
) : (
|
||||
<label
|
||||
key={option.text}
|
||||
className={clsx({ active: props.value === option.value })}
|
||||
title={option.text}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={props.group}
|
||||
onChange={() => props.onChange(option.value)}
|
||||
checked={props.value === option.value}
|
||||
data-testid={option.testId}
|
||||
/>
|
||||
{option.icon}
|
||||
</label>
|
||||
),
|
||||
)}
|
||||
/>
|
||||
{option.icon}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -165,12 +165,11 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
||||
shortcuts={[KEYS.E, KEYS["0"]]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("helpDialog.editLineArrowPoints")}
|
||||
shortcuts={[getShortcutKey("CtrlOrCmd+Enter")]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("helpDialog.editText")}
|
||||
shortcuts={[getShortcutKey("Enter")]}
|
||||
label={t("helpDialog.editSelectedShape")}
|
||||
shortcuts={[
|
||||
getShortcutKey("CtrlOrCmd+Enter"),
|
||||
getShortcutKey(`CtrlOrCmd + ${t("helpDialog.doubleClick")}`),
|
||||
]}
|
||||
/>
|
||||
<Shortcut
|
||||
label={t("helpDialog.textNewLine")}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { canvasToBlob } from "../data/blob";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { exportToCanvas } from "../scene/export";
|
||||
import { AppState, BinaryFiles } from "../types";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { clipboard } from "./icons";
|
||||
@@ -14,7 +15,6 @@ import { CheckboxItem } from "./CheckboxItem";
|
||||
import { DEFAULT_EXPORT_PADDING, isFirefox } from "../constants";
|
||||
import { nativeFileSystemSupported } from "../data/filesystem";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { exportToCanvas } from "../packages/utils";
|
||||
|
||||
const supportsContextFilters =
|
||||
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||
@@ -83,6 +83,7 @@ const ImageExportModal = ({
|
||||
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
||||
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
||||
const previewRef = useRef<HTMLDivElement>(null);
|
||||
const { exportBackground, viewBackgroundColor } = appState;
|
||||
const [renderError, setRenderError] = useState<Error | null>(null);
|
||||
|
||||
const exportedElements = exportSelected
|
||||
@@ -98,16 +99,10 @@ const ImageExportModal = ({
|
||||
if (!previewNode) {
|
||||
return;
|
||||
}
|
||||
const maxWidth = previewNode.offsetWidth;
|
||||
if (!maxWidth) {
|
||||
return;
|
||||
}
|
||||
exportToCanvas({
|
||||
elements: exportedElements,
|
||||
appState,
|
||||
files,
|
||||
exportToCanvas(exportedElements, appState, files, {
|
||||
exportBackground,
|
||||
viewBackgroundColor,
|
||||
exportPadding,
|
||||
maxWidthOrHeight: maxWidth,
|
||||
})
|
||||
.then((canvas) => {
|
||||
setRenderError(null);
|
||||
@@ -121,7 +116,14 @@ const ImageExportModal = ({
|
||||
console.error(error);
|
||||
setRenderError(error);
|
||||
});
|
||||
}, [appState, files, exportedElements, exportPadding]);
|
||||
}, [
|
||||
appState,
|
||||
files,
|
||||
exportedElements,
|
||||
exportBackground,
|
||||
exportPadding,
|
||||
viewBackgroundColor,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="ExportDialog">
|
||||
|
||||
@@ -12,7 +12,6 @@ import { MIME_TYPES } from "../constants";
|
||||
import Spinner from "./Spinner";
|
||||
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
|
||||
import clsx from "clsx";
|
||||
import { duplicateElements } from "../element/newElement";
|
||||
|
||||
const CELLS_PER_ROW = 4;
|
||||
|
||||
@@ -97,14 +96,7 @@ const LibraryMenuItems = ({
|
||||
} else {
|
||||
targetElements = libraryItems.filter((item) => item.id === id);
|
||||
}
|
||||
return targetElements.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
// duplicate each library item before inserting on canvas to confine
|
||||
// ids and bindings to each library item. See #6465
|
||||
elements: duplicateElements(item.elements),
|
||||
};
|
||||
});
|
||||
return targetElements;
|
||||
};
|
||||
|
||||
const createLibraryItemCompo = (params: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import clsx from "clsx";
|
||||
import oc from "open-color";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useDevice } from "../components/App";
|
||||
import { exportToSvg } from "../packages/utils";
|
||||
import { exportToSvg } from "../scene/export";
|
||||
import { LibraryItem } from "../types";
|
||||
import "./LibraryUnit.scss";
|
||||
import { CheckboxItem } from "./CheckboxItem";
|
||||
@@ -36,14 +36,14 @@ export const LibraryUnit = ({
|
||||
if (!elements) {
|
||||
return;
|
||||
}
|
||||
const svg = await exportToSvg({
|
||||
const svg = await exportToSvg(
|
||||
elements,
|
||||
appState: {
|
||||
{
|
||||
exportBackground: false,
|
||||
viewBackgroundColor: oc.white,
|
||||
},
|
||||
files: null,
|
||||
});
|
||||
null,
|
||||
);
|
||||
svg.querySelector(".style-fonts")?.remove();
|
||||
node.innerHTML = svg.outerHTML;
|
||||
})();
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
padding: 5px 0 5px;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-65
@@ -29,21 +29,13 @@ export const Popover = ({
|
||||
}: Props) => {
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const container = popoverRef.current;
|
||||
const container = popoverRef.current;
|
||||
|
||||
useEffect(() => {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// focus popover only if the caller didn't focus on something else nested
|
||||
// within the popover, which should take precedence. Fixes cases
|
||||
// like color picker listening to keydown events on containers nested
|
||||
// in the popover.
|
||||
if (!container.contains(document.activeElement)) {
|
||||
container.focus();
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === KEYS.TAB) {
|
||||
const focusableElements = queryFocusableElements(container);
|
||||
@@ -52,23 +44,15 @@ export const Popover = ({
|
||||
(element) => element === activeElement,
|
||||
);
|
||||
|
||||
if (activeElement === container) {
|
||||
if (event.shiftKey) {
|
||||
focusableElements[focusableElements.length - 1]?.focus();
|
||||
} else {
|
||||
focusableElements[0].focus();
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
} else if (currentIndex === 0 && event.shiftKey) {
|
||||
focusableElements[focusableElements.length - 1]?.focus();
|
||||
if (currentIndex === 0 && event.shiftKey) {
|
||||
focusableElements[focusableElements.length - 1].focus();
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
} else if (
|
||||
currentIndex === focusableElements.length - 1 &&
|
||||
!event.shiftKey
|
||||
) {
|
||||
focusableElements[0]?.focus();
|
||||
focusableElements[0].focus();
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
@@ -78,59 +62,35 @@ export const Popover = ({
|
||||
container.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => container.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
const lastInitializedPosRef = useRef<{ top: number; left: number } | null>(
|
||||
null,
|
||||
);
|
||||
}, [container]);
|
||||
|
||||
// ensure the popover doesn't overflow the viewport
|
||||
useLayoutEffect(() => {
|
||||
if (fitInViewport && popoverRef.current && top != null && left != null) {
|
||||
const container = popoverRef.current;
|
||||
const { width, height } = container.getBoundingClientRect();
|
||||
if (fitInViewport && popoverRef.current) {
|
||||
const element = popoverRef.current;
|
||||
const { x, y, width, height } = element.getBoundingClientRect();
|
||||
|
||||
// hack for StrictMode so this effect only runs once for
|
||||
// the same top/left position, otherwise
|
||||
// we'd potentically reposition twice (once for viewport overflow)
|
||||
// and once for top/left position afterwards
|
||||
if (
|
||||
lastInitializedPosRef.current?.top === top &&
|
||||
lastInitializedPosRef.current?.left === left
|
||||
) {
|
||||
return;
|
||||
//Position correctly when clicked on rightmost part or the bottom part of viewport
|
||||
if (x + width - offsetLeft > viewportWidth) {
|
||||
element.style.left = `${viewportWidth - width - 10}px`;
|
||||
}
|
||||
lastInitializedPosRef.current = { top, left };
|
||||
|
||||
if (width >= viewportWidth) {
|
||||
container.style.width = `${viewportWidth}px`;
|
||||
container.style.left = "0px";
|
||||
container.style.overflowX = "scroll";
|
||||
} else if (left + width - offsetLeft > viewportWidth) {
|
||||
container.style.left = `${viewportWidth - width - 10}px`;
|
||||
} else {
|
||||
container.style.left = `${left}px`;
|
||||
if (y + height - offsetTop > viewportHeight) {
|
||||
element.style.top = `${viewportHeight - height}px`;
|
||||
}
|
||||
|
||||
//Resize to fit viewport on smaller screens
|
||||
if (height >= viewportHeight) {
|
||||
container.style.height = `${viewportHeight - 20}px`;
|
||||
container.style.top = "10px";
|
||||
container.style.overflowY = "scroll";
|
||||
} else if (top + height - offsetTop > viewportHeight) {
|
||||
container.style.top = `${viewportHeight - height}px`;
|
||||
} else {
|
||||
container.style.top = `${top}px`;
|
||||
element.style.height = `${viewportHeight - 20}px`;
|
||||
element.style.top = "10px";
|
||||
element.style.overflowY = "scroll";
|
||||
}
|
||||
if (width >= viewportWidth) {
|
||||
element.style.width = `${viewportWidth}px`;
|
||||
element.style.left = "0px";
|
||||
element.style.overflowX = "scroll";
|
||||
}
|
||||
}
|
||||
}, [
|
||||
top,
|
||||
left,
|
||||
fitInViewport,
|
||||
viewportWidth,
|
||||
viewportHeight,
|
||||
offsetLeft,
|
||||
offsetTop,
|
||||
]);
|
||||
}, [fitInViewport, viewportWidth, viewportHeight, offsetLeft, offsetTop]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onCloseRequest) {
|
||||
@@ -145,7 +105,7 @@ export const Popover = ({
|
||||
}, [onCloseRequest]);
|
||||
|
||||
return (
|
||||
<div className="popover" ref={popoverRef} tabIndex={-1}>
|
||||
<div className="popover" style={{ top, left }} ref={popoverRef}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -93,80 +93,4 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.single-library-item {
|
||||
position: relative;
|
||||
|
||||
&-status {
|
||||
position: absolute;
|
||||
top: 0.3rem;
|
||||
left: 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
color: $oc-red-7;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.1rem 0.2rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
&__svg {
|
||||
background-color: $oc-white;
|
||||
padding: 0.3rem;
|
||||
width: 7.5rem;
|
||||
height: 7.5rem;
|
||||
border: 1px solid var(--button-gray-2);
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ToolIcon__icon {
|
||||
background-color: $oc-white;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
.ToolIcon,
|
||||
.ToolIcon_type_button:hover {
|
||||
background-color: white;
|
||||
}
|
||||
.required,
|
||||
.error {
|
||||
color: $oc-red-8;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
margin: 0.2rem;
|
||||
}
|
||||
.error {
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
padding: 0.3em 0;
|
||||
}
|
||||
|
||||
&--remove {
|
||||
position: absolute;
|
||||
top: 0.2rem;
|
||||
right: 1rem;
|
||||
|
||||
.ToolIcon__icon {
|
||||
margin: 0;
|
||||
}
|
||||
.ToolIcon__icon {
|
||||
background-color: $oc-red-6;
|
||||
&:hover {
|
||||
background-color: $oc-red-7;
|
||||
}
|
||||
&:active {
|
||||
background-color: $oc-red-8;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
color: $oc-white;
|
||||
padding: 0.26rem;
|
||||
border-radius: 0.3em;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { ReactNode, useCallback, useEffect, useState } from "react";
|
||||
import OpenColor from "open-color";
|
||||
|
||||
import { Dialog } from "./Dialog";
|
||||
import { t } from "../i18n";
|
||||
|
||||
import { AppState, LibraryItems, LibraryItem } from "../types";
|
||||
import { exportToCanvas, exportToSvg } from "../packages/utils";
|
||||
import { exportToCanvas } from "../packages/utils";
|
||||
import {
|
||||
EXPORT_DATA_TYPES,
|
||||
EXPORT_SOURCE,
|
||||
@@ -13,13 +13,12 @@ import {
|
||||
VERSIONS,
|
||||
} from "../constants";
|
||||
import { ExportedLibraryData } from "../data/types";
|
||||
|
||||
import "./PublishLibrary.scss";
|
||||
import SingleLibraryItem from "./SingleLibraryItem";
|
||||
import { canvasToBlob, resizeImageFile } from "../data/blob";
|
||||
import { chunk } from "../utils";
|
||||
import DialogActionButton from "./DialogActionButton";
|
||||
import { CloseIcon } from "./icons";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
|
||||
import "./PublishLibrary.scss";
|
||||
|
||||
interface PublishLibraryDataParams {
|
||||
authorName: string;
|
||||
@@ -127,99 +126,6 @@ const generatePreviewImage = async (libraryItems: LibraryItems) => {
|
||||
);
|
||||
};
|
||||
|
||||
const SingleLibraryItem = ({
|
||||
libItem,
|
||||
appState,
|
||||
index,
|
||||
onChange,
|
||||
onRemove,
|
||||
}: {
|
||||
libItem: LibraryItem;
|
||||
appState: AppState;
|
||||
index: number;
|
||||
onChange: (val: string, index: number) => void;
|
||||
onRemove: (id: string) => void;
|
||||
}) => {
|
||||
const svgRef = useRef<HTMLDivElement | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const node = svgRef.current;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
const svg = await exportToSvg({
|
||||
elements: libItem.elements,
|
||||
appState: {
|
||||
...appState,
|
||||
viewBackgroundColor: OpenColor.white,
|
||||
exportBackground: true,
|
||||
},
|
||||
files: null,
|
||||
});
|
||||
node.innerHTML = svg.outerHTML;
|
||||
})();
|
||||
}, [libItem.elements, appState]);
|
||||
|
||||
return (
|
||||
<div className="single-library-item">
|
||||
{libItem.status === "published" && (
|
||||
<span className="single-library-item-status">
|
||||
{t("labels.statusPublished")}
|
||||
</span>
|
||||
)}
|
||||
<div ref={svgRef} className="single-library-item__svg" />
|
||||
<ToolButton
|
||||
aria-label={t("buttons.remove")}
|
||||
type="button"
|
||||
icon={CloseIcon}
|
||||
className="single-library-item--remove"
|
||||
onClick={onRemove.bind(null, libItem.id)}
|
||||
title={t("buttons.remove")}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
margin: "0.8rem 0",
|
||||
width: "100%",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: "0.5em 0" }}>
|
||||
<span style={{ fontWeight: 500, color: OpenColor.gray[6] }}>
|
||||
{t("publishDialog.itemName")}
|
||||
</span>
|
||||
<span aria-hidden="true" className="required">
|
||||
*
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
style={{ width: "80%", padding: "0.2rem" }}
|
||||
defaultValue={libItem.name}
|
||||
placeholder="Item name"
|
||||
onChange={(event) => {
|
||||
onChange(event.target.value, index);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<span className="error">{libItem.error}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PublishLibrary = ({
|
||||
onClose,
|
||||
libraryItems,
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
@import "../css/variables.module";
|
||||
|
||||
.excalidraw {
|
||||
.single-library-item {
|
||||
position: relative;
|
||||
|
||||
&-status {
|
||||
position: absolute;
|
||||
top: 0.3rem;
|
||||
left: 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
color: $oc-red-7;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.1rem 0.2rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
&__svg {
|
||||
background-color: $oc-white;
|
||||
padding: 0.3rem;
|
||||
width: 7.5rem;
|
||||
height: 7.5rem;
|
||||
border: 1px solid var(--button-gray-2);
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ToolIcon__icon {
|
||||
background-color: $oc-white;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
.ToolIcon,
|
||||
.ToolIcon_type_button:hover {
|
||||
background-color: white;
|
||||
}
|
||||
.required,
|
||||
.error {
|
||||
color: $oc-red-8;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
margin: 0.2rem;
|
||||
}
|
||||
.error {
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
padding: 0.3em 0;
|
||||
}
|
||||
|
||||
&--remove {
|
||||
position: absolute;
|
||||
top: 0.2rem;
|
||||
right: 1rem;
|
||||
|
||||
.ToolIcon__icon {
|
||||
margin: 0;
|
||||
}
|
||||
.ToolIcon__icon {
|
||||
background-color: $oc-red-6;
|
||||
&:hover {
|
||||
background-color: $oc-red-7;
|
||||
}
|
||||
&:active {
|
||||
background-color: $oc-red-8;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
color: $oc-white;
|
||||
padding: 0.26rem;
|
||||
border-radius: 0.3em;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import oc from "open-color";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { t } from "../i18n";
|
||||
import { exportToSvg } from "../packages/utils";
|
||||
import { AppState, LibraryItem } from "../types";
|
||||
import { CloseIcon } from "./icons";
|
||||
|
||||
import "./SingleLibraryItem.scss";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
|
||||
const SingleLibraryItem = ({
|
||||
libItem,
|
||||
appState,
|
||||
index,
|
||||
onChange,
|
||||
onRemove,
|
||||
}: {
|
||||
libItem: LibraryItem;
|
||||
appState: AppState;
|
||||
index: number;
|
||||
onChange: (val: string, index: number) => void;
|
||||
onRemove: (id: string) => void;
|
||||
}) => {
|
||||
const svgRef = useRef<HTMLDivElement | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const node = svgRef.current;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
const svg = await exportToSvg({
|
||||
elements: libItem.elements,
|
||||
appState: {
|
||||
...appState,
|
||||
viewBackgroundColor: oc.white,
|
||||
exportBackground: true,
|
||||
},
|
||||
files: null,
|
||||
});
|
||||
node.innerHTML = svg.outerHTML;
|
||||
})();
|
||||
}, [libItem.elements, appState]);
|
||||
|
||||
return (
|
||||
<div className="single-library-item">
|
||||
{libItem.status === "published" && (
|
||||
<span className="single-library-item-status">
|
||||
{t("labels.statusPublished")}
|
||||
</span>
|
||||
)}
|
||||
<div ref={svgRef} className="single-library-item__svg" />
|
||||
<ToolButton
|
||||
aria-label={t("buttons.remove")}
|
||||
type="button"
|
||||
icon={CloseIcon}
|
||||
className="single-library-item--remove"
|
||||
onClick={onRemove.bind(null, libItem.id)}
|
||||
title={t("buttons.remove")}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
margin: "0.8rem 0",
|
||||
width: "100%",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: "0.5em 0" }}>
|
||||
<span style={{ fontWeight: 500, color: oc.gray[6] }}>
|
||||
{t("publishDialog.itemName")}
|
||||
</span>
|
||||
<span aria-hidden="true" className="required">
|
||||
*
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
style={{ width: "80%", padding: "0.2rem" }}
|
||||
defaultValue={libItem.name}
|
||||
placeholder="Item name"
|
||||
onChange={(event) => {
|
||||
onChange(event.target.value, index);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<span className="error">{libItem.error}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SingleLibraryItem;
|
||||
@@ -1008,13 +1008,6 @@ export const UngroupIcon = React.memo(({ theme }: { theme: Theme }) =>
|
||||
),
|
||||
);
|
||||
|
||||
export const FillZigZagIcon = createIcon(
|
||||
<g strokeWidth={1.25}>
|
||||
<path d="M5.879 2.625h8.242a3.27 3.27 0 0 1 3.254 3.254v8.242a3.27 3.27 0 0 1-3.254 3.254H5.88a3.27 3.27 0 0 1-3.254-3.254V5.88A3.27 3.27 0 0 1 5.88 2.626l-.001-.001ZM4.518 16.118l7.608-12.83m.198 13.934 5.051-9.897M2.778 9.675l9.348-6.387m-7.608 12.83 12.857-8.793" />
|
||||
</g>,
|
||||
modifiedTablerIconProps,
|
||||
);
|
||||
|
||||
export const FillHachureIcon = createIcon(
|
||||
<>
|
||||
<path
|
||||
|
||||
+1
-22
@@ -1,7 +1,6 @@
|
||||
import cssVariables from "./css/variables.module.scss";
|
||||
import { AppProps } from "./types";
|
||||
import { ExcalidrawElement, FontFamilyValues } from "./element/types";
|
||||
import oc from "open-color";
|
||||
import { FontFamilyValues } from "./element/types";
|
||||
|
||||
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
export const isWindows = /^Win/.test(navigator.platform);
|
||||
@@ -255,23 +254,3 @@ export const ROUNDNESS = {
|
||||
/** key containt id of precedeing elemnt id we use in reconciliation during
|
||||
* collaboration */
|
||||
export const PRECEDING_ELEMENT_KEY = "__precedingElement__";
|
||||
|
||||
export const DEFAULT_ELEMENT_PROPS: {
|
||||
strokeColor: ExcalidrawElement["strokeColor"];
|
||||
backgroundColor: ExcalidrawElement["backgroundColor"];
|
||||
fillStyle: ExcalidrawElement["fillStyle"];
|
||||
strokeWidth: ExcalidrawElement["strokeWidth"];
|
||||
strokeStyle: ExcalidrawElement["strokeStyle"];
|
||||
roughness: ExcalidrawElement["roughness"];
|
||||
opacity: ExcalidrawElement["opacity"];
|
||||
locked: ExcalidrawElement["locked"];
|
||||
} = {
|
||||
strokeColor: oc.black,
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 1,
|
||||
strokeStyle: "solid",
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
locked: false,
|
||||
};
|
||||
|
||||
@@ -155,9 +155,6 @@
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.welcome-screen-menu-item:focus-visible,
|
||||
.dropdown-menu-item:focus-visible,
|
||||
button:focus-visible,
|
||||
.buttonList label:focus-within,
|
||||
input:focus-visible {
|
||||
outline: transparent;
|
||||
|
||||
+16
-34
@@ -31,15 +31,14 @@ import {
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { bumpVersion } from "../element/mutateElement";
|
||||
import { getFontString, getUpdatedTimestamp, updateActiveTool } from "../utils";
|
||||
import { getUpdatedTimestamp, updateActiveTool } from "../utils";
|
||||
import { arrayToMap } from "../utils";
|
||||
import oc from "open-color";
|
||||
import { MarkOptional, Mutable } from "../utility-types";
|
||||
import {
|
||||
detectLineHeight,
|
||||
getDefaultLineHeight,
|
||||
measureBaseline,
|
||||
} from "../element/textElement";
|
||||
} from "../element/textMeasurements";
|
||||
|
||||
type RestoredAppState = Omit<
|
||||
AppState,
|
||||
@@ -175,24 +174,6 @@ const restoreElement = (
|
||||
}
|
||||
const text = element.text ?? "";
|
||||
|
||||
// line-height might not be specified either when creating elements
|
||||
// programmatically, or when importing old diagrams.
|
||||
// For the latter we want to detect the original line height which
|
||||
// will likely differ from our per-font fixed line height we now use,
|
||||
// to maintain backward compatibility.
|
||||
const lineHeight =
|
||||
element.lineHeight ||
|
||||
(element.height
|
||||
? // detect line-height from current element height and font-size
|
||||
detectLineHeight(element)
|
||||
: // no element height likely means programmatic use, so default
|
||||
// to a fixed line height
|
||||
getDefaultLineHeight(element.fontFamily));
|
||||
const baseline = measureBaseline(
|
||||
element.text,
|
||||
getFontString(element),
|
||||
lineHeight,
|
||||
);
|
||||
element = restoreElementWithProperties(element, {
|
||||
fontSize,
|
||||
fontFamily,
|
||||
@@ -201,9 +182,19 @@ const restoreElement = (
|
||||
verticalAlign: element.verticalAlign || DEFAULT_VERTICAL_ALIGN,
|
||||
containerId: element.containerId ?? null,
|
||||
originalText: element.originalText || text,
|
||||
|
||||
lineHeight,
|
||||
baseline,
|
||||
// line-height might not be specified either when creating elements
|
||||
// programmatically, or when importing old diagrams.
|
||||
// For the latter we want to detect the original line height which
|
||||
// will likely differ from our per-font fixed line height we now use,
|
||||
// to maintain backward compatibility.
|
||||
lineHeight:
|
||||
element.lineHeight ||
|
||||
(element.height
|
||||
? // detect line-height from current element height and font-size
|
||||
detectLineHeight(element)
|
||||
: // no element height likely means programmatic use, so default
|
||||
// to a fixed line height
|
||||
getDefaultLineHeight(element.fontFamily)),
|
||||
});
|
||||
|
||||
if (refreshDimensions) {
|
||||
@@ -369,9 +360,6 @@ export const restoreElements = (
|
||||
localElements: readonly ExcalidrawElement[] | null | undefined,
|
||||
opts?: { refreshDimensions?: boolean; repairBindings?: boolean } | undefined,
|
||||
): ExcalidrawElement[] => {
|
||||
// used to detect duplicate top-level element ids
|
||||
const existingIds = new Set<string>();
|
||||
|
||||
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
||||
const restoredElements = (elements || []).reduce((elements, element) => {
|
||||
// filtering out selection, which is legacy, no longer kept in elements,
|
||||
@@ -386,10 +374,6 @@ export const restoreElements = (
|
||||
if (localElement && localElement.version > migratedElement.version) {
|
||||
migratedElement = bumpVersion(migratedElement, localElement.version);
|
||||
}
|
||||
if (existingIds.has(migratedElement.id)) {
|
||||
migratedElement = { ...migratedElement, id: randomId() };
|
||||
}
|
||||
existingIds.add(migratedElement.id);
|
||||
elements.push(migratedElement);
|
||||
}
|
||||
}
|
||||
@@ -514,9 +498,7 @@ export const restoreAppState = (
|
||||
? {
|
||||
value: appState.zoom as NormalizedZoomValue,
|
||||
}
|
||||
: appState.zoom?.value
|
||||
? appState.zoom
|
||||
: defaultAppState.zoom,
|
||||
: 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:
|
||||
|
||||
+64
-339
@@ -1,9 +1,8 @@
|
||||
import { duplicateElement, duplicateElements } from "./newElement";
|
||||
import { duplicateElement } from "./newElement";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { API } from "../tests/helpers/api";
|
||||
import { FONT_FAMILY, ROUNDNESS } from "../constants";
|
||||
import { isPrimitive } from "../utils";
|
||||
import { ExcalidrawLinearElement } from "./types";
|
||||
|
||||
const assertCloneObjects = (source: any, clone: any) => {
|
||||
for (const key in clone) {
|
||||
@@ -16,353 +15,79 @@ const assertCloneObjects = (source: any, clone: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
describe("duplicating single elements", () => {
|
||||
it("clones arrow element", () => {
|
||||
const element = API.createElement({
|
||||
type: "arrow",
|
||||
x: 0,
|
||||
y: 0,
|
||||
strokeColor: "#000000",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 1,
|
||||
strokeStyle: "solid",
|
||||
roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS },
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
element.__proto__ = { hello: "world" };
|
||||
|
||||
mutateElement(element, {
|
||||
points: [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
],
|
||||
});
|
||||
|
||||
const copy = duplicateElement(null, new Map(), element);
|
||||
|
||||
assertCloneObjects(element, copy);
|
||||
|
||||
// assert we clone the object's prototype
|
||||
// @ts-ignore
|
||||
expect(copy.__proto__).toEqual({ hello: "world" });
|
||||
expect(copy.hasOwnProperty("hello")).toBe(false);
|
||||
|
||||
expect(copy.points).not.toBe(element.points);
|
||||
expect(copy).not.toHaveProperty("shape");
|
||||
expect(copy.id).not.toBe(element.id);
|
||||
expect(typeof copy.id).toBe("string");
|
||||
expect(copy.seed).not.toBe(element.seed);
|
||||
expect(typeof copy.seed).toBe("number");
|
||||
expect(copy).toEqual({
|
||||
...element,
|
||||
id: copy.id,
|
||||
seed: copy.seed,
|
||||
});
|
||||
it("clones arrow element", () => {
|
||||
const element = API.createElement({
|
||||
type: "arrow",
|
||||
x: 0,
|
||||
y: 0,
|
||||
strokeColor: "#000000",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 1,
|
||||
strokeStyle: "solid",
|
||||
roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS },
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
});
|
||||
|
||||
it("clones text element", () => {
|
||||
const element = API.createElement({
|
||||
type: "text",
|
||||
x: 0,
|
||||
y: 0,
|
||||
strokeColor: "#000000",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 1,
|
||||
strokeStyle: "solid",
|
||||
roundness: null,
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
text: "hello",
|
||||
fontSize: 20,
|
||||
fontFamily: FONT_FAMILY.Virgil,
|
||||
textAlign: "left",
|
||||
verticalAlign: "top",
|
||||
});
|
||||
// @ts-ignore
|
||||
element.__proto__ = { hello: "world" };
|
||||
|
||||
const copy = duplicateElement(null, new Map(), element);
|
||||
mutateElement(element, {
|
||||
points: [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
],
|
||||
});
|
||||
|
||||
assertCloneObjects(element, copy);
|
||||
const copy = duplicateElement(null, new Map(), element);
|
||||
|
||||
expect(copy).not.toHaveProperty("points");
|
||||
expect(copy).not.toHaveProperty("shape");
|
||||
expect(copy.id).not.toBe(element.id);
|
||||
expect(typeof copy.id).toBe("string");
|
||||
expect(typeof copy.seed).toBe("number");
|
||||
assertCloneObjects(element, copy);
|
||||
|
||||
// @ts-ignore
|
||||
expect(copy.__proto__).toEqual({ hello: "world" });
|
||||
expect(copy.hasOwnProperty("hello")).toBe(false);
|
||||
|
||||
expect(copy.points).not.toBe(element.points);
|
||||
expect(copy).not.toHaveProperty("shape");
|
||||
expect(copy.id).not.toBe(element.id);
|
||||
expect(typeof copy.id).toBe("string");
|
||||
expect(copy.seed).not.toBe(element.seed);
|
||||
expect(typeof copy.seed).toBe("number");
|
||||
expect(copy).toEqual({
|
||||
...element,
|
||||
id: copy.id,
|
||||
seed: copy.seed,
|
||||
});
|
||||
});
|
||||
|
||||
describe("duplicating multiple elements", () => {
|
||||
it("duplicateElements should clone bindings", () => {
|
||||
const rectangle1 = API.createElement({
|
||||
type: "rectangle",
|
||||
id: "rectangle1",
|
||||
boundElements: [
|
||||
{ id: "arrow1", type: "arrow" },
|
||||
{ id: "arrow2", type: "arrow" },
|
||||
{ id: "text1", type: "text" },
|
||||
],
|
||||
});
|
||||
|
||||
const text1 = API.createElement({
|
||||
type: "text",
|
||||
id: "text1",
|
||||
containerId: "rectangle1",
|
||||
});
|
||||
|
||||
const arrow1 = API.createElement({
|
||||
type: "arrow",
|
||||
id: "arrow1",
|
||||
startBinding: {
|
||||
elementId: "rectangle1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
},
|
||||
});
|
||||
|
||||
const arrow2 = API.createElement({
|
||||
type: "arrow",
|
||||
id: "arrow2",
|
||||
endBinding: {
|
||||
elementId: "rectangle1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
},
|
||||
boundElements: [{ id: "text2", type: "text" }],
|
||||
});
|
||||
|
||||
const text2 = API.createElement({
|
||||
type: "text",
|
||||
id: "text2",
|
||||
containerId: "arrow2",
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const origElements = [rectangle1, text1, arrow1, arrow2, text2] as const;
|
||||
const clonedElements = duplicateElements(origElements);
|
||||
|
||||
// generic id in-equality checks
|
||||
// --------------------------------------------------------------------------
|
||||
expect(origElements.map((e) => e.type)).toEqual(
|
||||
clonedElements.map((e) => e.type),
|
||||
);
|
||||
origElements.forEach((origElement, idx) => {
|
||||
const clonedElement = clonedElements[idx];
|
||||
expect(origElement).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.not.stringMatching(clonedElement.id),
|
||||
type: clonedElement.type,
|
||||
}),
|
||||
);
|
||||
if ("containerId" in origElement) {
|
||||
expect(origElement.containerId).not.toBe(
|
||||
(clonedElement as any).containerId,
|
||||
);
|
||||
}
|
||||
if ("endBinding" in origElement) {
|
||||
if (origElement.endBinding) {
|
||||
expect(origElement.endBinding.elementId).not.toBe(
|
||||
(clonedElement as any).endBinding?.elementId,
|
||||
);
|
||||
} else {
|
||||
expect((clonedElement as any).endBinding).toBeNull();
|
||||
}
|
||||
}
|
||||
if ("startBinding" in origElement) {
|
||||
if (origElement.startBinding) {
|
||||
expect(origElement.startBinding.elementId).not.toBe(
|
||||
(clonedElement as any).startBinding?.elementId,
|
||||
);
|
||||
} else {
|
||||
expect((clonedElement as any).startBinding).toBeNull();
|
||||
}
|
||||
}
|
||||
});
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
const clonedArrows = clonedElements.filter(
|
||||
(e) => e.type === "arrow",
|
||||
) as ExcalidrawLinearElement[];
|
||||
|
||||
const [clonedRectangle, clonedText1, , clonedArrow2, clonedArrowLabel] =
|
||||
clonedElements as any as typeof origElements;
|
||||
|
||||
expect(clonedText1.containerId).toBe(clonedRectangle.id);
|
||||
expect(
|
||||
clonedRectangle.boundElements!.find((e) => e.id === clonedText1.id),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: clonedText1.id,
|
||||
type: clonedText1.type,
|
||||
}),
|
||||
);
|
||||
|
||||
clonedArrows.forEach((arrow) => {
|
||||
// console.log(arrow);
|
||||
expect(
|
||||
clonedRectangle.boundElements!.find((e) => e.id === arrow.id),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: arrow.id,
|
||||
type: arrow.type,
|
||||
}),
|
||||
);
|
||||
|
||||
if (arrow.endBinding) {
|
||||
expect(arrow.endBinding.elementId).toBe(clonedRectangle.id);
|
||||
}
|
||||
if (arrow.startBinding) {
|
||||
expect(arrow.startBinding.elementId).toBe(clonedRectangle.id);
|
||||
}
|
||||
});
|
||||
|
||||
expect(clonedArrow2.boundElements).toEqual([
|
||||
{ type: "text", id: clonedArrowLabel.id },
|
||||
]);
|
||||
expect(clonedArrowLabel.containerId).toBe(clonedArrow2.id);
|
||||
it("clones text element", () => {
|
||||
const element = API.createElement({
|
||||
type: "text",
|
||||
x: 0,
|
||||
y: 0,
|
||||
strokeColor: "#000000",
|
||||
backgroundColor: "transparent",
|
||||
fillStyle: "hachure",
|
||||
strokeWidth: 1,
|
||||
strokeStyle: "solid",
|
||||
roundness: null,
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
text: "hello",
|
||||
fontSize: 20,
|
||||
fontFamily: FONT_FAMILY.Virgil,
|
||||
textAlign: "left",
|
||||
verticalAlign: "top",
|
||||
});
|
||||
|
||||
it("should remove id references of elements that aren't found", () => {
|
||||
const rectangle1 = API.createElement({
|
||||
type: "rectangle",
|
||||
id: "rectangle1",
|
||||
boundElements: [
|
||||
// should keep
|
||||
{ id: "arrow1", type: "arrow" },
|
||||
// should drop
|
||||
{ id: "arrow-not-exists", type: "arrow" },
|
||||
// should drop
|
||||
{ id: "text-not-exists", type: "text" },
|
||||
],
|
||||
});
|
||||
const copy = duplicateElement(null, new Map(), element);
|
||||
|
||||
const arrow1 = API.createElement({
|
||||
type: "arrow",
|
||||
id: "arrow1",
|
||||
startBinding: {
|
||||
elementId: "rectangle1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
},
|
||||
});
|
||||
assertCloneObjects(element, copy);
|
||||
|
||||
const text1 = API.createElement({
|
||||
type: "text",
|
||||
id: "text1",
|
||||
containerId: "rectangle-not-exists",
|
||||
});
|
||||
|
||||
const arrow2 = API.createElement({
|
||||
type: "arrow",
|
||||
id: "arrow2",
|
||||
startBinding: {
|
||||
elementId: "rectangle1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
},
|
||||
endBinding: {
|
||||
elementId: "rectangle-not-exists",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
},
|
||||
});
|
||||
|
||||
const arrow3 = API.createElement({
|
||||
type: "arrow",
|
||||
id: "arrow2",
|
||||
startBinding: {
|
||||
elementId: "rectangle-not-exists",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
},
|
||||
endBinding: {
|
||||
elementId: "rectangle1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
},
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const origElements = [rectangle1, text1, arrow1, arrow2, arrow3] as const;
|
||||
const clonedElements = duplicateElements(
|
||||
origElements,
|
||||
) as any as typeof origElements;
|
||||
const [
|
||||
clonedRectangle,
|
||||
clonedText1,
|
||||
clonedArrow1,
|
||||
clonedArrow2,
|
||||
clonedArrow3,
|
||||
] = clonedElements;
|
||||
|
||||
expect(clonedRectangle.boundElements).toEqual([
|
||||
{ id: clonedArrow1.id, type: "arrow" },
|
||||
]);
|
||||
|
||||
expect(clonedText1.containerId).toBe(null);
|
||||
|
||||
expect(clonedArrow2.startBinding).toEqual({
|
||||
...arrow2.startBinding,
|
||||
elementId: clonedRectangle.id,
|
||||
});
|
||||
expect(clonedArrow2.endBinding).toBe(null);
|
||||
|
||||
expect(clonedArrow3.startBinding).toBe(null);
|
||||
expect(clonedArrow3.endBinding).toEqual({
|
||||
...arrow3.endBinding,
|
||||
elementId: clonedRectangle.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe("should duplicate all group ids", () => {
|
||||
it("should regenerate all group ids and keep them consistent across elements", () => {
|
||||
const rectangle1 = API.createElement({
|
||||
type: "rectangle",
|
||||
groupIds: ["g1"],
|
||||
});
|
||||
const rectangle2 = API.createElement({
|
||||
type: "rectangle",
|
||||
groupIds: ["g2", "g1"],
|
||||
});
|
||||
const rectangle3 = API.createElement({
|
||||
type: "rectangle",
|
||||
groupIds: ["g2", "g1"],
|
||||
});
|
||||
|
||||
const origElements = [rectangle1, rectangle2, rectangle3] as const;
|
||||
const clonedElements = duplicateElements(
|
||||
origElements,
|
||||
) as any as typeof origElements;
|
||||
const [clonedRectangle1, clonedRectangle2, clonedRectangle3] =
|
||||
clonedElements;
|
||||
|
||||
expect(rectangle1.groupIds[0]).not.toBe(clonedRectangle1.groupIds[0]);
|
||||
expect(rectangle2.groupIds[0]).not.toBe(clonedRectangle2.groupIds[0]);
|
||||
expect(rectangle2.groupIds[1]).not.toBe(clonedRectangle2.groupIds[1]);
|
||||
|
||||
expect(clonedRectangle1.groupIds[0]).toBe(clonedRectangle2.groupIds[1]);
|
||||
expect(clonedRectangle2.groupIds[0]).toBe(clonedRectangle3.groupIds[0]);
|
||||
expect(clonedRectangle2.groupIds[1]).toBe(clonedRectangle3.groupIds[1]);
|
||||
});
|
||||
|
||||
it("should keep and regenerate ids of groups even if invalid", () => {
|
||||
// lone element shouldn't be able to be grouped with itself,
|
||||
// but hard to check against in a performant way so we ignore it
|
||||
const rectangle1 = API.createElement({
|
||||
type: "rectangle",
|
||||
groupIds: ["g1"],
|
||||
});
|
||||
|
||||
const [clonedRectangle1] = duplicateElements([rectangle1]);
|
||||
|
||||
expect(typeof clonedRectangle1.groupIds[0]).toBe("string");
|
||||
expect(rectangle1.groupIds[0]).not.toBe(clonedRectangle1.groupIds[0]);
|
||||
});
|
||||
});
|
||||
expect(copy).not.toHaveProperty("points");
|
||||
expect(copy).not.toHaveProperty("shape");
|
||||
expect(copy.id).not.toBe(element.id);
|
||||
expect(typeof copy.id).toBe("string");
|
||||
expect(typeof copy.seed).toBe("number");
|
||||
});
|
||||
|
||||
+56
-242
@@ -13,12 +13,7 @@ import {
|
||||
FontFamilyValues,
|
||||
ExcalidrawTextContainer,
|
||||
} from "../element/types";
|
||||
import {
|
||||
arrayToMap,
|
||||
getFontString,
|
||||
getUpdatedTimestamp,
|
||||
isTestEnv,
|
||||
} from "../utils";
|
||||
import { getFontString, getUpdatedTimestamp, isTestEnv } from "../utils";
|
||||
import { randomInteger, randomId } from "../random";
|
||||
import { mutateElement, newElementWith } from "./mutateElement";
|
||||
import { getNewGroupIdsForDuplication } from "../groups";
|
||||
@@ -30,22 +25,17 @@ import {
|
||||
getBoundTextElementOffset,
|
||||
getContainerDims,
|
||||
getContainerElement,
|
||||
measureText,
|
||||
normalizeText,
|
||||
wrapText,
|
||||
getMaxContainerWidth,
|
||||
getDefaultLineHeight,
|
||||
getBoundTextMaxWidth,
|
||||
} from "./textElement";
|
||||
import {
|
||||
DEFAULT_ELEMENT_PROPS,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
DEFAULT_VERTICAL_ALIGN,
|
||||
VERTICAL_ALIGN,
|
||||
} from "../constants";
|
||||
import { VERTICAL_ALIGN } from "../constants";
|
||||
import { isArrowElement } from "./typeChecks";
|
||||
import { MarkOptional, Merge, Mutable } from "../utility-types";
|
||||
import {
|
||||
measureText,
|
||||
wrapText,
|
||||
getDefaultLineHeight,
|
||||
} from "./textMeasurements";
|
||||
|
||||
type ElementConstructorOpts = MarkOptional<
|
||||
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
||||
@@ -58,15 +48,6 @@ type ElementConstructorOpts = MarkOptional<
|
||||
| "version"
|
||||
| "versionNonce"
|
||||
| "link"
|
||||
| "strokeStyle"
|
||||
| "fillStyle"
|
||||
| "strokeColor"
|
||||
| "backgroundColor"
|
||||
| "roughness"
|
||||
| "strokeWidth"
|
||||
| "roundness"
|
||||
| "locked"
|
||||
| "opacity"
|
||||
>;
|
||||
|
||||
const _newElementBase = <T extends ExcalidrawElement>(
|
||||
@@ -74,13 +55,13 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
{
|
||||
x,
|
||||
y,
|
||||
strokeColor = DEFAULT_ELEMENT_PROPS.strokeColor,
|
||||
backgroundColor = DEFAULT_ELEMENT_PROPS.backgroundColor,
|
||||
fillStyle = DEFAULT_ELEMENT_PROPS.fillStyle,
|
||||
strokeWidth = DEFAULT_ELEMENT_PROPS.strokeWidth,
|
||||
strokeStyle = DEFAULT_ELEMENT_PROPS.strokeStyle,
|
||||
roughness = DEFAULT_ELEMENT_PROPS.roughness,
|
||||
opacity = DEFAULT_ELEMENT_PROPS.opacity,
|
||||
strokeColor,
|
||||
backgroundColor,
|
||||
fillStyle,
|
||||
strokeWidth,
|
||||
strokeStyle,
|
||||
roughness,
|
||||
opacity,
|
||||
width = 0,
|
||||
height = 0,
|
||||
angle = 0,
|
||||
@@ -88,7 +69,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
roundness = null,
|
||||
boundElements = null,
|
||||
link = null,
|
||||
locked = DEFAULT_ELEMENT_PROPS.locked,
|
||||
locked,
|
||||
...rest
|
||||
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
|
||||
) => {
|
||||
@@ -154,44 +135,30 @@ const getTextElementPositionOffsets = (
|
||||
export const newTextElement = (
|
||||
opts: {
|
||||
text: string;
|
||||
fontSize?: number;
|
||||
fontFamily?: FontFamilyValues;
|
||||
textAlign?: TextAlign;
|
||||
verticalAlign?: VerticalAlign;
|
||||
fontSize: number;
|
||||
fontFamily: FontFamilyValues;
|
||||
textAlign: TextAlign;
|
||||
verticalAlign: VerticalAlign;
|
||||
containerId?: ExcalidrawTextContainer["id"];
|
||||
lineHeight?: ExcalidrawTextElement["lineHeight"];
|
||||
strokeWidth?: ExcalidrawTextElement["strokeWidth"];
|
||||
} & ElementConstructorOpts,
|
||||
): NonDeleted<ExcalidrawTextElement> => {
|
||||
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
||||
const fontSize = opts.fontSize || DEFAULT_FONT_SIZE;
|
||||
const lineHeight = opts.lineHeight || getDefaultLineHeight(fontFamily);
|
||||
const lineHeight = opts.lineHeight || getDefaultLineHeight(opts.fontFamily);
|
||||
const text = normalizeText(opts.text);
|
||||
const metrics = measureText(
|
||||
text,
|
||||
getFontString({ fontFamily, fontSize }),
|
||||
lineHeight,
|
||||
);
|
||||
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
|
||||
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
||||
const offsets = getTextElementPositionOffsets(
|
||||
{ textAlign, verticalAlign },
|
||||
metrics,
|
||||
);
|
||||
|
||||
const metrics = measureText(text, getFontString(opts), lineHeight);
|
||||
const offsets = getTextElementPositionOffsets(opts, metrics);
|
||||
const textElement = newElementWith(
|
||||
{
|
||||
..._newElementBase<ExcalidrawTextElement>("text", opts),
|
||||
text,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
textAlign,
|
||||
verticalAlign,
|
||||
fontSize: opts.fontSize,
|
||||
fontFamily: opts.fontFamily,
|
||||
textAlign: opts.textAlign,
|
||||
verticalAlign: opts.verticalAlign,
|
||||
x: opts.x - offsets.x,
|
||||
y: opts.y - offsets.y,
|
||||
width: metrics.width,
|
||||
height: metrics.height,
|
||||
baseline: metrics.baseline,
|
||||
containerId: opts.containerId || null,
|
||||
originalText: text,
|
||||
lineHeight,
|
||||
@@ -209,15 +176,14 @@ const getAdjustedDimensions = (
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
baseline: number;
|
||||
} => {
|
||||
const container = getContainerElement(element);
|
||||
|
||||
const {
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
baseline: nextBaseline,
|
||||
} = measureText(nextText, getFontString(element), element.lineHeight);
|
||||
const { width: nextWidth, height: nextHeight } = measureText(
|
||||
nextText,
|
||||
getFontString(element),
|
||||
element.lineHeight,
|
||||
);
|
||||
const { textAlign, verticalAlign } = element;
|
||||
let x: number;
|
||||
let y: number;
|
||||
@@ -292,7 +258,6 @@ const getAdjustedDimensions = (
|
||||
return {
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
baseline: nextBaseline,
|
||||
x: Number.isFinite(x) ? x : element.x,
|
||||
y: Number.isFinite(y) ? y : element.y,
|
||||
};
|
||||
@@ -302,15 +267,12 @@ export const refreshTextDimensions = (
|
||||
textElement: ExcalidrawTextElement,
|
||||
text = textElement.text,
|
||||
) => {
|
||||
if (textElement.isDeleted) {
|
||||
return;
|
||||
}
|
||||
const container = getContainerElement(textElement);
|
||||
if (container) {
|
||||
text = wrapText(
|
||||
text,
|
||||
getFontString(textElement),
|
||||
getMaxContainerWidth(container),
|
||||
getBoundTextMaxWidth(container),
|
||||
);
|
||||
}
|
||||
const dimensions = getAdjustedDimensions(textElement, text);
|
||||
@@ -390,24 +352,16 @@ export const newImageElement = (
|
||||
};
|
||||
};
|
||||
|
||||
// Simplified deep clone for the purpose of cloning ExcalidrawElement.
|
||||
//
|
||||
// Only clones plain objects and arrays. Doesn't clone Date, RegExp, Map, Set,
|
||||
// Typed arrays and other non-null objects.
|
||||
// Simplified deep clone for the purpose of cloning ExcalidrawElement only
|
||||
// (doesn't clone Date, RegExp, Map, Set, Typed arrays etc.)
|
||||
//
|
||||
// Adapted from https://github.com/lukeed/klona
|
||||
//
|
||||
// The reason for `deepCopyElement()` wrapper is type safety (only allow
|
||||
// passing ExcalidrawElement as the top-level argument).
|
||||
const _deepCopyElement = (val: any, depth: number = 0) => {
|
||||
// only clone non-primitives
|
||||
export const deepCopyElement = (val: any, depth: number = 0) => {
|
||||
if (val == null || typeof val !== "object") {
|
||||
return val;
|
||||
}
|
||||
|
||||
const objectType = Object.prototype.toString.call(val);
|
||||
|
||||
if (objectType === "[object Object]") {
|
||||
if (Object.prototype.toString.call(val) === "[object Object]") {
|
||||
const tmp =
|
||||
typeof val.constructor === "function"
|
||||
? Object.create(Object.getPrototypeOf(val))
|
||||
@@ -419,7 +373,7 @@ const _deepCopyElement = (val: any, depth: number = 0) => {
|
||||
if (depth === 0 && (key === "shape" || key === "canvas")) {
|
||||
continue;
|
||||
}
|
||||
tmp[key] = _deepCopyElement(val[key], depth + 1);
|
||||
tmp[key] = deepCopyElement(val[key], depth + 1);
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
@@ -429,67 +383,14 @@ const _deepCopyElement = (val: any, depth: number = 0) => {
|
||||
let k = val.length;
|
||||
const arr = new Array(k);
|
||||
while (k--) {
|
||||
arr[k] = _deepCopyElement(val[k], depth + 1);
|
||||
arr[k] = deepCopyElement(val[k], depth + 1);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
// we're not cloning non-array & non-plain-object objects because we
|
||||
// don't support them on excalidraw elements yet. If we do, we need to make
|
||||
// sure we start cloning them, so let's warn about it.
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (
|
||||
objectType !== "[object Object]" &&
|
||||
objectType !== "[object Array]" &&
|
||||
objectType.startsWith("[object ")
|
||||
) {
|
||||
console.warn(
|
||||
`_deepCloneElement: unexpected object type ${objectType}. This value will not be cloned!`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clones ExcalidrawElement data structure. Does not regenerate id, nonce, or
|
||||
* any value. The purpose is to to break object references for immutability
|
||||
* reasons, whenever we want to keep the original element, but ensure it's not
|
||||
* mutated.
|
||||
*
|
||||
* Only clones plain objects and arrays. Doesn't clone Date, RegExp, Map, Set,
|
||||
* Typed arrays and other non-null objects.
|
||||
*/
|
||||
export const deepCopyElement = <T extends ExcalidrawElement>(
|
||||
val: T,
|
||||
): Mutable<T> => {
|
||||
return _deepCopyElement(val);
|
||||
};
|
||||
|
||||
/**
|
||||
* utility wrapper to generate new id. In test env it reuses the old + postfix
|
||||
* for test assertions.
|
||||
*/
|
||||
const regenerateId = (
|
||||
/** supply null if no previous id exists */
|
||||
previousId: string | null,
|
||||
) => {
|
||||
if (isTestEnv() && previousId) {
|
||||
let nextId = `${previousId}_copy`;
|
||||
// `window.h` may not be defined in some unit tests
|
||||
if (
|
||||
window.h?.app
|
||||
?.getSceneElementsIncludingDeleted()
|
||||
.find((el) => el.id === nextId)
|
||||
) {
|
||||
nextId += "_copy";
|
||||
}
|
||||
return nextId;
|
||||
}
|
||||
return randomId();
|
||||
};
|
||||
|
||||
/**
|
||||
* Duplicate an element, often used in the alt-drag operation.
|
||||
* Note that this method has gotten a bit complicated since the
|
||||
@@ -504,15 +405,27 @@ const regenerateId = (
|
||||
* @param element Element to duplicate
|
||||
* @param overrides Any element properties to override
|
||||
*/
|
||||
export const duplicateElement = <TElement extends ExcalidrawElement>(
|
||||
export const duplicateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||
editingGroupId: AppState["editingGroupId"],
|
||||
groupIdMapForOperation: Map<GroupId, GroupId>,
|
||||
element: TElement,
|
||||
overrides?: Partial<TElement>,
|
||||
): Readonly<TElement> => {
|
||||
let copy = deepCopyElement(element);
|
||||
): TElement => {
|
||||
let copy: TElement = deepCopyElement(element);
|
||||
|
||||
copy.id = regenerateId(copy.id);
|
||||
if (isTestEnv()) {
|
||||
copy.id = `${copy.id}_copy`;
|
||||
// `window.h` may not be defined in some unit tests
|
||||
if (
|
||||
window.h?.app
|
||||
?.getSceneElementsIncludingDeleted()
|
||||
.find((el) => el.id === copy.id)
|
||||
) {
|
||||
copy.id += "_copy";
|
||||
}
|
||||
} else {
|
||||
copy.id = randomId();
|
||||
}
|
||||
copy.boundElements = null;
|
||||
copy.updated = getUpdatedTimestamp();
|
||||
copy.seed = randomInteger();
|
||||
@@ -521,7 +434,7 @@ export const duplicateElement = <TElement extends ExcalidrawElement>(
|
||||
editingGroupId,
|
||||
(groupId) => {
|
||||
if (!groupIdMapForOperation.has(groupId)) {
|
||||
groupIdMapForOperation.set(groupId, regenerateId(groupId));
|
||||
groupIdMapForOperation.set(groupId, randomId());
|
||||
}
|
||||
return groupIdMapForOperation.get(groupId)!;
|
||||
},
|
||||
@@ -531,102 +444,3 @@ export const duplicateElement = <TElement extends ExcalidrawElement>(
|
||||
}
|
||||
return copy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clones elements, regenerating their ids (including bindings) and group ids.
|
||||
*
|
||||
* If bindings don't exist in the elements array, they are removed. Therefore,
|
||||
* it's advised to supply the whole elements array, or sets of elements that
|
||||
* are encapsulated (such as library items), if the purpose is to retain
|
||||
* bindings to the cloned elements intact.
|
||||
*/
|
||||
export const duplicateElements = (elements: readonly ExcalidrawElement[]) => {
|
||||
const clonedElements: ExcalidrawElement[] = [];
|
||||
|
||||
const origElementsMap = arrayToMap(elements);
|
||||
|
||||
// used for for migrating old ids to new ids
|
||||
const elementNewIdsMap = new Map<
|
||||
/* orig */ ExcalidrawElement["id"],
|
||||
/* new */ ExcalidrawElement["id"]
|
||||
>();
|
||||
|
||||
const maybeGetNewId = (id: ExcalidrawElement["id"]) => {
|
||||
// if we've already migrated the element id, return the new one directly
|
||||
if (elementNewIdsMap.has(id)) {
|
||||
return elementNewIdsMap.get(id)!;
|
||||
}
|
||||
// if we haven't migrated the element id, but an old element with the same
|
||||
// id exists, generate a new id for it and return it
|
||||
if (origElementsMap.has(id)) {
|
||||
const newId = regenerateId(id);
|
||||
elementNewIdsMap.set(id, newId);
|
||||
return newId;
|
||||
}
|
||||
// if old element doesn't exist, return null to mark it for removal
|
||||
return null;
|
||||
};
|
||||
|
||||
const groupNewIdsMap = new Map</* orig */ GroupId, /* new */ GroupId>();
|
||||
|
||||
for (const element of elements) {
|
||||
const clonedElement: Mutable<ExcalidrawElement> = _deepCopyElement(element);
|
||||
|
||||
clonedElement.id = maybeGetNewId(element.id)!;
|
||||
|
||||
if (clonedElement.groupIds) {
|
||||
clonedElement.groupIds = clonedElement.groupIds.map((groupId) => {
|
||||
if (!groupNewIdsMap.has(groupId)) {
|
||||
groupNewIdsMap.set(groupId, regenerateId(groupId));
|
||||
}
|
||||
return groupNewIdsMap.get(groupId)!;
|
||||
});
|
||||
}
|
||||
|
||||
if ("containerId" in clonedElement && clonedElement.containerId) {
|
||||
const newContainerId = maybeGetNewId(clonedElement.containerId);
|
||||
clonedElement.containerId = newContainerId;
|
||||
}
|
||||
|
||||
if ("boundElements" in clonedElement && clonedElement.boundElements) {
|
||||
clonedElement.boundElements = clonedElement.boundElements.reduce(
|
||||
(
|
||||
acc: Mutable<NonNullable<ExcalidrawElement["boundElements"]>>,
|
||||
binding,
|
||||
) => {
|
||||
const newBindingId = maybeGetNewId(binding.id);
|
||||
if (newBindingId) {
|
||||
acc.push({ ...binding, id: newBindingId });
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
if ("endBinding" in clonedElement && clonedElement.endBinding) {
|
||||
const newEndBindingId = maybeGetNewId(clonedElement.endBinding.elementId);
|
||||
clonedElement.endBinding = newEndBindingId
|
||||
? {
|
||||
...clonedElement.endBinding,
|
||||
elementId: newEndBindingId,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
if ("startBinding" in clonedElement && clonedElement.startBinding) {
|
||||
const newEndBindingId = maybeGetNewId(
|
||||
clonedElement.startBinding.elementId,
|
||||
);
|
||||
clonedElement.startBinding = newEndBindingId
|
||||
? {
|
||||
...clonedElement.startBinding,
|
||||
elementId: newEndBindingId,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
clonedElements.push(clonedElement);
|
||||
}
|
||||
|
||||
return clonedElements;
|
||||
};
|
||||
|
||||
@@ -39,17 +39,16 @@ import {
|
||||
import { Point, PointerDownState } from "../types";
|
||||
import Scene from "../scene/Scene";
|
||||
import {
|
||||
getApproxMinLineWidth,
|
||||
getBoundTextElement,
|
||||
getBoundTextElementId,
|
||||
getContainerElement,
|
||||
handleBindTextResize,
|
||||
getMaxContainerWidth,
|
||||
getApproxMinLineHeight,
|
||||
measureText,
|
||||
getMaxContainerHeight,
|
||||
getBoundTextMaxWidth,
|
||||
} from "./textElement";
|
||||
|
||||
import {
|
||||
getApproxMinContainerHeight,
|
||||
getApproxMinContainerWidth,
|
||||
} from "./textMeasurements";
|
||||
export const normalizeAngle = (angle: number): number => {
|
||||
if (angle >= 2 * Math.PI) {
|
||||
return angle - 2 * Math.PI;
|
||||
@@ -195,8 +194,7 @@ const MIN_FONT_SIZE = 1;
|
||||
const measureFontSizeFromWidth = (
|
||||
element: NonDeleted<ExcalidrawTextElement>,
|
||||
nextWidth: number,
|
||||
nextHeight: number,
|
||||
): { size: number; baseline: number } | null => {
|
||||
): number | null => {
|
||||
// We only use width to scale font on resize
|
||||
let width = element.width;
|
||||
|
||||
@@ -204,22 +202,15 @@ const measureFontSizeFromWidth = (
|
||||
if (hasContainer) {
|
||||
const container = getContainerElement(element);
|
||||
if (container) {
|
||||
width = getMaxContainerWidth(container);
|
||||
width = getBoundTextMaxWidth(container);
|
||||
}
|
||||
}
|
||||
const nextFontSize = element.fontSize * (nextWidth / width);
|
||||
if (nextFontSize < MIN_FONT_SIZE) {
|
||||
return null;
|
||||
}
|
||||
const metrics = measureText(
|
||||
element.text,
|
||||
getFontString({ fontSize: nextFontSize, fontFamily: element.fontFamily }),
|
||||
element.lineHeight,
|
||||
);
|
||||
return {
|
||||
size: nextFontSize,
|
||||
baseline: metrics.baseline + (nextHeight - metrics.height),
|
||||
};
|
||||
|
||||
return nextFontSize;
|
||||
};
|
||||
|
||||
const getSidesForTransformHandle = (
|
||||
@@ -290,8 +281,8 @@ const resizeSingleTextElement = (
|
||||
if (scale > 0) {
|
||||
const nextWidth = element.width * scale;
|
||||
const nextHeight = element.height * scale;
|
||||
const metrics = measureFontSizeFromWidth(element, nextWidth, nextHeight);
|
||||
if (metrics === null) {
|
||||
const nextFontSize = measureFontSizeFromWidth(element, nextWidth);
|
||||
if (nextFontSize === null) {
|
||||
return;
|
||||
}
|
||||
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
||||
@@ -315,10 +306,9 @@ const resizeSingleTextElement = (
|
||||
deltaY2,
|
||||
);
|
||||
mutateElement(element, {
|
||||
fontSize: metrics.size,
|
||||
fontSize: nextFontSize,
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
baseline: metrics.baseline,
|
||||
x: nextElementX,
|
||||
y: nextElementY,
|
||||
});
|
||||
@@ -371,7 +361,7 @@ export const resizeSingleElement = (
|
||||
let scaleX = atStartBoundsWidth / boundsCurrentWidth;
|
||||
let scaleY = atStartBoundsHeight / boundsCurrentHeight;
|
||||
|
||||
let boundTextFont: { fontSize?: number; baseline?: number } = {};
|
||||
let boundTextFontSize: number | null = null;
|
||||
const boundTextElement = getBoundTextElement(element);
|
||||
|
||||
if (transformHandleDirection.includes("e")) {
|
||||
@@ -421,10 +411,7 @@ export const resizeSingleElement = (
|
||||
boundTextElement.id,
|
||||
) as typeof boundTextElement | undefined;
|
||||
if (stateOfBoundTextElementAtResize) {
|
||||
boundTextFont = {
|
||||
fontSize: stateOfBoundTextElementAtResize.fontSize,
|
||||
baseline: stateOfBoundTextElementAtResize.baseline,
|
||||
};
|
||||
boundTextFontSize = stateOfBoundTextElementAtResize.fontSize;
|
||||
}
|
||||
if (shouldMaintainAspectRatio) {
|
||||
const updatedElement = {
|
||||
@@ -433,24 +420,20 @@ export const resizeSingleElement = (
|
||||
height: eleNewHeight,
|
||||
};
|
||||
|
||||
const nextFont = measureFontSizeFromWidth(
|
||||
const nextFontSize = measureFontSizeFromWidth(
|
||||
boundTextElement,
|
||||
getMaxContainerWidth(updatedElement),
|
||||
getMaxContainerHeight(updatedElement),
|
||||
getBoundTextMaxWidth(updatedElement),
|
||||
);
|
||||
if (nextFont === null) {
|
||||
if (nextFontSize === null) {
|
||||
return;
|
||||
}
|
||||
boundTextFont = {
|
||||
fontSize: nextFont.size,
|
||||
baseline: nextFont.baseline,
|
||||
};
|
||||
boundTextFontSize = nextFontSize;
|
||||
} else {
|
||||
const minWidth = getApproxMinLineWidth(
|
||||
const minWidth = getApproxMinContainerWidth(
|
||||
getFontString(boundTextElement),
|
||||
boundTextElement.lineHeight,
|
||||
);
|
||||
const minHeight = getApproxMinLineHeight(
|
||||
const minHeight = getApproxMinContainerHeight(
|
||||
boundTextElement.fontSize,
|
||||
boundTextElement.lineHeight,
|
||||
);
|
||||
@@ -586,10 +569,9 @@ export const resizeSingleElement = (
|
||||
});
|
||||
|
||||
mutateElement(element, resizedElement);
|
||||
if (boundTextElement && boundTextFont != null) {
|
||||
if (boundTextElement && boundTextFontSize != null) {
|
||||
mutateElement(boundTextElement, {
|
||||
fontSize: boundTextFont.fontSize,
|
||||
baseline: boundTextFont.baseline,
|
||||
fontSize: boundTextFontSize,
|
||||
});
|
||||
}
|
||||
handleBindTextResize(element, transformHandleDirection);
|
||||
@@ -696,7 +678,6 @@ const resizeMultipleElements = (
|
||||
y: number;
|
||||
points?: Point[];
|
||||
fontSize?: number;
|
||||
baseline?: number;
|
||||
} = {
|
||||
width,
|
||||
height,
|
||||
@@ -705,7 +686,7 @@ const resizeMultipleElements = (
|
||||
...rescaledPoints,
|
||||
};
|
||||
|
||||
let boundTextUpdates: { fontSize: number; baseline: number } | null = null;
|
||||
let boundTextUpdates: { fontSize: number } | null = null;
|
||||
|
||||
const boundTextElement = getBoundTextElement(element.latest);
|
||||
|
||||
@@ -715,29 +696,24 @@ const resizeMultipleElements = (
|
||||
width,
|
||||
height,
|
||||
};
|
||||
const metrics = measureFontSizeFromWidth(
|
||||
const fontSize = measureFontSizeFromWidth(
|
||||
boundTextElement ?? (element.orig as ExcalidrawTextElement),
|
||||
boundTextElement
|
||||
? getMaxContainerWidth(updatedElement)
|
||||
? getBoundTextMaxWidth(updatedElement)
|
||||
: updatedElement.width,
|
||||
boundTextElement
|
||||
? getMaxContainerHeight(updatedElement)
|
||||
: updatedElement.height,
|
||||
);
|
||||
|
||||
if (!metrics) {
|
||||
if (!fontSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTextElement(element.orig)) {
|
||||
update.fontSize = metrics.size;
|
||||
update.baseline = metrics.baseline;
|
||||
update.fontSize = fontSize;
|
||||
}
|
||||
|
||||
if (boundTextElement) {
|
||||
boundTextUpdates = {
|
||||
fontSize: metrics.size,
|
||||
baseline: metrics.baseline,
|
||||
fontSize,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+56
-227
@@ -1,189 +1,11 @@
|
||||
import { BOUND_TEXT_PADDING, FONT_FAMILY } from "../constants";
|
||||
import { API } from "../tests/helpers/api";
|
||||
import {
|
||||
computeContainerDimensionForBoundText,
|
||||
getContainerCoords,
|
||||
getMaxContainerWidth,
|
||||
getMaxContainerHeight,
|
||||
wrapText,
|
||||
detectLineHeight,
|
||||
getLineHeightInPx,
|
||||
getDefaultLineHeight,
|
||||
getBoundTextMaxWidth,
|
||||
getBoundTextMaxHeight,
|
||||
} from "./textElement";
|
||||
import { FontString } from "./types";
|
||||
|
||||
describe("Test wrapText", () => {
|
||||
const font = "20px Cascadia, width: Segoe UI Emoji" as FontString;
|
||||
|
||||
it("shouldn't add new lines for trailing spaces", () => {
|
||||
const text = "Hello whats up ";
|
||||
const maxWidth = 200 - BOUND_TEXT_PADDING * 2;
|
||||
const res = wrapText(text, font, maxWidth);
|
||||
expect(res).toBe(text);
|
||||
});
|
||||
|
||||
it("should work with emojis", () => {
|
||||
const text = "😀";
|
||||
const maxWidth = 1;
|
||||
const res = wrapText(text, font, maxWidth);
|
||||
expect(res).toBe("😀");
|
||||
});
|
||||
|
||||
it("should show the text correctly when max width reached", () => {
|
||||
const text = "Hello😀";
|
||||
const maxWidth = 10;
|
||||
const res = wrapText(text, font, maxWidth);
|
||||
expect(res).toBe("H\ne\nl\nl\no\n😀");
|
||||
});
|
||||
|
||||
describe("When text doesn't contain new lines", () => {
|
||||
const text = "Hello whats up";
|
||||
|
||||
[
|
||||
{
|
||||
desc: "break all words when width of each word is less than container width",
|
||||
width: 80,
|
||||
res: `Hello \nwhats \nup`,
|
||||
},
|
||||
{
|
||||
desc: "break all characters when width of each character is less than container width",
|
||||
width: 25,
|
||||
res: `H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
w
|
||||
h
|
||||
a
|
||||
t
|
||||
s
|
||||
u
|
||||
p`,
|
||||
},
|
||||
{
|
||||
desc: "break words as per the width",
|
||||
|
||||
width: 140,
|
||||
res: `Hello whats \nup`,
|
||||
},
|
||||
{
|
||||
desc: "fit the container",
|
||||
|
||||
width: 250,
|
||||
res: "Hello whats up",
|
||||
},
|
||||
{
|
||||
desc: "should push the word if its equal to max width",
|
||||
width: 60,
|
||||
res: `Hello
|
||||
whats
|
||||
up`,
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("When text contain new lines", () => {
|
||||
const text = `Hello
|
||||
whats up`;
|
||||
[
|
||||
{
|
||||
desc: "break all words when width of each word is less than container width",
|
||||
width: 80,
|
||||
res: `Hello\nwhats \nup`,
|
||||
},
|
||||
{
|
||||
desc: "break all characters when width of each character is less than container width",
|
||||
width: 25,
|
||||
res: `H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
w
|
||||
h
|
||||
a
|
||||
t
|
||||
s
|
||||
u
|
||||
p`,
|
||||
},
|
||||
{
|
||||
desc: "break words as per the width",
|
||||
|
||||
width: 150,
|
||||
res: `Hello
|
||||
whats up`,
|
||||
},
|
||||
{
|
||||
desc: "fit the container",
|
||||
|
||||
width: 250,
|
||||
res: `Hello
|
||||
whats up`,
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should respect new lines and ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("When text is long", () => {
|
||||
const text = `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg break it now`;
|
||||
[
|
||||
{
|
||||
desc: "fit characters of long string as per container width",
|
||||
width: 170,
|
||||
res: `hellolongtextth\nisiswhatsupwith\nyouIamtypingggg\ngandtypinggg \nbreak it now`,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "fit characters of long string as per container width and break words as per the width",
|
||||
|
||||
width: 130,
|
||||
res: `hellolongte
|
||||
xtthisiswha
|
||||
tsupwithyou
|
||||
Iamtypinggg
|
||||
ggandtyping
|
||||
gg break it
|
||||
now`,
|
||||
},
|
||||
{
|
||||
desc: "fit the long text when container width is greater than text length and move the rest to next line",
|
||||
|
||||
width: 600,
|
||||
res: `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg \nbreak it now`,
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should wrap the text correctly when word length is exactly equal to max width", () => {
|
||||
const text = "Hello Excalidraw";
|
||||
// Length of "Excalidraw" is 100 and exacty equal to max width
|
||||
const res = wrapText(text, font, 100);
|
||||
expect(res).toEqual(`Hello \nExcalidraw`);
|
||||
});
|
||||
|
||||
it("should return the text as is if max width is invalid", () => {
|
||||
const text = "Hello Excalidraw";
|
||||
expect(wrapText(text, font, NaN)).toEqual(text);
|
||||
expect(wrapText(text, font, -1)).toEqual(text);
|
||||
expect(wrapText(text, font, Infinity)).toEqual(text);
|
||||
});
|
||||
});
|
||||
import { ExcalidrawTextElementWithContainer } from "./types";
|
||||
|
||||
describe("Test measureText", () => {
|
||||
describe("Test getContainerCoords", () => {
|
||||
@@ -260,7 +82,7 @@ describe("Test measureText", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test getMaxContainerWidth", () => {
|
||||
describe("Test getBoundTextMaxWidth", () => {
|
||||
const params = {
|
||||
width: 178,
|
||||
height: 194,
|
||||
@@ -268,77 +90,84 @@ describe("Test measureText", () => {
|
||||
|
||||
it("should return max width when container is rectangle", () => {
|
||||
const container = API.createElement({ type: "rectangle", ...params });
|
||||
expect(getMaxContainerWidth(container)).toBe(168);
|
||||
expect(getBoundTextMaxWidth(container)).toBe(168);
|
||||
});
|
||||
|
||||
it("should return max width when container is ellipse", () => {
|
||||
const container = API.createElement({ type: "ellipse", ...params });
|
||||
expect(getMaxContainerWidth(container)).toBe(116);
|
||||
expect(getBoundTextMaxWidth(container)).toBe(116);
|
||||
});
|
||||
|
||||
it("should return max width when container is diamond", () => {
|
||||
const container = API.createElement({ type: "diamond", ...params });
|
||||
expect(getMaxContainerWidth(container)).toBe(79);
|
||||
expect(getBoundTextMaxWidth(container)).toBe(79);
|
||||
});
|
||||
|
||||
it("should return max width when container is arrow", () => {
|
||||
const container = API.createElement({
|
||||
type: "arrow",
|
||||
...params,
|
||||
});
|
||||
expect(getBoundTextMaxWidth(container)).toBe(220);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test getMaxContainerHeight", () => {
|
||||
describe("Test getBoundTextMaxHeight", () => {
|
||||
const params = {
|
||||
width: 178,
|
||||
height: 194,
|
||||
id: "container-id",
|
||||
};
|
||||
|
||||
const boundTextElement = API.createElement({
|
||||
type: "text",
|
||||
id: "text-id",
|
||||
x: 560.51171875,
|
||||
y: 202.033203125,
|
||||
width: 154,
|
||||
height: 175,
|
||||
fontSize: 20,
|
||||
fontFamily: 1,
|
||||
text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams",
|
||||
textAlign: "center",
|
||||
verticalAlign: "middle",
|
||||
containerId: params.id,
|
||||
}) as ExcalidrawTextElementWithContainer;
|
||||
|
||||
it("should return max height when container is rectangle", () => {
|
||||
const container = API.createElement({ type: "rectangle", ...params });
|
||||
expect(getMaxContainerHeight(container)).toBe(184);
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(184);
|
||||
});
|
||||
|
||||
it("should return max height when container is ellipse", () => {
|
||||
const container = API.createElement({ type: "ellipse", ...params });
|
||||
expect(getMaxContainerHeight(container)).toBe(127);
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(127);
|
||||
});
|
||||
|
||||
it("should return max height when container is diamond", () => {
|
||||
const container = API.createElement({ type: "diamond", ...params });
|
||||
expect(getMaxContainerHeight(container)).toBe(87);
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(87);
|
||||
});
|
||||
|
||||
it("should return max height when container is arrow", () => {
|
||||
const container = API.createElement({
|
||||
type: "arrow",
|
||||
...params,
|
||||
});
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(194);
|
||||
});
|
||||
|
||||
it("should return max height when container is arrow and height is less than threshold", () => {
|
||||
const container = API.createElement({
|
||||
type: "arrow",
|
||||
...params,
|
||||
height: 70,
|
||||
boundElements: [{ type: "text", id: "text-id" }],
|
||||
});
|
||||
|
||||
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(
|
||||
boundTextElement.height,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const textElement = API.createElement({
|
||||
type: "text",
|
||||
text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams",
|
||||
fontSize: 20,
|
||||
fontFamily: 1,
|
||||
height: 175,
|
||||
});
|
||||
|
||||
describe("Test detectLineHeight", () => {
|
||||
it("should return correct line height", () => {
|
||||
expect(detectLineHeight(textElement)).toBe(1.25);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test getLineHeightInPx", () => {
|
||||
it("should return correct line height", () => {
|
||||
expect(
|
||||
getLineHeightInPx(textElement.fontSize, textElement.lineHeight),
|
||||
).toBe(25);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test getDefaultLineHeight", () => {
|
||||
it("should return line height using default font family when not passed", () => {
|
||||
//@ts-ignore
|
||||
expect(getDefaultLineHeight()).toBe(1.25);
|
||||
});
|
||||
|
||||
it("should return line height using default font family for unknown font", () => {
|
||||
const UNKNOWN_FONT = 5;
|
||||
expect(getDefaultLineHeight(UNKNOWN_FONT)).toBe(1.25);
|
||||
});
|
||||
|
||||
it("should return correct line height", () => {
|
||||
expect(getDefaultLineHeight(FONT_FAMILY.Cascadia)).toBe(1.2);
|
||||
});
|
||||
});
|
||||
|
||||
+47
-473
@@ -1,23 +1,13 @@
|
||||
import { getFontString, arrayToMap, isTestEnv } from "../utils";
|
||||
import { getFontString, arrayToMap } from "../utils";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawTextContainer,
|
||||
ExcalidrawTextElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
FontFamilyValues,
|
||||
FontString,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "./types";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import {
|
||||
BOUND_TEXT_PADDING,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
FONT_FAMILY,
|
||||
isSafari,
|
||||
TEXT_ALIGN,
|
||||
VERTICAL_ALIGN,
|
||||
} from "../constants";
|
||||
import { BOUND_TEXT_PADDING, TEXT_ALIGN, VERTICAL_ALIGN } from "../constants";
|
||||
import { MaybeTransformHandleType } from "./transformHandles";
|
||||
import Scene from "../scene/Scene";
|
||||
import { isTextElement } from ".";
|
||||
@@ -33,6 +23,7 @@ import {
|
||||
updateOriginalContainerCache,
|
||||
} from "./textWysiwyg";
|
||||
import { ExtractSetType } from "../utility-types";
|
||||
import { measureText, wrapText } from "./textMeasurements";
|
||||
|
||||
export const normalizeText = (text: string) => {
|
||||
return (
|
||||
@@ -44,10 +35,6 @@ export const normalizeText = (text: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const splitIntoLines = (text: string) => {
|
||||
return normalizeText(text).split("\n");
|
||||
};
|
||||
|
||||
export const redrawTextBoundingBox = (
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawElement | null,
|
||||
@@ -59,19 +46,17 @@ export const redrawTextBoundingBox = (
|
||||
text: textElement.text,
|
||||
width: textElement.width,
|
||||
height: textElement.height,
|
||||
baseline: textElement.baseline,
|
||||
};
|
||||
|
||||
boundTextUpdates.text = textElement.text;
|
||||
|
||||
if (container) {
|
||||
maxWidth = getMaxContainerWidth(container);
|
||||
maxWidth = getBoundTextMaxWidth(container);
|
||||
boundTextUpdates.text = wrapText(
|
||||
textElement.originalText,
|
||||
getFontString(textElement),
|
||||
maxWidth,
|
||||
);
|
||||
}
|
||||
|
||||
const metrics = measureText(
|
||||
boundTextUpdates.text,
|
||||
getFontString(textElement),
|
||||
@@ -80,38 +65,31 @@ export const redrawTextBoundingBox = (
|
||||
|
||||
boundTextUpdates.width = metrics.width;
|
||||
boundTextUpdates.height = metrics.height;
|
||||
boundTextUpdates.baseline = metrics.baseline;
|
||||
|
||||
if (container) {
|
||||
if (isArrowElement(container)) {
|
||||
const centerX = textElement.x + textElement.width / 2;
|
||||
const centerY = textElement.y + textElement.height / 2;
|
||||
const diffWidth = metrics.width - textElement.width;
|
||||
const diffHeight = metrics.height - textElement.height;
|
||||
boundTextUpdates.x = centerY - (textElement.height + diffHeight) / 2;
|
||||
boundTextUpdates.y = centerX - (textElement.width + diffWidth) / 2;
|
||||
} else {
|
||||
const containerDims = getContainerDims(container);
|
||||
let maxContainerHeight = getMaxContainerHeight(container);
|
||||
const containerDims = getContainerDims(container);
|
||||
const maxContainerHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
textElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
let nextHeight = containerDims.height;
|
||||
|
||||
let nextHeight = containerDims.height;
|
||||
if (metrics.height > maxContainerHeight) {
|
||||
nextHeight = computeContainerDimensionForBoundText(
|
||||
metrics.height,
|
||||
container.type,
|
||||
);
|
||||
mutateElement(container, { height: nextHeight });
|
||||
maxContainerHeight = getMaxContainerHeight(container);
|
||||
updateOriginalContainerCache(container.id, nextHeight);
|
||||
}
|
||||
const updatedTextElement = {
|
||||
...textElement,
|
||||
...boundTextUpdates,
|
||||
} as ExcalidrawTextElementWithContainer;
|
||||
const { x, y } = computeBoundTextPosition(container, updatedTextElement);
|
||||
boundTextUpdates.x = x;
|
||||
boundTextUpdates.y = y;
|
||||
if (metrics.height > maxContainerHeight) {
|
||||
nextHeight = computeContainerDimensionForBoundText(
|
||||
metrics.height,
|
||||
container.type,
|
||||
);
|
||||
mutateElement(container, { height: nextHeight });
|
||||
updateOriginalContainerCache(container.id, nextHeight);
|
||||
}
|
||||
|
||||
const updatedTextElement = {
|
||||
...textElement,
|
||||
...boundTextUpdates,
|
||||
} as ExcalidrawTextElementWithContainer;
|
||||
const { x, y } = computeBoundTextPosition(container, updatedTextElement);
|
||||
boundTextUpdates.x = x;
|
||||
boundTextUpdates.y = y;
|
||||
}
|
||||
|
||||
mutateElement(textElement, boundTextUpdates);
|
||||
@@ -183,10 +161,12 @@ export const handleBindTextResize = (
|
||||
let nextHeight = textElement.height;
|
||||
let nextWidth = textElement.width;
|
||||
const containerDims = getContainerDims(container);
|
||||
const maxWidth = getMaxContainerWidth(container);
|
||||
const maxHeight = getMaxContainerHeight(container);
|
||||
const maxWidth = getBoundTextMaxWidth(container);
|
||||
const maxHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
textElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
let containerHeight = containerDims.height;
|
||||
let nextBaseLine = textElement.baseline;
|
||||
if (transformHandleType !== "n" && transformHandleType !== "s") {
|
||||
if (text) {
|
||||
text = wrapText(
|
||||
@@ -195,14 +175,13 @@ export const handleBindTextResize = (
|
||||
maxWidth,
|
||||
);
|
||||
}
|
||||
const metrics = measureText(
|
||||
const dimensions = measureText(
|
||||
text,
|
||||
getFontString(textElement),
|
||||
textElement.lineHeight,
|
||||
);
|
||||
nextHeight = metrics.height;
|
||||
nextWidth = metrics.width;
|
||||
nextBaseLine = metrics.baseline;
|
||||
nextHeight = dimensions.height;
|
||||
nextWidth = dimensions.width;
|
||||
}
|
||||
// increase height in case text element height exceeds
|
||||
if (nextHeight > maxHeight) {
|
||||
@@ -230,7 +209,6 @@ export const handleBindTextResize = (
|
||||
text,
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
baseline: nextBaseLine,
|
||||
});
|
||||
|
||||
if (!isArrowElement(container)) {
|
||||
@@ -249,18 +227,18 @@ export const computeBoundTextPosition = (
|
||||
container: ExcalidrawElement,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
) => {
|
||||
const containerCoords = getContainerCoords(container);
|
||||
const maxContainerHeight = getBoundTextMaxHeight(container, boundTextElement);
|
||||
const maxContainerWidth = getBoundTextMaxWidth(container);
|
||||
|
||||
let x;
|
||||
let y;
|
||||
if (isArrowElement(container)) {
|
||||
return LinearElementEditor.getBoundTextElementPosition(
|
||||
container,
|
||||
boundTextElement,
|
||||
);
|
||||
}
|
||||
const containerCoords = getContainerCoords(container);
|
||||
const maxContainerHeight = getMaxContainerHeight(container);
|
||||
const maxContainerWidth = getMaxContainerWidth(container);
|
||||
|
||||
let x;
|
||||
let y;
|
||||
if (boundTextElement.verticalAlign === VERTICAL_ALIGN.TOP) {
|
||||
y = containerCoords.y;
|
||||
} else if (boundTextElement.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
||||
@@ -281,349 +259,6 @@ export const computeBoundTextPosition = (
|
||||
return { x, y };
|
||||
};
|
||||
|
||||
// https://github.com/grassator/canvas-text-editor/blob/master/lib/FontMetrics.js
|
||||
|
||||
export const measureText = (
|
||||
text: string,
|
||||
font: FontString,
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
text = text
|
||||
.split("\n")
|
||||
// replace empty lines with single space because leading/trailing empty
|
||||
// lines would be stripped from computation
|
||||
.map((x) => x || " ")
|
||||
.join("\n");
|
||||
const fontSize = parseFloat(font);
|
||||
const height = getTextHeight(text, fontSize, lineHeight);
|
||||
const width = getTextWidth(text, font);
|
||||
const baseline = measureBaseline(text, font, lineHeight);
|
||||
return { width, height, baseline };
|
||||
};
|
||||
|
||||
export const measureBaseline = (
|
||||
text: string,
|
||||
font: FontString,
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
wrapInContainer?: boolean,
|
||||
) => {
|
||||
const container = document.createElement("div");
|
||||
container.style.position = "absolute";
|
||||
container.style.whiteSpace = "pre";
|
||||
container.style.font = font;
|
||||
container.style.minHeight = "1em";
|
||||
if (wrapInContainer) {
|
||||
container.style.overflow = "hidden";
|
||||
container.style.wordBreak = "break-word";
|
||||
container.style.whiteSpace = "pre-wrap";
|
||||
}
|
||||
|
||||
container.style.lineHeight = String(lineHeight);
|
||||
|
||||
container.innerText = text;
|
||||
|
||||
// Baseline is important for positioning text on canvas
|
||||
document.body.appendChild(container);
|
||||
|
||||
const span = document.createElement("span");
|
||||
span.style.display = "inline-block";
|
||||
span.style.overflow = "hidden";
|
||||
span.style.width = "1px";
|
||||
span.style.height = "1px";
|
||||
container.appendChild(span);
|
||||
let baseline = span.offsetTop + span.offsetHeight;
|
||||
const height = container.offsetHeight;
|
||||
|
||||
if (isSafari) {
|
||||
const canvasHeight = getTextHeight(text, parseFloat(font), lineHeight);
|
||||
const fontSize = parseFloat(font);
|
||||
// In Safari the font size gets rounded off when rendering hence calculating the safari height and shifting the baseline if it differs
|
||||
// from the actual canvas height
|
||||
const domHeight = getTextHeight(text, Math.round(fontSize), lineHeight);
|
||||
if (canvasHeight > height) {
|
||||
baseline += canvasHeight - domHeight;
|
||||
}
|
||||
|
||||
if (height > canvasHeight) {
|
||||
baseline -= domHeight - canvasHeight;
|
||||
}
|
||||
}
|
||||
document.body.removeChild(container);
|
||||
return baseline;
|
||||
};
|
||||
|
||||
/**
|
||||
* To get unitless line-height (if unknown) we can calculate it by dividing
|
||||
* height-per-line by fontSize.
|
||||
*/
|
||||
export const detectLineHeight = (textElement: ExcalidrawTextElement) => {
|
||||
const lineCount = splitIntoLines(textElement.text).length;
|
||||
return (textElement.height /
|
||||
lineCount /
|
||||
textElement.fontSize) as ExcalidrawTextElement["lineHeight"];
|
||||
};
|
||||
|
||||
/**
|
||||
* We calculate the line height from the font size and the unitless line height,
|
||||
* aligning with the W3C spec.
|
||||
*/
|
||||
export const getLineHeightInPx = (
|
||||
fontSize: ExcalidrawTextElement["fontSize"],
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
return fontSize * lineHeight;
|
||||
};
|
||||
|
||||
// FIXME rename to getApproxMinContainerHeight
|
||||
export const getApproxMinLineHeight = (
|
||||
fontSize: ExcalidrawTextElement["fontSize"],
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
return getLineHeightInPx(fontSize, lineHeight) + BOUND_TEXT_PADDING * 2;
|
||||
};
|
||||
|
||||
let canvas: HTMLCanvasElement | undefined;
|
||||
|
||||
const getLineWidth = (text: string, font: FontString) => {
|
||||
if (!canvas) {
|
||||
canvas = document.createElement("canvas");
|
||||
}
|
||||
const canvas2dContext = canvas.getContext("2d")!;
|
||||
canvas2dContext.font = font;
|
||||
const width = canvas2dContext.measureText(text).width;
|
||||
|
||||
// since in test env the canvas measureText algo
|
||||
// doesn't measure text and instead just returns number of
|
||||
// characters hence we assume that each letteris 10px
|
||||
if (isTestEnv()) {
|
||||
return width * 10;
|
||||
}
|
||||
return width;
|
||||
};
|
||||
|
||||
export const getTextWidth = (text: string, font: FontString) => {
|
||||
const lines = splitIntoLines(text);
|
||||
let width = 0;
|
||||
lines.forEach((line) => {
|
||||
width = Math.max(width, getLineWidth(line, font));
|
||||
});
|
||||
return width;
|
||||
};
|
||||
|
||||
export const getTextHeight = (
|
||||
text: string,
|
||||
fontSize: number,
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
const lineCount = splitIntoLines(text).length;
|
||||
return getLineHeightInPx(fontSize, lineHeight) * lineCount;
|
||||
};
|
||||
|
||||
export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
// if maxWidth is not finite or NaN which can happen in case of bugs in
|
||||
// computation, we need to make sure we don't continue as we'll end up
|
||||
// in an infinite loop
|
||||
if (!Number.isFinite(maxWidth) || maxWidth < 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const lines: Array<string> = [];
|
||||
const originalLines = text.split("\n");
|
||||
const spaceWidth = getLineWidth(" ", font);
|
||||
|
||||
let currentLine = "";
|
||||
let currentLineWidthTillNow = 0;
|
||||
|
||||
const push = (str: string) => {
|
||||
if (str.trim()) {
|
||||
lines.push(str);
|
||||
}
|
||||
};
|
||||
|
||||
const resetParams = () => {
|
||||
currentLine = "";
|
||||
currentLineWidthTillNow = 0;
|
||||
};
|
||||
|
||||
originalLines.forEach((originalLine) => {
|
||||
const currentLineWidth = getTextWidth(originalLine, font);
|
||||
|
||||
//Push the line if its <= maxWidth
|
||||
if (currentLineWidth <= maxWidth) {
|
||||
lines.push(originalLine);
|
||||
return; // continue
|
||||
}
|
||||
const words = originalLine.split(" ");
|
||||
|
||||
resetParams();
|
||||
|
||||
let index = 0;
|
||||
|
||||
while (index < words.length) {
|
||||
const currentWordWidth = getLineWidth(words[index], font);
|
||||
|
||||
// This will only happen when single word takes entire width
|
||||
if (currentWordWidth === maxWidth) {
|
||||
push(words[index]);
|
||||
index++;
|
||||
}
|
||||
|
||||
// Start breaking longer words exceeding max width
|
||||
else if (currentWordWidth > maxWidth) {
|
||||
// push current line since the current word exceeds the max width
|
||||
// so will be appended in next line
|
||||
push(currentLine);
|
||||
|
||||
resetParams();
|
||||
|
||||
while (words[index].length > 0) {
|
||||
const currentChar = String.fromCodePoint(
|
||||
words[index].codePointAt(0)!,
|
||||
);
|
||||
const width = charWidth.calculate(currentChar, font);
|
||||
currentLineWidthTillNow += width;
|
||||
words[index] = words[index].slice(currentChar.length);
|
||||
|
||||
if (currentLineWidthTillNow >= maxWidth) {
|
||||
push(currentLine);
|
||||
currentLine = currentChar;
|
||||
currentLineWidthTillNow = width;
|
||||
} else {
|
||||
currentLine += currentChar;
|
||||
}
|
||||
}
|
||||
|
||||
// push current line if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
push(currentLine);
|
||||
resetParams();
|
||||
} else {
|
||||
// space needs to be appended before next word
|
||||
// as currentLine contains chars which couldn't be appended
|
||||
// to previous line
|
||||
currentLine += " ";
|
||||
currentLineWidthTillNow += spaceWidth;
|
||||
}
|
||||
index++;
|
||||
} else {
|
||||
// Start appending words in a line till max width reached
|
||||
while (currentLineWidthTillNow < maxWidth && index < words.length) {
|
||||
const word = words[index];
|
||||
currentLineWidthTillNow = getLineWidth(currentLine + word, font);
|
||||
|
||||
if (currentLineWidthTillNow > maxWidth) {
|
||||
push(currentLine);
|
||||
resetParams();
|
||||
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
currentLine += `${word} `;
|
||||
|
||||
// Push the word if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
const word = currentLine.slice(0, -1);
|
||||
push(word);
|
||||
resetParams();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentLine.slice(-1) === " ") {
|
||||
// only remove last trailing space which we have added when joining words
|
||||
currentLine = currentLine.slice(0, -1);
|
||||
push(currentLine);
|
||||
}
|
||||
});
|
||||
return lines.join("\n");
|
||||
};
|
||||
|
||||
export const charWidth = (() => {
|
||||
const cachedCharWidth: { [key: FontString]: Array<number> } = {};
|
||||
|
||||
const calculate = (char: string, font: FontString) => {
|
||||
const ascii = char.charCodeAt(0);
|
||||
if (!cachedCharWidth[font]) {
|
||||
cachedCharWidth[font] = [];
|
||||
}
|
||||
if (!cachedCharWidth[font][ascii]) {
|
||||
const width = getLineWidth(char, font);
|
||||
cachedCharWidth[font][ascii] = width;
|
||||
}
|
||||
|
||||
return cachedCharWidth[font][ascii];
|
||||
};
|
||||
|
||||
const getCache = (font: FontString) => {
|
||||
return cachedCharWidth[font];
|
||||
};
|
||||
return {
|
||||
calculate,
|
||||
getCache,
|
||||
};
|
||||
})();
|
||||
|
||||
const DUMMY_TEXT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toLocaleUpperCase();
|
||||
|
||||
// FIXME rename to getApproxMinContainerWidth
|
||||
export const getApproxMinLineWidth = (
|
||||
font: FontString,
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
const maxCharWidth = getMaxCharWidth(font);
|
||||
if (maxCharWidth === 0) {
|
||||
return (
|
||||
measureText(DUMMY_TEXT.split("").join("\n"), font, lineHeight).width +
|
||||
BOUND_TEXT_PADDING * 2
|
||||
);
|
||||
}
|
||||
return maxCharWidth + BOUND_TEXT_PADDING * 2;
|
||||
};
|
||||
|
||||
export const getMinCharWidth = (font: FontString) => {
|
||||
const cache = charWidth.getCache(font);
|
||||
if (!cache) {
|
||||
return 0;
|
||||
}
|
||||
const cacheWithOutEmpty = cache.filter((val) => val !== undefined);
|
||||
|
||||
return Math.min(...cacheWithOutEmpty);
|
||||
};
|
||||
|
||||
export const getMaxCharWidth = (font: FontString) => {
|
||||
const cache = charWidth.getCache(font);
|
||||
if (!cache) {
|
||||
return 0;
|
||||
}
|
||||
const cacheWithOutEmpty = cache.filter((val) => val !== undefined);
|
||||
return Math.max(...cacheWithOutEmpty);
|
||||
};
|
||||
|
||||
export const getApproxCharsToFitInWidth = (font: FontString, width: number) => {
|
||||
// Generally lower case is used so converting to lower case
|
||||
const dummyText = DUMMY_TEXT.toLocaleLowerCase();
|
||||
const batchLength = 6;
|
||||
let index = 0;
|
||||
let widthTillNow = 0;
|
||||
let str = "";
|
||||
while (widthTillNow <= width) {
|
||||
const batch = dummyText.substr(index, index + batchLength);
|
||||
str += batch;
|
||||
widthTillNow += getLineWidth(str, font);
|
||||
if (index === dummyText.length - 1) {
|
||||
index = 0;
|
||||
}
|
||||
index = index + batchLength;
|
||||
}
|
||||
|
||||
while (widthTillNow > width) {
|
||||
str = str.substr(0, str.length - 1);
|
||||
widthTillNow = getLineWidth(str, font);
|
||||
}
|
||||
return str.length;
|
||||
};
|
||||
|
||||
export const getBoundTextElementId = (container: ExcalidrawElement | null) => {
|
||||
return container?.boundElements?.length
|
||||
? container?.boundElements?.filter((ele) => ele.type === "text")[0]?.id ||
|
||||
@@ -749,18 +384,6 @@ export const getBoundTextElementOffset = (
|
||||
return BOUND_TEXT_PADDING;
|
||||
};
|
||||
|
||||
export const getBoundTextElementPosition = (
|
||||
container: ExcalidrawElement,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
) => {
|
||||
if (isArrowElement(container)) {
|
||||
return LinearElementEditor.getBoundTextElementPosition(
|
||||
container,
|
||||
boundTextElement,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const shouldAllowVerticalAlign = (
|
||||
selectedElements: NonDeletedExcalidrawElement[],
|
||||
) => {
|
||||
@@ -861,18 +484,10 @@ export const computeContainerDimensionForBoundText = (
|
||||
return dimension + padding;
|
||||
};
|
||||
|
||||
export const getMaxContainerWidth = (container: ExcalidrawElement) => {
|
||||
export const getBoundTextMaxWidth = (container: ExcalidrawElement) => {
|
||||
const width = getContainerDims(container).width;
|
||||
if (isArrowElement(container)) {
|
||||
const containerWidth = width - BOUND_TEXT_PADDING * 8 * 2;
|
||||
if (containerWidth <= 0) {
|
||||
const boundText = getBoundTextElement(container);
|
||||
if (boundText) {
|
||||
return boundText.width;
|
||||
}
|
||||
return BOUND_TEXT_PADDING * 8 * 2;
|
||||
}
|
||||
return containerWidth;
|
||||
return width - BOUND_TEXT_PADDING * 8 * 2;
|
||||
}
|
||||
|
||||
if (container.type === "ellipse") {
|
||||
@@ -889,16 +504,15 @@ export const getMaxContainerWidth = (container: ExcalidrawElement) => {
|
||||
return width - BOUND_TEXT_PADDING * 2;
|
||||
};
|
||||
|
||||
export const getMaxContainerHeight = (container: ExcalidrawElement) => {
|
||||
export const getBoundTextMaxHeight = (
|
||||
container: ExcalidrawElement,
|
||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||
) => {
|
||||
const height = getContainerDims(container).height;
|
||||
if (isArrowElement(container)) {
|
||||
const containerHeight = height - BOUND_TEXT_PADDING * 8 * 2;
|
||||
if (containerHeight <= 0) {
|
||||
const boundText = getBoundTextElement(container);
|
||||
if (boundText) {
|
||||
return boundText.height;
|
||||
}
|
||||
return BOUND_TEXT_PADDING * 8 * 2;
|
||||
return boundTextElement.height;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
@@ -915,43 +529,3 @@ export const getMaxContainerHeight = (container: ExcalidrawElement) => {
|
||||
}
|
||||
return height - BOUND_TEXT_PADDING * 2;
|
||||
};
|
||||
|
||||
export const isMeasureTextSupported = () => {
|
||||
const width = getTextWidth(
|
||||
DUMMY_TEXT,
|
||||
getFontString({
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
}),
|
||||
);
|
||||
return width > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unitless line height
|
||||
*
|
||||
* In previous versions we used `normal` line height, which browsers interpret
|
||||
* differently, and based on font-family and font-size.
|
||||
*
|
||||
* To make line heights consistent across browsers we hardcode the values for
|
||||
* each of our fonts based on most common average line-heights.
|
||||
* See https://github.com/excalidraw/excalidraw/pull/6360#issuecomment-1477635971
|
||||
* where the values come from.
|
||||
*/
|
||||
const DEFAULT_LINE_HEIGHT = {
|
||||
// ~1.25 is the average for Virgil in WebKit and Blink.
|
||||
// Gecko (FF) uses ~1.28.
|
||||
[FONT_FAMILY.Virgil]: 1.25 as ExcalidrawTextElement["lineHeight"],
|
||||
// ~1.15 is the average for Virgil in WebKit and Blink.
|
||||
// Gecko if all over the place.
|
||||
[FONT_FAMILY.Helvetica]: 1.15 as ExcalidrawTextElement["lineHeight"],
|
||||
// ~1.2 is the average for Virgil in WebKit and Blink, and kinda Gecko too
|
||||
[FONT_FAMILY.Cascadia]: 1.2 as ExcalidrawTextElement["lineHeight"],
|
||||
};
|
||||
|
||||
export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
|
||||
if (fontFamily in DEFAULT_LINE_HEIGHT) {
|
||||
return DEFAULT_LINE_HEIGHT[fontFamily];
|
||||
}
|
||||
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import { BOUND_TEXT_PADDING, FONT_FAMILY } from "../constants";
|
||||
import { API } from "../tests/helpers/api";
|
||||
import {
|
||||
detectLineHeight,
|
||||
getDefaultLineHeight,
|
||||
getLineHeightInPx,
|
||||
wrapText,
|
||||
} from "./textMeasurements";
|
||||
import { FontString } from "./types";
|
||||
|
||||
describe("Test wrapText", () => {
|
||||
const font = "20px Cascadia, width: Segoe UI Emoji" as FontString;
|
||||
|
||||
it("shouldn't add new lines for trailing spaces", () => {
|
||||
const text = "Hello whats up ";
|
||||
const maxWidth = 200 - BOUND_TEXT_PADDING * 2;
|
||||
const res = wrapText(text, font, maxWidth);
|
||||
expect(res).toBe(text);
|
||||
});
|
||||
|
||||
it("should work with emojis", () => {
|
||||
const text = "😀";
|
||||
const maxWidth = 1;
|
||||
const res = wrapText(text, font, maxWidth);
|
||||
expect(res).toBe("😀");
|
||||
});
|
||||
|
||||
it("should show the text correctly when max width reached", () => {
|
||||
const text = "Hello😀";
|
||||
const maxWidth = 10;
|
||||
const res = wrapText(text, font, maxWidth);
|
||||
expect(res).toBe("H\ne\nl\nl\no\n😀");
|
||||
});
|
||||
|
||||
describe("When text doesn't contain new lines", () => {
|
||||
const text = "Hello whats up";
|
||||
|
||||
[
|
||||
{
|
||||
desc: "break all words when width of each word is less than container width",
|
||||
width: 80,
|
||||
res: `Hello \nwhats \nup`,
|
||||
},
|
||||
{
|
||||
desc: "break all characters when width of each character is less than container width",
|
||||
width: 25,
|
||||
res: `H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
w
|
||||
h
|
||||
a
|
||||
t
|
||||
s
|
||||
u
|
||||
p`,
|
||||
},
|
||||
{
|
||||
desc: "break words as per the width",
|
||||
|
||||
width: 140,
|
||||
res: `Hello whats \nup`,
|
||||
},
|
||||
{
|
||||
desc: "fit the container",
|
||||
|
||||
width: 250,
|
||||
res: "Hello whats up",
|
||||
},
|
||||
{
|
||||
desc: "should push the word if its equal to max width",
|
||||
width: 60,
|
||||
res: `Hello
|
||||
whats
|
||||
up`,
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("When text contain new lines", () => {
|
||||
const text = `Hello
|
||||
whats up`;
|
||||
[
|
||||
{
|
||||
desc: "break all words when width of each word is less than container width",
|
||||
width: 80,
|
||||
res: `Hello\nwhats \nup`,
|
||||
},
|
||||
{
|
||||
desc: "break all characters when width of each character is less than container width",
|
||||
width: 25,
|
||||
res: `H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
w
|
||||
h
|
||||
a
|
||||
t
|
||||
s
|
||||
u
|
||||
p`,
|
||||
},
|
||||
{
|
||||
desc: "break words as per the width",
|
||||
|
||||
width: 150,
|
||||
res: `Hello
|
||||
whats up`,
|
||||
},
|
||||
{
|
||||
desc: "fit the container",
|
||||
|
||||
width: 250,
|
||||
res: `Hello
|
||||
whats up`,
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should respect new lines and ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("When text is long", () => {
|
||||
const text = `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg break it now`;
|
||||
[
|
||||
{
|
||||
desc: "fit characters of long string as per container width",
|
||||
width: 170,
|
||||
res: `hellolongtextth\nisiswhatsupwith\nyouIamtypingggg\ngandtypinggg \nbreak it now`,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "fit characters of long string as per container width and break words as per the width",
|
||||
|
||||
width: 130,
|
||||
res: `hellolongte
|
||||
xtthisiswha
|
||||
tsupwithyou
|
||||
Iamtypinggg
|
||||
ggandtyping
|
||||
gg break it
|
||||
now`,
|
||||
},
|
||||
{
|
||||
desc: "fit the long text when container width is greater than text length and move the rest to next line",
|
||||
|
||||
width: 600,
|
||||
res: `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg \nbreak it now`,
|
||||
},
|
||||
].forEach((data) => {
|
||||
it(`should ${data.desc}`, () => {
|
||||
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
||||
expect(res).toEqual(data.res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should wrap the text correctly when word length is exactly equal to max width", () => {
|
||||
const text = "Hello Excalidraw";
|
||||
// Length of "Excalidraw" is 100 and exacty equal to max width
|
||||
const res = wrapText(text, font, 100);
|
||||
expect(res).toEqual(`Hello \nExcalidraw`);
|
||||
});
|
||||
|
||||
it("should return the text as is if max width is invalid", () => {
|
||||
const text = "Hello Excalidraw";
|
||||
expect(wrapText(text, font, NaN)).toEqual(text);
|
||||
expect(wrapText(text, font, -1)).toEqual(text);
|
||||
expect(wrapText(text, font, Infinity)).toEqual(text);
|
||||
});
|
||||
});
|
||||
const textElement = API.createElement({
|
||||
type: "text",
|
||||
text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams",
|
||||
fontSize: 20,
|
||||
fontFamily: 1,
|
||||
height: 175,
|
||||
});
|
||||
|
||||
describe("Test detectLineHeight", () => {
|
||||
it("should return correct line height", () => {
|
||||
expect(detectLineHeight(textElement)).toBe(1.25);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test getLineHeightInPx", () => {
|
||||
it("should return correct line height", () => {
|
||||
expect(
|
||||
getLineHeightInPx(textElement.fontSize, textElement.lineHeight),
|
||||
).toBe(25);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test getDefaultLineHeight", () => {
|
||||
it("should return line height using default font family when not passed", () => {
|
||||
//@ts-ignore
|
||||
expect(getDefaultLineHeight()).toBe(1.25);
|
||||
});
|
||||
it("should return correct line height", () => {
|
||||
expect(getDefaultLineHeight(FONT_FAMILY.Cascadia)).toBe(1.2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,339 @@
|
||||
import {
|
||||
BOUND_TEXT_PADDING,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
FONT_FAMILY,
|
||||
} from "../constants";
|
||||
import { getFontString, isTestEnv } from "../utils";
|
||||
import { normalizeText } from "./textElement";
|
||||
import { ExcalidrawTextElement, FontFamilyValues, FontString } from "./types";
|
||||
|
||||
const DUMMY_TEXT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toLocaleUpperCase();
|
||||
|
||||
let canvas: HTMLCanvasElement | undefined;
|
||||
|
||||
// since in test env the canvas measureText algo
|
||||
// doesn't measure text and instead just returns number of
|
||||
// characters hence we assume that each letter is 10px
|
||||
const DUMMY_CHAR_WIDTH = 10;
|
||||
|
||||
const getLineWidth = (text: string, font: FontString) => {
|
||||
if (!canvas) {
|
||||
canvas = document.createElement("canvas");
|
||||
}
|
||||
const canvas2dContext = canvas.getContext("2d")!;
|
||||
canvas2dContext.font = font;
|
||||
const width = canvas2dContext.measureText(text).width;
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (isTestEnv()) {
|
||||
return width * DUMMY_CHAR_WIDTH;
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return width;
|
||||
};
|
||||
|
||||
export const getTextWidth = (text: string, font: FontString) => {
|
||||
const lines = splitIntoLines(text);
|
||||
let width = 0;
|
||||
lines.forEach((line) => {
|
||||
width = Math.max(width, getLineWidth(line, font));
|
||||
});
|
||||
return width;
|
||||
};
|
||||
|
||||
export const getTextHeight = (
|
||||
text: string,
|
||||
fontSize: number,
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
const lineCount = splitIntoLines(text).length;
|
||||
return getLineHeightInPx(fontSize, lineHeight) * lineCount;
|
||||
};
|
||||
|
||||
export const splitIntoLines = (text: string) => {
|
||||
return normalizeText(text).split("\n");
|
||||
};
|
||||
|
||||
export const measureText = (
|
||||
text: string,
|
||||
font: FontString,
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
text = text
|
||||
.split("\n")
|
||||
// replace empty lines with single space because leading/trailing empty
|
||||
// lines would be stripped from computation
|
||||
.map((x) => x || " ")
|
||||
.join("\n");
|
||||
const fontSize = parseFloat(font);
|
||||
const height = getTextHeight(text, fontSize, lineHeight);
|
||||
const width = getTextWidth(text, font);
|
||||
|
||||
return { width, height };
|
||||
};
|
||||
|
||||
export const getApproxMinContainerWidth = (
|
||||
font: FontString,
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
const maxCharWidth = getMaxCharWidth(font);
|
||||
if (maxCharWidth === 0) {
|
||||
return (
|
||||
measureText(DUMMY_TEXT.split("").join("\n"), font, lineHeight).width +
|
||||
BOUND_TEXT_PADDING * 2
|
||||
);
|
||||
}
|
||||
return maxCharWidth + BOUND_TEXT_PADDING * 2;
|
||||
};
|
||||
|
||||
export const getApproxMinContainerHeight = (
|
||||
fontSize: ExcalidrawTextElement["fontSize"],
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
return getLineHeightInPx(fontSize, lineHeight) + BOUND_TEXT_PADDING * 2;
|
||||
};
|
||||
|
||||
export const charWidth = (() => {
|
||||
const cachedCharWidth: { [key: FontString]: Array<number> } = {};
|
||||
|
||||
const calculate = (char: string, font: FontString) => {
|
||||
const ascii = char.charCodeAt(0);
|
||||
if (!cachedCharWidth[font]) {
|
||||
cachedCharWidth[font] = [];
|
||||
}
|
||||
if (!cachedCharWidth[font][ascii]) {
|
||||
const width = getLineWidth(char, font);
|
||||
cachedCharWidth[font][ascii] = width;
|
||||
}
|
||||
|
||||
return cachedCharWidth[font][ascii];
|
||||
};
|
||||
|
||||
const getCache = (font: FontString) => {
|
||||
return cachedCharWidth[font];
|
||||
};
|
||||
return {
|
||||
calculate,
|
||||
getCache,
|
||||
};
|
||||
})();
|
||||
|
||||
export const getMaxCharWidth = (font: FontString) => {
|
||||
const cache = charWidth.getCache(font);
|
||||
if (!cache) {
|
||||
return 0;
|
||||
}
|
||||
const cacheWithOutEmpty = cache.filter((val) => val !== undefined);
|
||||
return Math.max(...cacheWithOutEmpty);
|
||||
};
|
||||
|
||||
/** this is not used currently but might be useful
|
||||
* in future hence keeping it
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
export const getApproxCharsToFitInWidth = (font: FontString, width: number) => {
|
||||
// Generally lower case is used so converting to lower case
|
||||
const dummyText = DUMMY_TEXT.toLocaleLowerCase();
|
||||
const batchLength = 6;
|
||||
let index = 0;
|
||||
let widthTillNow = 0;
|
||||
let str = "";
|
||||
while (widthTillNow <= width) {
|
||||
const batch = dummyText.substr(index, index + batchLength);
|
||||
str += batch;
|
||||
widthTillNow += getLineWidth(str, font);
|
||||
if (index === dummyText.length - 1) {
|
||||
index = 0;
|
||||
}
|
||||
index = index + batchLength;
|
||||
}
|
||||
|
||||
while (widthTillNow > width) {
|
||||
str = str.substr(0, str.length - 1);
|
||||
widthTillNow = getLineWidth(str, font);
|
||||
}
|
||||
return str.length;
|
||||
};
|
||||
|
||||
export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
||||
// if maxWidth is not finite or NaN which can happen in case of bugs in
|
||||
// computation, we need to make sure we don't continue as we'll end up
|
||||
// in an infinite loop
|
||||
if (!Number.isFinite(maxWidth) || maxWidth < 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const lines: Array<string> = [];
|
||||
const originalLines = text.split("\n");
|
||||
const spaceWidth = getLineWidth(" ", font);
|
||||
|
||||
let currentLine = "";
|
||||
let currentLineWidthTillNow = 0;
|
||||
|
||||
const push = (str: string) => {
|
||||
if (str.trim()) {
|
||||
lines.push(str);
|
||||
}
|
||||
};
|
||||
|
||||
const resetParams = () => {
|
||||
currentLine = "";
|
||||
currentLineWidthTillNow = 0;
|
||||
};
|
||||
|
||||
originalLines.forEach((originalLine) => {
|
||||
const currentLineWidth = getTextWidth(originalLine, font);
|
||||
|
||||
//Push the line if its <= maxWidth
|
||||
if (currentLineWidth <= maxWidth) {
|
||||
lines.push(originalLine);
|
||||
return; // continue
|
||||
}
|
||||
const words = originalLine.split(" ");
|
||||
|
||||
resetParams();
|
||||
|
||||
let index = 0;
|
||||
|
||||
while (index < words.length) {
|
||||
const currentWordWidth = getLineWidth(words[index], font);
|
||||
|
||||
// This will only happen when single word takes entire width
|
||||
if (currentWordWidth === maxWidth) {
|
||||
push(words[index]);
|
||||
index++;
|
||||
}
|
||||
|
||||
// Start breaking longer words exceeding max width
|
||||
else if (currentWordWidth > maxWidth) {
|
||||
// push current line since the current word exceeds the max width
|
||||
// so will be appended in next line
|
||||
push(currentLine);
|
||||
|
||||
resetParams();
|
||||
|
||||
while (words[index].length > 0) {
|
||||
const currentChar = String.fromCodePoint(
|
||||
words[index].codePointAt(0)!,
|
||||
);
|
||||
const width = charWidth.calculate(currentChar, font);
|
||||
currentLineWidthTillNow += width;
|
||||
words[index] = words[index].slice(currentChar.length);
|
||||
|
||||
if (currentLineWidthTillNow >= maxWidth) {
|
||||
push(currentLine);
|
||||
currentLine = currentChar;
|
||||
currentLineWidthTillNow = width;
|
||||
} else {
|
||||
currentLine += currentChar;
|
||||
}
|
||||
}
|
||||
|
||||
// push current line if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
push(currentLine);
|
||||
resetParams();
|
||||
} else {
|
||||
// space needs to be appended before next word
|
||||
// as currentLine contains chars which couldn't be appended
|
||||
// to previous line
|
||||
currentLine += " ";
|
||||
currentLineWidthTillNow += spaceWidth;
|
||||
}
|
||||
index++;
|
||||
} else {
|
||||
// Start appending words in a line till max width reached
|
||||
while (currentLineWidthTillNow < maxWidth && index < words.length) {
|
||||
const word = words[index];
|
||||
currentLineWidthTillNow = getLineWidth(currentLine + word, font);
|
||||
|
||||
if (currentLineWidthTillNow > maxWidth) {
|
||||
push(currentLine);
|
||||
resetParams();
|
||||
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
currentLine += `${word} `;
|
||||
|
||||
// Push the word if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
const word = currentLine.slice(0, -1);
|
||||
push(word);
|
||||
resetParams();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentLine.slice(-1) === " ") {
|
||||
// only remove last trailing space which we have added when joining words
|
||||
currentLine = currentLine.slice(0, -1);
|
||||
push(currentLine);
|
||||
}
|
||||
});
|
||||
return lines.join("\n");
|
||||
};
|
||||
|
||||
export const isMeasureTextSupported = () => {
|
||||
const width = getTextWidth(
|
||||
DUMMY_TEXT,
|
||||
getFontString({
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
}),
|
||||
);
|
||||
return width > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* We calculate the line height from the font size and the unitless line height,
|
||||
* aligning with the W3C spec.
|
||||
*/
|
||||
export const getLineHeightInPx = (
|
||||
fontSize: ExcalidrawTextElement["fontSize"],
|
||||
lineHeight: ExcalidrawTextElement["lineHeight"],
|
||||
) => {
|
||||
return fontSize * lineHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* To get unitless line-height (if unknown) we can calculate it by dividing
|
||||
* height-per-line by fontSize.
|
||||
*/
|
||||
export const detectLineHeight = (textElement: ExcalidrawTextElement) => {
|
||||
const lineCount = splitIntoLines(textElement.text).length;
|
||||
return (textElement.height /
|
||||
lineCount /
|
||||
textElement.fontSize) as ExcalidrawTextElement["lineHeight"];
|
||||
};
|
||||
|
||||
/**
|
||||
* Unitless line height
|
||||
*
|
||||
* In previous versions we used `normal` line height, which browsers interpret
|
||||
* differently, and based on font-family and font-size.
|
||||
*
|
||||
* To make line heights consistent across browsers we hardcode the values for
|
||||
* each of our fonts based on most common average line-heights.
|
||||
* See https://github.com/excalidraw/excalidraw/pull/6360#issuecomment-1477635971
|
||||
* where the values come from.
|
||||
*/
|
||||
const DEFAULT_LINE_HEIGHT = {
|
||||
// ~1.25 is the average for Virgil in WebKit and Blink.
|
||||
// Gecko (FF) uses ~1.28.
|
||||
[FONT_FAMILY.Virgil]: 1.25 as ExcalidrawTextElement["lineHeight"],
|
||||
// ~1.15 is the average for Helvetica in WebKit and Blink.
|
||||
// Gecko if all over the place.
|
||||
[FONT_FAMILY.Helvetica]: 1.15 as ExcalidrawTextElement["lineHeight"],
|
||||
// ~1.2 is the average for Cascadia in WebKit and Blink, and kinda Gecko too
|
||||
[FONT_FAMILY.Cascadia]: 1.2 as ExcalidrawTextElement["lineHeight"],
|
||||
};
|
||||
|
||||
export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
|
||||
if (fontFamily) {
|
||||
return DEFAULT_LINE_HEIGHT[fontFamily];
|
||||
}
|
||||
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
|
||||
};
|
||||
@@ -526,44 +526,6 @@ describe("textWysiwyg", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("should compute the container height correctly and not throw error when height is updated while editing the text", async () => {
|
||||
const diamond = API.createElement({
|
||||
type: "diamond",
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 90,
|
||||
height: 75,
|
||||
});
|
||||
h.elements = [diamond];
|
||||
|
||||
expect(h.elements.length).toBe(1);
|
||||
expect(h.elements[0].id).toBe(diamond.id);
|
||||
|
||||
API.setSelectedElements([diamond]);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
|
||||
const editor = document.querySelector(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
const value = new Array(1000).fill("1").join("\n");
|
||||
|
||||
// Pasting large text to simulate height increase
|
||||
expect(() =>
|
||||
fireEvent.input(editor, { target: { value } }),
|
||||
).not.toThrow();
|
||||
|
||||
expect(diamond.height).toBe(50020);
|
||||
|
||||
// Clearing text to simulate height decrease
|
||||
expect(() =>
|
||||
fireEvent.input(editor, { target: { value: "" } }),
|
||||
).not.toThrow();
|
||||
|
||||
expect(diamond.height).toBe(70);
|
||||
});
|
||||
|
||||
it("should bind text to container when double clicked on center of transparent container", async () => {
|
||||
const rectangle = API.createElement({
|
||||
type: "rectangle",
|
||||
@@ -740,52 +702,6 @@ describe("textWysiwyg", () => {
|
||||
expect(rectangle.boundElements).toBe(null);
|
||||
});
|
||||
|
||||
it("should bind text to container when triggered via context menu", async () => {
|
||||
expect(h.elements.length).toBe(1);
|
||||
expect(h.elements[0].id).toBe(rectangle.id);
|
||||
|
||||
UI.clickTool("text");
|
||||
mouse.clickAt(20, 30);
|
||||
const editor = document.querySelector(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
fireEvent.change(editor, {
|
||||
target: {
|
||||
value: "Excalidraw is an opensource virtual collaborative whiteboard",
|
||||
},
|
||||
});
|
||||
|
||||
editor.dispatchEvent(new Event("input"));
|
||||
await new Promise((cb) => setTimeout(cb, 0));
|
||||
expect(h.elements.length).toBe(2);
|
||||
expect(h.elements[1].type).toBe("text");
|
||||
|
||||
API.setSelectedElements([h.elements[0], h.elements[1]]);
|
||||
fireEvent.contextMenu(GlobalTestState.canvas, {
|
||||
button: 2,
|
||||
clientX: 20,
|
||||
clientY: 30,
|
||||
});
|
||||
const contextMenu = document.querySelector(".context-menu");
|
||||
fireEvent.click(
|
||||
queryByText(contextMenu as HTMLElement, "Bind text to the container")!,
|
||||
);
|
||||
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
expect(rectangle.boundElements).toStrictEqual([
|
||||
{ id: h.elements[1].id, type: "text" },
|
||||
]);
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
expect(text.verticalAlign).toBe(VERTICAL_ALIGN.MIDDLE);
|
||||
expect(text.textAlign).toBe(TEXT_ALIGN.CENTER);
|
||||
expect(text.x).toBe(
|
||||
h.elements[0].x + h.elements[0].width / 2 - text.width / 2,
|
||||
);
|
||||
expect(text.y).toBe(
|
||||
h.elements[0].y + h.elements[0].height / 2 - text.height / 2,
|
||||
);
|
||||
});
|
||||
|
||||
it("should update font family correctly on undo/redo by selecting bounded text when font family was updated", async () => {
|
||||
expect(h.elements.length).toBe(1);
|
||||
|
||||
@@ -1265,7 +1181,9 @@ describe("textWysiwyg", () => {
|
||||
expect(
|
||||
(h.elements[1] as ExcalidrawTextElementWithContainer).fontSize,
|
||||
).toEqual(36);
|
||||
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(97);
|
||||
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(
|
||||
96.39999999999999,
|
||||
);
|
||||
});
|
||||
|
||||
it("should update line height when font family updated", async () => {
|
||||
@@ -1506,7 +1424,7 @@ describe("textWysiwyg", () => {
|
||||
expect.objectContaining({
|
||||
text: "Excalidraw is an opensource virtual collaborative whiteboard",
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
textAlign: TEXT_ALIGN.LEFT,
|
||||
boundElements: null,
|
||||
}),
|
||||
);
|
||||
|
||||
+38
-60
@@ -11,7 +11,7 @@ import {
|
||||
isBoundToContainer,
|
||||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
import { CLASSES, isSafari, VERTICAL_ALIGN } from "../constants";
|
||||
import { CLASSES } from "../constants";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
@@ -23,19 +23,14 @@ import { AppState } from "../types";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import {
|
||||
getBoundTextElementId,
|
||||
getContainerCoords,
|
||||
getContainerDims,
|
||||
getContainerElement,
|
||||
getTextElementAngle,
|
||||
getTextWidth,
|
||||
measureText,
|
||||
normalizeText,
|
||||
redrawTextBoundingBox,
|
||||
wrapText,
|
||||
getMaxContainerHeight,
|
||||
getMaxContainerWidth,
|
||||
computeContainerDimensionForBoundText,
|
||||
detectLineHeight,
|
||||
getBoundTextMaxHeight,
|
||||
getBoundTextMaxWidth,
|
||||
computeBoundTextPosition,
|
||||
} from "./textElement";
|
||||
import {
|
||||
actionDecreaseFontSize,
|
||||
@@ -45,6 +40,12 @@ import { actionZoomIn, actionZoomOut } from "../actions/actionCanvas";
|
||||
import App from "../components/App";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
import { parseClipboard } from "../clipboard";
|
||||
import {
|
||||
getTextWidth,
|
||||
measureText,
|
||||
wrapText,
|
||||
getTextHeight,
|
||||
} from "./textMeasurements";
|
||||
|
||||
const getTransform = (
|
||||
width: number,
|
||||
@@ -179,15 +180,12 @@ export const textWysiwyg = ({
|
||||
editable,
|
||||
);
|
||||
const containerDims = getContainerDims(container);
|
||||
// using editor.style.height to get the accurate height of text editor
|
||||
const editorHeight = Number(editable.style.height.slice(0, -2));
|
||||
if (editorHeight > 0) {
|
||||
textElementHeight = editorHeight;
|
||||
}
|
||||
if (propertiesUpdated) {
|
||||
// update height of the editor after properties updated
|
||||
textElementHeight = updatedTextElement.height;
|
||||
}
|
||||
|
||||
textElementHeight = getTextHeight(
|
||||
updatedTextElement.text,
|
||||
updatedTextElement.fontSize,
|
||||
updatedTextElement.lineHeight,
|
||||
);
|
||||
|
||||
let originalContainerData;
|
||||
if (propertiesUpdated) {
|
||||
@@ -205,17 +203,19 @@ export const textWysiwyg = ({
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth = getMaxContainerWidth(container);
|
||||
maxHeight = getMaxContainerHeight(container);
|
||||
maxWidth = getBoundTextMaxWidth(container);
|
||||
maxHeight = getBoundTextMaxHeight(
|
||||
container,
|
||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
|
||||
// autogrow container height if text exceeds
|
||||
if (!isArrowElement(container) && textElementHeight > maxHeight) {
|
||||
const targetContainerHeight = computeContainerDimensionForBoundText(
|
||||
textElementHeight,
|
||||
container.type,
|
||||
const diff = Math.min(
|
||||
textElementHeight - maxHeight,
|
||||
element.lineHeight,
|
||||
);
|
||||
|
||||
mutateElement(container, { height: targetContainerHeight });
|
||||
mutateElement(container, { height: containerDims.height + diff });
|
||||
return;
|
||||
} else if (
|
||||
// autoshrink container height until original container height
|
||||
@@ -224,27 +224,17 @@ export const textWysiwyg = ({
|
||||
containerDims.height > originalContainerData.height &&
|
||||
textElementHeight < maxHeight
|
||||
) {
|
||||
const targetContainerHeight = computeContainerDimensionForBoundText(
|
||||
textElementHeight,
|
||||
container.type,
|
||||
const diff = Math.min(
|
||||
maxHeight - textElementHeight,
|
||||
element.lineHeight,
|
||||
);
|
||||
mutateElement(container, { height: targetContainerHeight });
|
||||
}
|
||||
// Start pushing text upward until a diff of 30px (padding)
|
||||
// is reached
|
||||
else {
|
||||
const containerCoords = getContainerCoords(container);
|
||||
|
||||
// vertically center align the text
|
||||
if (verticalAlign === VERTICAL_ALIGN.MIDDLE) {
|
||||
if (!isArrowElement(container)) {
|
||||
coordY =
|
||||
containerCoords.y + maxHeight / 2 - textElementHeight / 2;
|
||||
}
|
||||
}
|
||||
if (verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
||||
coordY = containerCoords.y + (maxHeight - textElementHeight);
|
||||
}
|
||||
mutateElement(container, { height: containerDims.height - diff });
|
||||
} else {
|
||||
const { y } = computeBoundTextPosition(
|
||||
container,
|
||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
coordY = y;
|
||||
}
|
||||
}
|
||||
const [viewportX, viewportY] = getViewportCoords(coordX, coordY);
|
||||
@@ -272,24 +262,13 @@ export const textWysiwyg = ({
|
||||
} else {
|
||||
textElementWidth += 0.5;
|
||||
}
|
||||
|
||||
let lineHeight = updatedTextElement.lineHeight;
|
||||
|
||||
// In Safari the font size gets rounded off when rendering hence calculating the line height by rounding off font size
|
||||
if (isSafari) {
|
||||
lineHeight = detectLineHeight({
|
||||
...updatedTextElement,
|
||||
fontSize: Math.round(updatedTextElement.fontSize),
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure text editor height doesn't go beyond viewport
|
||||
const editorMaxHeight =
|
||||
(appState.height - viewportY) / appState.zoom.value;
|
||||
Object.assign(editable.style, {
|
||||
font: getFontString(updatedTextElement),
|
||||
// must be defined *after* font ¯\_(ツ)_/¯
|
||||
lineHeight,
|
||||
lineHeight: element.lineHeight,
|
||||
width: `${textElementWidth}px`,
|
||||
height: `${textElementHeight}px`,
|
||||
left: `${viewportX}px`,
|
||||
@@ -309,7 +288,6 @@ export const textWysiwyg = ({
|
||||
filter: "var(--theme-filter)",
|
||||
maxHeight: `${editorMaxHeight}px`,
|
||||
});
|
||||
editable.scrollTop = 0;
|
||||
// For some reason updating font attribute doesn't set font family
|
||||
// hence updating font family explicitly for test environment
|
||||
if (isTestEnv()) {
|
||||
@@ -377,7 +355,7 @@ export const textWysiwyg = ({
|
||||
const wrappedText = wrapText(
|
||||
`${editable.value}${data}`,
|
||||
font,
|
||||
getMaxContainerWidth(container),
|
||||
getBoundTextMaxWidth(container),
|
||||
);
|
||||
const width = getTextWidth(wrappedText, font);
|
||||
editable.style.width = `${width}px`;
|
||||
@@ -394,7 +372,7 @@ export const textWysiwyg = ({
|
||||
const wrappedText = wrapText(
|
||||
normalizeText(editable.value),
|
||||
font,
|
||||
getMaxContainerWidth(container!),
|
||||
getBoundTextMaxWidth(container!),
|
||||
);
|
||||
const { width, height } = measureText(
|
||||
wrappedText,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { MarkNonNullable, ValueOf } from "../utility-types";
|
||||
|
||||
export type ChartType = "bar" | "line";
|
||||
export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
|
||||
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
||||
export type FontFamilyKeys = keyof typeof FONT_FAMILY;
|
||||
export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys];
|
||||
export type Theme = typeof THEME[keyof typeof THEME];
|
||||
@@ -131,7 +131,6 @@ export type ExcalidrawTextElement = _ExcalidrawElementBase &
|
||||
fontSize: number;
|
||||
fontFamily: FontFamilyValues;
|
||||
text: string;
|
||||
baseline: number;
|
||||
textAlign: TextAlign;
|
||||
verticalAlign: VerticalAlign;
|
||||
containerId: ExcalidrawGenericElement["id"] | null;
|
||||
|
||||
Vendored
-2
@@ -18,8 +18,6 @@ interface Window {
|
||||
EXCALIDRAW_EXPORT_SOURCE: string;
|
||||
EXCALIDRAW_THROTTLE_RENDER: boolean | undefined;
|
||||
gtag: Function;
|
||||
_paq: any[];
|
||||
_mtm: any[];
|
||||
}
|
||||
|
||||
interface CanvasRenderingContext2D {
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "تكبير حجم الخط",
|
||||
"unbindText": "فك ربط النص",
|
||||
"bindText": "ربط النص بالحاوية",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "تعديل الرابط",
|
||||
"create": "إنشاء رابط",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "تعذر الاتصال بخادم التعاون. الرجاء إعادة تحميل الصفحة والمحاولة مرة أخرى.",
|
||||
"importLibraryError": "تعذر تحميل المكتبة",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "تعذر الحفظ في قاعدة البيانات، يبدو أن القماش كبير للغاية، يفضّل حفظ الملف محليا كي لا تفقد عملك."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "تحديد",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "انقر مرتين",
|
||||
"drag": "اسحب",
|
||||
"editor": "المحرر",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "تعديل الشكل المحدد (النص/السهم/الخط)",
|
||||
"github": "عثرت على مشكلة؟ إرسال",
|
||||
"howto": "اتبع التعليمات",
|
||||
"or": "أو",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "",
|
||||
"create": "",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Селекция",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "",
|
||||
"drag": "плъзнете",
|
||||
"editor": "Редактор",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "Намерихте проблем? Изпратете",
|
||||
"howto": "Следвайте нашите ръководства",
|
||||
"or": "или",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "লেখনীর মাত্রা বাড়ান",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "লিঙ্ক সংশোধন",
|
||||
"create": "লিঙ্ক তৈরী",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "কোল্যাব সার্ভারের সাথে সংযোগ করা যায়নি। পৃষ্ঠাটি পুনরায় লোড করে আবার চেষ্টা করুন।",
|
||||
"importLibraryError": "সংগ্রহ লোড করা যায়নি",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "বাছাই",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "অথবা",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Augmenta la mida de la lletra",
|
||||
"unbindText": "Desvincular el text",
|
||||
"bindText": "Ajusta el text al contenidor",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Edita l'enllaç",
|
||||
"create": "Crea un enllaç",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "No ha estat possible connectar amb el servidor collab. Si us plau recarregueu la pàgina i torneu a provar.",
|
||||
"importLibraryError": "No s'ha pogut carregar la biblioteca",
|
||||
"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": ""
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selecció",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "doble clic",
|
||||
"drag": "arrossega",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Edita la forma seleccionada (text, fletxa o línia)",
|
||||
"github": "Hi heu trobat un problema? Informeu-ne",
|
||||
"howto": "Seguiu les nostres guies",
|
||||
"or": "o",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Zvětšit písmo",
|
||||
"unbindText": "Zrušit vazbu textu",
|
||||
"bindText": "Vázat text s kontejnerem",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Upravit odkaz",
|
||||
"create": "Vytvořit odkaz",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Výběr",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dvojklik",
|
||||
"drag": "tažení",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "nebo",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "",
|
||||
"create": "",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Schrift vergrößern",
|
||||
"unbindText": "Text lösen",
|
||||
"bindText": "Text an Container binden",
|
||||
"createContainerFromText": "Text in Container einbetten",
|
||||
"link": {
|
||||
"edit": "Link bearbeiten",
|
||||
"create": "Link erstellen",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Konnte keine Verbindung zum Collab-Server herstellen. Bitte lade die Seite neu und versuche es erneut.",
|
||||
"importLibraryError": "Bibliothek konnte nicht geladen werden",
|
||||
"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"
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Auswahl",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "doppelklicken",
|
||||
"drag": "ziehen",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "Linien-/Pfeil-Punkte bearbeiten",
|
||||
"editText": "Text bearbeiten / Label hinzufügen",
|
||||
"editSelectedShape": "Ausgewählte Form bearbeiten (Text/Pfeil/Linie)",
|
||||
"github": "Ein Problem gefunden? Informiere uns",
|
||||
"howto": "Folge unseren Anleitungen",
|
||||
"or": "oder",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Αύξηση μεγέθους γραμματοσειράς",
|
||||
"unbindText": "Αποσύνδεση κειμένου",
|
||||
"bindText": "Δέσμευση κειμένου στο δοχείο",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Επεξεργασία συνδέσμου",
|
||||
"create": "Δημιουργία συνδέσμου",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Αδυναμία σύνδεσης με τον διακομιστή συνεργασίας. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά.",
|
||||
"importLibraryError": "Αδυναμία φόρτωσης βιβλιοθήκης",
|
||||
"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"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Η αποθήκευση στη βάση δεδομένων δεν ήταν δυνατή, ο καμβάς φαίνεται να είναι πολύ μεγάλος. Θα πρέπει να αποθηκεύσετε το αρχείο τοπικά για να βεβαιωθείτε ότι δεν θα χάσετε την εργασία σας."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Επιλογή",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "διπλό κλικ",
|
||||
"drag": "σύρε",
|
||||
"editor": "Επεξεργαστής",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Επεξεργασία επιλεγμένου σχήματος (κείμενο/βέλος/γραμμή)",
|
||||
"github": "Βρήκατε πρόβλημα; Υποβάλετε το",
|
||||
"howto": "Ακολουθήστε τους οδηγούς μας",
|
||||
"or": "ή",
|
||||
|
||||
+1
-2
@@ -319,8 +319,7 @@
|
||||
"doubleClick": "double-click",
|
||||
"drag": "drag",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "Edit line/arrow points",
|
||||
"editText": "Edit text / add label",
|
||||
"editSelectedShape": "Edit selected shape (text/arrow/line)",
|
||||
"github": "Found an issue? Submit",
|
||||
"howto": "Follow our guides",
|
||||
"or": "or",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Aumentar el tamaño de letra",
|
||||
"unbindText": "Desvincular texto",
|
||||
"bindText": "Vincular texto al contenedor",
|
||||
"createContainerFromText": "Envolver el texto en un contenedor",
|
||||
"link": {
|
||||
"edit": "Editar enlace",
|
||||
"create": "Crear enlace",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "No se pudo conectar al servidor colaborador. Por favor, vuelva a cargar la página y vuelva a intentarlo.",
|
||||
"importLibraryError": "No se pudo cargar la librería",
|
||||
"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": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"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"
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selección",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "doble clic",
|
||||
"drag": "arrastrar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Editar la forma seleccionada (texto/flecha/línea)",
|
||||
"github": "¿Ha encontrado un problema? Envíelo",
|
||||
"howto": "Siga nuestras guías",
|
||||
"or": "o",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Handitu letra tamaina",
|
||||
"unbindText": "Askatu testua",
|
||||
"bindText": "Lotu testua edukiontziari",
|
||||
"createContainerFromText": "Bilatu testua edukiontzi batean",
|
||||
"link": {
|
||||
"edit": "Editatu esteka",
|
||||
"create": "Sortu esteka",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Ezin izan da elkarlaneko zerbitzarira konektatu. Mesedez, berriro kargatu orria eta saiatu berriro.",
|
||||
"importLibraryError": "Ezin izan da liburutegia kargatu",
|
||||
"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"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Ezin izan da backend datu-basean gorde, ohiala handiegia dela dirudi. Fitxategia lokalean gorde beharko zenuke zure lana galtzen ez duzula ziurtatzeko."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Hautapena",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "klik bikoitza",
|
||||
"drag": "arrastatu",
|
||||
"editor": "Editorea",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Editatu hautatutako forma (testua/gezia/lerroa)",
|
||||
"github": "Arazorik izan al duzu? Eman horren berri",
|
||||
"howto": "Jarraitu gure gidak",
|
||||
"or": "edo",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "افزایش دادن اندازه فونت",
|
||||
"unbindText": "بازکردن نوشته",
|
||||
"bindText": "بستن نوشته",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "ویرایش لینک",
|
||||
"create": "ایجاد پیوند",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "به سرور collab متصل نشد. لطفا صفحه را مجددا بارگذاری کنید و دوباره تلاش کنید.",
|
||||
"importLibraryError": "دادهها بارگذاری نشدند",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "گزینش",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "دابل کلیک",
|
||||
"drag": "کشیدن",
|
||||
"editor": "ویرایشگر",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "ویرایش شکل انتخاب شده (متن/فلش/خط)",
|
||||
"github": "اشکالی می بینید؟ گزارش دهید",
|
||||
"howto": "راهنمای ما را دنبال کنید",
|
||||
"or": "یا",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Kasvata kirjasinkokoa",
|
||||
"unbindText": "Irroita teksti",
|
||||
"bindText": "Kiinnitä teksti säiliöön",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Muokkaa linkkiä",
|
||||
"create": "Luo linkki",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Yhteyden muodostaminen collab-palvelimeen epäonnistui. Virkistä sivu ja yritä uudelleen.",
|
||||
"importLibraryError": "Kokoelman lataaminen epäonnistui",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Ei voitu tallentaan palvelimen tietokantaan. Jos ongelmia esiintyy, sinun kannatta tallentaa tallentaa tiedosto paikallisesti varmistaaksesi, että et menetä työtäsi."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Valinta",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "kaksoisnapsautus",
|
||||
"drag": "vedä",
|
||||
"editor": "Muokkausohjelma",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Muokkaa valittua muotoa (teksti/nuoli/viiva)",
|
||||
"github": "Löysitkö ongelman? Kerro meille",
|
||||
"howto": "Seuraa oppaitamme",
|
||||
"or": "tai",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Augmenter la taille de la police",
|
||||
"unbindText": "Dissocier le texte",
|
||||
"bindText": "Associer le texte au conteneur",
|
||||
"createContainerFromText": "Encadrer le texte dans un conteneur",
|
||||
"link": {
|
||||
"edit": "Modifier le lien",
|
||||
"create": "Ajouter un lien",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Impossible de se connecter au serveur collaboratif. Veuillez recharger la page et réessayer.",
|
||||
"importLibraryError": "Impossible de charger la bibliothèque",
|
||||
"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"
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Sélection",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "double-clic",
|
||||
"drag": "glisser",
|
||||
"editor": "Éditeur",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Modifier la forme sélectionnée (texte/flèche/ligne)",
|
||||
"github": "Problème trouvé ? Soumettre",
|
||||
"howto": "Suivez nos guides",
|
||||
"or": "ou",
|
||||
|
||||
+3
-20
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Aumentar o tamaño da fonte",
|
||||
"unbindText": "Desvincular texto",
|
||||
"bindText": "Ligar o texto ao contedor",
|
||||
"createContainerFromText": "Envolver o texto nun contedor",
|
||||
"link": {
|
||||
"edit": "Editar ligazón",
|
||||
"create": "Crear ligazón",
|
||||
@@ -194,7 +193,7 @@
|
||||
"resetLibrary": "Isto limpará a súa biblioteca. Está seguro?",
|
||||
"removeItemsFromsLibrary": "Eliminar {{count}} elemento(s) da biblioteca?",
|
||||
"invalidEncryptionKey": "A clave de cifrado debe ter 22 caracteres. A colaboración en directo está desactivada.",
|
||||
"collabOfflineWarning": "Non hai conexión a Internet dispoñible.\nOs teus cambios non serán gardados!"
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Tipo de ficheiro non soportado.",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Non se puido conectar ao servidor de colaboración. Por favor recargue a páxina e probe de novo.",
|
||||
"importLibraryError": "Non se puido cargar a biblioteca",
|
||||
"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"
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selección",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dobre-clic",
|
||||
"drag": "arrastrar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Editar a forma seleccionada (texto/frecha/liña)",
|
||||
"github": "Encontrou un problema? Envíeo",
|
||||
"howto": "Sigue as nosas normas",
|
||||
"or": "ou",
|
||||
|
||||
+145
-162
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"labels": {
|
||||
"paste": "הדבק",
|
||||
"pasteAsPlaintext": "הדבק ללא עיצוב",
|
||||
"pasteAsPlaintext": "",
|
||||
"pasteCharts": "הדבק גרפים",
|
||||
"selectAll": "בחר הכל",
|
||||
"multiSelect": "הוסף רכיב לבחירה",
|
||||
"multiSelect": "הוסף אובייקט לבחירה",
|
||||
"moveCanvas": "הזז את הקנבס",
|
||||
"cut": "גזור",
|
||||
"cut": "חתוך",
|
||||
"copy": "העתק",
|
||||
"copyAsPng": "העתק ללוח כ PNG",
|
||||
"copyAsSvg": "העתק ללוח כ SVG",
|
||||
"copyText": "העתק ללוח כטקסט",
|
||||
"copyText": "העתק ללוח כ-PNG",
|
||||
"bringForward": "הבא שכבה קדימה",
|
||||
"sendToBack": "שלח אחורה",
|
||||
"sendToBack": "העבר לסוף",
|
||||
"bringToFront": "העבר לחזית",
|
||||
"sendBackward": "העבר שכבה אחורה",
|
||||
"delete": "מחק",
|
||||
@@ -26,7 +26,7 @@
|
||||
"strokeStyle_solid": "מלא",
|
||||
"strokeStyle_dashed": "מקווקו",
|
||||
"strokeStyle_dotted": "מנוקד",
|
||||
"sloppiness": "רישול",
|
||||
"sloppiness": "סגנון",
|
||||
"opacity": "אטימות",
|
||||
"textAlign": "יישור טקסט",
|
||||
"edges": "קצוות",
|
||||
@@ -35,57 +35,57 @@
|
||||
"arrowheads": "ראשי חצים",
|
||||
"arrowhead_none": "ללא",
|
||||
"arrowhead_arrow": "חץ",
|
||||
"arrowhead_bar": "קצה אנכי",
|
||||
"arrowhead_bar": "שורה",
|
||||
"arrowhead_dot": "נקודה",
|
||||
"arrowhead_triangle": "משולש",
|
||||
"fontSize": "גודל גופן",
|
||||
"fontFamily": "גופן",
|
||||
"fontFamily": "סוג הגופן",
|
||||
"onlySelected": "רק מה שנבחר",
|
||||
"withBackground": "רקע",
|
||||
"exportEmbedScene": "הטמעה של מידע הסצנה",
|
||||
"exportEmbedScene_details": "הייצוא יבוצע לקובץ מסוג PNG/SVG כדי שהמידע על הסצנה ישמר בו וניתן יהיה לבצע שחזור ממנו.\nיגדיל את גודל הקובץ של הייצוא.",
|
||||
"exportEmbedScene_details": "מידע התצוגה יישמר לקובץ המיוצא מסוג PNG/SVG כך שיהיה ניתן לשחזרה ממנו.\nהפעולה תגדיל את גודל הקובץ המיוצא.",
|
||||
"addWatermark": "הוסף \"נוצר באמצעות Excalidraw\"",
|
||||
"handDrawn": "ציור יד",
|
||||
"handDrawn": "כתב יד",
|
||||
"normal": "רגיל",
|
||||
"code": "קוד",
|
||||
"small": "קטן",
|
||||
"medium": "בינוני",
|
||||
"medium": "בנוני",
|
||||
"large": "גדול",
|
||||
"veryLarge": "גדול מאוד",
|
||||
"veryLarge": "ענק",
|
||||
"solid": "מוצק",
|
||||
"hachure": "קווים מקבילים קצרים להצגת כיוון וחדות שיפוע במפה",
|
||||
"crossHatch": "קווים מוצלבים שתי וערב",
|
||||
"hachure": "קווים משופעים",
|
||||
"crossHatch": "קווים מוצלבים",
|
||||
"thin": "דק",
|
||||
"bold": "מודגש",
|
||||
"left": "שמאל",
|
||||
"center": "מרכז",
|
||||
"right": "ימין",
|
||||
"extraBold": "מודגש במיוחד",
|
||||
"extraBold": "עבה",
|
||||
"architect": "ארכיטקט",
|
||||
"artist": "אמן",
|
||||
"cartoonist": "קריקטוריסט",
|
||||
"fileTitle": "שם קובץ",
|
||||
"colorPicker": "בוחר צבעים",
|
||||
"colorPicker": "בחירת צבע",
|
||||
"canvasColors": "בשימוש בקנבס",
|
||||
"canvasBackground": "רקע קנבס",
|
||||
"drawingCanvas": "קנבס ציור",
|
||||
"canvasBackground": "רקע הלוח",
|
||||
"drawingCanvas": "לוח ציור",
|
||||
"layers": "שכבות",
|
||||
"actions": "פעולות",
|
||||
"language": "שפה",
|
||||
"liveCollaboration": "התחל שיתוף חי...",
|
||||
"liveCollaboration": "",
|
||||
"duplicateSelection": "שכפל",
|
||||
"untitled": "ללא כותרת",
|
||||
"name": "שם",
|
||||
"yourName": "שמך",
|
||||
"yourName": "שם",
|
||||
"madeWithExcalidraw": "נוצר באמצעות Excalidraw",
|
||||
"group": "קבץ",
|
||||
"group": "אחד לקבוצה",
|
||||
"ungroup": "פרק קבוצה",
|
||||
"collaborators": "שותפים",
|
||||
"showGrid": "הצג רשת",
|
||||
"addToLibrary": "הוסף לספריה",
|
||||
"removeFromLibrary": "הסר מספריה",
|
||||
"libraryLoadingMessage": "טוען ספריה…",
|
||||
"libraries": "עיין בספריות",
|
||||
"libraries": "דפדף בספריות",
|
||||
"loadingScene": "טוען תצוגה…",
|
||||
"align": "יישר",
|
||||
"alignTop": "יישר למעלה",
|
||||
@@ -96,29 +96,28 @@
|
||||
"centerHorizontally": "מרכז אופקית",
|
||||
"distributeHorizontally": "חלוקה אופקית",
|
||||
"distributeVertically": "חלוקה אנכית",
|
||||
"flipHorizontal": "הפוך אופקית",
|
||||
"flipVertical": "הפוך אנכית",
|
||||
"flipHorizontal": "סובב אופקית",
|
||||
"flipVertical": "סובב אנכית",
|
||||
"viewMode": "מצב תצוגה",
|
||||
"toggleExportColorScheme": "מתג ערכת צבעים לייצוא",
|
||||
"toggleExportColorScheme": "שנה את ערכת צבעי הייצוא",
|
||||
"share": "שתף",
|
||||
"showStroke": "הצג בוחר צבע מברשת",
|
||||
"showBackground": "הצג בוחר צבע רקע",
|
||||
"showStroke": "הצג צבעי קו מתאר",
|
||||
"showBackground": "הצג צבעי רקע",
|
||||
"toggleTheme": "שינוי ערכת העיצוב",
|
||||
"personalLib": "ספריה פרטית",
|
||||
"excalidrawLib": "הספריה של Excalidraw",
|
||||
"decreaseFontSize": "הקטן את גודל הגופן",
|
||||
"increaseFontSize": "הגדל את גודל הגופן",
|
||||
"unbindText": "ביטול קיבוע הטקסט",
|
||||
"bindText": "קיבוע הטקסט למיכל",
|
||||
"createContainerFromText": "ארוז טקסט במיכל",
|
||||
"bindText": "קיבוע הטקסט לאוגד",
|
||||
"link": {
|
||||
"edit": "עריכת קישור",
|
||||
"create": "יצירת קישור",
|
||||
"label": "קישור"
|
||||
},
|
||||
"lineEditor": {
|
||||
"edit": "ערוך קו",
|
||||
"exit": "צא מעורך הקו"
|
||||
"edit": "",
|
||||
"exit": ""
|
||||
},
|
||||
"elementLock": {
|
||||
"lock": "נעילה",
|
||||
@@ -126,16 +125,16 @@
|
||||
"lockAll": "לנעול הכל",
|
||||
"unlockAll": "שחרור הכול"
|
||||
},
|
||||
"statusPublished": "פורסם",
|
||||
"sidebarLock": "שמור את סרגל הצד פתוח"
|
||||
"statusPublished": "",
|
||||
"sidebarLock": ""
|
||||
},
|
||||
"library": {
|
||||
"noItems": "עוד לא הוספת דברים...",
|
||||
"hint_emptyLibrary": "בחר משהו בקנבס כדי להוסיף אותו לכאן, או שתתקין ספריה מהספריה הציבורית מטה.",
|
||||
"hint_emptyPrivateLibrary": "בחר משהו בקנבס כדי להוסיף אותו לכאן."
|
||||
"noItems": "",
|
||||
"hint_emptyLibrary": "",
|
||||
"hint_emptyPrivateLibrary": ""
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "אפס את הקנבאס",
|
||||
"clearReset": "אפס את הלוח",
|
||||
"exportJSON": "ייצא לקובץ",
|
||||
"exportImage": "ייצוא התמונה...",
|
||||
"export": "שמור ל...",
|
||||
@@ -144,7 +143,7 @@
|
||||
"copyToClipboard": "העתק ללוח",
|
||||
"copyPngToClipboard": "העתק PNG ללוח",
|
||||
"scale": "קנה מידה",
|
||||
"save": "שמור לקובץ נוכחי",
|
||||
"save": "שמירת קובץ נוכחי",
|
||||
"saveAs": "שמירה בשם",
|
||||
"load": "פתח",
|
||||
"getShareableLink": "קבל קישור לשיתוף",
|
||||
@@ -160,73 +159,58 @@
|
||||
"undo": "בטל",
|
||||
"redo": "בצע מחדש",
|
||||
"resetLibrary": "איפוס ספריה",
|
||||
"createNewRoom": "צור חדר חדש",
|
||||
"createNewRoom": "צור חדר",
|
||||
"fullScreen": "מסך מלא",
|
||||
"darkMode": "מצב כהה",
|
||||
"lightMode": "מצב בהיר",
|
||||
"zenMode": "מצב זן",
|
||||
"exitZenMode": "צא ממצב זן",
|
||||
"exitZenMode": "צא ממצב תפריט מרחף",
|
||||
"cancel": "ביטול",
|
||||
"clear": "ניקוי",
|
||||
"remove": "הסר",
|
||||
"publishLibrary": "פרסום",
|
||||
"remove": "מחיקה",
|
||||
"publishLibrary": "פירסום",
|
||||
"submit": "שליחה",
|
||||
"confirm": "אשר"
|
||||
"confirm": "לאשר"
|
||||
},
|
||||
"alerts": {
|
||||
"clearReset": "פעולה זו תנקה את כל הקנבס. אתה בטוח?",
|
||||
"couldNotCreateShareableLink": "יצירת קישור לשיתוף נכשל.",
|
||||
"couldNotCreateShareableLinkTooBig": "יצירת קישור לשיתוף נכשל: התצוגה גדולה מדי",
|
||||
"couldNotLoadInvalidFile": "טעינת קובץ לא תקין נכשלה",
|
||||
"clearReset": "פעולה זו תנקה את כל הלוח. אתה בטוח?",
|
||||
"couldNotCreateShareableLink": "לא ניתן לייצר לינק לשיתוף.",
|
||||
"couldNotCreateShareableLinkTooBig": "לא הצלחנו לייצר קישור לשיתוף: התצוגה גדולה מדי",
|
||||
"couldNotLoadInvalidFile": "לא ניתן לטעון קובץ שאיננו תואם",
|
||||
"importBackendFailed": "ייבוא מהשרת נכשל.",
|
||||
"cannotExportEmptyCanvas": "לא ניתן לייצא קנבאס ריק.",
|
||||
"couldNotCopyToClipboard": "לא ניתן היה להעתיק ללוח.",
|
||||
"decryptFailed": "פיענוח ההצפנה של המידע נכשל.",
|
||||
"uploadedSecurly": "ההעלאה אובטחה באמצעות הצפנה מקצה לקצה, פירוש הדבר שהשרת של Excalidraw וגורמי צד ג׳ לא יכולים לקרוא את התוכן.",
|
||||
"cannotExportEmptyCanvas": "לא ניתן לייצא לוח ריק.",
|
||||
"couldNotCopyToClipboard": "לא ניתן היה להעתיק ללוח",
|
||||
"decryptFailed": "לא ניתן לפענח מידע.",
|
||||
"uploadedSecurly": "ההעלאה הוצפנה מקצה לקצה, ולכן שרת Excalidraw וצד שלישי לא יכולים לקרוא את התוכן.",
|
||||
"loadSceneOverridePrompt": "טעינה של ציור חיצוני תחליף את התוכן הקיים שלך. האם תרצה להמשיך?",
|
||||
"collabStopOverridePrompt": "עצירת השיתוף תוביל למחיקת הציור הקודם ששמור מקומית בדפדפן. האם אתה בטוח?\n\n(אם תרצה לשמור את הציור המקומי, סגור את הטאב של הדפדפן במקום.)",
|
||||
"collabStopOverridePrompt": "עצירת השיתוף תוביל למחיקת התרשימים השמורים בדפדפן. האם את/ה בטוח/ה?\n(אם תרצה לשמור את התרשימים הקיימים, תוכל לסגור את הדפדפן מבלי לסיים את השיתוף.)",
|
||||
"errorAddingToLibrary": "לא ניתן להוסיף פריט לספרייה",
|
||||
"errorRemovingFromLibrary": "לא ניתן להסיר פריט מהספריה",
|
||||
"confirmAddLibrary": "זה יוסיף {{numShapes}} צורה(ות) לספריה שלך. האם אתה בטוח?",
|
||||
"errorRemovingFromLibrary": "לא ניתן למחוק פריט מהספריה",
|
||||
"confirmAddLibrary": "הפעולה תוסיף {{numShapes}} צורה(ות) לספריה שלך. האם אתה בטוח?",
|
||||
"imageDoesNotContainScene": "נראה שהתמונה לא מכילה מידע על הסצינה. האם אפשרת הטמעת מידע הסצינה בעת השמירה?",
|
||||
"cannotRestoreFromImage": "לא הצלחנו לשחזר את הסצנה מקובץ התמונה",
|
||||
"invalidSceneUrl": "ייבוא מידע סצנה מהקישור שסופק כשל. או שהוא משובש, או שאינו מכיל מידע של Excalidraw בפורמט JSON.",
|
||||
"resetLibrary": "פעולה זו תנקה את כל הספריה שלך. אתה בטוח?",
|
||||
"removeItemsFromsLibrary": "מחק {{count}} פריט(ים) מהספריה?",
|
||||
"invalidEncryptionKey": "מפתח ההצפנה חייב להיות בן 22 תוים. השיתוף החי מנוטרל.",
|
||||
"collabOfflineWarning": "אין חיבור זמין לאינטרנט.\nהשינויים שלך לא ישמרו!"
|
||||
"cannotRestoreFromImage": "לא הצלחנו לשחזר את התצוגה מקובץ התמונה",
|
||||
"invalidSceneUrl": "ייבוא המידע מן סצינה מכתובת האינטרנט נכשלה. המידע בנוי באופן משובש או שהוא אינו קובץ JSON תקין של Excalidraw.",
|
||||
"resetLibrary": "פעולה זו תנקה את כל הלוח. אתה בטוח?",
|
||||
"removeItemsFromsLibrary": "מחיקת {{count}} פריטים(ים) מתוך הספריה?",
|
||||
"invalidEncryptionKey": "מפתח ההצפנה חייב להיות בן 22 תוים. השיתוף החי מבוטל.",
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "סוג הקובץ אינו נתמך.",
|
||||
"imageInsertError": "לא ניתן היה להוסיף את התמונה. אנא נסה שוב מאוחר יותר...",
|
||||
"fileTooBig": "הקובץ גדול מדי. הגודל המירבי המותר הינו {{maxSize}}.",
|
||||
"svgImageInsertError": "לא ניתן היה להוסיף את תמונת ה-SVG. הסימונים בתוך קובץ ה-SVG עשויים להיות שגויים.",
|
||||
"invalidSVGString": "SVG שגוי.",
|
||||
"cannotResolveCollabServer": "לא הצלחתי להתחבר לשרת השיתוף. אנא רענן את הדף ונסה שוב.",
|
||||
"imageInsertError": "לא ניתן היה להטמיע את התמונה, אנא נסו שוב מאוחר יותר...",
|
||||
"fileTooBig": "הקובץ כבד מדי. הגודל המקסימלי המותר הוא {{maxSize}}.",
|
||||
"svgImageInsertError": "לא ניתן היה להטמיע את תמונת ה-SVG. קידוד ה-SVG אינו תקני.",
|
||||
"invalidSVGString": "SVG בלתי תקני.",
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "לא ניתן היה לטעון את הספריה",
|
||||
"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"
|
||||
}
|
||||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "בחירה",
|
||||
"image": "הוספת תמונה",
|
||||
"rectangle": "מלבן",
|
||||
"diamond": "יהלום",
|
||||
"rectangle": "מרובע",
|
||||
"diamond": "מעוין",
|
||||
"ellipse": "אליפסה",
|
||||
"arrow": "חץ",
|
||||
"line": "קו",
|
||||
@@ -234,79 +218,79 @@
|
||||
"text": "טקסט",
|
||||
"library": "ספריה",
|
||||
"lock": "השאר את הכלי הנבחר פעיל גם לאחר סיום הציור",
|
||||
"penMode": "מצב עט - מנע נגיעה",
|
||||
"link": "הוספה/עדכון קישור של הצורה שנבחרה",
|
||||
"penMode": "",
|
||||
"link": "הוספה/עדכון של קישור עבור הצורה הנבחרת",
|
||||
"eraser": "מחק",
|
||||
"hand": "יד (כלי הזזה)"
|
||||
"hand": ""
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "פעולות קנבאס",
|
||||
"selectedShapeActions": "פעולות על הצורות שנבחרו",
|
||||
"canvasActions": "פעולות הלוח",
|
||||
"selectedShapeActions": "פעולות צורה שנבחרה",
|
||||
"shapes": "צורות"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "כדי להזיז את הקנבס, החזק את גלגל העכבר לחוץ או את מקש הרווח לחוץ תוך כדי גרירה, או השתמש בכלי היד",
|
||||
"linearElement": "לחץ להתחלת מספר נקודות, גרור לקו יחיד",
|
||||
"canvasPanning": "",
|
||||
"linearElement": "הקלק בשביל לבחור נקודות מרובות, גרור בשביל קו בודד",
|
||||
"freeDraw": "לחץ וגרור, שחרר כשסיימת",
|
||||
"text": "טיפ: אפשר להוסיף טקסט על ידי לחיצה כפולה בכל מקום עם כלי הבחירה",
|
||||
"text_selected": "לחץ לחיצה כפולה או הקש על אנטר לעריכת הטקסט",
|
||||
"text_editing": "כדי לסיים את העריכה לחץ על מקש Escape או על Ctrl (Cmd במחשבי אפל) ומקש Enter",
|
||||
"text_selected": "לחץ לחיצה כפולה או אנטר לעריכת הנקודות",
|
||||
"text_editing": "כדי לסיים את העריכה לחצו על מקש Escape או על Ctrl ומקש Enter (Cmd במחשבי אפל)",
|
||||
"linearElementMulti": "הקלק על הנקודה האחרונה או הקש Escape או Enter לסיום",
|
||||
"lockAngle": "ניתן להגביל את הזוויות על ידי החזקה של מקש ה- SHIFT",
|
||||
"lockAngle": "אתה יכול להגביל זווית ע״י לחיצה על SHIFT",
|
||||
"resize": "ניתן להגביל פרופורציות על ידי לחיצה על SHIFT תוך כדי שינוי גודל,\nהחזק ALT בשביל לשנות גודל ביחס למרכז",
|
||||
"resizeImage": "אתה יכול לשנות את הגודל בחופשיות על ידי החזקת מקש SHIFT,\nהחזק את מקש ALT כדי לבצע שינוי גודל מהמרכז",
|
||||
"resizeImage": "",
|
||||
"rotate": "ניתן להגביל זוויות על ידי לחיצה על SHIFT תוך כדי סיבוב",
|
||||
"lineEditor_info": "החזק Ctrl / Cmd ובצע לחיצה כפולה או לחץ Ctrl / Cmd + Enter לעריכת נקודות",
|
||||
"lineEditor_pointSelected": "לחץ Delete למחיקת נקודה/ות,\nCtrl / Cmd + D לשכפול, או גרור להזזה",
|
||||
"lineEditor_nothingSelected": "בחר נקודה כדי לערוך (החזק SHIFT לבחירת כמה),\nאו החזק Alt והקלק להוספת נקודות חדשות",
|
||||
"placeImage": "הקלק להנחת התמונה, או הקלק וגרור להגדרת הגודל שלו ידנית",
|
||||
"publishLibrary": "פרסם ספריה משלך",
|
||||
"bindTextToElement": "הקש Enter כדי להוספת טקסט",
|
||||
"deepBoxSelect": "החזק Ctrl / Cmd לבחירה עמוקה ולמניעת גרירה",
|
||||
"eraserRevert": "החזק Alt להחזרת רכיבים מסומנים למחיקה",
|
||||
"firefox_clipboard_write": "יכולות זה ניתנת להפעלה על ידי שינוי הדגל של \"dom.events.asyncClipboard.clipboardItem\" למצב \"true\". כדי לשנות את הדגל בדפדפן Firefox, בקר בעמוד ״about:config״."
|
||||
"lineEditor_info": "",
|
||||
"lineEditor_pointSelected": "",
|
||||
"lineEditor_nothingSelected": "",
|
||||
"placeImage": "",
|
||||
"publishLibrary": "פירסום ספריה אישית",
|
||||
"bindTextToElement": "יש להקיש Enter כדי להוסיף טקסט",
|
||||
"deepBoxSelect": "",
|
||||
"eraserRevert": "",
|
||||
"firefox_clipboard_write": ""
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "לא ניתן להראות תצוגה מקדימה",
|
||||
"cannotShowPreview": "לא הצלחנו להציג את התצוגה המקדימה",
|
||||
"canvasTooBig": "הקנבס עלול להיות גדול מדי.",
|
||||
"canvasTooBigTip": "טיפ: נסה להזיז את הרכיבים הרחוקים ביותר מעט קרוב יותר האחד לשני."
|
||||
"canvasTooBigTip": "טיפ: נסה להזיז את האלמנטים הרחוקים ביותר מעט קרוב יותר יחד."
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "אירעה שגיאה. נסה ",
|
||||
"headingMain_button": "טוען את העמוד מחדש.",
|
||||
"clearCanvasMessage": "אם טעינה מחדש לא עובדת, נסה ",
|
||||
"clearCanvasMessage_button": "מנקה את הקנבאס.",
|
||||
"clearCanvasCaveat": " זה יגרום לאובדן העבודה ",
|
||||
"trackedToSentry_pre": "השגיאה עם מזהה ",
|
||||
"clearCanvasMessage_button": "מנקה את הלוח.",
|
||||
"clearCanvasCaveat": " זה יביא לאובדן עבודה ",
|
||||
"trackedToSentry_pre": "שגיאה עם מזהה ",
|
||||
"trackedToSentry_post": " נמצאה במערכת שלנו.",
|
||||
"openIssueMessage_pre": "נזהרנו מאוד שלא לכלול מידע מהקנבאס שלך בשגיאה. אם המידע בקנבאס אינו אישי, שקול לבצע מעקב אחר הטיפול שלנו ",
|
||||
"openIssueMessage_pre": "נזהרנו מאוד שלא לכלול מידע שלך בשגיאה. אם המידע איננו אישי, בבקשה עקוב אחר ",
|
||||
"openIssueMessage_button": "מעקב באגים.",
|
||||
"openIssueMessage_post": " בבקשה כלול את המידע מטה באמצעות העתקתה שלו, והדבקה שלו ב- GitHub Issue.",
|
||||
"sceneContent": "תוכן הקנבאס:"
|
||||
"openIssueMessage_post": " בבקשה כלול את המידע למטה באמצעות העתקה והדבקה בנושא ב GitHub.",
|
||||
"sceneContent": "תוכן הלוח:"
|
||||
},
|
||||
"roomDialog": {
|
||||
"desc_intro": "אתה יכול להזמין אנשים לקנבאס הנוכחי שלך לעבודה משותפת.",
|
||||
"desc_intro": "אתה יכול להזמין אנשים ללוח הנוכחי שלך בכדי לשתף פעולה.",
|
||||
"desc_privacy": "אל דאגה, השיתוף מוצפן מקצה לקצה, כך שכל מה שתצייר ישאר פרטי. אפילו השרתים שלנו לא יוכלו לראות את מה שאתה ממציא.",
|
||||
"button_startSession": "התחל שיתוף",
|
||||
"button_stopSession": "הפסק שיתוף",
|
||||
"desc_inProgressIntro": "שיתוף חי פעיל כרגע.",
|
||||
"desc_inProgressIntro": "שיתוף חי כרגע בפעולה.",
|
||||
"desc_shareLink": "שתף את הקישור עם כל מי שאתה מעוניין לעבוד אתו:",
|
||||
"desc_exitSession": "עצירת השיתוף תנתק אותך מהחדר, אבל עדיין תוכל להמשיך לעבוד על הקנבאס, מקומית. שים לב שזה לא ישפיע על אנשים אחרים, והם עדיין יוכלו לבצע שיתוף עם הגרסה שלהם.",
|
||||
"shareTitle": "הצטרף לשיתוף לעבודה משותפת חיה, בזמן אמת, על גבי Excalidraw"
|
||||
"desc_exitSession": "עצירת השיתוף תנתק אותך מהחדר, אבל עדיין תוכל להמשיך לעבוד על הלוח, מקומית. שים לב שזה לא ישפיע על אנשים אחרים, והם עדיין יוכלו לשתף פעולה עם הגירסה שלהם.",
|
||||
"shareTitle": "הצטרף לסשן שיתוף בזמן אמת של Excalidraw"
|
||||
},
|
||||
"errorDialog": {
|
||||
"title": "שגיאה"
|
||||
},
|
||||
"exportDialog": {
|
||||
"disk_title": "שמור לכונן",
|
||||
"disk_details": "ייצא מידע של הקנבאס לקובץ שתוכל לייבא אחר כך.",
|
||||
"disk_details": "ייצוא מידע הסצינה לקובץ אותו ניתן יהיה לייבא בהמשך.",
|
||||
"disk_button": "שמירה לקובץ",
|
||||
"link_title": "קבל קישור לשיתוף",
|
||||
"link_title": "העתקת קישור לשיתוף",
|
||||
"link_details": "ייצוא כקישור לקריאה בלבד.",
|
||||
"link_button": "ייצוא לקישור",
|
||||
"excalidrawplus_description": "שמור את הקנבאס לסביבת העבודה שלך ב- +Excalidraw.",
|
||||
"link_button": "ייצוא כקישור",
|
||||
"excalidrawplus_description": "שמור את המפה לסביבת העבודה שלך ב-Excalidraw+.",
|
||||
"excalidrawplus_button": "ייצוא",
|
||||
"excalidrawplus_exportError": "לא הצלחתי לייצא ל- +Excalidraw כרגע..."
|
||||
"excalidrawplus_exportError": "הייצוא ל-Excalidraw+ לא הצליח לעת עתה..."
|
||||
},
|
||||
"helpDialog": {
|
||||
"blog": "קרא את הבלוג שלנו",
|
||||
@@ -317,31 +301,30 @@
|
||||
"curvedLine": "קו מעוגל",
|
||||
"documentation": "תיעוד",
|
||||
"doubleClick": "לחיצה כפולה",
|
||||
"drag": "גרור",
|
||||
"drag": "לגרור",
|
||||
"editor": "עורך",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "ערוך את הצורה הנבחרת (טקסט/חץ/קו)",
|
||||
"github": "מצאת בעיה? דווח",
|
||||
"howto": "עקוב אחר המדריכים שלנו",
|
||||
"or": "או",
|
||||
"preventBinding": "למנוע נעיצת חיצים",
|
||||
"tools": "כלים",
|
||||
"shortcuts": "קיצורי מקלדת",
|
||||
"textFinish": "סיים עריכה (עורך טקסט)",
|
||||
"textNewLine": "הוסף שורה חדשה (עורך טקסט)",
|
||||
"textFinish": "סיים עריכה (טקסט)",
|
||||
"textNewLine": "הוסף שורה חדשה (טקסט)",
|
||||
"title": "עזרה",
|
||||
"view": "תצוגה",
|
||||
"zoomToFit": "זום להתאמת כל האלמנטים למסך",
|
||||
"zoomToFit": "גלילה להצגת כל האלמנטים במסך",
|
||||
"zoomToSelection": "התמקד בבחירה",
|
||||
"toggleElementLock": "נעילה/ביטול הנעילה של הרכיבים הנבחרים",
|
||||
"movePageUpDown": "זוז עמוד למעלה/למטה",
|
||||
"movePageLeftRight": "זוז עמוד שמאלה/ימינה"
|
||||
"movePageUpDown": "",
|
||||
"movePageLeftRight": ""
|
||||
},
|
||||
"clearCanvasDialog": {
|
||||
"title": "ניקוי הקנבס"
|
||||
},
|
||||
"publishDialog": {
|
||||
"title": "פרסם ספריה",
|
||||
"title": "פרסום ספריה",
|
||||
"itemName": "שם הפריט",
|
||||
"authorName": "שם היוצר",
|
||||
"githubUsername": "שם המשתמש שלך ב-GitHub",
|
||||
@@ -350,11 +333,11 @@
|
||||
"libraryDesc": "תיאור הספריה",
|
||||
"website": "אתר",
|
||||
"placeholder": {
|
||||
"authorName": "שמך או שם המשתמש שלך",
|
||||
"authorName": "שם או שם משתמש",
|
||||
"libraryName": "תנו שם לספריה",
|
||||
"libraryDesc": "תיאור של הספריה שלך כדי לסייע למשתמשים להבין את השימוש בה",
|
||||
"githubHandle": "כינוי GitHub (לא חובה), כדי שתוכל לערוך את הספרית לאחר שנשלחה לבדיקה",
|
||||
"twitterHandle": "שם משתמש טוויטר (לא חובה), כדי שנדע למי לתת קרדיט כשאנחנו מפרסמים בטוויטר",
|
||||
"githubHandle": "",
|
||||
"twitterHandle": "",
|
||||
"website": "קישור לאתר הפרטי שלך או לכל מקום אחר (אופציונאלי)"
|
||||
},
|
||||
"errors": {
|
||||
@@ -362,44 +345,44 @@
|
||||
"website": "הזינו כתובת URL תקינה"
|
||||
},
|
||||
"noteDescription": {
|
||||
"pre": "הגש את הספריה שלך להכללתה ב ",
|
||||
"link": "מאגר הספריה הציבורית",
|
||||
"post": "לשימושם של אנשים אחרים בציורים שלהם."
|
||||
"pre": "להציע את הספריה שלך להיות כלולה ב",
|
||||
"link": "מאגר הספריה הציבורי",
|
||||
"post": "כך שאחרים יוכלו לעשות שימוש בציורים שלהם."
|
||||
},
|
||||
"noteGuidelines": {
|
||||
"pre": "הספריה צריכה לקבל אישור ידני קודם לכן. אנא קרא את ",
|
||||
"pre": "הספריה צריכה לקבל אישור ידני. אנא קרא את ",
|
||||
"link": "הנחיות",
|
||||
"post": " לפני השליחה. אתה תצטרך לחשבון GitHub כדי לתקשר ולבצע שינויים אם תתבקש, אבל זה לא דרישה הכרחית."
|
||||
"post": ""
|
||||
},
|
||||
"noteLicense": {
|
||||
"pre": "על ידי שליחה, אתה מסכים שהסיפריה תפורסם תחת ה- ",
|
||||
"pre": "",
|
||||
"link": "רישיון MIT, ",
|
||||
"post": "שאומר בקצרה, שכל אחד יכול לעשות בהם שימוש ללא מגבלות."
|
||||
"post": "שאומר בקצרה שכל אחד יכול לעשות בהם שימוש ללא מגבלות."
|
||||
},
|
||||
"noteItems": "לכל פריט בסיפריה חייב להיות שם כדי שאפשר יהיה לסנן. הפריטי סיפריה הבאים יהיו כלולים:",
|
||||
"atleastOneLibItem": "אנא בחר לפחות פריט אחד מספריה כדי להתחיל",
|
||||
"republishWarning": "הערה: חלק מהפריטים שבחרת מסומנים ככאלו שכבר פורסמו/נשלחו. אתה צריך לשלוח פריטים מחדש כאשר אתה מעדכן ספריה או הגשה קיימים."
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "הספריה הוגשה",
|
||||
"title": "הספריה נשלחה",
|
||||
"content": "תודה {{authorName}}. הספריה שלך נשלחה לבחינה. תוכל לעקוב אחרי סטטוס הפרסום",
|
||||
"link": "כאן"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "איפוס ספריה",
|
||||
"removeItemsFromLib": "הסר את הפריטים הנבחרים מהספריה"
|
||||
"removeItemsFromLib": "להסיר את הפריטים הנבחרים מהספריה"
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "הציורים שלך מוצפנים מקצה לקצה כך שהשרתים של Excalidraw לא יראו אותם לעולם.",
|
||||
"tooltip": "הרישומים שלך מוצפנים מקצה לקצה כך שהשרתים של Excalidraw לא יראו אותם לעולם.",
|
||||
"link": "פוסט בבלוג על הצפנה מקצה לקצב ב-Excalidraw"
|
||||
},
|
||||
"stats": {
|
||||
"angle": "זווית",
|
||||
"element": "רכיב",
|
||||
"elements": "רכיבים",
|
||||
"element": "אלמנט",
|
||||
"elements": "אלמנטים",
|
||||
"height": "גובה",
|
||||
"scene": "תצוגה",
|
||||
"selected": "נבחר",
|
||||
"selected": "נבחר/ים",
|
||||
"storage": "אחסון",
|
||||
"title": "סטטיסטיקות לחנונים",
|
||||
"total": "סה״כ",
|
||||
@@ -410,14 +393,14 @@
|
||||
},
|
||||
"toast": {
|
||||
"addedToLibrary": "נוסף לספריה",
|
||||
"copyStyles": "סגנונות הועתקו.",
|
||||
"copyToClipboard": "הועתק ללוח.",
|
||||
"copyStyles": "העתק סגנונות.",
|
||||
"copyToClipboard": "הועתק אל הלוח.",
|
||||
"copyToClipboardAsPng": "{{exportSelection}} הועתקה ללוח כ-PNG\n({{exportColorScheme}})",
|
||||
"fileSaved": "קובץ נשמר.",
|
||||
"fileSavedToFilename": "נשמר לקובץ {filename}",
|
||||
"canvas": "קנבאס",
|
||||
"canvas": "משטח ציור",
|
||||
"selection": "בחירה",
|
||||
"pasteAsSingleElement": "השתמש ב- {{shortcut}} כדי להדביק כפריט יחיד,\nאו הדבק לתוך עורך טקסט קיים"
|
||||
"pasteAsSingleElement": ""
|
||||
},
|
||||
"colors": {
|
||||
"ffffff": "לבן",
|
||||
@@ -460,23 +443,23 @@
|
||||
"364fc7": "כחול כהה 9",
|
||||
"1864ab": "כחול 9",
|
||||
"0b7285": "טורקיז 9",
|
||||
"087f5b": "ירוק-כחול 9",
|
||||
"087f5b": "ירקרק 9",
|
||||
"2b8a3e": "ירוק 9",
|
||||
"5c940d": "ליים 9",
|
||||
"e67700": "צהוב 9",
|
||||
"e67700": "ירוק 9",
|
||||
"d9480f": "כתום 9"
|
||||
},
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
"center_heading": "כל המידע שלח נשמר מקומית בדפדפן.",
|
||||
"center_heading_plus": "אתה רוצה ללכת אל Excalidraw+ במקום?",
|
||||
"menuHint": "ייצוא, העדפות, שפות, ..."
|
||||
"center_heading": "",
|
||||
"center_heading_plus": "",
|
||||
"menuHint": ""
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "ייצוא, העדפות, ועוד...",
|
||||
"center_heading": "איורים. נעשים. פשוטים.",
|
||||
"toolbarHint": "בחר כלי & והתחל לצייר!",
|
||||
"helpHint": "קיצורים & עזרה"
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "फ़ॉन्ट आकार बढ़ाएँ",
|
||||
"unbindText": "",
|
||||
"bindText": "लेखन को कोश से जोड़े",
|
||||
"createContainerFromText": "मूलपाठ कंटेनर में मोड के दिखाए",
|
||||
"link": {
|
||||
"edit": "",
|
||||
"create": "",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "कॉलेब सर्वर से कनेक्शन नहीं हो पा रहा. कृपया पृष्ठ को पुनः लाने का प्रयास करे.",
|
||||
"importLibraryError": "संग्रह प्रतिष्ठापित नहीं किया जा सका",
|
||||
"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\" पर लिख सकते हैं."
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।"
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "चयन",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "",
|
||||
"drag": "खींचें",
|
||||
"editor": "संपादक",
|
||||
"editLineArrowPoints": "रेखा/तीर बिंदु सम्पादित करे",
|
||||
"editText": "पाठ्य सम्पादित करे/ लेबल जोड़े",
|
||||
"editSelectedShape": "",
|
||||
"github": "मुद्दा मिला? प्रस्तुत करें",
|
||||
"howto": "हमारे गाइड का पालन करें",
|
||||
"or": "या",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Betűméret növelése",
|
||||
"unbindText": "Szövegkötés feloldása",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Hivatkozás szerkesztése",
|
||||
"create": "Hivatkozás létrehozása",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Kijelölés",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dupla kattintás",
|
||||
"drag": "vonszolás",
|
||||
"editor": "Szerkesztő",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Kijelölt alakzat szerkesztése (szöveg/nyíl/vonal)",
|
||||
"github": "Hibát találtál? Küld be",
|
||||
"howto": "Kövesd az útmutatóinkat",
|
||||
"or": "vagy",
|
||||
|
||||
+3
-20
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Besarkan ukuran font",
|
||||
"unbindText": "Lepas teks",
|
||||
"bindText": "Kunci teks ke kontainer",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Edit tautan",
|
||||
"create": "Buat tautan",
|
||||
@@ -194,7 +193,7 @@
|
||||
"resetLibrary": "Ini akan menghapus pustaka Anda. Anda yakin?",
|
||||
"removeItemsFromsLibrary": "Hapus {{count}} item dari pustaka?",
|
||||
"invalidEncryptionKey": "Sandi enkripsi harus 22 karakter. Kolaborasi langsung dinonaktifkan.",
|
||||
"collabOfflineWarning": "Tidak ada koneksi internet.\nPerubahan tidak akan disimpan!"
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Tipe file tidak didukung.",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Tidak dapat terhubung ke server kolab. Muat ulang laman dan coba lagi.",
|
||||
"importLibraryError": "Tidak dapat memuat pustaka",
|
||||
"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"
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Pilihan",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "klik-ganda",
|
||||
"drag": "seret",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Edit bentuk yang dipilih (teks/panah/garis)",
|
||||
"github": "Menemukan masalah? Kirimkan",
|
||||
"howto": "Ikuti panduan kami",
|
||||
"or": "atau",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Aumenta la dimensione dei caratteri",
|
||||
"unbindText": "Scollega testo",
|
||||
"bindText": "Associa il testo al container",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Modifica link",
|
||||
"create": "Crea link",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Impossibile connettersi al server di collab. Ricarica la pagina e riprova.",
|
||||
"importLibraryError": "Impossibile caricare la libreria",
|
||||
"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"
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selezione",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "doppio-click",
|
||||
"drag": "trascina",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "Modifica testo / aggiungi etichetta",
|
||||
"editSelectedShape": "Modifica la forma selezionata (testo/freccia/linea)",
|
||||
"github": "Trovato un problema? Segnalalo",
|
||||
"howto": "Segui le nostre guide",
|
||||
"or": "oppure",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "フォントサイズを拡大",
|
||||
"unbindText": "テキストのバインド解除",
|
||||
"bindText": "テキストをコンテナにバインド",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "リンクを編集",
|
||||
"create": "リンクを作成",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "コラボレーションサーバに接続できませんでした。ページを再読み込みして、もう一度お試しください。",
|
||||
"importLibraryError": "ライブラリを読み込めませんでした。",
|
||||
"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"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。"
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "選択",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "ダブルクリック",
|
||||
"drag": "ドラッグ",
|
||||
"editor": "エディタ",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "テキストの編集 / ラベルの追加",
|
||||
"editSelectedShape": "選択した図形の編集 (テキスト/矢印/線)",
|
||||
"github": "不具合報告はこちら",
|
||||
"howto": "ヘルプ・マニュアル",
|
||||
"or": "または",
|
||||
|
||||
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Sali tiddi n tsefsit",
|
||||
"unbindText": "Serreḥ iweḍris",
|
||||
"bindText": "Arez aḍris s anagbar",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Ẓreg aseɣwen",
|
||||
"create": "Snulfu-d aseɣwen",
|
||||
@@ -194,7 +193,7 @@
|
||||
"resetLibrary": "Ayagi ad isfeḍ tamkarḍit-inek•m. Tetḥeqqeḍ?",
|
||||
"removeItemsFromsLibrary": "Ad tekkseḍ {{count}} n uferdis (en) si temkarḍit?",
|
||||
"invalidEncryptionKey": "Tasarut n uwgelhen isefk ad tesɛu 22 n yiekkilen. Amɛiwen srid yensa.",
|
||||
"collabOfflineWarning": "Ulac tuqqna n internet.\nIbedilen-ik ur ttwaklasen ara!"
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Anaw n ufaylu ur yettwasefrak ara.",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Ulamek tuqqna s aqeddac n umyalel. Ma ulac uɣilif ales asali n usebter sakin eɛreḍ tikkelt-nniḍen.",
|
||||
"importLibraryError": "Ur d-ssalay ara tamkarḍit",
|
||||
"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": ""
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Tafrayt",
|
||||
@@ -245,7 +229,7 @@
|
||||
"shapes": "Talɣiwin"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "Akken ad tesmuttiḍ taɣzut n usuneɣ, ṭṭef ṛṛuda n umumed, neɣ seqdec afecku Afus",
|
||||
"canvasPanning": "",
|
||||
"linearElement": "Ssit akken ad tebduḍ aṭas n tenqiḍin, zuɣer i yiwen n yizirig",
|
||||
"freeDraw": "Ssit yerna zuɣer, serreḥ ticki tfukeḍ",
|
||||
"text": "Tixidest: tzemreḍ daɣen ad ternuḍ aḍris s usiti snat n tikkal anida tebɣiḍ s ufecku n tefrayt",
|
||||
@@ -256,7 +240,7 @@
|
||||
"resize": "Tzemreḍ ad tḥettemeḍ assaɣ s tuṭṭfa n tqeffalt SHIFT mi ara tettbeddileḍ tiddi,\nma teṭṭfeḍ ALT abeddel n tiddi ad yili si tlemmast",
|
||||
"resizeImage": "Tzemreḍ ad talseḍ tiddi s tilelli s tuṭṭfa n SHIFT,\nṭṭef ALT akken ad talseḍ tiddi si tlemmast",
|
||||
"rotate": "Tzemreḍ ad tḥettemeḍ tiɣemmar s tuṭṭfa n SHIFT di tuzzya",
|
||||
"lineEditor_info": "Ssed ɣef CtrlOrCmd yerna ssit snat n tikkal neɣ ssed ɣef CtrlOrCmd + Kcem akken ad tẓergeḍ tineqqiḍin",
|
||||
"lineEditor_info": "",
|
||||
"lineEditor_pointSelected": "Ssed taqeffalt kkes akken ad tekkseḍ tanqiḍ (tinqiḍin),\nCtrlOrCmd+D akken ad tsiselgeḍ, neɣ zuɣer akken ad tesmuttiḍ",
|
||||
"lineEditor_nothingSelected": "Fren tanqiḍt akken ad tẓergeḍ (ṭṭef SHIFT akken ad tferneḍ aṭas),\nneɣ ṭṭef Alt akken ad ternuḍ tinqiḍin timaynutin",
|
||||
"placeImage": "Ssit akken ad tserseḍ tugna, neɣ ssit u zuɣer akken ad tesbaduḍ tiddi-ines s ufus",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "ssit snat n tikkal",
|
||||
"drag": "zuɣer",
|
||||
"editor": "Amaẓrag",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Ẓreg talɣa yettwafernen (aḍris/taneccabt/izirig)",
|
||||
"github": "Tufiḍ-d ugur? Azen-aɣ-d",
|
||||
"howto": "Ḍfer imniren-nneɣ",
|
||||
"or": "neɣ",
|
||||
@@ -469,11 +452,11 @@
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
"center_heading": "",
|
||||
"center_heading_plus": "Tebɣiḍ ad tedduḍ ɣer Excalidraw+ deg umḍiq?",
|
||||
"menuHint": "Asifeḍ, ismenyifen, tutlayin, ..."
|
||||
"center_heading_plus": "",
|
||||
"menuHint": ""
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "Asifeḍ, ismenyifen, d wayen-nniḍen...",
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "",
|
||||
"create": "",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "қос шерту",
|
||||
"drag": "апару",
|
||||
"editor": "Өңдеу",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Таңдалған пішінді өңдеу (мәтін/нұсқар/сызық)",
|
||||
"github": "Қате таптыңыз ба? Жолдаңыз",
|
||||
"howto": "Біздің нұсқаулықтарды орындаңыз",
|
||||
"or": "немесе",
|
||||
|
||||
+10
-27
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "폰트 사이즈 키우기",
|
||||
"unbindText": "텍스트 분리",
|
||||
"bindText": "텍스트를 컨테이너에 결합",
|
||||
"createContainerFromText": "텍스트를 컨테이너에 담기",
|
||||
"link": {
|
||||
"edit": "링크 수정하기",
|
||||
"create": "링크 만들기",
|
||||
@@ -194,7 +193,7 @@
|
||||
"resetLibrary": "당신의 라이브러리를 초기화 합니다. 계속하시겠습니까?",
|
||||
"removeItemsFromsLibrary": "{{count}}개의 아이템을 라이브러리에서 삭제하시겠습니까?",
|
||||
"invalidEncryptionKey": "암호화 키는 반드시 22글자여야 합니다. 실시간 협업이 비활성화됩니다.",
|
||||
"collabOfflineWarning": "인터넷에 연결되어 있지 않습니다.\n변경 사항들이 저장되지 않습니다!"
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "지원하지 않는 파일 형식 입니다.",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "협업 서버에 접속하는데 실패했습니다. 페이지를 새로고침하고 다시 시도해보세요.",
|
||||
"importLibraryError": "라이브러리를 불러오지 못했습니다.",
|
||||
"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에 제보해주세요"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "데이터베이스에 저장하지 못했습니다. 캔버스가 너무 큰 거 같습니다. 문제가 계속 된다면, 작업 내용을 잃지 않도록 로컬 저장소에 저장해 주세요."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "선택",
|
||||
@@ -237,7 +221,7 @@
|
||||
"penMode": "펜 모드 - 터치 방지",
|
||||
"link": "선택한 도형에 대해서 링크를 추가/업데이트",
|
||||
"eraser": "지우개",
|
||||
"hand": "손 (패닝 도구)"
|
||||
"hand": ""
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "캔버스 동작",
|
||||
@@ -245,7 +229,7 @@
|
||||
"shapes": "모양"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "캔버스를 옮기려면 마우스 휠이나 스페이스바를 누르고 드래그하거나, 손 도구를 사용하기",
|
||||
"canvasPanning": "",
|
||||
"linearElement": "여러 점을 연결하려면 클릭하고, 직선을 그리려면 바로 드래그하세요.",
|
||||
"freeDraw": "클릭 후 드래그하세요. 완료되면 놓으세요.",
|
||||
"text": "팁: 선택 툴로 아무 곳이나 더블 클릭해 텍스트를 추가할 수도 있습니다.",
|
||||
@@ -264,7 +248,7 @@
|
||||
"bindTextToElement": "Enter 키를 눌러서 텍스트 추가하기",
|
||||
"deepBoxSelect": "CtrlOrCmd 키를 눌러서 깊게 선택하고, 드래그하지 않도록 하기",
|
||||
"eraserRevert": "Alt를 눌러서 삭제하도록 지정된 요소를 되돌리기",
|
||||
"firefox_clipboard_write": "이 기능은 설정에서 \"dom.events.asyncClipboard.clipboardItem\" 플래그를 \"true\"로 설정하여 활성화할 수 있습니다. Firefox에서 브라우저 플래그를 수정하려면, \"about:config\" 페이지에 접속하세요."
|
||||
"firefox_clipboard_write": ""
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "미리보기를 볼 수 없습니다",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "더블 클릭",
|
||||
"drag": "드래그",
|
||||
"editor": "에디터",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "선택한 도형 편집하기(텍스트/화살표/라인)",
|
||||
"github": "문제 제보하기",
|
||||
"howto": "가이드 참고하기",
|
||||
"or": "또는",
|
||||
@@ -468,14 +451,14 @@
|
||||
},
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
"center_heading": "모든 데이터는 브라우저에 안전하게 저장됩니다.",
|
||||
"center_heading": "당신의 모든 데이터는 브라우저에 저장되었습니다.",
|
||||
"center_heading_plus": "대신 Excalidraw+로 이동하시겠습니까?",
|
||||
"menuHint": "내보내기, 설정, 언어, ..."
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "내보내기, 설정, 등등...",
|
||||
"center_heading": "간단하게 만드는 다이어그램.",
|
||||
"toolbarHint": "도구를 선택하고, 그리세요!",
|
||||
"menuHint": "내보내기, 설정, 더 보기...",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "도구 선택 & 그리기 시작",
|
||||
"helpHint": "단축키 & 도움말"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "زایدکردنی قەبارەی فۆنت",
|
||||
"unbindText": "دەقەکە جیابکەرەوە",
|
||||
"bindText": "دەقەکە ببەستەوە بە کۆنتەینەرەکەوە",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "دەستکاریکردنی بەستەر",
|
||||
"create": "دروستکردنی بەستەر",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "ناتوانێت پەیوەندی بکات بە سێرڤەری کۆلاب. تکایە لاپەڕەکە دووبارە باربکەوە و دووبارە هەوڵ بدەوە.",
|
||||
"importLibraryError": "نەیتوانی کتێبخانە بار بکات",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "دەستنیشانکردن",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "دوو گرتە",
|
||||
"drag": "راکێشان",
|
||||
"editor": "دەستکاریکەر",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "دەستکاریکردنی شێوەی هەڵبژێردراو (دەق/تیر/هێڵ)",
|
||||
"github": "کێشەیەکت دۆزیەوە؟ پێشکەشکردن",
|
||||
"howto": "شوێن ڕینماییەکانمان بکەوە",
|
||||
"or": "یان",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Redeguoti nuorodą",
|
||||
"create": "Sukurti nuorodą",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Nepavyko prisijungti prie serverio bendradarbiavimui. Perkrauk puslapį ir pabandyk prisijungti dar kartą.",
|
||||
"importLibraryError": "Nepavyko įkelti bibliotekos",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Žymėjimas",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dvigubas paspaudimas",
|
||||
"drag": "vilkti",
|
||||
"editor": "Redaktorius",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "Radai klaidą? Pateik",
|
||||
"howto": "Vadovaukis mūsų gidu",
|
||||
"or": "arba",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Palielināt fonta izmēru",
|
||||
"unbindText": "Atdalīt tekstu",
|
||||
"bindText": "Piesaistīt tekstu figūrai",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Rediģēt saiti",
|
||||
"create": "Izveidot saiti",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Nevarēja savienoties ar sadarbošanās serveri. Lūdzu, pārlādējiet lapu un mēģiniet vēlreiz.",
|
||||
"importLibraryError": "Nevarēja ielādēt bibliotēku",
|
||||
"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": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
}
|
||||
"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."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Atlase",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dubultklikšķis",
|
||||
"drag": "vilkt",
|
||||
"editor": "Redaktors",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Rediģēt atlasīto figūru (tekstu/bultu/līniju)",
|
||||
"github": "Sastapāt kļūdu? Ziņot",
|
||||
"howto": "Sekojiet mūsu instrukcijām",
|
||||
"or": "vai",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "अक्षर आकार मोठा करा",
|
||||
"unbindText": "लेखन संबंध संपवा",
|
||||
"bindText": "शब्द समूह ला पात्रात घ्या",
|
||||
"createContainerFromText": "मजकूर कंटेनर मधे मोडून दाखवा",
|
||||
"link": {
|
||||
"edit": "दुवा संपादन",
|
||||
"create": "दुवा तयार करा",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "कॉलेब-सर्वर हे पोहोचत नाही आहे. पान परत लोड करायचा प्रयत्न करावे.",
|
||||
"importLibraryError": "संग्रह प्रतिस्थापित नाही करता आला",
|
||||
"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\" वर लिहां"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "लगता है कि पृष्ठ तल काफ़ी बड़ा है, इस्कारण अंदरूनी डेटाबेस में सहेजा नहीं जा सका। किये काम को खोने न देने के लिये अपनी फ़ाइल को स्थानीय रूप से सहेजे।\n\nबॅकएंड डेटाबेसमध्ये जतन करू शकत नाही, कॅनव्हास खूप मोठा असल्याचे दिसते. तुम्ही तुमचे काम गमावणार नाही याची खात्री करण्यासाठी तुम्ही फाइल स्थानिक पातळीवर जतन करावी."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "निवड",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "दुहेरी क्लिक",
|
||||
"drag": "ओढा",
|
||||
"editor": "संपादक",
|
||||
"editLineArrowPoints": "रेघ/तीर बिंदु सम्पादित करा",
|
||||
"editText": "पाठ्य सम्पादित करा/ लेबल जोडा",
|
||||
"editSelectedShape": "निवडलेला प्रकार संपादित करा (मजकूर/बाण/रेघ)",
|
||||
"github": "समस्या मिळाली? प्रस्तुत करा",
|
||||
"howto": "आमच्या मार्गदर्शकाचे अनुसरण करा",
|
||||
"or": "किंवा",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "",
|
||||
"create": "",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "ရွေးချယ်",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Øk skriftstørrelse",
|
||||
"unbindText": "Avbind tekst",
|
||||
"bindText": "Bind tekst til beholderen",
|
||||
"createContainerFromText": "La tekst flyte i en beholder",
|
||||
"link": {
|
||||
"edit": "Rediger lenke",
|
||||
"create": "Opprett lenke",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Kunne ikke koble til samarbeidsserveren. Vennligst oppdater siden og prøv på nytt.",
|
||||
"importLibraryError": "Kunne ikke laste bibliotek",
|
||||
"collabSaveFailed": "Kan ikke lagre i backend-databasen. Hvis problemer vedvarer, bør du lagre filen lokalt for å sikre at du ikke mister arbeidet.",
|
||||
"collabSaveFailed_sizeExceeded": "Kunne ikke lagre til backend-databasen, lerretet ser ut til å være for stort. Du bør lagre filen lokalt for å sikre at du ikke mister arbeidet ditt.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Ser ut som du bruker Brave nettleser med",
|
||||
"aggressive_block_fingerprint": "Blokker fingeravtrykk aggressivt",
|
||||
"setting_enabled": "innstilling aktivert",
|
||||
"break": "Dette kan føre til at den bryter",
|
||||
"text_elements": "Tekstelementer",
|
||||
"in_your_drawings": "i tegningene dine",
|
||||
"strongly_recommend": "Vi anbefaler på det sterkeste å deaktivere denne innstillingen. Du kan følge dette",
|
||||
"steps": "disse trinnene",
|
||||
"how": "om hvordan det skal gjøres",
|
||||
"disable_setting": " Hvis deaktivering av denne innstillingen ikke fikser visningen av tekstelementer, kan du åpne en",
|
||||
"issue": "sak",
|
||||
"write": "på vår GitHub, eller skriv oss på",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Kunne ikke lagre til backend-databasen, lerretet ser ut til å være for stort. Du bør lagre filen lokalt for å sikre at du ikke mister arbeidet ditt."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Velg",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dobbeltklikk",
|
||||
"drag": "dra",
|
||||
"editor": "Redigeringsvisning",
|
||||
"editLineArrowPoints": "Rediger linje/pilpunkter",
|
||||
"editText": "Rediger tekst / legg til etikett",
|
||||
"editSelectedShape": "Rediger valgt figur (tekst/pil/linje)",
|
||||
"github": "Funnet et problem? Send inn",
|
||||
"howto": "Følg våre veiledninger",
|
||||
"or": "eller",
|
||||
|
||||
+15
-32
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Letters vergroten",
|
||||
"unbindText": "Ontkoppel tekst",
|
||||
"bindText": "Koppel tekst aan de container",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Wijzig link",
|
||||
"create": "Maak link",
|
||||
@@ -194,7 +193,7 @@
|
||||
"resetLibrary": "Dit zal je bibliotheek wissen. Weet je het zeker?",
|
||||
"removeItemsFromsLibrary": "Verwijder {{count}} item(s) uit bibliotheek?",
|
||||
"invalidEncryptionKey": "Encryptiesleutel moet 22 tekens zijn. Live samenwerking is uitgeschakeld.",
|
||||
"collabOfflineWarning": "Geen internetverbinding beschikbaar.\nJe wijzigingen worden niet opgeslagen!"
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Niet-ondersteund bestandstype.",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Kan geen verbinding maken met de collab server. Herlaad de pagina en probeer het opnieuw.",
|
||||
"importLibraryError": "Kon bibliotheek niet laden",
|
||||
"collabSaveFailed": "Kan niet opslaan in de backend database. Als de problemen blijven bestaan, moet u het bestand lokaal opslaan om ervoor te zorgen dat u uw werk niet verliest.",
|
||||
"collabSaveFailed_sizeExceeded": "Kan de backend database niet opslaan, het canvas lijkt te groot te zijn. U moet het bestand lokaal opslaan om ervoor te zorgen dat u uw werk niet verliest.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Het ziet er uit dat u de Brave browser gebruikt met de",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "instelling ingeschakeld",
|
||||
"break": "Dit kan leiden tot het breken van de",
|
||||
"text_elements": "Tekst Elementen",
|
||||
"in_your_drawings": "in je tekeningen",
|
||||
"strongly_recommend": "We raden u ten zeerste aan deze instelling uit te schakelen. U kunt dit volgen",
|
||||
"steps": "deze stappen",
|
||||
"how": "over hoe dit te doen",
|
||||
"disable_setting": " Indien het uitschakelen van deze instelling de weergave van tekst elementen niet wijzigt, open dan een",
|
||||
"issue": "probleem",
|
||||
"write": "",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Kan de backend database niet opslaan, het canvas lijkt te groot te zijn. U moet het bestand lokaal opslaan om ervoor te zorgen dat u uw werk niet verliest."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selectie",
|
||||
@@ -235,7 +219,7 @@
|
||||
"library": "Bibliotheek",
|
||||
"lock": "Geselecteerde tool actief houden na tekenen",
|
||||
"penMode": "Pen modus - Blokkeer aanraken",
|
||||
"link": "Link toevoegen / bijwerken voor een geselecteerde vorm",
|
||||
"link": "",
|
||||
"eraser": "Gum",
|
||||
"hand": ""
|
||||
},
|
||||
@@ -245,7 +229,7 @@
|
||||
"shapes": "Vormen"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "Om de canvas te verplaatsen, houd muiswiel of spatiebalk ingedrukt tijdens slepen, of gebruik het handgereedschap",
|
||||
"canvasPanning": "",
|
||||
"linearElement": "Klik om meerdere punten te starten, sleep voor één lijn",
|
||||
"freeDraw": "Klik en sleep, laat los als je klaar bent",
|
||||
"text": "Tip: je kunt tekst toevoegen door ergens dubbel te klikken met de selectietool",
|
||||
@@ -261,7 +245,7 @@
|
||||
"lineEditor_nothingSelected": "",
|
||||
"placeImage": "",
|
||||
"publishLibrary": "Publiceer je eigen bibliotheek",
|
||||
"bindTextToElement": "Druk op enter om tekst toe te voegen",
|
||||
"bindTextToElement": "",
|
||||
"deepBoxSelect": "",
|
||||
"eraserRevert": "",
|
||||
"firefox_clipboard_write": ""
|
||||
@@ -311,7 +295,7 @@
|
||||
"helpDialog": {
|
||||
"blog": "Lees onze blog",
|
||||
"click": "klik",
|
||||
"deepSelect": "Deep selecteer",
|
||||
"deepSelect": "",
|
||||
"deepBoxSelect": "",
|
||||
"curvedArrow": "Gebogen pijl",
|
||||
"curvedLine": "Kromme lijn",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dubbelklikken",
|
||||
"drag": "slepen",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Bewerk geselecteerde vorm (tekst/pijl/lijn)",
|
||||
"github": "Probleem gevonden? Verzenden",
|
||||
"howto": "Volg onze handleidingen",
|
||||
"or": "of",
|
||||
@@ -381,13 +364,13 @@
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "Bibliotheek ingediend",
|
||||
"title": "",
|
||||
"content": "",
|
||||
"link": "Hier"
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "Reset bibliotheek",
|
||||
"removeItemsFromLib": "Verwijder geselecteerde items uit bibliotheek"
|
||||
"resetLibrary": "",
|
||||
"removeItemsFromLib": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "Je tekeningen zijn beveiligd met end-to-end encryptie, dus Excalidraw's servers zullen nooit zien wat je tekent.",
|
||||
@@ -409,7 +392,7 @@
|
||||
"width": "Breedte"
|
||||
},
|
||||
"toast": {
|
||||
"addedToLibrary": "Toegevoegd aan bibliotheek",
|
||||
"addedToLibrary": "",
|
||||
"copyStyles": "Stijlen gekopieerd.",
|
||||
"copyToClipboard": "Gekopieerd naar het klembord.",
|
||||
"copyToClipboardAsPng": "{{exportSelection}} naar klembord gekopieerd als PNG\n({{exportColorScheme}})",
|
||||
@@ -473,10 +456,10 @@
|
||||
"menuHint": ""
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "Exporteren, voorkeuren en meer...",
|
||||
"center_heading": "Diagrammen. Eenvoudig. Gemaakt.",
|
||||
"toolbarHint": "Kies een tool & begin met tekenen!",
|
||||
"helpHint": "Snelkoppelingen en hulp"
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Gjer skriftstorleik større",
|
||||
"unbindText": "Avbind tekst",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Rediger lenke",
|
||||
"create": "Lag lenke",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Kunne ikkje kople til samarbeidsserveren. Ver vennleg å oppdatere inn sida og prøv på nytt.",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Vel",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dobbelklikk",
|
||||
"drag": "drag",
|
||||
"editor": "Redigering",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Rediger valt form (tekst/pil/linje)",
|
||||
"github": "Funne eit problem? Send inn",
|
||||
"howto": "Følg vegleiinga vår",
|
||||
"or": "eller",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Aumentar talha polissa",
|
||||
"unbindText": "Dessociar lo tèxte",
|
||||
"bindText": "Ligar lo tèxt al contenidor",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Modificar lo ligam",
|
||||
"create": "Crear un ligam",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Connexion impossibla al servidor collab. Mercés de recargar la pagina e tornar ensajar.",
|
||||
"importLibraryError": "Impossible de cargar la bibliotèca",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Seleccion",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "doble clic",
|
||||
"drag": "lisar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Modificar la fòrma seleccionada (tèxt/sageta/linha)",
|
||||
"github": "Problèma trobat ? Senhalatz-lo",
|
||||
"howto": "Seguissètz nòstras guidas",
|
||||
"or": "o",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "ਫੌਂਟ ਦਾ ਅਕਾਰ ਵਧਾਓ",
|
||||
"unbindText": "",
|
||||
"bindText": "ਪਾਠ ਨੂੰ ਕੰਟੇਨਰ ਨਾਲ ਬੰਨ੍ਹੋ",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "ਕੜੀ ਸੋਧੋ",
|
||||
"create": "ਕੜੀ ਬਣਾਓ",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "ਚੋਣਕਾਰ",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "ਡਬਲ-ਕਲਿੱਕ",
|
||||
"drag": "ਘਸੀਟੋ",
|
||||
"editor": "ਸੋਧਕ",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "ਚੁਣਿਆ ਰੂਪ ਸੋਧੋ (ਪਾਠ/ਤੀਰ/ਲਾਈਨ)",
|
||||
"github": "ਕੋਈ ਸਮੱਸਿਆ ਲੱਭੀ? ਜਮ੍ਹਾਂ ਕਰਵਾਓ",
|
||||
"howto": "ਸਾਡੀਆਂ ਗਾਈਡਾਂ ਦੀ ਪਾਲਣਾ ਕਰੋ",
|
||||
"or": "ਜਾਂ",
|
||||
|
||||
@@ -1,53 +1,52 @@
|
||||
{
|
||||
"ar-SA": 89,
|
||||
"bg-BG": 52,
|
||||
"bn-BD": 57,
|
||||
"ca-ES": 96,
|
||||
"cs-CZ": 72,
|
||||
"da-DK": 31,
|
||||
"ar-SA": 92,
|
||||
"bg-BG": 54,
|
||||
"bn-BD": 59,
|
||||
"ca-ES": 100,
|
||||
"cs-CZ": 74,
|
||||
"da-DK": 32,
|
||||
"de-DE": 100,
|
||||
"el-GR": 98,
|
||||
"el-GR": 99,
|
||||
"en": 100,
|
||||
"es-ES": 99,
|
||||
"eu-ES": 99,
|
||||
"fa-IR": 91,
|
||||
"fi-FI": 96,
|
||||
"fr-FR": 99,
|
||||
"es-ES": 100,
|
||||
"eu-ES": 100,
|
||||
"fa-IR": 95,
|
||||
"fi-FI": 100,
|
||||
"fr-FR": 100,
|
||||
"gl-ES": 99,
|
||||
"he-IL": 99,
|
||||
"hi-IN": 73,
|
||||
"hu-HU": 85,
|
||||
"id-ID": 98,
|
||||
"it-IT": 99,
|
||||
"ja-JP": 97,
|
||||
"he-IL": 89,
|
||||
"hi-IN": 71,
|
||||
"hu-HU": 88,
|
||||
"id-ID": 99,
|
||||
"it-IT": 100,
|
||||
"ja-JP": 100,
|
||||
"kab-KAB": 93,
|
||||
"kk-KZ": 19,
|
||||
"ko-KR": 99,
|
||||
"ku-TR": 91,
|
||||
"lt-LT": 61,
|
||||
"lv-LV": 93,
|
||||
"kk-KZ": 20,
|
||||
"ko-KR": 98,
|
||||
"ku-TR": 95,
|
||||
"lt-LT": 63,
|
||||
"lv-LV": 97,
|
||||
"mr-IN": 100,
|
||||
"my-MM": 40,
|
||||
"my-MM": 41,
|
||||
"nb-NO": 100,
|
||||
"nl-NL": 92,
|
||||
"nn-NO": 86,
|
||||
"oc-FR": 94,
|
||||
"pa-IN": 79,
|
||||
"pl-PL": 87,
|
||||
"pt-BR": 96,
|
||||
"pt-PT": 99,
|
||||
"nl-NL": 90,
|
||||
"nn-NO": 89,
|
||||
"oc-FR": 98,
|
||||
"pa-IN": 82,
|
||||
"pl-PL": 84,
|
||||
"pt-BR": 100,
|
||||
"pt-PT": 100,
|
||||
"ro-RO": 100,
|
||||
"ru-RU": 96,
|
||||
"ru-RU": 100,
|
||||
"si-LK": 8,
|
||||
"sk-SK": 99,
|
||||
"sk-SK": 100,
|
||||
"sl-SI": 100,
|
||||
"sv-SE": 99,
|
||||
"ta-IN": 90,
|
||||
"th-TH": 39,
|
||||
"tr-TR": 98,
|
||||
"uk-UA": 93,
|
||||
"vi-VN": 52,
|
||||
"zh-CN": 99,
|
||||
"sv-SE": 100,
|
||||
"ta-IN": 94,
|
||||
"tr-TR": 97,
|
||||
"uk-UA": 96,
|
||||
"vi-VN": 20,
|
||||
"zh-CN": 100,
|
||||
"zh-HK": 25,
|
||||
"zh-TW": 100
|
||||
}
|
||||
|
||||
+30
-47
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"labels": {
|
||||
"paste": "Wklej",
|
||||
"pasteAsPlaintext": "Wklej jako zwykły tekst",
|
||||
"pasteAsPlaintext": "",
|
||||
"pasteCharts": "Wklej wykresy",
|
||||
"selectAll": "Zaznacz wszystko",
|
||||
"multiSelect": "Dodaj element do zaznaczenia",
|
||||
@@ -72,7 +72,7 @@
|
||||
"layers": "Warstwy",
|
||||
"actions": "Akcje",
|
||||
"language": "Język",
|
||||
"liveCollaboration": "Współpraca w czasie rzeczywistym...",
|
||||
"liveCollaboration": "",
|
||||
"duplicateSelection": "Powiel",
|
||||
"untitled": "Bez tytułu",
|
||||
"name": "Nazwa",
|
||||
@@ -96,8 +96,8 @@
|
||||
"centerHorizontally": "Wyśrodkuj w poziomie",
|
||||
"distributeHorizontally": "Rozłóż poziomo",
|
||||
"distributeVertically": "Rozłóż pionowo",
|
||||
"flipHorizontal": "Odwróć w poziomie",
|
||||
"flipVertical": "Odwróć w pionie",
|
||||
"flipHorizontal": "Odbij w poziomie",
|
||||
"flipVertical": "Odbij w pionie",
|
||||
"viewMode": "Tryb widoku",
|
||||
"toggleExportColorScheme": "Przełącz schemat kolorów przy eksporcie",
|
||||
"share": "Udostępnij",
|
||||
@@ -106,47 +106,46 @@
|
||||
"toggleTheme": "Przełącz motyw",
|
||||
"personalLib": "Biblioteka prywatna",
|
||||
"excalidrawLib": "Biblioteka Excalidraw",
|
||||
"decreaseFontSize": "Zmniejsz rozmiar czcionki",
|
||||
"increaseFontSize": "Zwiększ rozmiar czcionki",
|
||||
"decreaseFontSize": "",
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Edytuj łącze",
|
||||
"create": "Utwórz łącze",
|
||||
"label": "Łącze"
|
||||
"edit": "",
|
||||
"create": "",
|
||||
"label": ""
|
||||
},
|
||||
"lineEditor": {
|
||||
"edit": "",
|
||||
"exit": ""
|
||||
},
|
||||
"elementLock": {
|
||||
"lock": "Zablokuj",
|
||||
"unlock": "Odblokuj",
|
||||
"lockAll": "Zablokuj wszystko",
|
||||
"unlockAll": "Odblokuj wszystko"
|
||||
"lock": "",
|
||||
"unlock": "",
|
||||
"lockAll": "",
|
||||
"unlockAll": ""
|
||||
},
|
||||
"statusPublished": "Opublikowano",
|
||||
"sidebarLock": "Panel boczny zawsze otwarty"
|
||||
"statusPublished": "",
|
||||
"sidebarLock": ""
|
||||
},
|
||||
"library": {
|
||||
"noItems": "Nie dodano jeszcze żadnych elementów...",
|
||||
"noItems": "",
|
||||
"hint_emptyLibrary": "",
|
||||
"hint_emptyPrivateLibrary": "Wybierz element, aby dodać go tutaj."
|
||||
"hint_emptyPrivateLibrary": ""
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "Wyczyść dokument i zresetuj kolor dokumentu",
|
||||
"exportJSON": "Eksportuj do pliku",
|
||||
"exportImage": "Eksportuj obraz...",
|
||||
"export": "Zapisz jako...",
|
||||
"exportImage": "",
|
||||
"export": "",
|
||||
"exportToPng": "Zapisz jako PNG",
|
||||
"exportToSvg": "Zapisz jako SVG",
|
||||
"copyToClipboard": "Skopiuj do schowka",
|
||||
"copyPngToClipboard": "Skopiuj do schowka jako plik PNG",
|
||||
"scale": "Skala",
|
||||
"save": "Zapisz do bieżącego pliku",
|
||||
"save": "",
|
||||
"saveAs": "Zapisz jako",
|
||||
"load": "Otwórz",
|
||||
"load": "",
|
||||
"getShareableLink": "Udostępnij",
|
||||
"close": "Zamknij",
|
||||
"selectLanguage": "Wybierz język",
|
||||
@@ -180,11 +179,11 @@
|
||||
"couldNotLoadInvalidFile": "Nie udało się otworzyć pliku. Wybrany plik jest nieprawidłowy.",
|
||||
"importBackendFailed": "Wystąpił błąd podczas importowania pliku.",
|
||||
"cannotExportEmptyCanvas": "Najpierw musisz coś narysować, aby zapisać dokument.",
|
||||
"couldNotCopyToClipboard": "Nie udało się skopiować do schowka.",
|
||||
"couldNotCopyToClipboard": "",
|
||||
"decryptFailed": "Nie udało się odszyfrować danych.",
|
||||
"uploadedSecurly": "By zapewnić Ci prywatność, udostępnianie projektu jest zabezpieczone szyfrowaniem end-to-end, co oznacza, że poza tobą i osobą z którą podzielisz się linkiem, nikt nie ma dostępu do tego co udostępniasz.",
|
||||
"loadSceneOverridePrompt": "Wczytanie zewnętrznego rysunku zastąpi istniejącą zawartość. Czy chcesz kontynuować?",
|
||||
"collabStopOverridePrompt": "Zatrzymanie sesji nadpisze poprzedni, zapisany lokalnie rysunek. Czy jesteś pewien?\n\n(Jeśli chcesz zachować swój lokalny rysunek, po prostu zamknij zakładkę przeglądarki.)",
|
||||
"collabStopOverridePrompt": "Zatrzymanie sesji nadpisze poprzedni, zapisany lokalnie rysunk. Jesteś pewien?\n\n(Jeśli chcesz zachować swój lokalny rysunek, po prostu zamknij zakładkę przeglądarki.)",
|
||||
"errorAddingToLibrary": "Nie udało się dodać elementu do biblioteki",
|
||||
"errorRemovingFromLibrary": "Nie udało się usunąć elementu z biblioteki",
|
||||
"confirmAddLibrary": "To doda {{numShapes}} kształtów do twojej biblioteki. Jesteś pewien?",
|
||||
@@ -194,7 +193,7 @@
|
||||
"resetLibrary": "To wyczyści twoją bibliotekę. Jesteś pewien?",
|
||||
"removeItemsFromsLibrary": "Usunąć {{count}} element(ów) z biblioteki?",
|
||||
"invalidEncryptionKey": "Klucz szyfrowania musi składać się z 22 znaków. Współpraca na żywo jest wyłączona.",
|
||||
"collabOfflineWarning": "Brak połączenia z Internetem.\nTwoje zmiany nie zostaną zapisane!"
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Nieobsługiwany typ pliku.",
|
||||
@@ -202,25 +201,10 @@
|
||||
"fileTooBig": "Plik jest zbyt duży. Maksymalny dozwolony rozmiar to {{maxSize}}.",
|
||||
"svgImageInsertError": "Nie udało się wstawić obrazu SVG. Znacznik SVG wygląda na nieprawidłowy.",
|
||||
"invalidSVGString": "Nieprawidłowy SVG.",
|
||||
"cannotResolveCollabServer": "Nie można połączyć się z serwerem współpracy w czasie rzeczywistym. Proszę odświeżyć stronę i spróbować ponownie.",
|
||||
"importLibraryError": "Wystąpił błąd w trakcie ładowania biblioteki",
|
||||
"collabSaveFailed": "Nie udało się zapisać w bazie danych. Jeśli problemy nie ustąpią, zapisz plik lokalnie, aby nie utracić swojej pracy.",
|
||||
"collabSaveFailed_sizeExceeded": "Nie udało się zapisać w bazie danych — dokument jest za duży. Zapisz plik lokalnie, aby nie utracić swojej pracy.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "problem",
|
||||
"write": "na naszym GitHubie lub napisz do nas na",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Zaznaczenie",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "podwójne kliknięcie",
|
||||
"drag": "przeciągnij",
|
||||
"editor": "Edytor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Edytuj wybrany kształt (tekst/strzałka/linia)",
|
||||
"github": "Znalazłeś problem? Prześlij",
|
||||
"howto": "Skorzystaj z instrukcji",
|
||||
"or": "lub",
|
||||
@@ -334,7 +317,7 @@
|
||||
"zoomToFit": "Powiększ, aby wyświetlić wszystkie elementy",
|
||||
"zoomToSelection": "Przybliż do zaznaczenia",
|
||||
"toggleElementLock": "",
|
||||
"movePageUpDown": "Przesuń stronę w górę/w dół",
|
||||
"movePageUpDown": "",
|
||||
"movePageLeftRight": ""
|
||||
},
|
||||
"clearCanvasDialog": {
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Aumentar o tamanho da fonte",
|
||||
"unbindText": "Desvincular texto",
|
||||
"bindText": "Vincular texto ao contêiner",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Editar link",
|
||||
"create": "Criar link",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Não foi possível conectar-se ao servidor colaborativo. Por favor, recarregue a página e tente novamente.",
|
||||
"importLibraryError": "Não foi possível carregar a biblioteca",
|
||||
"collabSaveFailed": "Não foi possível salvar no banco de dados do servidor. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.",
|
||||
"collabSaveFailed_sizeExceeded": "Não foi possível salvar no banco de dados do servidor, a tela parece ser muito grande. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho.",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Não foi possível salvar no banco de dados do servidor, a tela parece ser muito grande. Se os problemas persistirem, salve o arquivo localmente para garantir que não perca o seu trabalho."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Seleção",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "clique duplo",
|
||||
"drag": "arrastar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Editar forma selecionada (texto/seta/linha)",
|
||||
"github": "Encontrou algum problema? Nos informe",
|
||||
"howto": "Siga nossos guias",
|
||||
"or": "ou",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Aumentar o tamanho do tipo de letra",
|
||||
"unbindText": "Desvincular texto",
|
||||
"bindText": "Ligar texto ao recipiente",
|
||||
"createContainerFromText": "Envolver texto num recipiente",
|
||||
"link": {
|
||||
"edit": "Editar ligação",
|
||||
"create": "Criar ligação",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Não foi possível fazer a ligação ao servidor colaborativo. Por favor, volte a carregar a página e tente novamente.",
|
||||
"importLibraryError": "Não foi possível carregar a biblioteca",
|
||||
"collabSaveFailed": "Não foi possível guardar na base de dados de backend. Se os problemas persistirem, guarde o ficheiro localmente para garantir que não perde o seu trabalho.",
|
||||
"collabSaveFailed_sizeExceeded": "Não foi possível guardar na base de dados de backend, o ecrã parece estar muito grande. Deve guardar o ficheiro localmente para garantir que não perde o seu trabalho.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Parece que está a usar o navegador Brave com o",
|
||||
"aggressive_block_fingerprint": "Bloqueio Agressivo via Impressão Digital",
|
||||
"setting_enabled": "activo",
|
||||
"break": "Isso pode desconfigurar os",
|
||||
"text_elements": "Elementos de Texto",
|
||||
"in_your_drawings": "nos seus desenhos",
|
||||
"strongly_recommend": "Recomendamos fortemente a desactivação desta configuração. Pode seguir",
|
||||
"steps": "os seguintes passos",
|
||||
"how": "para saber como o fazer",
|
||||
"disable_setting": " Se desactivar esta configuração não consertar a exibição de elementos de texto, por favor, abra um",
|
||||
"issue": "problema",
|
||||
"write": "no nosso GitHub ou então escreva-nos no",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Não foi possível guardar na base de dados de backend, o ecrã parece estar muito grande. Deve guardar o ficheiro localmente para garantir que não perde o seu trabalho."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Seleção",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "clique duplo",
|
||||
"drag": "arrastar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Editar forma selecionada (texto/seta/linha)",
|
||||
"github": "Encontrou algum problema? Informe-nos",
|
||||
"howto": "Siga os nossos guias",
|
||||
"or": "ou",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Mărește dimensiunea fontului",
|
||||
"unbindText": "Deconectare text",
|
||||
"bindText": "Legare text de container",
|
||||
"createContainerFromText": "Încadrare text într-un container",
|
||||
"link": {
|
||||
"edit": "Editare URL",
|
||||
"create": "Creare URL",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Nu a putut fi realizată conexiunea la serverul de colaborare. Reîncarcă pagina și încearcă din nou.",
|
||||
"importLibraryError": "Biblioteca nu a putut fi încărcată",
|
||||
"collabSaveFailed": "Nu s-a putut salva în baza de date la nivel de server. Dacă problemele persistă, ar trebui să salvezi fișierul la nivel local pentru a te asigura că nu îți pierzi munca.",
|
||||
"collabSaveFailed_sizeExceeded": "Nu s-a putut salva în baza de date la nivel de server, întrucât se pare că pânza este prea mare. Ar trebui să salvezi fișierul la nivel local pentru a te asigura că nu îți pierzi munca.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Se pare că folosești navigatorul Brave cu",
|
||||
"aggressive_block_fingerprint": "setarea „Blocați amprentarea” în modul strict",
|
||||
"setting_enabled": "activată",
|
||||
"break": "Acest lucru ar putea duce la întreruperea",
|
||||
"text_elements": "elementelor de text",
|
||||
"in_your_drawings": "din desenele tale",
|
||||
"strongly_recommend": "Recomandăm insistent dezactivarea acestei setări. Poți urma",
|
||||
"steps": "acești pași",
|
||||
"how": "pentru efectuarea procedurii",
|
||||
"disable_setting": " Dacă dezactivarea acestei setări nu remediază afișarea elementelor de text, deschide",
|
||||
"issue": "un tichet cu probleme",
|
||||
"write": "pe pagina noastră de GitHub sau scrie-ne pe",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Nu s-a putut salva în baza de date la nivel de server, întrucât se pare că pânza este prea mare. Ar trebui să salvezi fișierul la nivel local pentru a te asigura că nu îți pierzi munca."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selecție",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dublu clic",
|
||||
"drag": "glisare",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "Editare puncte de săgeată/rând",
|
||||
"editText": "Editare text/adăugare etichetă",
|
||||
"editSelectedShape": "Editează forma selectată (text/săgeată/linie)",
|
||||
"github": "Ai întâmpinat o problemă? Trimite un raport",
|
||||
"howto": "Urmărește ghidurile noastre",
|
||||
"or": "sau",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Увеличить шрифт",
|
||||
"unbindText": "Отвязать текст",
|
||||
"bindText": "Привязать текст к контейнеру",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Редактировать ссылку",
|
||||
"create": "Создать ссылку",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Не удалось подключиться к серверу совместного редактирования. Перезагрузите страницу и повторите попытку.",
|
||||
"importLibraryError": "Не удалось загрузить библиотеку",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Не удалось сохранить в базу данных. Похоже, что холст слишком большой. Нужно сохранить файл локально, чтобы быть уверенным, что вы не потеряете вашу работу."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Выделение области",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "двойной клик",
|
||||
"drag": "перетащить",
|
||||
"editor": "Редактор",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Редактировать выбранную фигуру (текст/стрелка/линия)",
|
||||
"github": "Нашли проблему? Отправьте",
|
||||
"howto": "Следуйте нашим инструкциям",
|
||||
"or": "или",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "",
|
||||
"create": "",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Zväčšiť veľkosť písma",
|
||||
"unbindText": "Zrušiť previazanie textu",
|
||||
"bindText": "Previazať text s kontajnerom",
|
||||
"createContainerFromText": "Zabaliť text do kontajneru",
|
||||
"link": {
|
||||
"edit": "Upraviť odkaz",
|
||||
"create": "Vytvoriť odkaz",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Nepodarilo sa pripojiť ku kolaboračnému serveru. Prosím obnovte stránku a skúste to znovu.",
|
||||
"importLibraryError": "Nepodarilo sa načítať knižnicu",
|
||||
"collabSaveFailed": "Uloženie do databázy sa nepodarilo. Ak tento problém pretrváva uložte si váš súbor lokálne aby ste nestratili vašu prácu.",
|
||||
"collabSaveFailed_sizeExceeded": "Uloženie do databázy sa nepodarilo, pretože veľkosť plátna je príliš veľká. Uložte si váš súbor lokálne aby ste nestratili vašu prácu.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Vyzerá to, že používate prehliadač Brave s",
|
||||
"aggressive_block_fingerprint": "agresívnym blokovaním sledovania",
|
||||
"setting_enabled": "zapnutým",
|
||||
"break": "Toto môže znemožniť používanie",
|
||||
"text_elements": "textových prvkov",
|
||||
"in_your_drawings": "vo vašich kresbách",
|
||||
"strongly_recommend": "Odporúčame vypnutie tohto nastavenia. Postupuje podľa",
|
||||
"steps": "týchto krokov",
|
||||
"how": "pre jeho vypnutie",
|
||||
"disable_setting": " Ak sa vypnutím tohto nastavenia problém zobrazenia textových prvkov neodstráni, prosím vytvorte",
|
||||
"issue": "issue",
|
||||
"write": "na našom Github-e alebo nám to oznámte na",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Uloženie do databázy sa nepodarilo, pretože veľkosť plátna je príliš veľká. Uložte si váš súbor lokálne aby ste nestratili vašu prácu."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Výber",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dvojklik",
|
||||
"drag": "potiahnutie",
|
||||
"editor": "Editovanie",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Editovať zvolený tvar (text/šípka/čiara)",
|
||||
"github": "Objavili ste problém? Nahláste ho",
|
||||
"howto": "Postupujte podľa naších návodov",
|
||||
"or": "alebo",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Povečaj velikost pisave",
|
||||
"unbindText": "Veži besedilo",
|
||||
"bindText": "Veži besedilo na element",
|
||||
"createContainerFromText": "Zavij besedilo v vsebnik",
|
||||
"link": {
|
||||
"edit": "Uredi povezavo",
|
||||
"create": "Ustvari povezavo",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Povezave s strežnikom za sodelovanje ni bilo mogoče vzpostaviti. Ponovno naložite stran in poskusite znova.",
|
||||
"importLibraryError": "Nalaganje knjižnice ni uspelo",
|
||||
"collabSaveFailed": "Ni bilo mogoče shraniti v zaledno bazo podatkov. Če se težave nadaljujejo, shranite datoteko lokalno, da ne boste izgubili svojega dela.",
|
||||
"collabSaveFailed_sizeExceeded": "Ni bilo mogoče shraniti v zaledno bazo podatkov, zdi se, da je platno preveliko. Datoteko shranite lokalno, da ne izgubite svojega dela.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Videti je, da uporabljate brskalnik Brave z omogočeno nastavitvijo",
|
||||
"aggressive_block_fingerprint": "Agresivno blokiranje prstnih odtisov",
|
||||
"setting_enabled": " ",
|
||||
"break": "To bi lahko povzročilo motnje v obnašanju",
|
||||
"text_elements": "besedilnih elementov",
|
||||
"in_your_drawings": "v vaših risbah",
|
||||
"strongly_recommend": "Močno priporočamo, da onemogočite to nastavitev. Sledite",
|
||||
"steps": "tem korakom,",
|
||||
"how": "kako to storiti",
|
||||
"disable_setting": " Če onemogočanje te nastavitve ne popravi prikaza besedilnih elementov, odprite",
|
||||
"issue": "vprašanje",
|
||||
"write": "na našem GitHubu ali nam pišite na",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Ni bilo mogoče shraniti v zaledno bazo podatkov, zdi se, da je platno preveliko. Datoteko shranite lokalno, da ne izgubite svojega dela."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Izbor",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dvojni klik",
|
||||
"drag": "vleci",
|
||||
"editor": "Urejevalnik",
|
||||
"editLineArrowPoints": "Uredi črto/točke puščice",
|
||||
"editText": "Uredi besedilo / dodaj oznako",
|
||||
"editSelectedShape": "Uredi izbrano obliko (besedilo/puščica/črta)",
|
||||
"github": "Ste našli težavo? Pošljite",
|
||||
"howto": "Sledite našim vodičem",
|
||||
"or": "ali",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Öka fontstorleken",
|
||||
"unbindText": "Koppla bort text",
|
||||
"bindText": "Bind texten till behållaren",
|
||||
"createContainerFromText": "Radbryt text i en avgränsad yta",
|
||||
"link": {
|
||||
"edit": "Redigera länk",
|
||||
"create": "Skapa länk",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Det gick inte att ansluta till samarbets-servern. Ladda om sidan och försök igen.",
|
||||
"importLibraryError": "Kunde inte ladda bibliotek",
|
||||
"collabSaveFailed": "Det gick inte att spara i backend-databasen. Om problemen kvarstår bör du spara filen lokalt för att se till att du inte förlorar ditt arbete.",
|
||||
"collabSaveFailed_sizeExceeded": "Det gick inte att spara till backend-databasen, whiteboarden verkar vara för stor. Du bör spara filen lokalt för att du inte ska förlora ditt arbete.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Det ser ut som att du använder webbläsaren Brave med",
|
||||
"aggressive_block_fingerprint": "Blockera \"Fingerprinting\" aggressivt",
|
||||
"setting_enabled": "inställningen aktiverad",
|
||||
"break": "Detta kan resultera i att ha sönder",
|
||||
"text_elements": "Textelement",
|
||||
"in_your_drawings": "i dina skisser",
|
||||
"strongly_recommend": "Vi rekommenderar starkt att inaktivera denna inställning. Du kan följa",
|
||||
"steps": "dessa steg",
|
||||
"how": "om hur man gör det",
|
||||
"disable_setting": " Om inaktivering av den här inställningen inte åtgärdar visningen av textelement, vänligen skapa ett",
|
||||
"issue": "ärende",
|
||||
"write": "på vår GitHub, eller skriv till oss på",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Det gick inte att spara till backend-databasen, whiteboarden verkar vara för stor. Du bör spara filen lokalt för att du inte ska förlora ditt arbete."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Markering",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "dubbelklicka",
|
||||
"drag": "dra",
|
||||
"editor": "Redigerare",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Redigera markerad form (text/pil/linje)",
|
||||
"github": "Hittat ett problem? Rapportera",
|
||||
"howto": "Följ våra guider",
|
||||
"or": "eller",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "எழுத்துரு அளவை அதிகரி",
|
||||
"unbindText": "உரையைப் பிணைவவிழ்",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "தொடுப்பைத் திருத்து",
|
||||
"create": "தொடுப்பைப் படை",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "நூலகத்தை ஏற்ற முடியவில்லை",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "தெரிவு",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "இரு-சொடுக்கு",
|
||||
"drag": "பிடித்திழு",
|
||||
"editor": "திருத்தி",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "தேர்ந்த வடிவத்தைத் திருத்து (உரை/அம்பு/வரி)",
|
||||
"github": "சிக்கலைக் கண்டீரா? சமர்ப்பி",
|
||||
"howto": "எங்கள் கையேடுகளைப் பின்பற்றுக",
|
||||
"or": "அ",
|
||||
|
||||
@@ -1,482 +0,0 @@
|
||||
{
|
||||
"labels": {
|
||||
"paste": "วาง",
|
||||
"pasteAsPlaintext": "วางโดยไม่มีการจัดรูปแบบ",
|
||||
"pasteCharts": "วางแผนภูมิ",
|
||||
"selectAll": "เลือกทั้งหมด",
|
||||
"multiSelect": "",
|
||||
"moveCanvas": "",
|
||||
"cut": "ตัด",
|
||||
"copy": "คัดลอก",
|
||||
"copyAsPng": "คัดลองไปยังคลิปบอร์ดเป็น PNG",
|
||||
"copyAsSvg": "คัดลองไปยังคลิปบอร์ดเป็น SVG",
|
||||
"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": "เพิ่มลายน้ำ \"สร้างด้วย Excalidraw\"",
|
||||
"handDrawn": "ลายมือ",
|
||||
"normal": "ปกติ",
|
||||
"code": "โค้ด",
|
||||
"small": "เล็ก",
|
||||
"medium": "กลาง",
|
||||
"large": "ใหญ่",
|
||||
"veryLarge": "ใหญ่มาก",
|
||||
"solid": "",
|
||||
"hachure": "",
|
||||
"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": "คลังของ Excalidraw",
|
||||
"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": "ส่งออกไปเป็น PNG",
|
||||
"exportToSvg": "ส่งออกไปเป็น SVG",
|
||||
"copyToClipboard": "คัดลอกไปยังคลิปบอร์ด",
|
||||
"copyPngToClipboard": "คัดลอก PNG ไปยังคลิปบอร์ด",
|
||||
"scale": "อัตราส่วน",
|
||||
"save": "",
|
||||
"saveAs": "",
|
||||
"load": "เปิด",
|
||||
"getShareableLink": "สร้างลิงค์ที่แชร์ได้",
|
||||
"close": "ปิด",
|
||||
"selectLanguage": "เลือกภาษา",
|
||||
"scrollBackToContent": "เลื่อนกลับไปด้านบน",
|
||||
"zoomIn": "ซูมเข้า",
|
||||
"zoomOut": "ซูมออก",
|
||||
"resetZoom": "รีเซ็ตการซูม",
|
||||
"menu": "เมนู",
|
||||
"done": "เสร็จสิ้น",
|
||||
"edit": "แก้ไข",
|
||||
"undo": "เลิกทำ",
|
||||
"redo": "ทำซ้ำ",
|
||||
"resetLibrary": "รีเซ็ตคลัง",
|
||||
"createNewRoom": "สร้างห้องใหม่",
|
||||
"fullScreen": "เต็มหน้าจอ",
|
||||
"darkMode": "โหมดกลางคืน",
|
||||
"lightMode": "โหมดกลางวัน",
|
||||
"zenMode": "โหมด Zen",
|
||||
"exitZenMode": "ออกจากโหมด Zen",
|
||||
"cancel": "ยกเลิก",
|
||||
"clear": "เคลียร์",
|
||||
"remove": "ลบ",
|
||||
"publishLibrary": "เผยแพร่",
|
||||
"submit": "ตกลง",
|
||||
"confirm": "ยืนยัน"
|
||||
},
|
||||
"alerts": {
|
||||
"clearReset": "",
|
||||
"couldNotCreateShareableLink": "",
|
||||
"couldNotCreateShareableLinkTooBig": "",
|
||||
"couldNotLoadInvalidFile": "ไม่สามารถโหลดไฟล์ที่ผิดพลาดได้",
|
||||
"importBackendFailed": "",
|
||||
"cannotExportEmptyCanvas": "",
|
||||
"couldNotCopyToClipboard": "ไม่สามารถคัดลอกไปยังคลิปบอร์ดได้",
|
||||
"decryptFailed": "ไม่สามารถถอดรหัสข้อมูลได้",
|
||||
"uploadedSecurly": "การอัพโหลดได้ถูกเข้ารหัสแบบ end-to-end หมายความว่าเซิร์ฟเวอร์ของ Excalidraw และบุคคลอื่นไม่สามารถอ่านข้อมูลได้",
|
||||
"loadSceneOverridePrompt": "",
|
||||
"collabStopOverridePrompt": "",
|
||||
"errorAddingToLibrary": "ไม่สามารถเพิ่มรายการเข้าไปในคลังได้",
|
||||
"errorRemovingFromLibrary": "ไม่สามารถลบรายการนี้ออกจากคลังได้",
|
||||
"confirmAddLibrary": "",
|
||||
"imageDoesNotContainScene": "",
|
||||
"cannotRestoreFromImage": "",
|
||||
"invalidSceneUrl": "",
|
||||
"resetLibrary": "",
|
||||
"removeItemsFromsLibrary": "",
|
||||
"invalidEncryptionKey": "",
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "ไม่รองรับชนิดของไฟล์นี้",
|
||||
"imageInsertError": "ไม่สามารถเพิ่มรูปภาพได้ ลองอีกครั้งในภายหลัง",
|
||||
"fileTooBig": "",
|
||||
"svgImageInsertError": "",
|
||||
"invalidSVGString": "ไฟล์ SVG ผิดพลาด",
|
||||
"cannotResolveCollabServer": "ไม่สามารถเชื่อต่อกับ collab เซิร์ฟเวอร์ได้ โปรดลองโหลดหน้านี้ใหม่และลองอีกครั้ง",
|
||||
"importLibraryError": "",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": "คลิกสองครั้งหรือกด ENTER เพื่อแก้ไขข้อความ",
|
||||
"text_editing": "กดปุ่ม Esc หรือกด Ctrl, Cmd + Enter เพื่อเสร็จการแก้ไข",
|
||||
"linearElementMulti": "คลิกที่จุดสุดท้ายหรือกด Escape หรือ Enter เพื่อเสร็จสิ้น",
|
||||
"lockAngle": "",
|
||||
"resize": "",
|
||||
"resizeImage": "",
|
||||
"rotate": "",
|
||||
"lineEditor_info": "",
|
||||
"lineEditor_pointSelected": "กดปุ่ม Delete เพื่อลบจุด\nกด Ctrl หรือ Cmd + D เพื่อทำซ้ำหรือลากเพื่อเคลื่อนย้าย",
|
||||
"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": "ไม่สามารถส่งออกไปที่ Excalidraw+ ได้ในขณะนี้"
|
||||
},
|
||||
"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": "ชื่อผู้ใช้ GitHub",
|
||||
"twitterUsername": "ชื่อผู้ใช้ Twitter",
|
||||
"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": "สีเทา 0",
|
||||
"f1f3f5": "สีเทา 1",
|
||||
"fff5f5": "สีแดง 0",
|
||||
"fff0f6": "สีชมพู 0",
|
||||
"f8f0fc": "",
|
||||
"f3f0ff": "",
|
||||
"edf2ff": "",
|
||||
"e7f5ff": "",
|
||||
"e3fafc": "",
|
||||
"e6fcf5": "",
|
||||
"ebfbee": "",
|
||||
"f4fce3": "",
|
||||
"fff9db": "",
|
||||
"fff4e6": "",
|
||||
"transparent": "",
|
||||
"ced4da": "สีเทา 4",
|
||||
"868e96": "สีเทา 6",
|
||||
"fa5252": "สีแดง 6",
|
||||
"e64980": "สีชมพู 6",
|
||||
"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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-24
@@ -66,7 +66,7 @@
|
||||
"cartoonist": "Karikatürist",
|
||||
"fileTitle": "Dosya adı",
|
||||
"colorPicker": "Renk seçici",
|
||||
"canvasColors": "Tuvalin üzerinde kullanıldı",
|
||||
"canvasColors": "Tuvallerin üzerinde kullanıldı",
|
||||
"canvasBackground": "Tuval arka planı",
|
||||
"drawingCanvas": "Çizim tuvali",
|
||||
"layers": "Katmanlar",
|
||||
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Yazı Tipi Boyutunu Büyült",
|
||||
"unbindText": "Metni çöz",
|
||||
"bindText": "Metni taşıyıcıya bağla",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Bağlantıyı düzenle",
|
||||
"create": "Bağlantı oluştur",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "İş birliği sunucusuna bağlanılamıyor. Lütfen sayfayı yenileyip tekrar deneyin.",
|
||||
"importLibraryError": "Kütüphane yüklenemedi",
|
||||
"collabSaveFailed": "Backend veritabanına kaydedilemedi. Eğer problem devam ederse, çalışmanızı korumak için dosyayı yerel olarak kaydetmelisiniz.",
|
||||
"collabSaveFailed_sizeExceeded": "Backend veritabanına kaydedilemedi; tuval çok büyük. Çalışmanızı korumak için dosyayı yerel olarak kaydetmelisiniz.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Görünüşe göre Brave gezginini",
|
||||
"aggressive_block_fingerprint": "Agresif parmakizi bloklama",
|
||||
"setting_enabled": "ayarları etkin şeklide kullanıyor gibisiniz",
|
||||
"break": "Bu bir takım sorunlara yol açabilir",
|
||||
"text_elements": "Metin elementlerinde bozulma",
|
||||
"in_your_drawings": "çizimlerde bozulma gibi",
|
||||
"strongly_recommend": "Bu ayarı devre dışı bırakmanızı şiddetle öneririz. Şu adımları",
|
||||
"steps": "takip ederek",
|
||||
"how": "nasıl yapılacağını",
|
||||
"disable_setting": " Yapabilirsiniz. Eğer devre dışı bırakmak işe yaramazsa, lütfen",
|
||||
"issue": "konu hakkında",
|
||||
"write": "github'da sorun belirtin, ya da bize",
|
||||
"discord": "Discord üzerinden iletin"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "Backend veritabanına kaydedilemedi; tuval çok büyük. Çalışmanızı korumak için dosyayı yerel olarak kaydetmelisiniz."
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Seçme",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "çift-tıklama",
|
||||
"drag": "sürükle",
|
||||
"editor": "Düzenleyici",
|
||||
"editLineArrowPoints": "Çizgi/ok noktalarını düzenle",
|
||||
"editText": "Etiket / metin düzenle",
|
||||
"editSelectedShape": "Seçili şekli düzenle (metin/ok/çizgi)",
|
||||
"github": "Bir hata mı buldun? Bildir",
|
||||
"howto": "Rehberlerimizi takip edin",
|
||||
"or": "veya",
|
||||
@@ -470,13 +453,13 @@
|
||||
"app": {
|
||||
"center_heading": "",
|
||||
"center_heading_plus": "",
|
||||
"menuHint": "Dışa aktar, seçenekler, diller, ..."
|
||||
"menuHint": ""
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "Dışa aktar, seçenekler, ve daha fazlası...",
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "Bir araç seçin ve çizime başlayın!",
|
||||
"helpHint": "Kısayollar & yardım"
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "Збільшити розмір шрифту",
|
||||
"unbindText": "Відв'язати текст",
|
||||
"bindText": "Прив’язати текст до контейнера",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Редагування посилання",
|
||||
"create": "Створити посилання",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "Не вдалося приєднатися до сервера. Перезавантажте сторінку та повторіть спробу.",
|
||||
"importLibraryError": "Не вдалося завантажити бібліотеку",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Виділення",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "подвійний клік",
|
||||
"drag": "перетягнути",
|
||||
"editor": "Редактор",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "Змінити вибрану фігуру (текст/стрілку/рядок)",
|
||||
"github": "Знайшли помилку? Повідомте",
|
||||
"howto": "Дотримуйтесь наших інструкцій",
|
||||
"or": "або",
|
||||
|
||||
+125
-142
@@ -5,7 +5,7 @@
|
||||
"pasteCharts": "Dán biểu đồ",
|
||||
"selectAll": "Chọn tất cả",
|
||||
"multiSelect": "Thêm mới vào Select",
|
||||
"moveCanvas": "Di chuyển canvas",
|
||||
"moveCanvas": "Di chuyển Canvas",
|
||||
"cut": "Cắt",
|
||||
"copy": "Sao chép",
|
||||
"copyAsPng": "Sao chép vào bộ nhớ tạm dưới dạng PNG",
|
||||
@@ -60,11 +60,11 @@
|
||||
"left": "Trái",
|
||||
"center": "Giữa",
|
||||
"right": "Phải",
|
||||
"extraBold": "Nét siêu đậm",
|
||||
"extraBold": "",
|
||||
"architect": "Kiến trúc sư",
|
||||
"artist": "Nghệ sỹ",
|
||||
"cartoonist": "Hoạt hình",
|
||||
"fileTitle": "Tên tập tin",
|
||||
"fileTitle": "",
|
||||
"colorPicker": "Chọn màu",
|
||||
"canvasColors": "Đã dùng trên canvas",
|
||||
"canvasBackground": "Nền canvas",
|
||||
@@ -72,28 +72,28 @@
|
||||
"layers": "Lớp",
|
||||
"actions": "Chức năng",
|
||||
"language": "Ngôn ngữ",
|
||||
"liveCollaboration": "Hợp tác trực tiếp...",
|
||||
"liveCollaboration": "",
|
||||
"duplicateSelection": "Tạo bản sao",
|
||||
"untitled": "Không có tiêu đề",
|
||||
"name": "Tên",
|
||||
"yourName": "Tên của bạn",
|
||||
"madeWithExcalidraw": "Làm với Excalidraw",
|
||||
"group": "Gộp nhóm lại lựa chọn",
|
||||
"ungroup": "Tách nhóm lựa chọn",
|
||||
"group": "",
|
||||
"ungroup": "",
|
||||
"collaborators": "Cộng tác viên",
|
||||
"showGrid": "Hiển thị lưới",
|
||||
"addToLibrary": "Thêm vào thư viện",
|
||||
"removeFromLibrary": "Xóa khỏi thư viện",
|
||||
"libraryLoadingMessage": "Đang tải thư viện…",
|
||||
"libraries": "Xem thư viện",
|
||||
"showGrid": "",
|
||||
"addToLibrary": "",
|
||||
"removeFromLibrary": "",
|
||||
"libraryLoadingMessage": "",
|
||||
"libraries": "",
|
||||
"loadingScene": "",
|
||||
"align": "Căn chỉnh",
|
||||
"alignTop": "Căn trên",
|
||||
"alignBottom": "Căn dưới",
|
||||
"alignLeft": "Canh trái",
|
||||
"alignRight": "Canh phải",
|
||||
"centerVertically": "Giữa theo chiều dọc",
|
||||
"centerHorizontally": "Giữa theo chiều ngang",
|
||||
"align": "",
|
||||
"alignTop": "",
|
||||
"alignBottom": "",
|
||||
"alignLeft": "",
|
||||
"alignRight": "",
|
||||
"centerVertically": "",
|
||||
"centerHorizontally": "",
|
||||
"distributeHorizontally": "Phân bố theo chiều ngang",
|
||||
"distributeVertically": "Phân bố theo chiều dọc",
|
||||
"flipHorizontal": "Lật ngang",
|
||||
@@ -105,43 +105,42 @@
|
||||
"showBackground": "Hiện thị chọn màu nền",
|
||||
"toggleTheme": "",
|
||||
"personalLib": "",
|
||||
"excalidrawLib": "Thư viện Excalidraw",
|
||||
"decreaseFontSize": "Giảm cỡ chữ",
|
||||
"increaseFontSize": "Tăng cỡ chữ",
|
||||
"excalidrawLib": "",
|
||||
"decreaseFontSize": "",
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "Sửa liên kết",
|
||||
"create": "Tạo liên kết",
|
||||
"label": "Liên kết"
|
||||
"edit": "",
|
||||
"create": "",
|
||||
"label": ""
|
||||
},
|
||||
"lineEditor": {
|
||||
"edit": "Điều chỉnh nét",
|
||||
"exit": "Thoát chỉnh nét"
|
||||
"edit": "",
|
||||
"exit": ""
|
||||
},
|
||||
"elementLock": {
|
||||
"lock": "Khoá",
|
||||
"unlock": "Mở khoá",
|
||||
"lockAll": "Khóa tất cả",
|
||||
"unlockAll": "Mở khóa tất cả"
|
||||
"lock": "",
|
||||
"unlock": "",
|
||||
"lockAll": "",
|
||||
"unlockAll": ""
|
||||
},
|
||||
"statusPublished": "Đã đăng tải",
|
||||
"sidebarLock": "Giữ thanh bên luôn mở"
|
||||
"statusPublished": "",
|
||||
"sidebarLock": ""
|
||||
},
|
||||
"library": {
|
||||
"noItems": "Chưa có món nào...",
|
||||
"hint_emptyLibrary": "Chọn một món trên canvas để thêm nó vào đây, hoặc cài đặt thư viện từ kho lưu trữ công cộng, ở bên dưới.",
|
||||
"hint_emptyPrivateLibrary": "Chọn một món trên canvas để thêm nó vào đây."
|
||||
"noItems": "",
|
||||
"hint_emptyLibrary": "",
|
||||
"hint_emptyPrivateLibrary": ""
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "Reset canvas",
|
||||
"exportJSON": "Xuất ra tập tin",
|
||||
"exportImage": "Xuất file ảnh...",
|
||||
"export": "Lưu vào...",
|
||||
"exportToPng": "Xuất ra tập tin PNG",
|
||||
"exportToSvg": "Xuất ra tập tin SVG",
|
||||
"copyToClipboard": "Sao chép vào bộ nhớ tạm",
|
||||
"clearReset": "",
|
||||
"exportJSON": "",
|
||||
"exportImage": "",
|
||||
"export": "",
|
||||
"exportToPng": "",
|
||||
"exportToSvg": "",
|
||||
"copyToClipboard": "",
|
||||
"copyPngToClipboard": "",
|
||||
"scale": "",
|
||||
"save": "",
|
||||
@@ -161,99 +160,84 @@
|
||||
"redo": "",
|
||||
"resetLibrary": "",
|
||||
"createNewRoom": "",
|
||||
"fullScreen": "Toàn màn hình",
|
||||
"darkMode": "Chế độ tối",
|
||||
"lightMode": "Chế độ sáng",
|
||||
"zenMode": "Chế độ zen",
|
||||
"exitZenMode": "Thoát chể độ zen",
|
||||
"cancel": "Hủy",
|
||||
"clear": "Làm sạch",
|
||||
"remove": "Xóa",
|
||||
"publishLibrary": "Đăng tải",
|
||||
"submit": "Gửi",
|
||||
"confirm": "Xác nhận"
|
||||
"fullScreen": "",
|
||||
"darkMode": "",
|
||||
"lightMode": "",
|
||||
"zenMode": "",
|
||||
"exitZenMode": "",
|
||||
"cancel": "",
|
||||
"clear": "",
|
||||
"remove": "",
|
||||
"publishLibrary": "",
|
||||
"submit": "",
|
||||
"confirm": ""
|
||||
},
|
||||
"alerts": {
|
||||
"clearReset": "Điều này sẽ dọn hết canvas. Bạn có chắc không?",
|
||||
"couldNotCreateShareableLink": "Không thể tạo đường dẫn chia sẻ.",
|
||||
"couldNotCreateShareableLinkTooBig": "Không thể tạo đường dẫn chia sẻ: bản vẽ quá lớn",
|
||||
"couldNotLoadInvalidFile": "Không thể load tập tin không hợp lệ",
|
||||
"clearReset": "",
|
||||
"couldNotCreateShareableLink": "",
|
||||
"couldNotCreateShareableLinkTooBig": "",
|
||||
"couldNotLoadInvalidFile": "",
|
||||
"importBackendFailed": "",
|
||||
"cannotExportEmptyCanvas": "Không thể xuất canvas trống.",
|
||||
"cannotExportEmptyCanvas": "",
|
||||
"couldNotCopyToClipboard": "",
|
||||
"decryptFailed": "",
|
||||
"uploadedSecurly": "",
|
||||
"loadSceneOverridePrompt": "",
|
||||
"collabStopOverridePrompt": "Dừng phiên sẽ ghi đè lên bản vẽ được lưu trữ cục bộ trước đó của bạn. Bạn có chắc không?\n\n(Nếu bạn muốn giữ bản vẽ cục bộ của mình, chỉ cần đóng tab trình duyệt.)",
|
||||
"errorAddingToLibrary": "Không thể thêm món vào thư viện",
|
||||
"errorRemovingFromLibrary": "Không thể xoá món khỏi thư viện",
|
||||
"confirmAddLibrary": "Hình {{numShapes}} sẽ được thêm vào thư viện. Bạn chắc chứ?",
|
||||
"imageDoesNotContainScene": "Hình ảnh này dường như không chứa bất kỳ dữ liệu cảnh nào. Bạn đã bật tính năng nhúng cảnh khi xuất chưa?",
|
||||
"collabStopOverridePrompt": "",
|
||||
"errorAddingToLibrary": "",
|
||||
"errorRemovingFromLibrary": "",
|
||||
"confirmAddLibrary": "",
|
||||
"imageDoesNotContainScene": "",
|
||||
"cannotRestoreFromImage": "",
|
||||
"invalidSceneUrl": "",
|
||||
"resetLibrary": "",
|
||||
"removeItemsFromsLibrary": "Xoá {{count}} món từ thư viện?",
|
||||
"invalidEncryptionKey": "Khóa mã hóa phải có 22 ký tự. Hợp tác trực tiếp bị vô hiệu hóa.",
|
||||
"collabOfflineWarning": "Không có kết nối internet.\nThay đổi của bạn sẽ không được lưu!"
|
||||
"removeItemsFromsLibrary": "",
|
||||
"invalidEncryptionKey": "",
|
||||
"collabOfflineWarning": ""
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Loại tập tin không được hỗ trợ.",
|
||||
"imageInsertError": "Không thể thêm ảnh. Hãy thử lại sau...",
|
||||
"fileTooBig": "Tệp tin quá lớn. Dung lượng tối đa cho phép là {{maxSize}}.",
|
||||
"svgImageInsertError": "Không thể thêm ảnh SVG. Mã SVG có vẻ sai.",
|
||||
"invalidSVGString": "SVG không hợp lệ.",
|
||||
"cannotResolveCollabServer": "Không thể kết nối với máy chủ hợp tác. Hãy tải lại trang và thử lại.",
|
||||
"importLibraryError": "Không thể tải thư viện",
|
||||
"collabSaveFailed": "Không thể lưu vào cơ sở dữ liệu. Nếu vấn đề tiếp tục xảy ra, bạn nên lưu tệp vào máy để đảm bảo bạn không bị mất công việc.",
|
||||
"collabSaveFailed_sizeExceeded": "Không thể lưu vào cơ sở dữ liệu, canvas có vẻ quá lớn. Bạn nên lưu tệp cục bộ để đảm bảo bạn không bị mất công việc.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "Có vẻ bạn đang sử dụng trình duyện Brave với chức năng",
|
||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
||||
"setting_enabled": "được bật",
|
||||
"break": "Điều này có thể xảy ra lỗi các",
|
||||
"text_elements": "Yếu Tố Chữ",
|
||||
"in_your_drawings": "trong bản vẽ của bạn",
|
||||
"strongly_recommend": "Chúng tôi khuyên rằng bạn nên tắt chức năng này. Bạn có thể theo",
|
||||
"steps": "các bước sau đây",
|
||||
"how": "để tắt nó",
|
||||
"disable_setting": " Nếu tắt chức năng này vẫn không sửa lại lỗi hiện thị các yếu tố chữ, hảy mở",
|
||||
"issue": "issue",
|
||||
"write": "trên trang GitHUb của chúng tôi, hoặc nhắn chúng tôi tại",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"unsupportedFileType": "",
|
||||
"imageInsertError": "",
|
||||
"fileTooBig": "",
|
||||
"svgImageInsertError": "",
|
||||
"invalidSVGString": "",
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Lựa chọn",
|
||||
"image": "Chèn ảnh",
|
||||
"rectangle": "Hình chữ nhật",
|
||||
"diamond": "Kim cương",
|
||||
"ellipse": "Hình elíp",
|
||||
"arrow": "Mũi tên",
|
||||
"line": "Đường kẻ",
|
||||
"freedraw": "Vẽ",
|
||||
"text": "Văn bản",
|
||||
"library": "Thư viện",
|
||||
"lock": "Giữ dụng cũ hiện tại sau khi vẽ",
|
||||
"penMode": "Chế độ bút vẽ - ngăn ngừa chạm nhầm",
|
||||
"link": "Thêm/ Chỉnh sửa liên kết cho hình được chọn",
|
||||
"eraser": "Xóa",
|
||||
"hand": "Tay kéo"
|
||||
"selection": "",
|
||||
"image": "",
|
||||
"rectangle": "",
|
||||
"diamond": "",
|
||||
"ellipse": "",
|
||||
"arrow": "",
|
||||
"line": "",
|
||||
"freedraw": "",
|
||||
"text": "",
|
||||
"library": "",
|
||||
"lock": "",
|
||||
"penMode": "",
|
||||
"link": "",
|
||||
"eraser": "",
|
||||
"hand": ""
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "Hành động canvas",
|
||||
"selectedShapeActions": "Các hành động cho hình dạng đã chọn",
|
||||
"shapes": "Các hình khối"
|
||||
"canvasActions": "",
|
||||
"selectedShapeActions": "",
|
||||
"shapes": ""
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "Để di chuyển canvas, giữ con lăn chuột hoặc phím cách trong khi kéo, hoặc sử dụng công cụ cầm tay",
|
||||
"linearElement": "Ấn để bắt đầu nhiểm điểm vẽ, kéo để vẽ một đường thẳng",
|
||||
"freeDraw": "Ấn bà kéo, thả khi bạn xong",
|
||||
"text": "Mẹo: bạn có thể thêm văn bản tại bất cứ đâu bằng cách ấn hai lần bằng tool lựa chọn",
|
||||
"text_selected": "Ấn 2 lần hoặc nhấn ENTER để chỉnh văn bản",
|
||||
"text_editing": "Nhấn Escape hoặc Ctrl/Cmd+ENTER để hoàn thành chỉnh sửa",
|
||||
"linearElementMulti": "Nhấn vào điểm cuối hoặc nhấn Escape hoặc Enter để kết thúc",
|
||||
"lockAngle": "Bạn có thể chỉnh lại góc bằng cách giữ phím SHIFT",
|
||||
"resize": "Bạn có thể chỉnh tỷ lệ bằng cách giữ SHIFT khi chỉnh kích cỡ,\ngiữ ALT để chỉnh kích cỡ từ trung tâm",
|
||||
"canvasPanning": "",
|
||||
"linearElement": "",
|
||||
"freeDraw": "",
|
||||
"text": "",
|
||||
"text_selected": "",
|
||||
"text_editing": "",
|
||||
"linearElementMulti": "",
|
||||
"lockAngle": "",
|
||||
"resize": "",
|
||||
"resizeImage": "",
|
||||
"rotate": "",
|
||||
"lineEditor_info": "",
|
||||
@@ -264,19 +248,19 @@
|
||||
"bindTextToElement": "",
|
||||
"deepBoxSelect": "",
|
||||
"eraserRevert": "",
|
||||
"firefox_clipboard_write": "Tính năng này có thể được bật bằng cách đặt cờ \"dom.events.asyncClipboard.clipboardItem\" thành \"true\". Để thay đổi cờ trình duyệt trong Firefox, hãy truy cập trang \"about:config\"."
|
||||
"firefox_clipboard_write": ""
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "Không thể xem trước",
|
||||
"canvasTooBig": "Canvas này có thể hơi lớn.",
|
||||
"canvasTooBigTip": "Mẹo: hãy thử di chuyển các elements nhất lại gần nhau hơn một chút."
|
||||
"cannotShowPreview": "",
|
||||
"canvasTooBig": "",
|
||||
"canvasTooBigTip": ""
|
||||
},
|
||||
"errorSplash": {
|
||||
"headingMain_pre": "",
|
||||
"headingMain_button": "",
|
||||
"clearCanvasMessage": "Nếu không tải lại được, hãy thử ",
|
||||
"clearCanvasMessage_button": "dọn canvas.",
|
||||
"clearCanvasCaveat": " Điều này sẽ dẫn đến mất dữ liệu bạn đã làm ",
|
||||
"clearCanvasMessage": "",
|
||||
"clearCanvasMessage_button": "",
|
||||
"clearCanvasCaveat": "",
|
||||
"trackedToSentry_pre": "",
|
||||
"trackedToSentry_post": "",
|
||||
"openIssueMessage_pre": "",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
@@ -338,11 +321,11 @@
|
||||
"movePageLeftRight": ""
|
||||
},
|
||||
"clearCanvasDialog": {
|
||||
"title": "Dọn canvas"
|
||||
"title": ""
|
||||
},
|
||||
"publishDialog": {
|
||||
"title": "",
|
||||
"itemName": "Tên món",
|
||||
"itemName": "",
|
||||
"authorName": "",
|
||||
"githubUsername": "",
|
||||
"twitterUsername": "",
|
||||
@@ -376,9 +359,9 @@
|
||||
"link": "",
|
||||
"post": ""
|
||||
},
|
||||
"noteItems": "Từng món trong thư viện phải có tên riêng để có thể lọc. Các món thư viện sau đây sẽ thêm:",
|
||||
"atleastOneLibItem": "Vui lòng chọn ít nhất một món thư viện để bắt đầu",
|
||||
"republishWarning": "Lưu ý: một số món đã chọn được đánh dấu là đã xuất bản/đã gửi. Bạn chỉ nên gửi lại các món khi cập nhật thư viện hiện có hoặc gửi."
|
||||
"noteItems": "",
|
||||
"atleastOneLibItem": "",
|
||||
"republishWarning": ""
|
||||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "",
|
||||
@@ -387,7 +370,7 @@
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "",
|
||||
"removeItemsFromLib": "Xóa món đã chọn khỏi thư viện"
|
||||
"removeItemsFromLib": ""
|
||||
},
|
||||
"encrypted": {
|
||||
"tooltip": "",
|
||||
@@ -415,7 +398,7 @@
|
||||
"copyToClipboardAsPng": "",
|
||||
"fileSaved": "",
|
||||
"fileSavedToFilename": "",
|
||||
"canvas": "canvas",
|
||||
"canvas": "",
|
||||
"selection": "",
|
||||
"pasteAsSingleElement": ""
|
||||
},
|
||||
@@ -457,14 +440,14 @@
|
||||
"a61e4d": "",
|
||||
"862e9c": "",
|
||||
"5f3dc4": "",
|
||||
"364fc7": "Chàm 9",
|
||||
"1864ab": "Xanh Dương 9",
|
||||
"0b7285": "Lục Lam 9",
|
||||
"087f5b": "Xanh Mòng Két 9",
|
||||
"2b8a3e": "Xanh Lá 9",
|
||||
"5c940d": "Chanh Xanh 9",
|
||||
"e67700": "Vàng 9",
|
||||
"d9480f": "Cam 9"
|
||||
"364fc7": "",
|
||||
"1864ab": "",
|
||||
"0b7285": "",
|
||||
"087f5b": "",
|
||||
"2b8a3e": "",
|
||||
"5c940d": "",
|
||||
"e67700": "",
|
||||
"d9480f": ""
|
||||
},
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
|
||||
+5
-22
@@ -35,12 +35,12 @@
|
||||
"arrowheads": "端点",
|
||||
"arrowhead_none": "无",
|
||||
"arrowhead_arrow": "箭头",
|
||||
"arrowhead_bar": "条状",
|
||||
"arrowhead_bar": "条",
|
||||
"arrowhead_dot": "圆点",
|
||||
"arrowhead_triangle": "三角箭头",
|
||||
"fontSize": "字体大小",
|
||||
"fontFamily": "字体",
|
||||
"onlySelected": "仅选中",
|
||||
"onlySelected": "仅被选中",
|
||||
"withBackground": "背景",
|
||||
"exportEmbedScene": "包含画布数据",
|
||||
"exportEmbedScene_details": "画布数据将被保存到导出的 PNG/SVG 文件,以便恢复。\n将会增加导出的文件大小。",
|
||||
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "放大字体大小",
|
||||
"unbindText": "取消文本绑定",
|
||||
"bindText": "将文本绑定到容器",
|
||||
"createContainerFromText": "将文本包围在容器中",
|
||||
"link": {
|
||||
"edit": "编辑链接",
|
||||
"create": "新建链接",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "无法连接到实时协作服务器。请重新加载页面并重试。",
|
||||
"importLibraryError": "无法加载素材库",
|
||||
"collabSaveFailed": "无法保存到后端数据库。如果问题持续存在,您应该保存文件到本地,以确保您的工作不会丢失。",
|
||||
"collabSaveFailed_sizeExceeded": "无法保存到后端数据库,画布似乎过大。您应该保存文件到本地,以确保您的工作不会丢失。",
|
||||
"brave_measure_text_error": {
|
||||
"start": "看起来您正在使用 Brave 浏览器并启用了",
|
||||
"aggressive_block_fingerprint": "积极阻止指纹识别",
|
||||
"setting_enabled": "设置",
|
||||
"break": "这可能会破坏绘图中的",
|
||||
"text_elements": "文本元素",
|
||||
"in_your_drawings": " ",
|
||||
"strongly_recommend": "我们强烈建议禁用此设置。您可以按照",
|
||||
"steps": "这些步骤",
|
||||
"how": "来设置",
|
||||
"disable_setting": " 如果禁用此设置无法修复文本元素的显示,请在 GitHub 提交一个",
|
||||
"issue": "issue",
|
||||
"write": ",或者通过",
|
||||
"discord": "Discord 反馈"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "无法保存到后端数据库,画布似乎过大。您应该保存文件到本地,以确保您的工作不会丢失。"
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "选择",
|
||||
@@ -264,7 +248,7 @@
|
||||
"bindTextToElement": "按下 Enter 以添加文本",
|
||||
"deepBoxSelect": "按住 CtrlOrCmd 以深度选择,并避免拖拽",
|
||||
"eraserRevert": "按住 Alt 以反选被标记删除的元素",
|
||||
"firefox_clipboard_write": "将高级配置首选项“dom.events.asyncClipboard.clipboardItem”设置为“true”可以启用此功能。要更改 Firefox 的高级配置首选项,请前往“about:config”页面。"
|
||||
"firefox_clipboard_write": "将高级配置首选项“dom.events.asyncClipboard.lipboarditem”设置为“true”可以启用此功能。要更改 Firefox 的高级配置首选项,请前往“about:config”页面。"
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "无法显示预览",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "双击",
|
||||
"drag": "拖动",
|
||||
"editor": "编辑器",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "编辑选中的形状 (文本、箭头或线条)",
|
||||
"github": "提交问题",
|
||||
"howto": "帮助文档",
|
||||
"or": "或",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "",
|
||||
"unbindText": "",
|
||||
"bindText": "",
|
||||
"createContainerFromText": "",
|
||||
"link": {
|
||||
"edit": "",
|
||||
"create": "",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "",
|
||||
"importLibraryError": "",
|
||||
"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": ""
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": ""
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "",
|
||||
"drag": "",
|
||||
"editor": "",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editSelectedShape": "",
|
||||
"github": "",
|
||||
"howto": "",
|
||||
"or": "",
|
||||
|
||||
+2
-19
@@ -110,7 +110,6 @@
|
||||
"increaseFontSize": "放大文字",
|
||||
"unbindText": "取消綁定文字",
|
||||
"bindText": "結合文字至容器",
|
||||
"createContainerFromText": "將文字包於容器中",
|
||||
"link": {
|
||||
"edit": "編輯連結",
|
||||
"create": "建立連結",
|
||||
@@ -205,22 +204,7 @@
|
||||
"cannotResolveCollabServer": "無法連結至 collab 伺服器。請重新整理後再試一次。",
|
||||
"importLibraryError": "無法載入資料庫",
|
||||
"collabSaveFailed": "無法儲存至後端資料庫。若此問題持續發生,請將檔案儲存於本機以確保資料不會遺失。",
|
||||
"collabSaveFailed_sizeExceeded": "無法儲存至後端資料庫,可能的原因為畫布尺寸過大。請將檔案儲存於本機以確保資料不會遺失。",
|
||||
"brave_measure_text_error": {
|
||||
"start": "您似乎正在使用 Brave 瀏覽器並且將",
|
||||
"aggressive_block_fingerprint": "\"Aggressively Block Fingerprinting\" 設定",
|
||||
"setting_enabled": "設為開啟",
|
||||
"break": "這可能導致破壞",
|
||||
"text_elements": "文字元素",
|
||||
"in_your_drawings": "在您的繪圖中",
|
||||
"strongly_recommend": "我們強烈建議您關掉此設定。您可以參考",
|
||||
"steps": "這些步驟",
|
||||
"how": "來變更",
|
||||
"disable_setting": " 若關閉此設定無法修正文字元素的顯示問題,請回報",
|
||||
"issue": "問題",
|
||||
"write": "至我們的 GitHub,或反應在我們的",
|
||||
"discord": "Discord"
|
||||
}
|
||||
"collabSaveFailed_sizeExceeded": "無法儲存至後端資料庫,可能的原因為畫布尺寸過大。請將檔案儲存於本機以確保資料不會遺失。"
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "選取",
|
||||
@@ -319,8 +303,7 @@
|
||||
"doubleClick": "雙擊",
|
||||
"drag": "拖曳",
|
||||
"editor": "編輯器",
|
||||
"editLineArrowPoints": "編輯線/箭頭控制點",
|
||||
"editText": "編輯文字/增加標籤",
|
||||
"editSelectedShape": "編輯選定的形狀(文字/箭號/線條)",
|
||||
"github": "發現異常?回報問題",
|
||||
"howto": "參照我們的說明",
|
||||
"or": "或",
|
||||
|
||||
@@ -11,12 +11,10 @@ The change should be grouped under one of the below section and must contain PR
|
||||
Please add the latest change on the top under the correct section.
|
||||
-->
|
||||
|
||||
## 0.15.0 (2023-04-18)
|
||||
## Unreleased
|
||||
|
||||
### Features
|
||||
|
||||
- [`ExcalidrawAPI.scrolToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) has new opts object allowing you to fit viewport to content, and animate the scrolling. [#6319](https://github.com/excalidraw/excalidraw/pull/6319)
|
||||
|
||||
- Expose `useI18n()` hook return an object containing `t()` i18n helper and current `langCode`. You can use this in components you render as `<Excalidraw>` children to render any of our i18n locale strings. [#6224](https://github.com/excalidraw/excalidraw/pull/6224)
|
||||
|
||||
- [`restoreElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restoreelements) API now takes an optional parameter `opts` which currently supports the below attributes
|
||||
@@ -33,158 +31,6 @@ For more details refer to the [docs](https://docs.excalidraw.com)
|
||||
|
||||
- The optional parameter `refreshDimensions` in [`restoreElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restoreelements) has been removed and can be enabled via `opts`
|
||||
|
||||
### Fixes
|
||||
|
||||
- Exporting labelled arrows via export utils [#6443](https://github.com/excalidraw/excalidraw/pull/6443)
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
### Features
|
||||
|
||||
- Constrain export dialog preview size [#6475](https://github.com/excalidraw/excalidraw/pull/6475)
|
||||
|
||||
- Zigzag fill easter egg [#6439](https://github.com/excalidraw/excalidraw/pull/6439)
|
||||
|
||||
- Add container to multiple text elements [#6428](https://github.com/excalidraw/excalidraw/pull/6428)
|
||||
|
||||
- Starting migration from GA to Matomo for better privacy [#6398](https://github.com/excalidraw/excalidraw/pull/6398)
|
||||
|
||||
- Add line height attribute to text element [#6360](https://github.com/excalidraw/excalidraw/pull/6360)
|
||||
|
||||
- Add thai lang support [#6314](https://github.com/excalidraw/excalidraw/pull/6314)
|
||||
|
||||
- Create bound container from text [#6301](https://github.com/excalidraw/excalidraw/pull/6301)
|
||||
|
||||
- Improve text measurements in bound containers [#6187](https://github.com/excalidraw/excalidraw/pull/6187)
|
||||
|
||||
- Bind text to container if double clicked on filled shape or stroke [#6250](https://github.com/excalidraw/excalidraw/pull/6250)
|
||||
|
||||
- Make repair and refreshDimensions configurable in restoreElements [#6238](https://github.com/excalidraw/excalidraw/pull/6238)
|
||||
|
||||
- Show error message when not connected to internet while collabo… [#6165](https://github.com/excalidraw/excalidraw/pull/6165)
|
||||
|
||||
- Shortcut for clearCanvas confirmDialog [#6114](https://github.com/excalidraw/excalidraw/pull/6114)
|
||||
|
||||
- Disable canvas smoothing (antialiasing) for right-angled elements [#6186](https://github.com/excalidraw/excalidraw/pull/6186)Co-authored-by: Ignacio Cuadra <67276174+ignacio-cuadra@users.noreply.github.com>
|
||||
|
||||
### Fixes
|
||||
|
||||
- Center align text when wrapped in container via context menu [#6480](https://github.com/excalidraw/excalidraw/pull/6480)
|
||||
|
||||
- Restore original container height when unbinding text which was binded via context menu [#6444](https://github.com/excalidraw/excalidraw/pull/6444)
|
||||
|
||||
- Mark more props as optional for element [#6448](https://github.com/excalidraw/excalidraw/pull/6448)
|
||||
|
||||
- Improperly cache-busting on canvas scale instead of zoom [#6473](https://github.com/excalidraw/excalidraw/pull/6473)
|
||||
|
||||
- Incorrectly duplicating items on paste/library insert [#6467](https://github.com/excalidraw/excalidraw/pull/6467)
|
||||
|
||||
- Library ids cross-contamination on multiple insert [#6466](https://github.com/excalidraw/excalidraw/pull/6466)
|
||||
|
||||
- Color picker keyboard handling not working [#6464](https://github.com/excalidraw/excalidraw/pull/6464)
|
||||
|
||||
- Abort freedraw line if second touch is detected [#6440](https://github.com/excalidraw/excalidraw/pull/6440)
|
||||
|
||||
- Utils leaking Scene state [#6461](https://github.com/excalidraw/excalidraw/pull/6461)
|
||||
|
||||
- Split "Edit selected shape" shortcut [#6457](https://github.com/excalidraw/excalidraw/pull/6457)
|
||||
|
||||
- Center align text when bind to container via context menu [#6451](https://github.com/excalidraw/excalidraw/pull/6451)
|
||||
|
||||
- Update coords when text unbinded from its container [#6445](https://github.com/excalidraw/excalidraw/pull/6445)
|
||||
|
||||
- Autoredirect to plus in prod only [#6446](https://github.com/excalidraw/excalidraw/pull/6446)
|
||||
|
||||
- Fixing popover overflow on small screen [#6433](https://github.com/excalidraw/excalidraw/pull/6433)
|
||||
|
||||
- Introduce baseline to fix the layout shift when switching to text editor [#6397](https://github.com/excalidraw/excalidraw/pull/6397)
|
||||
|
||||
- Don't refresh dimensions for deleted text elements [#6438](https://github.com/excalidraw/excalidraw/pull/6438)
|
||||
|
||||
- Element vanishes when zoomed in [#6417](https://github.com/excalidraw/excalidraw/pull/6417)
|
||||
|
||||
- Don't jump text to end when out of viewport in safari [#6416](https://github.com/excalidraw/excalidraw/pull/6416)
|
||||
|
||||
- GetDefaultLineHeight should return default font family line height for unknown font [#6399](https://github.com/excalidraw/excalidraw/pull/6399)
|
||||
|
||||
- Revert use `ideographic` textBaseline to improve layout shift when editing text" [#6400](https://github.com/excalidraw/excalidraw/pull/6400)
|
||||
|
||||
- Call stack size exceeded when paste large text [#6373](https://github.com/excalidraw/excalidraw/pull/6373) (#6396)
|
||||
|
||||
- Use `ideographic` textBaseline to improve layout shift when editing text [#6384](https://github.com/excalidraw/excalidraw/pull/6384)
|
||||
|
||||
- Chrome crashing when embedding scene on chrome arm [#6383](https://github.com/excalidraw/excalidraw/pull/6383)
|
||||
|
||||
- Division by zero in findFocusPointForEllipse leads to infinite loop in wrapText freezing Excalidraw [#6377](https://github.com/excalidraw/excalidraw/pull/6377)
|
||||
|
||||
- Containerizing text incorrectly updates arrow bindings [#6369](https://github.com/excalidraw/excalidraw/pull/6369)
|
||||
|
||||
- Ensure export preview is centered [#6337](https://github.com/excalidraw/excalidraw/pull/6337)
|
||||
|
||||
- Hide text align for labelled arrows [#6339](https://github.com/excalidraw/excalidraw/pull/6339)
|
||||
|
||||
- Refresh dimensions when elements loaded from shareable link and blob [#6333](https://github.com/excalidraw/excalidraw/pull/6333)
|
||||
|
||||
- Show error message when measureText API breaks in brave [#6336](https://github.com/excalidraw/excalidraw/pull/6336)
|
||||
|
||||
- Add an offset of 0.5px for text editor in containers [#6328](https://github.com/excalidraw/excalidraw/pull/6328)
|
||||
|
||||
- Move utility types out of `.d.ts` file to fix exported declaration files [#6315](https://github.com/excalidraw/excalidraw/pull/6315)
|
||||
|
||||
- More jotai scopes missing [#6313](https://github.com/excalidraw/excalidraw/pull/6313)
|
||||
|
||||
- Provide HelpButton title prop [#6209](https://github.com/excalidraw/excalidraw/pull/6209)
|
||||
|
||||
- Respect text align when wrapping in a container [#6310](https://github.com/excalidraw/excalidraw/pull/6310)
|
||||
|
||||
- Compute bounding box correctly for text element when multiple element resizing [#6307](https://github.com/excalidraw/excalidraw/pull/6307)
|
||||
|
||||
- Use jotai scope for editor-specific atoms [#6308](https://github.com/excalidraw/excalidraw/pull/6308)
|
||||
|
||||
- Consider arrow for bound text element [#6297](https://github.com/excalidraw/excalidraw/pull/6297)
|
||||
|
||||
- Text never goes beyond max width for unbound text elements [#6288](https://github.com/excalidraw/excalidraw/pull/6288)
|
||||
|
||||
- Svg text baseline [#6285](https://github.com/excalidraw/excalidraw/pull/6273)
|
||||
|
||||
- Compute container height from bound text correctly [#6273](https://github.com/excalidraw/excalidraw/pull/6273)
|
||||
|
||||
- Fit mobile toolbar and make scrollable [#6270](https://github.com/excalidraw/excalidraw/pull/6270)
|
||||
|
||||
- Indenting via `tab` clashing with IME compositor [#6258](https://github.com/excalidraw/excalidraw/pull/6258)
|
||||
|
||||
- Improve text wrapping inside rhombus and more fixes [#6265](https://github.com/excalidraw/excalidraw/pull/6265)
|
||||
|
||||
- Improve text wrapping in ellipse and alignment [#6172](https://github.com/excalidraw/excalidraw/pull/6172)
|
||||
|
||||
- Don't allow blank space in collab name [#6211](https://github.com/excalidraw/excalidraw/pull/6211)
|
||||
|
||||
- Docker build architecture:linux/amd64 error occur on linux/arm64 instance [#6197](https://github.com/excalidraw/excalidraw/pull/6197)
|
||||
|
||||
- Sort bound text elements to fix text duplication z-index error [#5130](https://github.com/excalidraw/excalidraw/pull/5130)
|
||||
|
||||
- Hide welcome screen on mobile once user interacts [#6185](https://github.com/excalidraw/excalidraw/pull/6185)
|
||||
|
||||
- Edit link in docs [#6182](https://github.com/excalidraw/excalidraw/pull/6182)
|
||||
|
||||
### Refactor
|
||||
|
||||
- Inline `SingleLibraryItem` into `PublishLibrary` [#6462](https://github.com/excalidraw/excalidraw/pull/6462)
|
||||
|
||||
- Make the example React app reusable without duplication [#6188](https://github.com/excalidraw/excalidraw/pull/6188)
|
||||
|
||||
### Performance
|
||||
|
||||
- Break early if the line width <= max width of the container [#6347](https://github.com/excalidraw/excalidraw/pull/6347)
|
||||
|
||||
### Build
|
||||
|
||||
- Move TS and types to devDependencies [#6346](https://github.com/excalidraw/excalidraw/pull/6346)
|
||||
|
||||
---
|
||||
|
||||
## 0.14.2 (2023-02-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.15.0",
|
||||
"version": "0.14.2",
|
||||
"main": "main.js",
|
||||
"types": "types/packages/excalidraw/index.d.ts",
|
||||
"files": [
|
||||
|
||||
+9
-46
@@ -5,6 +5,7 @@ import {
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import { AppState, BinaryFiles } from "../types";
|
||||
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { restore } from "../data/restore";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import { encodePngMetadata } from "../data/image";
|
||||
@@ -14,24 +15,6 @@ import {
|
||||
copyTextToSystemClipboard,
|
||||
copyToClipboard,
|
||||
} from "../clipboard";
|
||||
import Scene from "../scene/Scene";
|
||||
import { duplicateElements } from "../element/newElement";
|
||||
|
||||
// getContainerElement and getBoundTextElement and potentially other helpers
|
||||
// depend on `Scene` which will not be available when these pure utils are
|
||||
// called outside initialized Excalidraw editor instance or even if called
|
||||
// from inside Excalidraw if the elements were never cached by Scene (e.g.
|
||||
// for library elements).
|
||||
//
|
||||
// As such, before passing the elements down, we need to initialize a custom
|
||||
// Scene instance and assign them to it.
|
||||
//
|
||||
// FIXME This is a super hacky workaround and we'll need to rewrite this soon.
|
||||
const passElementsSafely = (elements: readonly ExcalidrawElement[]) => {
|
||||
const scene = new Scene();
|
||||
scene.replaceAllElements(duplicateElements(elements));
|
||||
return scene.getNonDeletedElements();
|
||||
};
|
||||
|
||||
export { MIME_TYPES };
|
||||
|
||||
@@ -63,7 +46,7 @@ export const exportToCanvas = ({
|
||||
);
|
||||
const { exportBackground, viewBackgroundColor } = restoredAppState;
|
||||
return _exportToCanvas(
|
||||
passElementsSafely(restoredElements),
|
||||
getNonDeletedElements(restoredElements),
|
||||
{ ...restoredAppState, offsetTop: 0, offsetLeft: 0, width: 0, height: 0 },
|
||||
files || {},
|
||||
{ exportBackground, exportPadding, viewBackgroundColor },
|
||||
@@ -79,11 +62,7 @@ export const exportToCanvas = ({
|
||||
|
||||
const max = Math.max(width, height);
|
||||
|
||||
// if content is less then maxWidthOrHeight, fallback on supplied scale
|
||||
const scale =
|
||||
maxWidthOrHeight < max
|
||||
? maxWidthOrHeight / max
|
||||
: appState?.exportScale ?? 1;
|
||||
const scale = maxWidthOrHeight / max;
|
||||
|
||||
canvas.width = width * scale;
|
||||
canvas.height = height * scale;
|
||||
@@ -135,10 +114,8 @@ export const exportToBlob = async (
|
||||
};
|
||||
}
|
||||
|
||||
const canvas = await exportToCanvas({
|
||||
...opts,
|
||||
elements: passElementsSafely(opts.elements),
|
||||
});
|
||||
const canvas = await exportToCanvas(opts);
|
||||
|
||||
quality = quality ? quality : /image\/jpe?g/.test(mimeType) ? 0.92 : 0.8;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -155,9 +132,6 @@ export const exportToBlob = async (
|
||||
blob = await encodePngMetadata({
|
||||
blob,
|
||||
metadata: serializeAsJSON(
|
||||
// NOTE as long as we're using the Scene hack, we need to ensure
|
||||
// we pass the original, uncloned elements when serializing
|
||||
// so that we keep ids stable
|
||||
opts.elements,
|
||||
opts.appState,
|
||||
opts.files || {},
|
||||
@@ -186,24 +160,13 @@ export const exportToSvg = async ({
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const exportAppState = {
|
||||
...restoredAppState,
|
||||
exportPadding,
|
||||
};
|
||||
|
||||
return _exportToSvg(
|
||||
passElementsSafely(restoredElements),
|
||||
exportAppState,
|
||||
files,
|
||||
getNonDeletedElements(restoredElements),
|
||||
{
|
||||
// NOTE as long as we're using the Scene hack, we need to ensure
|
||||
// we pass the original, uncloned elements when serializing
|
||||
// so that we keep ids stable. Hence adding the serializeAsJSON helper
|
||||
// support into the downstream exportToSvg function.
|
||||
serializeAsJSON: () =>
|
||||
serializeAsJSON(restoredElements, exportAppState, files || {}, "local"),
|
||||
...restoredAppState,
|
||||
exportPadding,
|
||||
},
|
||||
files,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -43,11 +43,11 @@ import {
|
||||
getBoundTextElement,
|
||||
getContainerCoords,
|
||||
getContainerElement,
|
||||
getLineHeightInPx,
|
||||
getMaxContainerHeight,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxHeight,
|
||||
getBoundTextMaxWidth,
|
||||
} from "../element/textElement";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { getLineHeightInPx } from "../element/textMeasurements";
|
||||
|
||||
// using a stronger invert (100% vs our regular 93%) and saturate
|
||||
// as a temp hack to make images in dark theme look closer to original
|
||||
@@ -87,67 +87,12 @@ export interface ExcalidrawElementWithCanvas {
|
||||
element: ExcalidrawElement | ExcalidrawTextElement;
|
||||
canvas: HTMLCanvasElement;
|
||||
theme: RenderConfig["theme"];
|
||||
scale: number;
|
||||
zoomValue: RenderConfig["zoom"]["value"];
|
||||
canvasZoom: Zoom["value"];
|
||||
canvasOffsetX: number;
|
||||
canvasOffsetY: number;
|
||||
boundTextElementVersion: number | null;
|
||||
}
|
||||
|
||||
const cappedElementCanvasSize = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
zoom: Zoom,
|
||||
): {
|
||||
width: number;
|
||||
height: number;
|
||||
scale: number;
|
||||
} => {
|
||||
// these limits are ballpark, they depend on specific browsers and device.
|
||||
// We've chosen lower limits to be safe. We might want to change these limits
|
||||
// based on browser/device type, if we get reports of low quality rendering
|
||||
// on zoom.
|
||||
//
|
||||
// ~ safari mobile canvas area limit
|
||||
const AREA_LIMIT = 16777216;
|
||||
// ~ safari width/height limit based on developer.mozilla.org.
|
||||
const WIDTH_HEIGHT_LIMIT = 32767;
|
||||
|
||||
const padding = getCanvasPadding(element);
|
||||
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
const elementWidth =
|
||||
isLinearElement(element) || isFreeDrawElement(element)
|
||||
? distance(x1, x2)
|
||||
: element.width;
|
||||
const elementHeight =
|
||||
isLinearElement(element) || isFreeDrawElement(element)
|
||||
? distance(y1, y2)
|
||||
: element.height;
|
||||
|
||||
let width = elementWidth * window.devicePixelRatio + padding * 2;
|
||||
let height = elementHeight * window.devicePixelRatio + padding * 2;
|
||||
|
||||
let scale: number = zoom.value;
|
||||
|
||||
// rescale to ensure width and height is within limits
|
||||
if (
|
||||
width * scale > WIDTH_HEIGHT_LIMIT ||
|
||||
height * scale > WIDTH_HEIGHT_LIMIT
|
||||
) {
|
||||
scale = Math.min(WIDTH_HEIGHT_LIMIT / width, WIDTH_HEIGHT_LIMIT / height);
|
||||
}
|
||||
|
||||
// rescale to ensure canvas area is within limits
|
||||
if (width * height * scale * scale > AREA_LIMIT) {
|
||||
scale = Math.sqrt(AREA_LIMIT / (width * height));
|
||||
}
|
||||
|
||||
width = Math.floor(width * scale);
|
||||
height = Math.floor(height * scale);
|
||||
|
||||
return { width, height, scale };
|
||||
};
|
||||
|
||||
const generateElementCanvas = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
zoom: Zoom,
|
||||
@@ -157,35 +102,44 @@ const generateElementCanvas = (
|
||||
const context = canvas.getContext("2d")!;
|
||||
const padding = getCanvasPadding(element);
|
||||
|
||||
const { width, height, scale } = cappedElementCanvasSize(element, zoom);
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
let canvasOffsetX = 0;
|
||||
let canvasOffsetY = 0;
|
||||
|
||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
|
||||
canvas.width =
|
||||
distance(x1, x2) * window.devicePixelRatio * zoom.value +
|
||||
padding * zoom.value * 2;
|
||||
canvas.height =
|
||||
distance(y1, y2) * window.devicePixelRatio * zoom.value +
|
||||
padding * zoom.value * 2;
|
||||
|
||||
canvasOffsetX =
|
||||
element.x > x1
|
||||
? distance(element.x, x1) * window.devicePixelRatio * scale
|
||||
? distance(element.x, x1) * window.devicePixelRatio * zoom.value
|
||||
: 0;
|
||||
|
||||
canvasOffsetY =
|
||||
element.y > y1
|
||||
? distance(element.y, y1) * window.devicePixelRatio * scale
|
||||
? distance(element.y, y1) * window.devicePixelRatio * zoom.value
|
||||
: 0;
|
||||
|
||||
context.translate(canvasOffsetX, canvasOffsetY);
|
||||
} else {
|
||||
canvas.width =
|
||||
element.width * window.devicePixelRatio * zoom.value +
|
||||
padding * zoom.value * 2;
|
||||
canvas.height =
|
||||
element.height * window.devicePixelRatio * zoom.value +
|
||||
padding * zoom.value * 2;
|
||||
}
|
||||
|
||||
context.save();
|
||||
context.translate(padding * scale, padding * scale);
|
||||
context.translate(padding * zoom.value, padding * zoom.value);
|
||||
context.scale(
|
||||
window.devicePixelRatio * scale,
|
||||
window.devicePixelRatio * scale,
|
||||
window.devicePixelRatio * zoom.value,
|
||||
window.devicePixelRatio * zoom.value,
|
||||
);
|
||||
|
||||
const rc = rough.canvas(canvas);
|
||||
@@ -202,8 +156,7 @@ const generateElementCanvas = (
|
||||
element,
|
||||
canvas,
|
||||
theme: renderConfig.theme,
|
||||
scale,
|
||||
zoomValue: zoom.value,
|
||||
canvasZoom: zoom.value,
|
||||
canvasOffsetX,
|
||||
canvasOffsetY,
|
||||
boundTextElementVersion: getBoundTextElement(element)?.version || null,
|
||||
@@ -247,6 +200,7 @@ const drawImagePlaceholder = (
|
||||
size,
|
||||
);
|
||||
};
|
||||
|
||||
const drawElementOnCanvas = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
rc: RoughCanvas,
|
||||
@@ -325,23 +279,24 @@ const drawElementOnCanvas = (
|
||||
|
||||
// Canvas does not support multiline text by default
|
||||
const lines = element.text.replace(/\r\n?/g, "\n").split("\n");
|
||||
|
||||
const horizontalOffset =
|
||||
element.textAlign === "center"
|
||||
? element.width / 2
|
||||
: element.textAlign === "right"
|
||||
? element.width
|
||||
: 0;
|
||||
context.textBaseline = "bottom";
|
||||
|
||||
const lineHeightPx = getLineHeightInPx(
|
||||
element.fontSize,
|
||||
element.lineHeight,
|
||||
);
|
||||
const verticalOffset = element.height - element.baseline;
|
||||
|
||||
for (let index = 0; index < lines.length; index++) {
|
||||
context.fillText(
|
||||
lines[index],
|
||||
horizontalOffset,
|
||||
(index + 1) * lineHeightPx - verticalOffset,
|
||||
(index + 1) * lineHeightPx,
|
||||
);
|
||||
}
|
||||
context.restore();
|
||||
@@ -714,7 +669,7 @@ const generateElementWithCanvas = (
|
||||
const prevElementWithCanvas = elementWithCanvasCache.get(element);
|
||||
const shouldRegenerateBecauseZoom =
|
||||
prevElementWithCanvas &&
|
||||
prevElementWithCanvas.zoomValue !== zoom.value &&
|
||||
prevElementWithCanvas.canvasZoom !== zoom.value &&
|
||||
!renderConfig?.shouldCacheIgnoreZoom;
|
||||
const boundTextElementVersion = getBoundTextElement(element)?.version || null;
|
||||
|
||||
@@ -745,7 +700,7 @@ const drawElementFromCanvas = (
|
||||
) => {
|
||||
const element = elementWithCanvas.element;
|
||||
const padding = getCanvasPadding(element);
|
||||
const zoom = elementWithCanvas.scale;
|
||||
const zoom = elementWithCanvas.canvasZoom;
|
||||
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||
|
||||
// Free draw elements will otherwise "shuffle" as the min x and y change
|
||||
@@ -772,10 +727,10 @@ const drawElementFromCanvas = (
|
||||
const maxDim = Math.max(distance(x1, x2), distance(y1, y2));
|
||||
tempCanvas.width =
|
||||
maxDim * window.devicePixelRatio * zoom +
|
||||
padding * elementWithCanvas.scale * 10;
|
||||
padding * elementWithCanvas.canvasZoom * 10;
|
||||
tempCanvas.height =
|
||||
maxDim * window.devicePixelRatio * zoom +
|
||||
padding * elementWithCanvas.scale * 10;
|
||||
padding * elementWithCanvas.canvasZoom * 10;
|
||||
const offsetX = (tempCanvas.width - elementWithCanvas.canvas!.width) / 2;
|
||||
const offsetY = (tempCanvas.height - elementWithCanvas.canvas!.height) / 2;
|
||||
|
||||
@@ -856,25 +811,28 @@ const drawElementFromCanvas = (
|
||||
context.drawImage(
|
||||
elementWithCanvas.canvas!,
|
||||
(x1 + renderConfig.scrollX) * window.devicePixelRatio -
|
||||
(padding * elementWithCanvas.scale) / elementWithCanvas.scale,
|
||||
(padding * elementWithCanvas.canvasZoom) / elementWithCanvas.canvasZoom,
|
||||
(y1 + renderConfig.scrollY) * window.devicePixelRatio -
|
||||
(padding * elementWithCanvas.scale) / elementWithCanvas.scale,
|
||||
elementWithCanvas.canvas!.width / elementWithCanvas.scale,
|
||||
elementWithCanvas.canvas!.height / elementWithCanvas.scale,
|
||||
(padding * elementWithCanvas.canvasZoom) / elementWithCanvas.canvasZoom,
|
||||
elementWithCanvas.canvas!.width / elementWithCanvas.canvasZoom,
|
||||
elementWithCanvas.canvas!.height / elementWithCanvas.canvasZoom,
|
||||
);
|
||||
|
||||
if (
|
||||
process.env.REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX &&
|
||||
hasBoundTextElement(element)
|
||||
) {
|
||||
const textElement = getBoundTextElement(
|
||||
element,
|
||||
) as ExcalidrawTextElementWithContainer;
|
||||
const coords = getContainerCoords(element);
|
||||
context.strokeStyle = "#c92a2a";
|
||||
context.lineWidth = 3;
|
||||
context.strokeRect(
|
||||
(coords.x + renderConfig.scrollX) * window.devicePixelRatio,
|
||||
(coords.y + renderConfig.scrollY) * window.devicePixelRatio,
|
||||
getMaxContainerWidth(element) * window.devicePixelRatio,
|
||||
getMaxContainerHeight(element) * window.devicePixelRatio,
|
||||
getBoundTextMaxWidth(element) * window.devicePixelRatio,
|
||||
getBoundTextMaxHeight(element, textElement) * window.devicePixelRatio,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-6
@@ -90,9 +90,6 @@ export const exportToSvg = async (
|
||||
exportEmbedScene?: boolean;
|
||||
},
|
||||
files: BinaryFiles | null,
|
||||
opts?: {
|
||||
serializeAsJSON?: () => string;
|
||||
},
|
||||
): Promise<SVGSVGElement> => {
|
||||
const {
|
||||
exportPadding = DEFAULT_EXPORT_PADDING,
|
||||
@@ -106,9 +103,7 @@ export const exportToSvg = async (
|
||||
metadata = await (
|
||||
await import(/* webpackChunkName: "image" */ "../../src/data/image")
|
||||
).encodeSvgMetadata({
|
||||
text: opts?.serializeAsJSON
|
||||
? opts?.serializeAsJSON?.()
|
||||
: serializeAsJSON(elements, appState, files || {}, "local"),
|
||||
text: serializeAsJSON(elements, appState, files || {}, "local"),
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
||||
@@ -121,7 +121,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"name": "createContainerFromText",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
@@ -4518,7 +4518,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"name": "createContainerFromText",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
@@ -5068,7 +5068,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"name": "createContainerFromText",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
@@ -5917,7 +5917,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"name": "createContainerFromText",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
@@ -6263,7 +6263,7 @@ Object {
|
||||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"name": "createContainerFromText",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
|
||||
@@ -13431,7 +13431,7 @@ Object {
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [
|
||||
"id4_copy",
|
||||
"id6",
|
||||
],
|
||||
"height": 10,
|
||||
"id": "id0_copy",
|
||||
@@ -13464,7 +13464,7 @@ Object {
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [
|
||||
"id4_copy",
|
||||
"id6",
|
||||
],
|
||||
"height": 10,
|
||||
"id": "id1_copy",
|
||||
@@ -13497,7 +13497,7 @@ Object {
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [
|
||||
"id4_copy",
|
||||
"id6",
|
||||
],
|
||||
"height": 10,
|
||||
"id": "id2_copy",
|
||||
@@ -13981,7 +13981,7 @@ Object {
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [
|
||||
"id4_copy",
|
||||
"id6",
|
||||
],
|
||||
"height": 10,
|
||||
"id": "id0_copy",
|
||||
@@ -14011,7 +14011,7 @@ Object {
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [
|
||||
"id4_copy",
|
||||
"id6",
|
||||
],
|
||||
"height": 10,
|
||||
"id": "id1_copy",
|
||||
@@ -14041,7 +14041,7 @@ Object {
|
||||
"boundElements": null,
|
||||
"fillStyle": "hachure",
|
||||
"groupIds": Array [
|
||||
"id4_copy",
|
||||
"id6",
|
||||
],
|
||||
"height": 10,
|
||||
"id": "id2_copy",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { getTransformHandles } from "../element/transformHandles";
|
||||
import { API } from "./helpers/api";
|
||||
import { KEYS } from "../keys";
|
||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
@@ -277,7 +277,7 @@ describe("element binding", () => {
|
||||
|
||||
expect(h.state.selectedElementIds[text1.id]).toBe(true);
|
||||
|
||||
h.app.actionManager.executeAction(actionWrapTextInContainer);
|
||||
h.app.actionManager.executeAction(actionCreateContainerFromText);
|
||||
|
||||
// new text container will be placed before the text element
|
||||
const container = h.elements.at(-2)!;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user