mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
fix: merge branch 'master' into alex-kim-dev/flip-multiple-elements
This commit is contained in:
commit
0ca1140e13
100 changed files with 985 additions and 453 deletions
|
@ -31,10 +31,29 @@ You can pass `null` / `undefined` if not applicable.
|
|||
restoreElements(
|
||||
elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/>
|
||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/>
|
||||
refreshDimensions?: boolean<br/>
|
||||
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
||||
)
|
||||
</pre>
|
||||
|
||||
| Prop | Type | Description |
|
||||
| ---- | ---- | ---- |
|
||||
| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
|
||||
| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined | When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
|
||||
| [`opts`](#opts) | `Object` | The extra optional parameter to configure restored elements
|
||||
|
||||
#### localElements
|
||||
|
||||
When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`.
|
||||
Use this when you `import` elements which may already be present in the scene to ensure that you do not disregard the newly imported elements if you're using element version to detect the update
|
||||
|
||||
#### opts
|
||||
The extra optional parameter to configure restored elements. It has the following attributes
|
||||
|
||||
| Prop | Type | Description|
|
||||
| --- | --- | ------|
|
||||
| `refreshDimensions` | `boolean` | Indicates whether we should also `recalculate` text element dimensions. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration. |
|
||||
| `repairBindings` |`boolean` | Indicates whether the `bindings` for the elements should be repaired. This is to make sure there are no containers with non existent bound text element id and no bound text elements with non existent container id. |
|
||||
|
||||
**_How to use_**
|
||||
|
||||
```js
|
||||
|
@ -43,9 +62,6 @@ import { restoreElements } from "@excalidraw/excalidraw";
|
|||
|
||||
This function will make sure all properties of element is correctly set and if any attribute is missing, it will be set to its default value.
|
||||
|
||||
When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`.
|
||||
Use this when you import elements which may already be present in the scene to ensure that you do not disregard the newly imported elements if you're using element version to detect the updates.
|
||||
|
||||
Parameter `refreshDimensions` indicates whether we should also `recalculate` text element dimensions. Defaults to `false`. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration.
|
||||
|
||||
### restore
|
||||
|
@ -56,7 +72,9 @@ Parameter `refreshDimensions` indicates whether we should also `recalculate` tex
|
|||
restore(
|
||||
data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState</a>,<br/>
|
||||
localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null | undefined,<br/>
|
||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a>
|
||||
localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a><br/>
|
||||
opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
|
||||
|
||||
)
|
||||
</pre>
|
||||
|
||||
|
|
|
@ -339,3 +339,47 @@ The `device` has the following `attributes`
|
|||
| `isMobile` | `boolean` | Set to `true` when the device is `mobile` |
|
||||
| `isTouchScreen` | `boolean` | Set to `true` for `touch` devices |
|
||||
| `canDeviceFitSidebar` | `boolean` | Implies whether there is enough space to fit the `sidebar` |
|
||||
|
||||
### i18n
|
||||
|
||||
To help with localization, we export the following.
|
||||
|
||||
| name | type |
|
||||
| --- | --- |
|
||||
| `defaultLang` | `string` |
|
||||
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
|
||||
| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
|
||||
|
||||
```js
|
||||
import { defaultLang, languages, useI18n } from "@excalidraw/excalidraw";
|
||||
```
|
||||
|
||||
#### defaultLang
|
||||
|
||||
Default language code, `en`.
|
||||
|
||||
#### languages
|
||||
|
||||
List of supported language codes. You can pass any of these to `Excalidraw`'s [`langCode` prop](/docs/@excalidraw/excalidraw/api/props/#langcode).
|
||||
|
||||
#### useI18n
|
||||
|
||||
A hook that returns the current language code and translation helper function. You can use this to translate strings in the components you render as children of `<Excalidraw>`.
|
||||
|
||||
```jsx live
|
||||
function App() {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div style={{ height: "500px" }}>
|
||||
<Excalidraw>
|
||||
<button
|
||||
style={{ position: "absolute", zIndex: 10, height: "2rem" }}
|
||||
onClick={() => window.alert(t("labels.madeWithExcalidraw"))}
|
||||
>
|
||||
{t("buttons.confirm")}
|
||||
</button>
|
||||
</Excalidraw>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"@docusaurus/theme-live-codeblock": "2.2.0",
|
||||
"@excalidraw/excalidraw": "0.14.2",
|
||||
"@excalidraw/excalidraw": "0.15.2",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-plugin-sass": "0.2.3",
|
||||
|
|
|
@ -24,6 +24,7 @@ const ExcalidrawScope = {
|
|||
Sidebar: ExcalidrawComp.Sidebar,
|
||||
exportToCanvas: ExcalidrawComp.exportToCanvas,
|
||||
initialData,
|
||||
useI18n: ExcalidrawComp.useI18n,
|
||||
};
|
||||
|
||||
export default ExcalidrawScope;
|
||||
|
|
|
@ -1631,10 +1631,10 @@
|
|||
url-loader "^4.1.1"
|
||||
webpack "^5.73.0"
|
||||
|
||||
"@excalidraw/excalidraw@0.14.2":
|
||||
version "0.14.2"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.14.2.tgz#150cb4b7a1bf0d11cd64295936c930e7e0db8375"
|
||||
integrity sha512-8LdjpTBWEK5waDWB7Bt/G9YBI4j0OxkstUhvaDGz7dwQGfzF6FW5CXBoYHNEoX0qmb+Fg/NPOlZ7FrKsrSVCqg==
|
||||
"@excalidraw/excalidraw@0.15.2":
|
||||
version "0.15.2"
|
||||
resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.15.2.tgz#7dba4f6e10c52015a007efb75a9fc1afe598574c"
|
||||
integrity sha512-rTI02kgWSTXiUdIkBxt9u/581F3eXcqQgJdIxmz54TFtG3ughoxO5fr4t7Fr2LZIturBPqfocQHGKZ0t2KLKgw==
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.3.0"
|
||||
|
@ -7159,9 +7159,9 @@ typescript@^4.7.4:
|
|||
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
|
||||
|
||||
ua-parser-js@^0.7.30:
|
||||
version "0.7.31"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6"
|
||||
integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
|
||||
version "0.7.33"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532"
|
||||
integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==
|
||||
|
||||
unescape@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
|
|
@ -150,6 +150,14 @@
|
|||
</script>
|
||||
<% if (process.env.REACT_APP_DISABLE_TRACKING !== 'true') { %>
|
||||
|
||||
<!-- Fathom - privacy-friendly analytics -->
|
||||
<script
|
||||
src="https://cdn.usefathom.com/script.js"
|
||||
data-site="VMSBUEYA"
|
||||
defer
|
||||
></script>
|
||||
<!-- / Fathom -->
|
||||
|
||||
<!-- LEGACY GOOGLE ANALYTICS -->
|
||||
<% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
|
||||
<script
|
||||
|
@ -166,31 +174,6 @@
|
|||
</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) -->
|
||||
|
@ -244,5 +227,17 @@
|
|||
<h1 class="visually-hidden">Excalidraw</h1>
|
||||
</header>
|
||||
<div id="root"></div>
|
||||
<!-- 100% privacy friendly analytics -->
|
||||
<script
|
||||
async
|
||||
defer
|
||||
src="https://scripts.simpleanalyticscdn.com/latest.js"
|
||||
></script>
|
||||
<noscript
|
||||
><img
|
||||
src="https://queue.simpleanalyticscdn.com/noscript.gif"
|
||||
alt=""
|
||||
referrerpolicy="no-referrer-when-downgrade"
|
||||
/></noscript>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,22 +1,9 @@
|
|||
const fs = require("fs");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const excalidrawDir = `${__dirname}/../src/packages/excalidraw`;
|
||||
const excalidrawPackage = `${excalidrawDir}/package.json`;
|
||||
const pkg = require(excalidrawPackage);
|
||||
|
||||
const originalReadMe = fs.readFileSync(`${excalidrawDir}/README.md`, "utf8");
|
||||
|
||||
const updateReadme = () => {
|
||||
const excalidrawIndex = originalReadMe.indexOf("### Excalidraw");
|
||||
|
||||
// remove note for stable readme
|
||||
const data = originalReadMe.slice(excalidrawIndex);
|
||||
|
||||
// update readme
|
||||
fs.writeFileSync(`${excalidrawDir}/README.md`, data, "utf8");
|
||||
};
|
||||
|
||||
const publish = () => {
|
||||
try {
|
||||
execSync(`yarn --frozen-lockfile`);
|
||||
|
@ -30,15 +17,8 @@ const publish = () => {
|
|||
};
|
||||
|
||||
const release = () => {
|
||||
updateReadme();
|
||||
console.info("Note for stable readme removed");
|
||||
|
||||
publish();
|
||||
console.info(`Published ${pkg.version}!`);
|
||||
|
||||
// revert readme after release
|
||||
fs.writeFileSync(`${excalidrawDir}/README.md`, originalReadMe, "utf8");
|
||||
console.info("Readme reverted");
|
||||
};
|
||||
|
||||
release();
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
import {
|
||||
getOriginalContainerHeightFromCache,
|
||||
resetOriginalContainerCache,
|
||||
updateOriginalContainerCache,
|
||||
} from "../element/textWysiwyg";
|
||||
import {
|
||||
hasBoundTextElement,
|
||||
|
@ -145,7 +146,11 @@ 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),
|
||||
|
@ -191,8 +196,8 @@ const pushContainerBelowText = (
|
|||
return updatedElements;
|
||||
};
|
||||
|
||||
export const actionCreateContainerFromText = register({
|
||||
name: "createContainerFromText",
|
||||
export const actionWrapTextInContainer = register({
|
||||
name: "wrapTextInContainer",
|
||||
contextItemLabel: "labels.createContainerFromText",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState) => {
|
||||
|
@ -281,6 +286,7 @@ export const actionCreateContainerFromText = register({
|
|||
containerId: container.id,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
boundElements: null,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ export const actionCopy = register({
|
|||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = getSelectedElements(elements, appState, true);
|
||||
|
||||
copyToClipboard(selectedElements, appState, app.files);
|
||||
copyToClipboard(selectedElements, app.files);
|
||||
|
||||
return {
|
||||
commitToHistory: false,
|
||||
|
|
|
@ -84,7 +84,7 @@ import {
|
|||
isSomeElementSelected,
|
||||
} from "../scene";
|
||||
import { hasStrokeColor } from "../scene/comparisons";
|
||||
import { arrayToMap } from "../utils";
|
||||
import { arrayToMap, getShortcutKey } from "../utils";
|
||||
import { register } from "./register";
|
||||
|
||||
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
||||
|
@ -314,9 +314,9 @@ export const actionChangeFillStyle = register({
|
|||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const allElementsZigZag = selectedElements.every(
|
||||
(el) => el.fillStyle === "zigzag",
|
||||
);
|
||||
const allElementsZigZag =
|
||||
selectedElements.length > 0 &&
|
||||
selectedElements.every((el) => el.fillStyle === "zigzag");
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
|
@ -326,7 +326,9 @@ export const actionChangeFillStyle = register({
|
|||
options={[
|
||||
{
|
||||
value: "hachure",
|
||||
text: t("labels.hachure"),
|
||||
text: `${
|
||||
allElementsZigZag ? t("labels.zigzag") : t("labels.hachure")
|
||||
} (${getShortcutKey("Alt-Click")})`,
|
||||
icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
|
||||
active: allElementsZigZag ? true : undefined,
|
||||
},
|
||||
|
|
|
@ -115,7 +115,7 @@ export type ActionName =
|
|||
| "toggleLinearEditor"
|
||||
| "toggleEraserTool"
|
||||
| "toggleHandTool"
|
||||
| "createContainerFromText";
|
||||
| "wrapTextInContainer";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
|
|
@ -20,9 +20,20 @@ export const trackEvent = (
|
|||
});
|
||||
}
|
||||
|
||||
// MATOMO event tracking _paq must be same as the one in index.html
|
||||
if (window._paq) {
|
||||
window._paq.push(["trackEvent", category, action, label, value]);
|
||||
if (window.sa_event) {
|
||||
window.sa_event(action, {
|
||||
category,
|
||||
label,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
if (window.fathom) {
|
||||
window.fathom.trackEvent(action, {
|
||||
category,
|
||||
label,
|
||||
value,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("error during analytics", error);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import oc from "open-color";
|
||||
import {
|
||||
DEFAULT_ELEMENT_PROPS,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
|
@ -23,18 +24,18 @@ export const getDefaultAppState = (): Omit<
|
|||
theme: THEME.LIGHT,
|
||||
collaborators: new Map(),
|
||||
currentChartType: "bar",
|
||||
currentItemBackgroundColor: "transparent",
|
||||
currentItemBackgroundColor: DEFAULT_ELEMENT_PROPS.backgroundColor,
|
||||
currentItemEndArrowhead: "arrow",
|
||||
currentItemFillStyle: "hachure",
|
||||
currentItemFillStyle: DEFAULT_ELEMENT_PROPS.fillStyle,
|
||||
currentItemFontFamily: DEFAULT_FONT_FAMILY,
|
||||
currentItemFontSize: DEFAULT_FONT_SIZE,
|
||||
currentItemOpacity: 100,
|
||||
currentItemRoughness: 1,
|
||||
currentItemOpacity: DEFAULT_ELEMENT_PROPS.opacity,
|
||||
currentItemRoughness: DEFAULT_ELEMENT_PROPS.roughness,
|
||||
currentItemStartArrowhead: null,
|
||||
currentItemStrokeColor: oc.black,
|
||||
currentItemStrokeColor: DEFAULT_ELEMENT_PROPS.strokeColor,
|
||||
currentItemRoundness: "round",
|
||||
currentItemStrokeStyle: "solid",
|
||||
currentItemStrokeWidth: 1,
|
||||
currentItemStrokeStyle: DEFAULT_ELEMENT_PROPS.strokeStyle,
|
||||
currentItemStrokeWidth: DEFAULT_ELEMENT_PROPS.strokeWidth,
|
||||
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
|
||||
cursorButton: "up",
|
||||
draggingElement: null,
|
||||
|
@ -44,7 +45,7 @@ export const getDefaultAppState = (): Omit<
|
|||
activeTool: {
|
||||
type: "selection",
|
||||
customType: null,
|
||||
locked: false,
|
||||
locked: DEFAULT_ELEMENT_PROPS.locked,
|
||||
lastActiveTool: null,
|
||||
},
|
||||
penMode: false,
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import colors from "./colors";
|
||||
import {
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
ENV,
|
||||
VERTICAL_ALIGN,
|
||||
} from "./constants";
|
||||
import { DEFAULT_FONT_SIZE, ENV } from "./constants";
|
||||
import { newElement, newLinearElement, newTextElement } from "./element";
|
||||
import { NonDeletedExcalidrawElement } from "./element/types";
|
||||
import { randomId } from "./random";
|
||||
|
@ -166,17 +161,7 @@ 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) => {
|
||||
|
@ -323,7 +308,6 @@ const chartBaseElements = (
|
|||
x: x + chartWidth / 2,
|
||||
y: y - BAR_HEIGHT - BAR_GAP * 2 - DEFAULT_FONT_SIZE,
|
||||
roundness: null,
|
||||
strokeStyle: "solid",
|
||||
textAlign: "center",
|
||||
})
|
||||
: null;
|
||||
|
|
|
@ -2,12 +2,12 @@ import {
|
|||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "./element/types";
|
||||
import { AppState, BinaryFiles } from "./types";
|
||||
import { BinaryFiles } from "./types";
|
||||
import { SVG_EXPORT_TAG } from "./scene/export";
|
||||
import { tryParseSpreadsheet, Spreadsheet, VALID_SPREADSHEET } from "./charts";
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "./constants";
|
||||
import { isInitializedImageElement } from "./element/typeChecks";
|
||||
import { isPromiseLike } from "./utils";
|
||||
import { isPromiseLike, isTestEnv } from "./utils";
|
||||
|
||||
type ElementsClipboard = {
|
||||
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
|
||||
|
@ -55,24 +55,40 @@ const clipboardContainsElements = (
|
|||
|
||||
export const copyToClipboard = async (
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
appState: AppState,
|
||||
files: BinaryFiles | null,
|
||||
) => {
|
||||
let foundFile = false;
|
||||
|
||||
const _files = elements.reduce((acc, element) => {
|
||||
if (isInitializedImageElement(element)) {
|
||||
foundFile = true;
|
||||
if (files && files[element.fileId]) {
|
||||
acc[element.fileId] = files[element.fileId];
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {} as BinaryFiles);
|
||||
|
||||
if (foundFile && !files) {
|
||||
console.warn(
|
||||
"copyToClipboard: attempting to file element(s) without providing associated `files` object.",
|
||||
);
|
||||
}
|
||||
|
||||
// select binded text elements when copying
|
||||
const contents: ElementsClipboard = {
|
||||
type: EXPORT_DATA_TYPES.excalidrawClipboard,
|
||||
elements,
|
||||
files: files
|
||||
? elements.reduce((acc, element) => {
|
||||
if (isInitializedImageElement(element) && files[element.fileId]) {
|
||||
acc[element.fileId] = files[element.fileId];
|
||||
}
|
||||
return acc;
|
||||
}, {} as BinaryFiles)
|
||||
: undefined,
|
||||
files: files ? _files : undefined,
|
||||
};
|
||||
const json = JSON.stringify(contents);
|
||||
|
||||
if (isTestEnv()) {
|
||||
return json;
|
||||
}
|
||||
|
||||
CLIPBOARD = json;
|
||||
|
||||
try {
|
||||
PREFER_APP_CLIPBOARD = false;
|
||||
await copyTextToSystemClipboard(json);
|
||||
|
|
|
@ -60,6 +60,7 @@ import {
|
|||
ENV,
|
||||
EVENT,
|
||||
GRID_SIZE,
|
||||
IMAGE_MIME_TYPES,
|
||||
IMAGE_RENDER_TIMEOUT,
|
||||
isAndroid,
|
||||
isBrave,
|
||||
|
@ -295,7 +296,7 @@ import {
|
|||
} from "../actions/actionCanvas";
|
||||
import { jotaiStore } from "../jotai";
|
||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||
|
||||
const deviceContextInitialValue = {
|
||||
|
@ -1589,6 +1590,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
elements: data.elements,
|
||||
files: data.files || null,
|
||||
position: "cursor",
|
||||
retainSeed: isPlainPaste,
|
||||
});
|
||||
} else if (data.text) {
|
||||
this.addTextFromPaste(data.text, isPlainPaste);
|
||||
|
@ -1602,6 +1604,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
elements: readonly ExcalidrawElement[];
|
||||
files: BinaryFiles | null;
|
||||
position: { clientX: number; clientY: number } | "cursor" | "center";
|
||||
retainSeed?: boolean;
|
||||
}) => {
|
||||
const elements = restoreElements(opts.elements, null);
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||
|
@ -1639,6 +1642,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
y: element.y + gridY - minY,
|
||||
});
|
||||
}),
|
||||
{
|
||||
randomizeSeed: !opts.retainSeed,
|
||||
},
|
||||
);
|
||||
|
||||
const nextElements = [
|
||||
|
@ -2732,7 +2738,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
strokeStyle: this.state.currentItemStrokeStyle,
|
||||
roughness: this.state.currentItemRoughness,
|
||||
opacity: this.state.currentItemOpacity,
|
||||
roundness: null,
|
||||
text: "",
|
||||
fontSize,
|
||||
fontFamily,
|
||||
|
@ -2744,8 +2749,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||
: DEFAULT_VERTICAL_ALIGN,
|
||||
containerId: shouldBindToContainer ? container?.id : undefined,
|
||||
groupIds: container?.groupIds ?? [],
|
||||
locked: false,
|
||||
lineHeight,
|
||||
angle: container?.angle ?? 0,
|
||||
});
|
||||
|
||||
if (!existingTextElement && shouldBindToContainer && container) {
|
||||
|
@ -4721,7 +4726,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||
pointerDownState.drag.hasOccurred = true;
|
||||
// prevent dragging even if we're no longer holding cmd/ctrl otherwise
|
||||
// it would have weird results (stuff jumping all over the screen)
|
||||
if (selectedElements.length > 0 && !pointerDownState.withCmdOrCtrl) {
|
||||
// Checking for editingElement to avoid jump while editing on mobile #6503
|
||||
if (
|
||||
selectedElements.length > 0 &&
|
||||
!pointerDownState.withCmdOrCtrl &&
|
||||
!this.state.editingElement
|
||||
) {
|
||||
const [dragX, dragY] = getGridPoint(
|
||||
pointerCoords.x - pointerDownState.drag.offset.x,
|
||||
pointerCoords.y - pointerDownState.drag.offset.y,
|
||||
|
@ -5744,7 +5754,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
|
||||
const imageFile = await fileOpen({
|
||||
description: "Image",
|
||||
extensions: ["jpg", "png", "svg", "gif"],
|
||||
extensions: Object.keys(
|
||||
IMAGE_MIME_TYPES,
|
||||
) as (keyof typeof IMAGE_MIME_TYPES)[],
|
||||
});
|
||||
|
||||
const imageElement = this.createImageElement({
|
||||
|
@ -6366,7 +6378,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
actionGroup,
|
||||
actionUnbindText,
|
||||
actionBindText,
|
||||
actionCreateContainerFromText,
|
||||
actionWrapTextInContainer,
|
||||
actionUngroup,
|
||||
CONTEXT_MENU_SEPARATOR,
|
||||
actionAddToLibrary,
|
||||
|
|
|
@ -183,6 +183,7 @@
|
|||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
font-family: inherit;
|
||||
background-color: transparent;
|
||||
color: var(--text-primary-color);
|
||||
border: 0;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
background-color: transparent;
|
||||
border: none;
|
||||
white-space: nowrap;
|
||||
font-family: inherit;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 0.2fr;
|
||||
|
|
|
@ -4,7 +4,6 @@ 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";
|
||||
|
@ -15,6 +14,7 @@ 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,7 +83,6 @@ 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
|
||||
|
@ -99,10 +98,16 @@ const ImageExportModal = ({
|
|||
if (!previewNode) {
|
||||
return;
|
||||
}
|
||||
exportToCanvas(exportedElements, appState, files, {
|
||||
exportBackground,
|
||||
viewBackgroundColor,
|
||||
const maxWidth = previewNode.offsetWidth;
|
||||
if (!maxWidth) {
|
||||
return;
|
||||
}
|
||||
exportToCanvas({
|
||||
elements: exportedElements,
|
||||
appState,
|
||||
files,
|
||||
exportPadding,
|
||||
maxWidthOrHeight: maxWidth,
|
||||
})
|
||||
.then((canvas) => {
|
||||
setRenderError(null);
|
||||
|
@ -116,14 +121,7 @@ const ImageExportModal = ({
|
|||
console.error(error);
|
||||
setRenderError(error);
|
||||
});
|
||||
}, [
|
||||
appState,
|
||||
files,
|
||||
exportedElements,
|
||||
exportBackground,
|
||||
exportPadding,
|
||||
viewBackgroundColor,
|
||||
]);
|
||||
}, [appState, files, exportedElements, exportPadding]);
|
||||
|
||||
return (
|
||||
<div className="ExportDialog">
|
||||
|
|
|
@ -102,7 +102,7 @@ const LibraryMenuItems = ({
|
|||
...item,
|
||||
// duplicate each library item before inserting on canvas to confine
|
||||
// ids and bindings to each library item. See #6465
|
||||
elements: duplicateElements(item.elements),
|
||||
elements: duplicateElements(item.elements, { randomizeSeed: true }),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
// container in body where the actual tooltip is appended to
|
||||
.excalidraw-tooltip {
|
||||
--ui-font: Assistant, system-ui, BlinkMacSystemFont, -apple-system, Segoe UI,
|
||||
Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family: var(--ui-font);
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import cssVariables from "./css/variables.module.scss";
|
||||
import { AppProps } from "./types";
|
||||
import { FontFamilyValues } from "./element/types";
|
||||
import { ExcalidrawElement, FontFamilyValues } from "./element/types";
|
||||
import oc from "open-color";
|
||||
|
||||
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
export const isWindows = /^Win/.test(navigator.platform);
|
||||
|
@ -104,20 +105,30 @@ export const CANVAS_ONLY_ACTIONS = ["selectAll"];
|
|||
|
||||
export const GRID_SIZE = 20; // TODO make it configurable?
|
||||
|
||||
export const MIME_TYPES = {
|
||||
excalidraw: "application/vnd.excalidraw+json",
|
||||
excalidrawlib: "application/vnd.excalidrawlib+json",
|
||||
json: "application/json",
|
||||
export const IMAGE_MIME_TYPES = {
|
||||
svg: "image/svg+xml",
|
||||
"excalidraw.svg": "image/svg+xml",
|
||||
png: "image/png",
|
||||
"excalidraw.png": "image/png",
|
||||
jpg: "image/jpeg",
|
||||
gif: "image/gif",
|
||||
webp: "image/webp",
|
||||
bmp: "image/bmp",
|
||||
ico: "image/x-icon",
|
||||
avif: "image/avif",
|
||||
jfif: "image/jfif",
|
||||
} as const;
|
||||
|
||||
export const MIME_TYPES = {
|
||||
json: "application/json",
|
||||
// excalidraw data
|
||||
excalidraw: "application/vnd.excalidraw+json",
|
||||
excalidrawlib: "application/vnd.excalidrawlib+json",
|
||||
// image-encoded excalidraw data
|
||||
"excalidraw.svg": "image/svg+xml",
|
||||
"excalidraw.png": "image/png",
|
||||
// binary
|
||||
binary: "application/octet-stream",
|
||||
// image
|
||||
...IMAGE_MIME_TYPES,
|
||||
} as const;
|
||||
|
||||
export const EXPORT_DATA_TYPES = {
|
||||
|
@ -188,16 +199,6 @@ export const DEFAULT_EXPORT_PADDING = 10; // px
|
|||
|
||||
export const DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT = 1440;
|
||||
|
||||
export const ALLOWED_IMAGE_MIME_TYPES = [
|
||||
MIME_TYPES.png,
|
||||
MIME_TYPES.jpg,
|
||||
MIME_TYPES.svg,
|
||||
MIME_TYPES.gif,
|
||||
MIME_TYPES.webp,
|
||||
MIME_TYPES.bmp,
|
||||
MIME_TYPES.ico,
|
||||
] as const;
|
||||
|
||||
export const MAX_ALLOWED_FILE_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
export const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
@ -254,3 +255,23 @@ 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,
|
||||
};
|
||||
|
|
|
@ -354,6 +354,7 @@
|
|||
border-radius: var(--space-factor);
|
||||
border: 1px solid var(--button-gray-2);
|
||||
font-size: 0.8rem;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
background-image: var(--dropdown-icon);
|
||||
|
@ -413,6 +414,7 @@
|
|||
bottom: 30px;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: all;
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { nanoid } from "nanoid";
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||
import { IMAGE_MIME_TYPES, MIME_TYPES } from "../constants";
|
||||
import { clearElementsForExport } from "../element";
|
||||
import { ExcalidrawElement, FileId } from "../element/types";
|
||||
import { CanvasError } from "../errors";
|
||||
|
@ -117,11 +117,9 @@ export const isImageFileHandle = (handle: FileSystemHandle | null) => {
|
|||
|
||||
export const isSupportedImageFile = (
|
||||
blob: Blob | null | undefined,
|
||||
): blob is Blob & { type: typeof ALLOWED_IMAGE_MIME_TYPES[number] } => {
|
||||
): blob is Blob & { type: ValueOf<typeof IMAGE_MIME_TYPES> } => {
|
||||
const { type } = blob || {};
|
||||
return (
|
||||
!!type && (ALLOWED_IMAGE_MIME_TYPES as readonly string[]).includes(type)
|
||||
);
|
||||
return !!type && (Object.values(IMAGE_MIME_TYPES) as string[]).includes(type);
|
||||
};
|
||||
|
||||
export const loadSceneOrLibraryFromBlob = async (
|
||||
|
@ -157,7 +155,7 @@ export const loadSceneOrLibraryFromBlob = async (
|
|||
},
|
||||
localAppState,
|
||||
localElements,
|
||||
{ repairBindings: true, refreshDimensions: true },
|
||||
{ repairBindings: true, refreshDimensions: false },
|
||||
),
|
||||
};
|
||||
} else if (isValidLibrary(data)) {
|
||||
|
|
|
@ -8,16 +8,7 @@ import { EVENT, MIME_TYPES } from "../constants";
|
|||
import { AbortError } from "../errors";
|
||||
import { debounce } from "../utils";
|
||||
|
||||
type FILE_EXTENSION =
|
||||
| "gif"
|
||||
| "jpg"
|
||||
| "png"
|
||||
| "excalidraw.png"
|
||||
| "svg"
|
||||
| "excalidraw.svg"
|
||||
| "json"
|
||||
| "excalidraw"
|
||||
| "excalidrawlib";
|
||||
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
|
||||
|
||||
const INPUT_CHANGE_INTERVAL_MS = 500;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
isTestEnv,
|
||||
} from "../utils";
|
||||
import { randomInteger, randomId } from "../random";
|
||||
import { mutateElement, newElementWith } from "./mutateElement";
|
||||
import { bumpVersion, mutateElement, newElementWith } from "./mutateElement";
|
||||
import { getNewGroupIdsForDuplication } from "../groups";
|
||||
import { AppState } from "../types";
|
||||
import { getElementAbsoluteCoords } from ".";
|
||||
|
@ -33,10 +33,17 @@ import {
|
|||
measureText,
|
||||
normalizeText,
|
||||
wrapText,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxWidth,
|
||||
getDefaultLineHeight,
|
||||
} from "./textElement";
|
||||
import { VERTICAL_ALIGN } from "../constants";
|
||||
import {
|
||||
DEFAULT_ELEMENT_PROPS,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_TEXT_ALIGN,
|
||||
DEFAULT_VERTICAL_ALIGN,
|
||||
VERTICAL_ALIGN,
|
||||
} from "../constants";
|
||||
import { isArrowElement } from "./typeChecks";
|
||||
import { MarkOptional, Merge, Mutable } from "../utility-types";
|
||||
|
||||
|
@ -51,6 +58,15 @@ type ElementConstructorOpts = MarkOptional<
|
|||
| "version"
|
||||
| "versionNonce"
|
||||
| "link"
|
||||
| "strokeStyle"
|
||||
| "fillStyle"
|
||||
| "strokeColor"
|
||||
| "backgroundColor"
|
||||
| "roughness"
|
||||
| "strokeWidth"
|
||||
| "roundness"
|
||||
| "locked"
|
||||
| "opacity"
|
||||
>;
|
||||
|
||||
const _newElementBase = <T extends ExcalidrawElement>(
|
||||
|
@ -58,13 +74,13 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
|||
{
|
||||
x,
|
||||
y,
|
||||
strokeColor,
|
||||
backgroundColor,
|
||||
fillStyle,
|
||||
strokeWidth,
|
||||
strokeStyle,
|
||||
roughness,
|
||||
opacity,
|
||||
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,
|
||||
width = 0,
|
||||
height = 0,
|
||||
angle = 0,
|
||||
|
@ -72,7 +88,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
|||
roundness = null,
|
||||
boundElements = null,
|
||||
link = null,
|
||||
locked,
|
||||
locked = DEFAULT_ELEMENT_PROPS.locked,
|
||||
...rest
|
||||
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
|
||||
) => {
|
||||
|
@ -138,27 +154,39 @@ 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 lineHeight = opts.lineHeight || getDefaultLineHeight(opts.fontFamily);
|
||||
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
||||
const fontSize = opts.fontSize || DEFAULT_FONT_SIZE;
|
||||
const lineHeight = opts.lineHeight || getDefaultLineHeight(fontFamily);
|
||||
const text = normalizeText(opts.text);
|
||||
const metrics = measureText(text, getFontString(opts), lineHeight);
|
||||
const offsets = getTextElementPositionOffsets(opts, metrics);
|
||||
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 textElement = newElementWith(
|
||||
{
|
||||
..._newElementBase<ExcalidrawTextElement>("text", opts),
|
||||
text,
|
||||
fontSize: opts.fontSize,
|
||||
fontFamily: opts.fontFamily,
|
||||
textAlign: opts.textAlign,
|
||||
verticalAlign: opts.verticalAlign,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
textAlign,
|
||||
verticalAlign,
|
||||
x: opts.x - offsets.x,
|
||||
y: opts.y - offsets.y,
|
||||
width: metrics.width,
|
||||
|
@ -282,7 +310,7 @@ export const refreshTextDimensions = (
|
|||
text = wrapText(
|
||||
text,
|
||||
getFontString(textElement),
|
||||
getMaxContainerWidth(container),
|
||||
getBoundTextMaxWidth(container),
|
||||
);
|
||||
}
|
||||
const dimensions = getAdjustedDimensions(textElement, text);
|
||||
|
@ -511,8 +539,16 @@ export const duplicateElement = <TElement extends ExcalidrawElement>(
|
|||
* 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.
|
||||
*
|
||||
* NOTE by default does not randomize or regenerate anything except the id.
|
||||
*/
|
||||
export const duplicateElements = (elements: readonly ExcalidrawElement[]) => {
|
||||
export const duplicateElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
opts?: {
|
||||
/** NOTE also updates version flags and `updated` */
|
||||
randomizeSeed: boolean;
|
||||
},
|
||||
) => {
|
||||
const clonedElements: ExcalidrawElement[] = [];
|
||||
|
||||
const origElementsMap = arrayToMap(elements);
|
||||
|
@ -546,6 +582,11 @@ export const duplicateElements = (elements: readonly ExcalidrawElement[]) => {
|
|||
|
||||
clonedElement.id = maybeGetNewId(element.id)!;
|
||||
|
||||
if (opts?.randomizeSeed) {
|
||||
clonedElement.seed = randomInteger();
|
||||
bumpVersion(clonedElement);
|
||||
}
|
||||
|
||||
if (clonedElement.groupIds) {
|
||||
clonedElement.groupIds = clonedElement.groupIds.map((groupId) => {
|
||||
if (!groupNewIdsMap.has(groupId)) {
|
||||
|
|
|
@ -46,10 +46,10 @@ import {
|
|||
getBoundTextElementId,
|
||||
getContainerElement,
|
||||
handleBindTextResize,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxWidth,
|
||||
getApproxMinLineHeight,
|
||||
measureText,
|
||||
getMaxContainerHeight,
|
||||
getBoundTextMaxHeight,
|
||||
} from "./textElement";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
|
||||
|
@ -210,7 +210,7 @@ const measureFontSizeFromWidth = (
|
|||
if (hasContainer) {
|
||||
const container = getContainerElement(element);
|
||||
if (container) {
|
||||
width = getMaxContainerWidth(container);
|
||||
width = getBoundTextMaxWidth(container);
|
||||
}
|
||||
}
|
||||
const nextFontSize = element.fontSize * (nextWidth / width);
|
||||
|
@ -441,8 +441,8 @@ export const resizeSingleElement = (
|
|||
|
||||
const nextFont = measureFontSizeFromWidth(
|
||||
boundTextElement,
|
||||
getMaxContainerWidth(updatedElement),
|
||||
getMaxContainerHeight(updatedElement),
|
||||
getBoundTextMaxWidth(updatedElement),
|
||||
getBoundTextMaxHeight(updatedElement, boundTextElement),
|
||||
);
|
||||
if (nextFont === null) {
|
||||
return;
|
||||
|
|
|
@ -3,14 +3,15 @@ import { API } from "../tests/helpers/api";
|
|||
import {
|
||||
computeContainerDimensionForBoundText,
|
||||
getContainerCoords,
|
||||
getMaxContainerWidth,
|
||||
getMaxContainerHeight,
|
||||
getBoundTextMaxWidth,
|
||||
getBoundTextMaxHeight,
|
||||
wrapText,
|
||||
detectLineHeight,
|
||||
getLineHeightInPx,
|
||||
getDefaultLineHeight,
|
||||
parseTokens,
|
||||
} from "./textElement";
|
||||
import { FontString } from "./types";
|
||||
import { ExcalidrawTextElementWithContainer, FontString } from "./types";
|
||||
|
||||
describe("Test wrapText", () => {
|
||||
const font = "20px Cascadia, width: Segoe UI Emoji" as FontString;
|
||||
|
@ -183,6 +184,56 @@ now`,
|
|||
expect(wrapText(text, font, -1)).toEqual(text);
|
||||
expect(wrapText(text, font, Infinity)).toEqual(text);
|
||||
});
|
||||
|
||||
it("should wrap the text correctly when text contains hyphen", () => {
|
||||
let text =
|
||||
"Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects";
|
||||
const res = wrapText(text, font, 110);
|
||||
expect(res).toBe(
|
||||
`Wikipedia \nis hosted \nby \nWikimedia-\nFoundation,\na non-\nprofit \norganizati\non that \nalso hosts\na range-of\nother \nprojects`,
|
||||
);
|
||||
|
||||
text = "Hello thereusing-now";
|
||||
expect(wrapText(text, font, 100)).toEqual("Hello \nthereusin\ng-now");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test parseTokens", () => {
|
||||
it("should split into tokens correctly", () => {
|
||||
let text = "Excalidraw is a virtual collaborative whiteboard";
|
||||
expect(parseTokens(text)).toEqual([
|
||||
"Excalidraw",
|
||||
"is",
|
||||
"a",
|
||||
"virtual",
|
||||
"collaborative",
|
||||
"whiteboard",
|
||||
]);
|
||||
|
||||
text =
|
||||
"Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects";
|
||||
expect(parseTokens(text)).toEqual([
|
||||
"Wikipedia",
|
||||
"is",
|
||||
"hosted",
|
||||
"by",
|
||||
"Wikimedia-",
|
||||
"",
|
||||
"Foundation,",
|
||||
"a",
|
||||
"non-",
|
||||
"profit",
|
||||
"organization",
|
||||
"that",
|
||||
"also",
|
||||
"hosts",
|
||||
"a",
|
||||
"range-",
|
||||
"of",
|
||||
"other",
|
||||
"projects",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test measureText", () => {
|
||||
|
@ -260,7 +311,7 @@ describe("Test measureText", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("Test getMaxContainerWidth", () => {
|
||||
describe("Test getBoundTextMaxWidth", () => {
|
||||
const params = {
|
||||
width: 178,
|
||||
height: 194,
|
||||
|
@ -268,39 +319,76 @@ 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);
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -65,7 +65,7 @@ export const redrawTextBoundingBox = (
|
|||
boundTextUpdates.text = textElement.text;
|
||||
|
||||
if (container) {
|
||||
maxWidth = getMaxContainerWidth(container);
|
||||
maxWidth = getBoundTextMaxWidth(container);
|
||||
boundTextUpdates.text = wrapText(
|
||||
textElement.originalText,
|
||||
getFontString(textElement),
|
||||
|
@ -83,35 +83,28 @@ export const redrawTextBoundingBox = (
|
|||
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;
|
||||
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;
|
||||
let nextHeight = containerDims.height;
|
||||
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,8 +176,11 @@ 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") {
|
||||
|
@ -256,8 +252,8 @@ export const computeBoundTextPosition = (
|
|||
);
|
||||
}
|
||||
const containerCoords = getContainerCoords(container);
|
||||
const maxContainerHeight = getMaxContainerHeight(container);
|
||||
const maxContainerWidth = getMaxContainerWidth(container);
|
||||
const maxContainerHeight = getBoundTextMaxHeight(container, boundTextElement);
|
||||
const maxContainerWidth = getBoundTextMaxWidth(container);
|
||||
|
||||
let x;
|
||||
let y;
|
||||
|
@ -419,6 +415,24 @@ export const getTextHeight = (
|
|||
return getLineHeightInPx(fontSize, lineHeight) * lineCount;
|
||||
};
|
||||
|
||||
export const parseTokens = (text: string) => {
|
||||
// Splitting words containing "-" as those are treated as separate words
|
||||
// by css wrapping algorithm eg non-profit => non-, profit
|
||||
const words = text.split("-");
|
||||
if (words.length > 1) {
|
||||
// non-proft org => ['non-', 'profit org']
|
||||
words.forEach((word, index) => {
|
||||
if (index !== words.length - 1) {
|
||||
words[index] = word += "-";
|
||||
}
|
||||
});
|
||||
}
|
||||
// Joining the words with space and splitting them again with space to get the
|
||||
// final list of tokens
|
||||
// ['non-', 'profit org'] =>,'non- proft org' => ['non-','profit','org']
|
||||
return words.join(" ").split(" ");
|
||||
};
|
||||
|
||||
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
|
||||
|
@ -444,17 +458,16 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
|||
currentLine = "";
|
||||
currentLineWidthTillNow = 0;
|
||||
};
|
||||
|
||||
originalLines.forEach((originalLine) => {
|
||||
const currentLineWidth = getTextWidth(originalLine, font);
|
||||
|
||||
//Push the line if its <= maxWidth
|
||||
// Push the line if its <= maxWidth
|
||||
if (currentLineWidth <= maxWidth) {
|
||||
lines.push(originalLine);
|
||||
return; // continue
|
||||
}
|
||||
const words = originalLine.split(" ");
|
||||
|
||||
const words = parseTokens(originalLine);
|
||||
resetParams();
|
||||
|
||||
let index = 0;
|
||||
|
@ -472,6 +485,7 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
|||
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();
|
||||
|
@ -492,15 +506,15 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
|||
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
|
||||
// to previous line unless the line ends with hyphen to sync
|
||||
// with css word-wrap
|
||||
} else if (!currentLine.endsWith("-")) {
|
||||
currentLine += " ";
|
||||
currentLineWidthTillNow += spaceWidth;
|
||||
}
|
||||
|
@ -518,12 +532,23 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
|||
break;
|
||||
}
|
||||
index++;
|
||||
currentLine += `${word} `;
|
||||
|
||||
// if word ends with "-" then we don't need to add space
|
||||
// to sync with css word-wrap
|
||||
const shouldAppendSpace = !word.endsWith("-");
|
||||
currentLine += word;
|
||||
|
||||
if (shouldAppendSpace) {
|
||||
currentLine += " ";
|
||||
}
|
||||
|
||||
// Push the word if appending space exceeds max width
|
||||
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
|
||||
const word = currentLine.slice(0, -1);
|
||||
push(word);
|
||||
if (shouldAppendSpace) {
|
||||
lines.push(currentLine.slice(0, -1));
|
||||
} else {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
resetParams();
|
||||
break;
|
||||
}
|
||||
|
@ -861,18 +886,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 +906,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;
|
||||
}
|
||||
|
|
|
@ -526,6 +526,36 @@ describe("textWysiwyg", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("should set the text element angle to same as container angle when binding to rotated container", async () => {
|
||||
const rectangle = API.createElement({
|
||||
type: "rectangle",
|
||||
width: 90,
|
||||
height: 75,
|
||||
angle: 45,
|
||||
});
|
||||
h.elements = [rectangle];
|
||||
mouse.doubleClickAt(rectangle.x + 10, rectangle.y + 10);
|
||||
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||
expect(text.type).toBe("text");
|
||||
expect(text.containerId).toBe(rectangle.id);
|
||||
expect(rectangle.boundElements).toStrictEqual([
|
||||
{ id: text.id, type: "text" },
|
||||
]);
|
||||
expect(text.angle).toBe(rectangle.angle);
|
||||
mouse.down();
|
||||
const editor = document.querySelector(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
fireEvent.change(editor, { target: { value: "Hello World!" } });
|
||||
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
editor.blur();
|
||||
expect(rectangle.boundElements).toStrictEqual([
|
||||
{ id: text.id, type: "text" },
|
||||
]);
|
||||
});
|
||||
|
||||
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",
|
||||
|
@ -1506,7 +1536,7 @@ describe("textWysiwyg", () => {
|
|||
expect.objectContaining({
|
||||
text: "Excalidraw is an opensource virtual collaborative whiteboard",
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
textAlign: TEXT_ALIGN.LEFT,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
boundElements: null,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -32,8 +32,8 @@ import {
|
|||
normalizeText,
|
||||
redrawTextBoundingBox,
|
||||
wrapText,
|
||||
getMaxContainerHeight,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxHeight,
|
||||
getBoundTextMaxWidth,
|
||||
computeContainerDimensionForBoundText,
|
||||
detectLineHeight,
|
||||
} from "./textElement";
|
||||
|
@ -205,8 +205,11 @@ 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) {
|
||||
|
@ -377,7 +380,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 +397,7 @@ export const textWysiwyg = ({
|
|||
const wrappedText = wrapText(
|
||||
normalizeText(editable.value),
|
||||
font,
|
||||
getMaxContainerWidth(container!),
|
||||
getBoundTextMaxWidth(container!),
|
||||
);
|
||||
const { width, height } = measureText(
|
||||
wrappedText,
|
||||
|
|
|
@ -65,7 +65,7 @@ export const reconcileElements = (
|
|||
|
||||
// Mark duplicate for removal as it'll be replaced with the remote element
|
||||
if (local) {
|
||||
// Unless the ramote and local elements are the same element in which case
|
||||
// Unless the remote and local elements are the same element in which case
|
||||
// we need to keep it as we'd otherwise discard it from the resulting
|
||||
// array.
|
||||
if (local[0] === remoteElement) {
|
||||
|
|
|
@ -263,7 +263,7 @@ export const loadScene = async (
|
|||
await importFromBackend(id, privateKey),
|
||||
localDataState?.appState,
|
||||
localDataState?.elements,
|
||||
{ repairBindings: true, refreshDimensions: true },
|
||||
{ repairBindings: true, refreshDimensions: false },
|
||||
);
|
||||
} else {
|
||||
data = restore(localDataState || null, null, null, {
|
||||
|
|
4
src/global.d.ts
vendored
4
src/global.d.ts
vendored
|
@ -18,8 +18,8 @@ interface Window {
|
|||
EXCALIDRAW_EXPORT_SOURCE: string;
|
||||
EXCALIDRAW_THROTTLE_RENDER: boolean | undefined;
|
||||
gtag: Function;
|
||||
_paq: any[];
|
||||
_mtm: any[];
|
||||
sa_event: Function;
|
||||
fathom: { trackEvent: Function };
|
||||
}
|
||||
|
||||
interface CanvasRenderingContext2D {
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "كبير جدا",
|
||||
"solid": "كامل",
|
||||
"hachure": "خطوط",
|
||||
"zigzag": "",
|
||||
"crossHatch": "خطوط متقطعة",
|
||||
"thin": "نحيف",
|
||||
"bold": "داكن",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Много голям",
|
||||
"solid": "Солиден",
|
||||
"hachure": "Хералдика",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Двойно-пресечено",
|
||||
"thin": "Тънък",
|
||||
"bold": "Ясно очертан",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "অনেক বড়",
|
||||
"solid": "দৃঢ়",
|
||||
"hachure": "ভ্রুলেখা",
|
||||
"zigzag": "",
|
||||
"crossHatch": "ক্রস হ্যাচ",
|
||||
"thin": "পাতলা",
|
||||
"bold": "পুরু",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Molt gran",
|
||||
"solid": "Sòlid",
|
||||
"hachure": "Ratlletes",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Ratlletes creuades",
|
||||
"thin": "Fi",
|
||||
"bold": "Negreta",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Velmi velké",
|
||||
"solid": "Plný",
|
||||
"hachure": "",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "Tenký",
|
||||
"bold": "Tlustý",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Meget stor",
|
||||
"solid": "Solid",
|
||||
"hachure": "Skravering",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Krydsskravering",
|
||||
"thin": "Tynd",
|
||||
"bold": "Fed",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Sehr groß",
|
||||
"solid": "Deckend",
|
||||
"hachure": "Schraffiert",
|
||||
"zigzag": "Zickzack",
|
||||
"crossHatch": "Kreuzschraffiert",
|
||||
"thin": "Dünn",
|
||||
"bold": "Fett",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Πολύ μεγάλο",
|
||||
"solid": "Συμπαγής",
|
||||
"hachure": "Εκκόλαψη",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Διασταυρούμενη εκκόλαψη",
|
||||
"thin": "Λεπτή",
|
||||
"bold": "Έντονη",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Very large",
|
||||
"solid": "Solid",
|
||||
"hachure": "Hachure",
|
||||
"zigzag": "Zigzag",
|
||||
"crossHatch": "Cross-hatch",
|
||||
"thin": "Thin",
|
||||
"bold": "Bold",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Muy grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Folleto",
|
||||
"zigzag": "Zigzag",
|
||||
"crossHatch": "Rayado transversal",
|
||||
"thin": "Fino",
|
||||
"bold": "Grueso",
|
||||
|
@ -207,8 +208,8 @@
|
|||
"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": "",
|
||||
"start": "Parece que estás usando el navegador Brave",
|
||||
"aggressive_block_fingerprint": "Bloquear huellas dactilares agresivamente",
|
||||
"setting_enabled": "ajuste activado",
|
||||
"break": "Esto podría resultar en romper los",
|
||||
"text_elements": "Elementos de texto",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "doble clic",
|
||||
"drag": "arrastrar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Editar puntos de línea/flecha",
|
||||
"editText": "Editar texto / añadir etiqueta",
|
||||
"github": "¿Ha encontrado un problema? Envíelo",
|
||||
"howto": "Siga nuestras guías",
|
||||
"or": "o",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Oso handia",
|
||||
"solid": "Solidoa",
|
||||
"hachure": "Itzalduna",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Marraduna",
|
||||
"thin": "Mehea",
|
||||
"bold": "Lodia",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "بسیار بزرگ",
|
||||
"solid": "توپر",
|
||||
"hachure": "هاشور",
|
||||
"zigzag": "",
|
||||
"crossHatch": "هاشور متقاطع",
|
||||
"thin": "نازک",
|
||||
"bold": "ضخیم",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Erittäin suuri",
|
||||
"solid": "Yhtenäinen",
|
||||
"hachure": "Vinoviivoitus",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Ristiviivoitus",
|
||||
"thin": "Ohut",
|
||||
"bold": "Lihavoitu",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Très grande",
|
||||
"solid": "Solide",
|
||||
"hachure": "Hachures",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Hachures croisées",
|
||||
"thin": "Fine",
|
||||
"bold": "Épaisse",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "double-clic",
|
||||
"drag": "glisser",
|
||||
"editor": "Éditeur",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Modifier les points de ligne/flèche",
|
||||
"editText": "Modifier le texte / ajouter un libellé",
|
||||
"github": "Problème trouvé ? Soumettre",
|
||||
"howto": "Suivez nos guides",
|
||||
"or": "ou",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Moi grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Folleto",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Raiado transversal",
|
||||
"thin": "Estreito",
|
||||
"bold": "Groso",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "גדול מאוד",
|
||||
"solid": "מוצק",
|
||||
"hachure": "קווים מקבילים קצרים להצגת כיוון וחדות שיפוע במפה",
|
||||
"zigzag": "",
|
||||
"crossHatch": "קווים מוצלבים שתי וערב",
|
||||
"thin": "דק",
|
||||
"bold": "מודגש",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "बहुत बड़ा",
|
||||
"solid": "दृढ़",
|
||||
"hachure": "हैशूर",
|
||||
"zigzag": "तेढ़ी मेढ़ी",
|
||||
"crossHatch": "क्रॉस हैच",
|
||||
"thin": "पतला",
|
||||
"bold": "मोटा",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Nagyon nagy",
|
||||
"solid": "Kitöltött",
|
||||
"hachure": "Vonalkázott",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Keresztcsíkozott",
|
||||
"thin": "Vékony",
|
||||
"bold": "Félkövér",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Sangat besar",
|
||||
"solid": "Padat",
|
||||
"hachure": "Garis-garis",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Asiran silang",
|
||||
"thin": "Lembut",
|
||||
"bold": "Tebal",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Molto grande",
|
||||
"solid": "Pieno",
|
||||
"hachure": "Tratteggio obliquo",
|
||||
"zigzag": "Zig zag",
|
||||
"crossHatch": "Tratteggio incrociato",
|
||||
"thin": "Sottile",
|
||||
"bold": "Grassetto",
|
||||
|
@ -319,7 +320,7 @@
|
|||
"doubleClick": "doppio-click",
|
||||
"drag": "trascina",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editLineArrowPoints": "Modifica punti linea/freccia",
|
||||
"editText": "Modifica testo / aggiungi etichetta",
|
||||
"github": "Trovato un problema? Segnalalo",
|
||||
"howto": "Segui le nostre guide",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "特大",
|
||||
"solid": "ベタ塗り",
|
||||
"hachure": "斜線",
|
||||
"zigzag": "",
|
||||
"crossHatch": "網掛け",
|
||||
"thin": "細",
|
||||
"bold": "太字",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Meqqer aṭas",
|
||||
"solid": "Aččuran",
|
||||
"hachure": "Azerreg",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Azerreg anmidag",
|
||||
"thin": "Arqaq",
|
||||
"bold": "Azuran",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Өте үлкен",
|
||||
"solid": "",
|
||||
"hachure": "",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "",
|
||||
"bold": "",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "매우 크게",
|
||||
"solid": "단색",
|
||||
"hachure": "평행선",
|
||||
"zigzag": "지그재그",
|
||||
"crossHatch": "교차선",
|
||||
"thin": "얇게",
|
||||
"bold": "굵게",
|
||||
|
@ -256,7 +257,7 @@
|
|||
"resize": "SHIFT 키를 누르면서 조정하면 크기의 비율이 제한됩니다.\nALT를 누르면서 조정하면 중앙을 기준으로 크기를 조정합니다.",
|
||||
"resizeImage": "SHIFT를 눌러서 자유롭게 크기를 변경하거나,\nALT를 눌러서 중앙을 고정하고 크기를 변경하기",
|
||||
"rotate": "SHIFT 키를 누르면서 회전하면 각도를 제한할 수 있습니다.",
|
||||
"lineEditor_info": "포인트를 편집하려면 Ctrl/Cmd을 누르고 더블 클릭을 하거나 Ctrl/Cmd + Enter를 누르세요",
|
||||
"lineEditor_info": "꼭짓점을 수정하려면 CtrlOrCmd 키를 누르고 더블 클릭을 하거나 CtrlOrCmd + Enter를 누르세요.",
|
||||
"lineEditor_pointSelected": "Delete 키로 꼭짓점을 제거하거나,\nCtrlOrCmd+D 로 복제하거나, 드래그 해서 이동시키기",
|
||||
"lineEditor_nothingSelected": "꼭짓점을 선택해서 수정하거나 (SHIFT를 눌러서 여러개 선택),\nAlt를 누르고 클릭해서 새로운 꼭짓점 추가하기",
|
||||
"placeImage": "클릭해서 이미지를 배치하거나, 클릭하고 드래그해서 사이즈를 조정하기",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "더블 클릭",
|
||||
"drag": "드래그",
|
||||
"editor": "에디터",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "직선 / 화살표 꼭짓점 수정",
|
||||
"editText": "텍스트 수정 / 라벨 추가",
|
||||
"github": "문제 제보하기",
|
||||
"howto": "가이드 참고하기",
|
||||
"or": "또는",
|
||||
|
@ -382,8 +383,8 @@
|
|||
},
|
||||
"publishSuccessDialog": {
|
||||
"title": "라이브러리 제출됨",
|
||||
"content": "{{authorName}}님 감사합니다. 당신의 라이브러리가 심사를 위해 제출되었습니다. 진행 상황을 다음의 링크에서 확인할 수 있습니다.",
|
||||
"link": "여기"
|
||||
"content": "{{authorName}}님 감사합니다. 당신의 라이브러리가 심사를 위해 제출되었습니다. 진행 상황을",
|
||||
"link": "여기에서 확인하실 수 있습니다."
|
||||
},
|
||||
"confirmDialog": {
|
||||
"resetLibrary": "라이브러리 리셋",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"labels": {
|
||||
"paste": "دانانەوە",
|
||||
"pasteAsPlaintext": "",
|
||||
"pasteAsPlaintext": "دایبنێ وەک دەقی سادە",
|
||||
"pasteCharts": "دانانەوەی خشتەکان",
|
||||
"selectAll": "دیاریکردنی هەموو",
|
||||
"multiSelect": "زیادکردنی بۆ دیاریکراوەکان",
|
||||
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "زۆر گهوره",
|
||||
"solid": "سادە",
|
||||
"hachure": "هاچور",
|
||||
"zigzag": "زیگزاگ",
|
||||
"crossHatch": "کرۆس هاتچ",
|
||||
"thin": "تەنک",
|
||||
"bold": "تۆخ",
|
||||
|
@ -110,7 +111,7 @@
|
|||
"increaseFontSize": "زایدکردنی قەبارەی فۆنت",
|
||||
"unbindText": "دەقەکە جیابکەرەوە",
|
||||
"bindText": "دەقەکە ببەستەوە بە کۆنتەینەرەکەوە",
|
||||
"createContainerFromText": "",
|
||||
"createContainerFromText": "دەق لە چوارچێوەیەکدا بپێچە",
|
||||
"link": {
|
||||
"edit": "دەستکاریکردنی بەستەر",
|
||||
"create": "دروستکردنی بەستەر",
|
||||
|
@ -194,7 +195,7 @@
|
|||
"resetLibrary": "ئەمە کتێبخانەکەت خاوێن دەکاتەوە. ئایا دڵنیایت?",
|
||||
"removeItemsFromsLibrary": "سڕینەوەی {{count}} ئایتم(ەکان) لە کتێبخانە؟",
|
||||
"invalidEncryptionKey": "کلیلی رەمزاندن دەبێت لە 22 پیت بێت. هاوکاری ڕاستەوخۆ لە کارخراوە.",
|
||||
"collabOfflineWarning": ""
|
||||
"collabOfflineWarning": "هێڵی ئینتەرنێت بەردەست نییە.\n گۆڕانکارییەکانت سەیڤ ناکرێن!"
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "جۆری فایلی پشتگیری نەکراو.",
|
||||
|
@ -204,22 +205,22 @@
|
|||
"invalidSVGString": "ئێس ڤی جی نادروستە.",
|
||||
"cannotResolveCollabServer": "ناتوانێت پەیوەندی بکات بە سێرڤەری کۆلاب. تکایە لاپەڕەکە دووبارە باربکەوە و دووبارە هەوڵ بدەوە.",
|
||||
"importLibraryError": "نەیتوانی کتێبخانە بار بکات",
|
||||
"collabSaveFailed": "",
|
||||
"collabSaveFailed_sizeExceeded": "",
|
||||
"collabSaveFailed": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت. ئەگەر کێشەکان بەردەوام بوون، پێویستە فایلەکەت لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
||||
"collabSaveFailed_sizeExceeded": "نەتوانرا لە بنکەدراوەی ڕاژەدا پاشەکەوت بکرێت، پێدەچێت تابلۆکە زۆر گەورە بێت. پێویستە فایلەکە لە ناوخۆدا هەڵبگریت بۆ ئەوەی دڵنیا بیت کە کارەکانت لەدەست نادەیت.",
|
||||
"brave_measure_text_error": {
|
||||
"start": "",
|
||||
"aggressive_block_fingerprint": "",
|
||||
"setting_enabled": "",
|
||||
"break": "",
|
||||
"text_elements": "",
|
||||
"in_your_drawings": "",
|
||||
"strongly_recommend": "",
|
||||
"steps": "",
|
||||
"how": "",
|
||||
"disable_setting": "",
|
||||
"issue": "",
|
||||
"write": "",
|
||||
"discord": ""
|
||||
"start": "پێدەچێت وێبگەڕی Brave بەکاربهێنیت لەگەڵ",
|
||||
"aggressive_block_fingerprint": "بلۆککردنی Fingerprinting بەشێوەیەکی توندوتیژانە",
|
||||
"setting_enabled": "ڕێکخستن چالاک کراوە",
|
||||
"break": "ئەمە دەکرێت ببێتە هۆی تێکدانی",
|
||||
"text_elements": "دانە دەقییەکان",
|
||||
"in_your_drawings": "لە وێنەکێشانەکانتدا",
|
||||
"strongly_recommend": "بە توندی پێشنیار دەکەین ئەم ڕێکخستنە لەکاربخەیت. دەتوانیت بڕۆیت بە دوای",
|
||||
"steps": "ئەم هەنگاوانەدا",
|
||||
"how": "بۆ ئەوەی ئەنجامی بدەیت",
|
||||
"disable_setting": " ئەگەر لەکارخستنی ئەم ڕێکخستنە پیشاندانی توخمەکانی دەق چاک نەکاتەوە، تکایە هەڵبستە بە کردنەوەی",
|
||||
"issue": "کێشەیەک",
|
||||
"write": "لەسەر گیتهەبەکەمان، یان بۆمان بنوسە لە",
|
||||
"discord": "دیسکۆرد"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -237,7 +238,7 @@
|
|||
"penMode": "شێوازی قەڵەم - دەست لێدان ڕابگرە",
|
||||
"link": "زیادکردن/ نوێکردنەوەی لینک بۆ شێوەی دیاریکراو",
|
||||
"eraser": "سڕەر",
|
||||
"hand": ""
|
||||
"hand": "دەست (ئامرازی پانکردن)"
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "کردارەکانی تابلۆ",
|
||||
|
@ -245,7 +246,7 @@
|
|||
"shapes": "شێوەکان"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "",
|
||||
"canvasPanning": "بۆ جوڵاندنی تابلۆ، ویلی ماوسەکەت یان دوگمەی سپەیس بگرە لەکاتی ڕاکێشاندە، یانیش ئامرازی دەستەکە بەکاربهێنە",
|
||||
"linearElement": "کرتە بکە بۆ دەستپێکردنی چەند خاڵێک، ڕایبکێشە بۆ یەک هێڵ",
|
||||
"freeDraw": "کرتە بکە و ڕایبکێشە، کاتێک تەواو بوویت دەست هەڵگرە",
|
||||
"text": "زانیاری: هەروەها دەتوانیت دەق زیادبکەیت بە دوو کرتەکردن لە هەر شوێنێک لەگەڵ ئامڕازی دەستنیشانکردن",
|
||||
|
@ -256,7 +257,7 @@
|
|||
"resize": "دەتوانیت ڕێژەکان سنووردار بکەیت بە ڕاگرتنی SHIFT لەکاتی گۆڕینی قەبارەدا،\nALT ڕابگرە بۆ گۆڕینی قەبارە لە ناوەندەوە",
|
||||
"resizeImage": "دەتوانیت بە ئازادی قەبارە بگۆڕیت بە ڕاگرتنی SHIFT،\nALT ڕابگرە بۆ گۆڕینی قەبارە لە ناوەندەوە",
|
||||
"rotate": "دەتوانیت گۆشەکان سنووردار بکەیت بە ڕاگرتنی SHIFT لەکاتی سوڕانەوەدا",
|
||||
"lineEditor_info": "",
|
||||
"lineEditor_info": "یان Ctrl یان Cmd بگرە و دوانە کلیک بکە یانیش پەنجە بنێ بە Ctrl یان Cmd + ئینتەر بۆ دەستکاریکردنی خاڵەکان",
|
||||
"lineEditor_pointSelected": "بۆ لابردنی خاڵەکان Delete دابگرە، Ctrl Cmd+D بکە بۆ لەبەرگرتنەوە، یان بۆ جووڵە ڕاکێشان بکە",
|
||||
"lineEditor_nothingSelected": "خاڵێک هەڵبژێرە بۆ دەستکاریکردن (SHIFT ڕابگرە بۆ هەڵبژاردنی چەندین)،\nیان Alt ڕابگرە و کلیک بکە بۆ زیادکردنی خاڵە نوێیەکان",
|
||||
"placeImage": "کلیک بکە بۆ دانانی وێنەکە، یان کلیک بکە و ڕایبکێشە بۆ ئەوەی قەبارەکەی بە دەستی دابنێیت",
|
||||
|
@ -264,7 +265,7 @@
|
|||
"bindTextToElement": "بۆ زیادکردنی دەق enter بکە",
|
||||
"deepBoxSelect": "CtrlOrCmd ڕابگرە بۆ هەڵبژاردنی قووڵ، و بۆ ڕێگریکردن لە ڕاکێشان",
|
||||
"eraserRevert": "بۆ گەڕاندنەوەی ئەو توخمانەی کە بۆ سڕینەوە نیشانە کراون، Alt ڕابگرە",
|
||||
"firefox_clipboard_write": ""
|
||||
"firefox_clipboard_write": "ئەم تایبەتمەندییە بە ئەگەرێکی زۆرەوە دەتوانرێت چالاک بکرێت بە ڕێکخستنی ئاڵای \"dom.events.asyncClipboard.clipboardItem\" بۆ \"true\". بۆ گۆڕینی ئاڵاکانی وێبگەڕ لە فایەرفۆکسدا، سەردانی لاپەڕەی \"about:config\" بکە."
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "ناتوانرێ پێشبینین پیشان بدرێت",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "دوو گرتە",
|
||||
"drag": "راکێشان",
|
||||
"editor": "دەستکاریکەر",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "دەستکاری خاڵەکانی هێڵ/تیر بکە",
|
||||
"editText": "دەستکاری دەق بکە / لەیبڵێک زیاد بکە",
|
||||
"github": "کێشەیەکت دۆزیەوە؟ پێشکەشکردن",
|
||||
"howto": "شوێن ڕینماییەکانمان بکەوە",
|
||||
"or": "یان",
|
||||
|
@ -334,8 +335,8 @@
|
|||
"zoomToFit": "زووم بکە بۆ ئەوەی لەگەڵ هەموو توخمەکاندا بگونجێت",
|
||||
"zoomToSelection": "زووم بکە بۆ دەستنیشانکراوەکان",
|
||||
"toggleElementLock": "قفڵ/کردنەوەی دەستنیشانکراوەکان",
|
||||
"movePageUpDown": "",
|
||||
"movePageLeftRight": ""
|
||||
"movePageUpDown": "لاپەڕەکە بجوڵێنە بۆ سەرەوە/خوارەوە",
|
||||
"movePageLeftRight": "لاپەڕەکە بجوڵێنە بۆ چەپ/ڕاست"
|
||||
},
|
||||
"clearCanvasDialog": {
|
||||
"title": "تابلۆکە خاوێن بکەرەوە"
|
||||
|
@ -417,7 +418,7 @@
|
|||
"fileSavedToFilename": "هەڵگیراوە بۆ {filename}",
|
||||
"canvas": "تابلۆ",
|
||||
"selection": "دەستنیشانکراوەکان",
|
||||
"pasteAsSingleElement": ""
|
||||
"pasteAsSingleElement": "بۆ دانانەوە وەکو یەک توخم یان دانانەوە بۆ نێو دەسکاریکەرێکی دەق کە بوونی هەیە {{shortcut}} بەکاربهێنە"
|
||||
},
|
||||
"colors": {
|
||||
"ffffff": "سپی",
|
||||
|
@ -468,15 +469,15 @@
|
|||
},
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
"center_heading": "",
|
||||
"center_heading_plus": "",
|
||||
"menuHint": ""
|
||||
"center_heading": "هەموو داتاکانت لە ناوخۆی وێنگەڕەکەتدا پاشەکەوت کراوە.",
|
||||
"center_heading_plus": "ویستت بڕۆیت بۆ Excalidraw+?",
|
||||
"menuHint": "هەناردەکردن، ڕێکخستنەکان، زمانەکان، ..."
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
"menuHint": "هەناردەکردن، ڕێکخستنەکان، و زیاتر...",
|
||||
"center_heading": "دایاگرامەکان. ئاسان. کراون.",
|
||||
"toolbarHint": "ئامرازێک هەڵبگرە و دەستبکە بە کێشان!",
|
||||
"helpHint": "قەدبڕەکان و یارمەتی"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Labai didelis",
|
||||
"solid": "",
|
||||
"hachure": "",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "Plonas",
|
||||
"bold": "Pastorintas",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Ļoti liels",
|
||||
"solid": "Pilns",
|
||||
"hachure": "Svītrots",
|
||||
"zigzag": "Zigzaglīnija",
|
||||
"crossHatch": "Šķērssvītrots",
|
||||
"thin": "Šaurs",
|
||||
"bold": "Trekns",
|
||||
|
@ -110,7 +111,7 @@
|
|||
"increaseFontSize": "Palielināt fonta izmēru",
|
||||
"unbindText": "Atdalīt tekstu",
|
||||
"bindText": "Piesaistīt tekstu figūrai",
|
||||
"createContainerFromText": "",
|
||||
"createContainerFromText": "Ietilpināt tekstu figurā",
|
||||
"link": {
|
||||
"edit": "Rediģēt saiti",
|
||||
"create": "Izveidot saiti",
|
||||
|
@ -194,7 +195,7 @@
|
|||
"resetLibrary": "Šī funkcija iztukšos bibliotēku. Vai turpināt?",
|
||||
"removeItemsFromsLibrary": "Vai izņemt {{count}} vienumu(s) no bibliotēkas?",
|
||||
"invalidEncryptionKey": "Šifrēšanas atslēgai jābūt 22 simbolus garai. Tiešsaistes sadarbība ir izslēgta.",
|
||||
"collabOfflineWarning": ""
|
||||
"collabOfflineWarning": "Nav pieejams interneta pieslēgums.\nJūsu izmaiņas netiks saglabātas!"
|
||||
},
|
||||
"errors": {
|
||||
"unsupportedFileType": "Neatbalstīts datnes veids.",
|
||||
|
@ -207,19 +208,19 @@
|
|||
"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": ""
|
||||
"start": "Izskatās, ka izmanto Brave interneta plārlūku ar ieslēgtu",
|
||||
"aggressive_block_fingerprint": "Aggressively Block Fingerprinting",
|
||||
"setting_enabled": "ieslēgtu iestatījumu",
|
||||
"break": "Tas var salauzt",
|
||||
"text_elements": "Teksta elementus",
|
||||
"in_your_drawings": "tavos zīmējumos",
|
||||
"strongly_recommend": "Mēs iesakām izslēgt šo iestatījumu. Tu vari sekot",
|
||||
"steps": "šiem soļiem",
|
||||
"how": "kā to izdarīt",
|
||||
"disable_setting": " Ja šī iestatījuma izslēgšana neatrisina teksta elementu attēlošanu, tad, lūdzu, atver",
|
||||
"issue": "problēmu",
|
||||
"write": "mūsu GitHub vai raksti mums",
|
||||
"discord": "Discord"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -237,7 +238,7 @@
|
|||
"penMode": "Pildspalvas režīms – novērst pieskaršanos",
|
||||
"link": "Pievienot/rediģēt atlasītās figūras saiti",
|
||||
"eraser": "Dzēšgumija",
|
||||
"hand": ""
|
||||
"hand": "Roka (panoramēšanas rīks)"
|
||||
},
|
||||
"headings": {
|
||||
"canvasActions": "Tāfeles darbības",
|
||||
|
@ -245,7 +246,7 @@
|
|||
"shapes": "Formas"
|
||||
},
|
||||
"hints": {
|
||||
"canvasPanning": "",
|
||||
"canvasPanning": "Lai bīdītu tāfeli, turiet nospiestu ritināšanas vai atstarpes taustiņu, vai izmanto rokas rīku",
|
||||
"linearElement": "Klikšķiniet, lai sāktu zīmēt vairākus punktus; velciet, lai zīmētu līniju",
|
||||
"freeDraw": "Spiediet un velciet; atlaidiet, kad pabeidzat",
|
||||
"text": "Ieteikums: lai pievienotu tekstu, varat arī jebkur dubultklikšķināt ar atlases rīku",
|
||||
|
@ -264,7 +265,7 @@
|
|||
"bindTextToElement": "Spiediet ievades taustiņu, lai pievienotu tekstu",
|
||||
"deepBoxSelect": "Turient nospiestu Ctrl vai Cmd, lai atlasītu dziļumā un lai nepieļautu objektu pavilkšanu",
|
||||
"eraserRevert": "Turiet Alt, lai noņemtu elementus no dzēsšanas atlases",
|
||||
"firefox_clipboard_write": ""
|
||||
"firefox_clipboard_write": "Šis iestatījums var tikt ieslēgts ar \"dom.events.asyncClipboard.clipboardItem\" marķieri pārslēgtu uz \"true\". Lai mainītu pārlūka marķierus Firefox, apmeklē \"about:config\" lapu."
|
||||
},
|
||||
"canvasError": {
|
||||
"cannotShowPreview": "Nevar rādīt priekšskatījumu",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "dubultklikšķis",
|
||||
"drag": "vilkt",
|
||||
"editor": "Redaktors",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Rediģēt līniju/bultu punktus",
|
||||
"editText": "Rediģēt tekstu/pievienot birku",
|
||||
"github": "Sastapāt kļūdu? Ziņot",
|
||||
"howto": "Sekojiet mūsu instrukcijām",
|
||||
"or": "vai",
|
||||
|
@ -468,15 +469,15 @@
|
|||
},
|
||||
"welcomeScreen": {
|
||||
"app": {
|
||||
"center_heading": "",
|
||||
"center_heading_plus": "",
|
||||
"menuHint": ""
|
||||
"center_heading": "Visi jūsu dati tiek glabāti uz vietas jūsu pārlūkā.",
|
||||
"center_heading_plus": "Vai tā vietā vēlies doties uz Excalidraw+?",
|
||||
"menuHint": "Eksportēšana, iestatījumi, valodas..."
|
||||
},
|
||||
"defaults": {
|
||||
"menuHint": "",
|
||||
"center_heading": "",
|
||||
"toolbarHint": "",
|
||||
"helpHint": ""
|
||||
"menuHint": "Eksportēšana, iestatījumi un vēl...",
|
||||
"center_heading": "Diagrammas. Izveidotas. Vienkārši.",
|
||||
"toolbarHint": "Izvēlies rīku un sāc zīmēt!",
|
||||
"helpHint": "Īsceļi un palīdzība"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "फार मोठं",
|
||||
"solid": "भरीव",
|
||||
"hachure": "हैशूर रेखांकन",
|
||||
"zigzag": "वाकडी तिकड़ी",
|
||||
"crossHatch": "आडव्या रेघा",
|
||||
"thin": "पातळ",
|
||||
"bold": "जाड",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "ပိုကြီး",
|
||||
"solid": "အပြည့်",
|
||||
"hachure": "မျဉ်းစောင်း",
|
||||
"zigzag": "",
|
||||
"crossHatch": "ဇကာကွက်",
|
||||
"thin": "ပါး",
|
||||
"bold": "ထူ",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Svært stor",
|
||||
"solid": "Helfarge",
|
||||
"hachure": "Skravert",
|
||||
"zigzag": "Sikk-sakk",
|
||||
"crossHatch": "Krysskravert",
|
||||
"thin": "Tynn",
|
||||
"bold": "Tykk",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Zeer groot",
|
||||
"solid": "Ingekleurd",
|
||||
"hachure": "Arcering",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Tweemaal gearceerd",
|
||||
"thin": "Dun",
|
||||
"bold": "Vet",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Svært stor",
|
||||
"solid": "Solid",
|
||||
"hachure": "Skravert",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Krysskravert",
|
||||
"thin": "Tynn",
|
||||
"bold": "Tjukk",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Gradassa",
|
||||
"solid": "Solide",
|
||||
"hachure": "Raia",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Raia crosada",
|
||||
"thin": "Fin",
|
||||
"bold": "Espés",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "ਬਹੁਤ ਵੱਡਾ",
|
||||
"solid": "ਠੋਸ",
|
||||
"hachure": "ਤਿਰਛੀਆਂ ਗਰਿੱਲਾਂ",
|
||||
"zigzag": "",
|
||||
"crossHatch": "ਜਾਲੀ",
|
||||
"thin": "ਪਤਲੀ",
|
||||
"bold": "ਮੋਟੀ",
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"ar-SA": 89,
|
||||
"ar-SA": 88,
|
||||
"bg-BG": 52,
|
||||
"bn-BD": 57,
|
||||
"ca-ES": 96,
|
||||
"cs-CZ": 72,
|
||||
"ca-ES": 95,
|
||||
"cs-CZ": 71,
|
||||
"da-DK": 31,
|
||||
"de-DE": 100,
|
||||
"el-GR": 98,
|
||||
"en": 100,
|
||||
"es-ES": 99,
|
||||
"es-ES": 100,
|
||||
"eu-ES": 99,
|
||||
"fa-IR": 91,
|
||||
"fi-FI": 96,
|
||||
"fi-FI": 95,
|
||||
"fr-FR": 99,
|
||||
"gl-ES": 99,
|
||||
"he-IL": 99,
|
||||
|
@ -19,35 +19,35 @@
|
|||
"hu-HU": 85,
|
||||
"id-ID": 98,
|
||||
"it-IT": 99,
|
||||
"ja-JP": 97,
|
||||
"ja-JP": 96,
|
||||
"kab-KAB": 93,
|
||||
"kk-KZ": 19,
|
||||
"ko-KR": 99,
|
||||
"ku-TR": 91,
|
||||
"ko-KR": 100,
|
||||
"ku-TR": 100,
|
||||
"lt-LT": 61,
|
||||
"lv-LV": 93,
|
||||
"lv-LV": 100,
|
||||
"mr-IN": 100,
|
||||
"my-MM": 40,
|
||||
"my-MM": 39,
|
||||
"nb-NO": 100,
|
||||
"nl-NL": 92,
|
||||
"nn-NO": 86,
|
||||
"nn-NO": 85,
|
||||
"oc-FR": 94,
|
||||
"pa-IN": 79,
|
||||
"pl-PL": 87,
|
||||
"pt-BR": 96,
|
||||
"pt-PT": 99,
|
||||
"pt-BR": 95,
|
||||
"pt-PT": 100,
|
||||
"ro-RO": 100,
|
||||
"ru-RU": 96,
|
||||
"ru-RU": 100,
|
||||
"si-LK": 8,
|
||||
"sk-SK": 99,
|
||||
"sl-SI": 100,
|
||||
"sv-SE": 99,
|
||||
"sv-SE": 100,
|
||||
"ta-IN": 90,
|
||||
"th-TH": 39,
|
||||
"tr-TR": 98,
|
||||
"uk-UA": 93,
|
||||
"tr-TR": 97,
|
||||
"uk-UA": 92,
|
||||
"vi-VN": 52,
|
||||
"zh-CN": 99,
|
||||
"zh-HK": 25,
|
||||
"zh-HK": 24,
|
||||
"zh-TW": 100
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Bardzo duży",
|
||||
"solid": "Pełne",
|
||||
"hachure": "Linie",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Zakreślone",
|
||||
"thin": "Cienkie",
|
||||
"bold": "Pogrubione",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Muito grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Hachura",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Hachura cruzada",
|
||||
"thin": "Fino",
|
||||
"bold": "Espesso",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Muito grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Eclosão",
|
||||
"zigzag": "ziguezague",
|
||||
"crossHatch": "Sombreado",
|
||||
"thin": "Fino",
|
||||
"bold": "Espesso",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "clique duplo",
|
||||
"drag": "arrastar",
|
||||
"editor": "Editor",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Editar pontos de linha/seta",
|
||||
"editText": "Editar texto / adicionar etiqueta",
|
||||
"github": "Encontrou algum problema? Informe-nos",
|
||||
"howto": "Siga os nossos guias",
|
||||
"or": "ou",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Foarte mare",
|
||||
"solid": "Plină",
|
||||
"hachure": "Hașură",
|
||||
"zigzag": "Zigzag",
|
||||
"crossHatch": "Hașură transversală",
|
||||
"thin": "Subțire",
|
||||
"bold": "Îngroșată",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Очень большой",
|
||||
"solid": "Однотонная",
|
||||
"hachure": "Штрихованная",
|
||||
"zigzag": "Зигзаг",
|
||||
"crossHatch": "Перекрестная",
|
||||
"thin": "Тонкая",
|
||||
"bold": "Жирная",
|
||||
|
@ -110,7 +111,7 @@
|
|||
"increaseFontSize": "Увеличить шрифт",
|
||||
"unbindText": "Отвязать текст",
|
||||
"bindText": "Привязать текст к контейнеру",
|
||||
"createContainerFromText": "",
|
||||
"createContainerFromText": "Поместить текст в контейнер",
|
||||
"link": {
|
||||
"edit": "Редактировать ссылку",
|
||||
"create": "Создать ссылку",
|
||||
|
@ -207,19 +208,19 @@
|
|||
"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": ""
|
||||
"start": "Похоже, вы используете браузер Brave с",
|
||||
"aggressive_block_fingerprint": "Агрессивно блокировать фингерпринтинг",
|
||||
"setting_enabled": "параметр включен",
|
||||
"break": "Это может привести к поломке",
|
||||
"text_elements": "Текстовых элементов",
|
||||
"in_your_drawings": "в ваших рисунках",
|
||||
"strongly_recommend": "Мы настоятельно рекомендуем отключить эту настройку. Вы можете выполнить",
|
||||
"steps": "эти действия",
|
||||
"how": "для отключения",
|
||||
"disable_setting": " Если отключение этого параметра не исправит отображение текстовых элементов, пожалуйста, откройте",
|
||||
"issue": "issue",
|
||||
"write": "на нашем GitHub, или напишите нам в",
|
||||
"discord": "Discord"
|
||||
}
|
||||
},
|
||||
"toolBar": {
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "двойной клик",
|
||||
"drag": "перетащить",
|
||||
"editor": "Редактор",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Редактировать концы линий/стрелок",
|
||||
"editText": "Редактировать текст / добавить метку",
|
||||
"github": "Нашли проблему? Отправьте",
|
||||
"howto": "Следуйте нашим инструкциям",
|
||||
"or": "или",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "ඉතා විශාල",
|
||||
"solid": "විශාල",
|
||||
"hachure": "මධ්යම",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "කෙට්ටු",
|
||||
"bold": "තද",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Veľmi veľké",
|
||||
"solid": "Plná",
|
||||
"hachure": "Šrafovaná",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Mriežkovaná",
|
||||
"thin": "Tenká",
|
||||
"bold": "Hrubá",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "dvojklik",
|
||||
"drag": "potiahnutie",
|
||||
"editor": "Editovanie",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Editácia bodov čiary/šípky",
|
||||
"editText": "Editácia textu / pridanie štítku",
|
||||
"github": "Objavili ste problém? Nahláste ho",
|
||||
"howto": "Postupujte podľa naších návodov",
|
||||
"or": "alebo",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Zelo velika",
|
||||
"solid": "Polno",
|
||||
"hachure": "Šrafura",
|
||||
"zigzag": "Cikcak",
|
||||
"crossHatch": "Križno",
|
||||
"thin": "Tanko",
|
||||
"bold": "Krepko",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Mycket stor",
|
||||
"solid": "Solid",
|
||||
"hachure": "Skraffering",
|
||||
"zigzag": "Sicksack",
|
||||
"crossHatch": "Skraffera med kors",
|
||||
"thin": "Tunn",
|
||||
"bold": "Fet",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "dubbelklicka",
|
||||
"drag": "dra",
|
||||
"editor": "Redigerare",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "Redigera linje-/pilpunkter",
|
||||
"editText": "Redigera text / lägg till etikett",
|
||||
"github": "Hittat ett problem? Rapportera",
|
||||
"howto": "Följ våra guider",
|
||||
"or": "eller",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "மிகப் பெரிய",
|
||||
"solid": "திடமான",
|
||||
"hachure": "மலைக்குறிக்கோடு",
|
||||
"zigzag": "",
|
||||
"crossHatch": "குறுக்குகதவு",
|
||||
"thin": "மெல்லிய",
|
||||
"bold": "பட்டை",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "ใหญ่มาก",
|
||||
"solid": "",
|
||||
"hachure": "",
|
||||
"zigzag": "",
|
||||
"crossHatch": "",
|
||||
"thin": "บาง",
|
||||
"bold": "หนา",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Çok geniş",
|
||||
"solid": "Dolu",
|
||||
"hachure": "Taralı",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Çapraz-taralı",
|
||||
"thin": "İnce",
|
||||
"bold": "Kalın",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Дуже великий",
|
||||
"solid": "Суцільна",
|
||||
"hachure": "Штриховка",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Перехресна штриховка",
|
||||
"thin": "Тонкий",
|
||||
"bold": "Жирний",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "Rất lớn",
|
||||
"solid": "Đặc",
|
||||
"hachure": "Nét gạch gạch",
|
||||
"zigzag": "",
|
||||
"crossHatch": "Nét gạch chéo",
|
||||
"thin": "Mỏng",
|
||||
"bold": "In đậm",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "加大",
|
||||
"solid": "实心",
|
||||
"hachure": "线条",
|
||||
"zigzag": "",
|
||||
"crossHatch": "交叉线条",
|
||||
"thin": "细",
|
||||
"bold": "粗",
|
||||
|
@ -319,8 +320,8 @@
|
|||
"doubleClick": "双击",
|
||||
"drag": "拖动",
|
||||
"editor": "编辑器",
|
||||
"editLineArrowPoints": "",
|
||||
"editText": "",
|
||||
"editLineArrowPoints": "编辑线条或箭头的点",
|
||||
"editText": "添加或编辑文本",
|
||||
"github": "提交问题",
|
||||
"howto": "帮助文档",
|
||||
"or": "或",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "勁大",
|
||||
"solid": "實心",
|
||||
"hachure": "斜線",
|
||||
"zigzag": "",
|
||||
"crossHatch": "交叉格仔",
|
||||
"thin": "幼",
|
||||
"bold": "粗",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"veryLarge": "特大",
|
||||
"solid": "實心",
|
||||
"hachure": "斜線筆觸",
|
||||
"zigzag": "Z字形",
|
||||
"crossHatch": "交叉筆觸",
|
||||
"thin": "細",
|
||||
"bold": "粗",
|
||||
|
|
|
@ -11,7 +11,39 @@ 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.
|
||||
-->
|
||||
|
||||
## Unreleased
|
||||
## 0.15.2 (2023-04-20)
|
||||
|
||||
### Docs
|
||||
|
||||
- Fix docs link in readme [#6486](https://github.com/excalidraw/excalidraw/pull/6486)
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
### Fixes
|
||||
|
||||
- Rotate the text element when binding to a rotated container [#6477](https://github.com/excalidraw/excalidraw/pull/6477)
|
||||
|
||||
- Support breaking words containing hyphen - [#6014](https://github.com/excalidraw/excalidraw/pull/6014)
|
||||
|
||||
- Incorrect background fill button active state [#6491](https://github.com/excalidraw/excalidraw/pull/6491)
|
||||
|
||||
---
|
||||
|
||||
## 0.15.1 (2023-04-18)
|
||||
|
||||
### Docs
|
||||
|
||||
- Add the readme back to the package which was mistakenly removed [#6484](https://github.com/excalidraw/excalidraw/pull/6484)
|
||||
|
||||
## Excalidraw Library
|
||||
|
||||
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
|
||||
|
||||
---
|
||||
|
||||
## 0.15.0 (2023-04-18)
|
||||
|
||||
### Features
|
||||
|
||||
|
@ -37,6 +69,154 @@ For more details refer to the [docs](https://docs.excalidraw.com)
|
|||
|
||||
- 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
|
||||
|
|
|
@ -38,8 +38,8 @@ Excalidraw takes _100%_ of `width` and `height` of the containing block so make
|
|||
|
||||
## Integration
|
||||
|
||||
Head over to the [docs](https://docs.excalidraw.com/docs/package/integration)
|
||||
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/integration)
|
||||
|
||||
## API
|
||||
|
||||
Head over to the [docs](https://docs.excalidraw.com/docs/package/api)
|
||||
Head over to the [docs](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@excalidraw/excalidraw",
|
||||
"version": "0.14.2",
|
||||
"version": "0.15.2",
|
||||
"main": "main.js",
|
||||
"types": "types/packages/excalidraw/index.d.ts",
|
||||
"files": [
|
||||
|
|
|
@ -79,7 +79,11 @@ export const exportToCanvas = ({
|
|||
|
||||
const max = Math.max(width, height);
|
||||
|
||||
const scale = maxWidthOrHeight / max;
|
||||
// if content is less then maxWidthOrHeight, fallback on supplied scale
|
||||
const scale =
|
||||
maxWidthOrHeight < max
|
||||
? maxWidthOrHeight / max
|
||||
: appState?.exportScale ?? 1;
|
||||
|
||||
canvas.width = width * scale;
|
||||
canvas.height = height * scale;
|
||||
|
@ -216,15 +220,7 @@ export const exportToClipboard = async (
|
|||
} else if (opts.type === "png") {
|
||||
await copyBlobToClipboardAsPng(exportToBlob(opts));
|
||||
} else if (opts.type === "json") {
|
||||
const appState = {
|
||||
offsetTop: 0,
|
||||
offsetLeft: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
...getDefaultAppState(),
|
||||
...opts.appState,
|
||||
};
|
||||
await copyToClipboard(opts.elements, appState, opts.files);
|
||||
await copyToClipboard(opts.elements, opts.files);
|
||||
} else {
|
||||
throw new Error("Invalid export type");
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ import {
|
|||
getContainerCoords,
|
||||
getContainerElement,
|
||||
getLineHeightInPx,
|
||||
getMaxContainerHeight,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxHeight,
|
||||
getBoundTextMaxWidth,
|
||||
} from "../element/textElement";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
||||
|
@ -864,17 +864,21 @@ const drawElementFromCanvas = (
|
|||
);
|
||||
|
||||
if (
|
||||
process.env.REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX &&
|
||||
process.env.REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX ===
|
||||
"true" &&
|
||||
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,5 +1,6 @@
|
|||
import { isTextElement, refreshTextDimensions } from "../element";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { isBoundToContainer } from "../element/typeChecks";
|
||||
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||
import { invalidateShapeForElement } from "../renderer/renderElement";
|
||||
import { getFontString } from "../utils";
|
||||
|
@ -52,7 +53,7 @@ export class Fonts {
|
|||
let didUpdate = false;
|
||||
|
||||
this.scene.mapElements((element) => {
|
||||
if (isTextElement(element)) {
|
||||
if (isTextElement(element) && !isBoundToContainer(element)) {
|
||||
invalidateShapeForElement(element);
|
||||
didUpdate = true;
|
||||
return newElementWith(element, {
|
||||
|
|
|
@ -121,7 +121,7 @@ Object {
|
|||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
|
@ -4518,7 +4518,7 @@ Object {
|
|||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
|
@ -5068,7 +5068,7 @@ Object {
|
|||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
|
@ -5917,7 +5917,7 @@ Object {
|
|||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
|
@ -6263,7 +6263,7 @@ Object {
|
|||
},
|
||||
Object {
|
||||
"contextItemLabel": "labels.createContainerFromText",
|
||||
"name": "createContainerFromText",
|
||||
"name": "wrapTextInContainer",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": Object {
|
||||
|
|
|
@ -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 { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||
import { actionWrapTextInContainer } 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(actionCreateContainerFromText);
|
||||
h.app.actionManager.executeAction(actionWrapTextInContainer);
|
||||
|
||||
// new text container will be placed before the text element
|
||||
const container = h.elements.at(-2)!;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import ReactDOM from "react-dom";
|
||||
import { render, waitFor, GlobalTestState } from "./test-utils";
|
||||
import {
|
||||
render,
|
||||
waitFor,
|
||||
GlobalTestState,
|
||||
createPasteEvent,
|
||||
} from "./test-utils";
|
||||
import { Pointer, Keyboard } from "./helpers/ui";
|
||||
import ExcalidrawApp from "../excalidraw-app";
|
||||
import { KEYS } from "../keys";
|
||||
|
@ -9,6 +14,8 @@ import {
|
|||
} from "../element/textElement";
|
||||
import { getElementBounds } from "../element";
|
||||
import { NormalizedZoomValue } from "../types";
|
||||
import { API } from "./helpers/api";
|
||||
import { copyToClipboard } from "../clipboard";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
|
@ -35,38 +42,28 @@ const setClipboardText = (text: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
const sendPasteEvent = () => {
|
||||
const clipboardEvent = new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
});
|
||||
|
||||
// set `clipboardData` properties.
|
||||
// @ts-ignore
|
||||
clipboardEvent.clipboardData = {
|
||||
getData: () => window.navigator.clipboard.readText(),
|
||||
files: [],
|
||||
};
|
||||
|
||||
const sendPasteEvent = (text?: string) => {
|
||||
const clipboardEvent = createPasteEvent(
|
||||
text || (() => window.navigator.clipboard.readText()),
|
||||
);
|
||||
document.dispatchEvent(clipboardEvent);
|
||||
};
|
||||
|
||||
const pasteWithCtrlCmdShiftV = () => {
|
||||
const pasteWithCtrlCmdShiftV = (text?: string) => {
|
||||
Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
|
||||
//triggering keydown with an empty clipboard
|
||||
Keyboard.keyPress(KEYS.V);
|
||||
//triggering paste event with faked clipboard
|
||||
sendPasteEvent();
|
||||
sendPasteEvent(text);
|
||||
});
|
||||
};
|
||||
|
||||
const pasteWithCtrlCmdV = () => {
|
||||
const pasteWithCtrlCmdV = (text?: string) => {
|
||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||
//triggering keydown with an empty clipboard
|
||||
Keyboard.keyPress(KEYS.V);
|
||||
//triggering paste event with faked clipboard
|
||||
sendPasteEvent();
|
||||
sendPasteEvent(text);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -89,6 +86,32 @@ beforeEach(async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("general paste behavior", () => {
|
||||
it("should randomize seed on paste", async () => {
|
||||
const rectangle = API.createElement({ type: "rectangle" });
|
||||
const clipboardJSON = (await copyToClipboard([rectangle], null))!;
|
||||
|
||||
pasteWithCtrlCmdV(clipboardJSON);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(h.elements.length).toBe(1);
|
||||
expect(h.elements[0].seed).not.toBe(rectangle.seed);
|
||||
});
|
||||
});
|
||||
|
||||
it("should retain seed on shift-paste", async () => {
|
||||
const rectangle = API.createElement({ type: "rectangle" });
|
||||
const clipboardJSON = (await copyToClipboard([rectangle], null))!;
|
||||
|
||||
// assert we don't randomize seed on shift-paste
|
||||
pasteWithCtrlCmdShiftV(clipboardJSON);
|
||||
await waitFor(() => {
|
||||
expect(h.elements.length).toBe(1);
|
||||
expect(h.elements[0].seed).toBe(rectangle.seed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("paste text as single lines", () => {
|
||||
it("should create an element for each line when copying with Ctrl/Cmd+V", async () => {
|
||||
const text = "sajgfakfn\naaksfnknas\nakefnkasf";
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import ReactDOM from "react-dom";
|
||||
import { GlobalTestState, render, waitFor } from "./test-utils";
|
||||
import {
|
||||
createPasteEvent,
|
||||
GlobalTestState,
|
||||
render,
|
||||
waitFor,
|
||||
} from "./test-utils";
|
||||
import { UI, Pointer } from "./helpers/ui";
|
||||
import { API } from "./helpers/api";
|
||||
import { actionFlipHorizontal, actionFlipVertical } from "../actions";
|
||||
|
@ -693,19 +698,7 @@ describe("freedraw", () => {
|
|||
describe("image", () => {
|
||||
const createImage = async () => {
|
||||
const sendPasteEvent = (file?: File) => {
|
||||
const clipboardEvent = new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
});
|
||||
|
||||
// set `clipboardData` properties.
|
||||
// @ts-ignore
|
||||
clipboardEvent.clipboardData = {
|
||||
getData: () => window.navigator.clipboard.readText(),
|
||||
files: [file],
|
||||
};
|
||||
|
||||
const clipboardEvent = createPasteEvent("", file ? [file] : []);
|
||||
document.dispatchEvent(clipboardEvent);
|
||||
};
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import { resize, rotate } from "./utils";
|
|||
import {
|
||||
getBoundTextElementPosition,
|
||||
wrapText,
|
||||
getMaxContainerWidth,
|
||||
getBoundTextMaxWidth,
|
||||
} from "../element/textElement";
|
||||
import * as textElementUtils from "../element/textElement";
|
||||
import { ROUNDNESS, VERTICAL_ALIGN } from "../constants";
|
||||
|
@ -729,7 +729,7 @@ describe("Test Linear Elements", () => {
|
|||
type: "text",
|
||||
x: 0,
|
||||
y: 0,
|
||||
text: wrapText(text, font, getMaxContainerWidth(container)),
|
||||
text: wrapText(text, font, getBoundTextMaxWidth(container)),
|
||||
containerId: container.id,
|
||||
width: 30,
|
||||
height: 20,
|
||||
|
@ -1149,7 +1149,7 @@ describe("Test Linear Elements", () => {
|
|||
expect(rect.x).toBe(400);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(
|
||||
wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)),
|
||||
wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)),
|
||||
).toMatchInlineSnapshot(`
|
||||
"Online whiteboard collaboration
|
||||
made easy"
|
||||
|
@ -1172,7 +1172,7 @@ describe("Test Linear Elements", () => {
|
|||
false,
|
||||
);
|
||||
expect(
|
||||
wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)),
|
||||
wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)),
|
||||
).toMatchInlineSnapshot(`
|
||||
"Online whiteboard
|
||||
collaboration made
|
||||
|
|
|
@ -190,3 +190,24 @@ export const toggleMenu = (container: HTMLElement) => {
|
|||
// open menu
|
||||
fireEvent.click(container.querySelector(".dropdown-menu-button")!);
|
||||
};
|
||||
|
||||
export const createPasteEvent = (
|
||||
text:
|
||||
| string
|
||||
| /* getData function */ ((type: string) => string | Promise<string>),
|
||||
files?: File[],
|
||||
) => {
|
||||
return Object.assign(
|
||||
new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
}),
|
||||
{
|
||||
clipboardData: {
|
||||
getData: typeof text === "string" ? () => text : text,
|
||||
files: files || [],
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
|
|
@ -29,9 +29,9 @@ import { isOverScrollBars } from "./scene";
|
|||
import { MaybeTransformHandleType } from "./element/transformHandles";
|
||||
import Library from "./data/library";
|
||||
import type { FileSystemHandle } from "./data/filesystem";
|
||||
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||
import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||
import { ContextMenuItems } from "./components/ContextMenu";
|
||||
import { Merge, ForwardRef } from "./utility-types";
|
||||
import { Merge, ForwardRef, ValueOf } from "./utility-types";
|
||||
import React from "react";
|
||||
|
||||
export type Point = Readonly<RoughPoint>;
|
||||
|
@ -60,7 +60,7 @@ export type DataURL = string & { _brand: "DataURL" };
|
|||
|
||||
export type BinaryFileData = {
|
||||
mimeType:
|
||||
| typeof ALLOWED_IMAGE_MIME_TYPES[number]
|
||||
| ValueOf<typeof IMAGE_MIME_TYPES>
|
||||
// future user or unknown file type
|
||||
| typeof MIME_TYPES.binary;
|
||||
id: FileId;
|
||||
|
@ -419,7 +419,7 @@ export type AppClassProperties = {
|
|||
FileId,
|
||||
{
|
||||
image: HTMLImageElement | Promise<HTMLImageElement>;
|
||||
mimeType: typeof ALLOWED_IMAGE_MIME_TYPES[number];
|
||||
mimeType: ValueOf<typeof IMAGE_MIME_TYPES>;
|
||||
}
|
||||
>;
|
||||
files: BinaryFiles;
|
||||
|
|
|
@ -10601,9 +10601,9 @@ webpack-sources@^3.2.3:
|
|||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||
|
||||
webpack@^5.64.4:
|
||||
version "5.75.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152"
|
||||
integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==
|
||||
version "5.76.1"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.1.tgz#7773de017e988bccb0f13c7d75ec245f377d295c"
|
||||
integrity sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.3"
|
||||
"@types/estree" "^0.0.51"
|
||||
|
|
Loading…
Add table
Reference in a new issue