Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8da178ebd | ||
|
|
3ddcc48e4c | ||
|
|
29a5e982c3 | ||
|
|
b33fa6d6f6 | ||
|
|
b7350f9707 | ||
|
|
8dfa2a98bb | ||
|
|
fb01ce2a00 | ||
|
|
3d57112480 | ||
|
|
7558a4e2be | ||
|
|
6d56634289 | ||
|
|
0aa1e66486 | ||
|
|
7f7128ec09 | ||
|
|
6de6a96abf | ||
|
|
28ab6531c9 |
+1
-5
@@ -20,14 +20,10 @@ REACT_APP_DEV_ENABLE_SW=
|
|||||||
# whether to disable live reload / HMR. Usuaully what you want to do when
|
# whether to disable live reload / HMR. Usuaully what you want to do when
|
||||||
# debugging Service Workers.
|
# debugging Service Workers.
|
||||||
REACT_APP_DEV_DISABLE_LIVE_RELOAD=
|
REACT_APP_DEV_DISABLE_LIVE_RELOAD=
|
||||||
|
REACT_APP_DISABLE_TRACKING=true
|
||||||
|
|
||||||
FAST_REFRESH=false
|
FAST_REFRESH=false
|
||||||
|
|
||||||
# MATOMO
|
|
||||||
REACT_APP_MATOMO_URL=
|
|
||||||
REACT_APP_CDN_MATOMO_TRACKER_URL=
|
|
||||||
REACT_APP_MATOMO_SITE_ID=
|
|
||||||
|
|
||||||
#Debug flags
|
#Debug flags
|
||||||
|
|
||||||
# To enable bounding box for text containers
|
# To enable bounding box for text containers
|
||||||
|
|||||||
+1
-10
@@ -11,14 +11,5 @@ REACT_APP_WS_SERVER_URL=
|
|||||||
|
|
||||||
REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
|
REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
|
||||||
|
|
||||||
# production-only vars
|
|
||||||
# GOOGLE ANALYTICS
|
|
||||||
REACT_APP_GOOGLE_ANALYTICS_ID=UA-387204-13
|
|
||||||
# MATOMO
|
|
||||||
REACT_APP_MATOMO_URL=https://excalidraw.matomo.cloud/
|
|
||||||
REACT_APP_CDN_MATOMO_TRACKER_URL=//cdn.matomo.cloud/excalidraw.matomo.cloud/matomo.js
|
|
||||||
REACT_APP_MATOMO_SITE_ID=1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
REACT_APP_PLUS_APP=https://app.excalidraw.com
|
REACT_APP_PLUS_APP=https://app.excalidraw.com
|
||||||
|
REACT_APP_DISABLE_TRACKING=
|
||||||
|
|||||||
@@ -306,30 +306,32 @@ This is the history API. history.clear() will clear the history.
|
|||||||
|
|
||||||
## scrollToContent
|
## scrollToContent
|
||||||
|
|
||||||
<pre>
|
```tsx
|
||||||
(<br />
|
(
|
||||||
{" "}
|
target?: ExcalidrawElement | ExcalidrawElement[],
|
||||||
target?:{" "}
|
opts?:
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
|
| {
|
||||||
ExcalidrawElement
|
fitToContent?: boolean;
|
||||||
</a>{" "}
|
animate?: boolean;
|
||||||
|{" "}
|
duration?: number;
|
||||||
<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
|
}
|
||||||
ExcalidrawElement
|
| {
|
||||||
</a>
|
fitToViewport?: boolean;
|
||||||
[],
|
viewportZoomFactor?: number;
|
||||||
<br />
|
animate?: boolean;
|
||||||
{" "}opts?: { fitToContent?: boolean; animate?: boolean; duration?: number
|
duration?: number;
|
||||||
}
|
}
|
||||||
<br />) => void
|
) => void
|
||||||
</pre>
|
```
|
||||||
|
|
||||||
Scroll the nearest element out of the elements supplied to the center of the viewport. Defaults to the elements on the scene.
|
Scroll the nearest element out of the elements supplied to the center of the viewport. Defaults to the elements on the scene.
|
||||||
|
|
||||||
| Attribute | type | default | Description |
|
| Attribute | type | default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| target | <code>ExcalidrawElement | ExcalidrawElement[]</code> | All scene elements | The element(s) to scroll to. |
|
| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
|
||||||
| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. |
|
| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. Note that the zoom range is between 10%-100%. |
|
||||||
|
| opts.fitToViewport | boolean | false | Similar to fitToContent but the zoom range is not limited. If elements are smaller than the viewport, zoom will go above 100%. |
|
||||||
|
| opts.viewportZoomFactor | number | 0.7 | when fitToViewport=true, how much screen should the content cover, between 0.1 (10%) and 1 (100%) |
|
||||||
| opts.animate | boolean | false | Whether to animate between starting and ending position. Note that for larger scenes the animation may not be smooth due to performance issues. |
|
| opts.animate | boolean | false | Whether to animate between starting and ending position. Note that for larger scenes the animation may not be smooth due to performance issues. |
|
||||||
| opts.duration | number | 500 | Duration of the animation if `opts.animate` is `true`. |
|
| opts.duration | number | 500 | Duration of the animation if `opts.animate` is `true`. |
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
Pull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw/issues/new) first to discuss what you would like to change.
|
Pull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw/issues/new) first to discuss what you would like to change.
|
||||||
|
|
||||||
|
We have a [roadmap](https://github.com/orgs/excalidraw/projects/3) which we strongly recommend to go through and check if something interests you.
|
||||||
|
For new contributors we would recommend to start with *Easy* tasks.
|
||||||
|
|
||||||
|
In case you want to pick up something from the roadmap, comment on that issue and one of the project maintainers will assign it to you, post which you can discuss in the issue and start working on it.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### Option 1 - Manual
|
### Option 1 - Manual
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@braintree/sanitize-url": "6.0.2",
|
||||||
"@excalidraw/random-username": "1.0.0",
|
"@excalidraw/random-username": "1.0.0",
|
||||||
"@radix-ui/react-popover": "1.0.3",
|
"@radix-ui/react-popover": "1.0.3",
|
||||||
"@radix-ui/react-tabs": "1.0.2",
|
"@radix-ui/react-tabs": "1.0.2",
|
||||||
|
|||||||
+33
-38
@@ -148,33 +148,6 @@
|
|||||||
// setting this so that libraries installation reuses this window tab.
|
// setting this so that libraries installation reuses this window tab.
|
||||||
window.name = "_excalidraw";
|
window.name = "_excalidraw";
|
||||||
</script>
|
</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
|
|
||||||
async
|
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=%REACT_APP_GOOGLE_ANALYTICS_ID%"
|
|
||||||
></script>
|
|
||||||
<script>
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag() {
|
|
||||||
dataLayer.push(arguments);
|
|
||||||
}
|
|
||||||
gtag("js", new Date());
|
|
||||||
gtag("config", "%REACT_APP_GOOGLE_ANALYTICS_ID%");
|
|
||||||
</script>
|
|
||||||
<% } %>
|
|
||||||
<!-- end LEGACY GOOGLE ANALYTICS -->
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<!-- FIXME: remove this when we update CRA (fix SW caching) -->
|
<!-- FIXME: remove this when we update CRA (fix SW caching) -->
|
||||||
<style>
|
<style>
|
||||||
@@ -227,17 +200,39 @@
|
|||||||
<h1 class="visually-hidden">Excalidraw</h1>
|
<h1 class="visually-hidden">Excalidraw</h1>
|
||||||
</header>
|
</header>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<% if (process.env.REACT_APP_DISABLE_TRACKING !== 'true') { %>
|
||||||
<!-- 100% privacy friendly analytics -->
|
<!-- 100% privacy friendly analytics -->
|
||||||
<script
|
<script>
|
||||||
async
|
// need to load this script dynamically bcs. of iframe embed tracking
|
||||||
defer
|
var scriptEle = document.createElement("script");
|
||||||
src="https://scripts.simpleanalyticscdn.com/latest.js"
|
scriptEle.setAttribute(
|
||||||
></script>
|
"src",
|
||||||
<noscript
|
"https://scripts.simpleanalyticscdn.com/latest.js",
|
||||||
><img
|
);
|
||||||
src="https://queue.simpleanalyticscdn.com/noscript.gif"
|
scriptEle.setAttribute("type", "text/javascript");
|
||||||
alt=""
|
scriptEle.setAttribute("defer", true);
|
||||||
referrerpolicy="no-referrer-when-downgrade"
|
scriptEle.setAttribute("async", true);
|
||||||
/></noscript>
|
// if iframe
|
||||||
|
if (window.self !== window.top) {
|
||||||
|
scriptEle.setAttribute("data-auto-collect", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(scriptEle);
|
||||||
|
|
||||||
|
// if iframe
|
||||||
|
if (window.self !== window.top) {
|
||||||
|
scriptEle.addEventListener("load", () => {
|
||||||
|
if (window.sa_pageview) {
|
||||||
|
window.window.sa_event(action, {
|
||||||
|
category: "iframe",
|
||||||
|
label: "embed",
|
||||||
|
value: window.location.pathname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- end LEGACY GOOGLE ANALYTICS -->
|
||||||
|
<% } %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+101
-35
@@ -20,7 +20,6 @@ import {
|
|||||||
isHandToolActive,
|
isHandToolActive,
|
||||||
} from "../appState";
|
} from "../appState";
|
||||||
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
|
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
|
||||||
import { excludeElementsInFramesFromSelection } from "../scene/selection";
|
|
||||||
import { Bounds } from "../element/bounds";
|
import { Bounds } from "../element/bounds";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor = register({
|
export const actionChangeViewBackgroundColor = register({
|
||||||
@@ -226,52 +225,96 @@ const zoomValueToFitBoundsOnViewport = (
|
|||||||
return clampedZoomValueToFitElements as NormalizedZoomValue;
|
return clampedZoomValueToFitElements as NormalizedZoomValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const zoomToFitElements = (
|
export const zoomToFit = ({
|
||||||
elements: readonly ExcalidrawElement[],
|
targetElements,
|
||||||
appState: Readonly<AppState>,
|
appState,
|
||||||
zoomToSelection: boolean,
|
fitToViewport = false,
|
||||||
) => {
|
viewportZoomFactor = 0.7,
|
||||||
const nonDeletedElements = getNonDeletedElements(elements);
|
}: {
|
||||||
const selectedElements = getSelectedElements(nonDeletedElements, appState);
|
targetElements: readonly ExcalidrawElement[];
|
||||||
|
appState: Readonly<AppState>;
|
||||||
const commonBounds =
|
/** whether to fit content to viewport (beyond >100%) */
|
||||||
zoomToSelection && selectedElements.length > 0
|
fitToViewport: boolean;
|
||||||
? getCommonBounds(excludeElementsInFramesFromSelection(selectedElements))
|
/** zoom content to cover X of the viewport, when fitToViewport=true */
|
||||||
: getCommonBounds(
|
viewportZoomFactor?: number;
|
||||||
excludeElementsInFramesFromSelection(nonDeletedElements),
|
}) => {
|
||||||
);
|
const commonBounds = getCommonBounds(getNonDeletedElements(targetElements));
|
||||||
|
|
||||||
const newZoom = {
|
|
||||||
value: zoomValueToFitBoundsOnViewport(commonBounds, {
|
|
||||||
width: appState.width,
|
|
||||||
height: appState.height,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = commonBounds;
|
const [x1, y1, x2, y2] = commonBounds;
|
||||||
const centerX = (x1 + x2) / 2;
|
const centerX = (x1 + x2) / 2;
|
||||||
const centerY = (y1 + y2) / 2;
|
const centerY = (y1 + y2) / 2;
|
||||||
|
|
||||||
|
let newZoomValue;
|
||||||
|
let scrollX;
|
||||||
|
let scrollY;
|
||||||
|
|
||||||
|
if (fitToViewport) {
|
||||||
|
const commonBoundsWidth = x2 - x1;
|
||||||
|
const commonBoundsHeight = y2 - y1;
|
||||||
|
|
||||||
|
newZoomValue =
|
||||||
|
Math.min(
|
||||||
|
appState.width / commonBoundsWidth,
|
||||||
|
appState.height / commonBoundsHeight,
|
||||||
|
) * Math.min(1, Math.max(viewportZoomFactor, 0.1));
|
||||||
|
|
||||||
|
// Apply clamping to newZoomValue to be between 10% and 3000%
|
||||||
|
newZoomValue = Math.min(
|
||||||
|
Math.max(newZoomValue, 0.1),
|
||||||
|
30.0,
|
||||||
|
) as NormalizedZoomValue;
|
||||||
|
|
||||||
|
scrollX = (appState.width / 2) * (1 / newZoomValue) - centerX;
|
||||||
|
scrollY = (appState.height / 2) * (1 / newZoomValue) - centerY;
|
||||||
|
} else {
|
||||||
|
newZoomValue = zoomValueToFitBoundsOnViewport(commonBounds, {
|
||||||
|
width: appState.width,
|
||||||
|
height: appState.height,
|
||||||
|
});
|
||||||
|
|
||||||
|
const centerScroll = centerScrollOn({
|
||||||
|
scenePoint: { x: centerX, y: centerY },
|
||||||
|
viewportDimensions: {
|
||||||
|
width: appState.width,
|
||||||
|
height: appState.height,
|
||||||
|
},
|
||||||
|
zoom: { value: newZoomValue },
|
||||||
|
});
|
||||||
|
|
||||||
|
scrollX = centerScroll.scrollX;
|
||||||
|
scrollY = centerScroll.scrollY;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
...centerScrollOn({
|
scrollX,
|
||||||
scenePoint: { x: centerX, y: centerY },
|
scrollY,
|
||||||
viewportDimensions: {
|
zoom: { value: newZoomValue },
|
||||||
width: appState.width,
|
|
||||||
height: appState.height,
|
|
||||||
},
|
|
||||||
zoom: newZoom,
|
|
||||||
}),
|
|
||||||
zoom: newZoom,
|
|
||||||
},
|
},
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionZoomToSelected = register({
|
// Note, this action differs from actionZoomToFitSelection in that it doesn't
|
||||||
name: "zoomToSelection",
|
// zoom beyond 100%. In other words, if the content is smaller than viewport
|
||||||
|
// size, it won't be zoomed in.
|
||||||
|
export const actionZoomToFitSelectionInViewport = register({
|
||||||
|
name: "zoomToFitSelectionInViewport",
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
perform: (elements, appState) => zoomToFitElements(elements, appState, true),
|
perform: (elements, appState) => {
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
getNonDeletedElements(elements),
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
return zoomToFit({
|
||||||
|
targetElements: selectedElements.length ? selectedElements : elements,
|
||||||
|
appState,
|
||||||
|
fitToViewport: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// NOTE shift-2 should have been assigned actionZoomToFitSelection.
|
||||||
|
// TBD on how proceed
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
event.code === CODES.TWO &&
|
event.code === CODES.TWO &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
@@ -279,11 +322,34 @@ export const actionZoomToSelected = register({
|
|||||||
!event[KEYS.CTRL_OR_CMD],
|
!event[KEYS.CTRL_OR_CMD],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const actionZoomToFitSelection = register({
|
||||||
|
name: "zoomToFitSelection",
|
||||||
|
trackEvent: { category: "canvas" },
|
||||||
|
perform: (elements, appState) => {
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
getNonDeletedElements(elements),
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
return zoomToFit({
|
||||||
|
targetElements: selectedElements.length ? selectedElements : elements,
|
||||||
|
appState,
|
||||||
|
fitToViewport: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// NOTE this action should use shift-2 per figma, alas
|
||||||
|
keyTest: (event) =>
|
||||||
|
event.code === CODES.THREE &&
|
||||||
|
event.shiftKey &&
|
||||||
|
!event.altKey &&
|
||||||
|
!event[KEYS.CTRL_OR_CMD],
|
||||||
|
});
|
||||||
|
|
||||||
export const actionZoomToFit = register({
|
export const actionZoomToFit = register({
|
||||||
name: "zoomToFit",
|
name: "zoomToFit",
|
||||||
viewMode: true,
|
viewMode: true,
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
perform: (elements, appState) => zoomToFitElements(elements, appState, false),
|
perform: (elements, appState) =>
|
||||||
|
zoomToFit({ targetElements: elements, appState, fitToViewport: false }),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
event.code === CODES.ONE &&
|
event.code === CODES.ONE &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ export type ActionName =
|
|||||||
| "zoomOut"
|
| "zoomOut"
|
||||||
| "resetZoom"
|
| "resetZoom"
|
||||||
| "zoomToFit"
|
| "zoomToFit"
|
||||||
| "zoomToSelection"
|
| "zoomToFitSelection"
|
||||||
|
| "zoomToFitSelectionInViewport"
|
||||||
| "changeFontFamily"
|
| "changeFontFamily"
|
||||||
| "changeTextAlign"
|
| "changeTextAlign"
|
||||||
| "changeVerticalAlign"
|
| "changeVerticalAlign"
|
||||||
|
|||||||
+5
-14
@@ -5,6 +5,9 @@ export const trackEvent = (
|
|||||||
value?: number,
|
value?: number,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
|
// place here categories that you want to track as events
|
||||||
|
// KEEP IN MIND THE PRICING
|
||||||
|
const ALLOWED_CATEGORIES_TO_TRACK = [] as string[];
|
||||||
// Uncomment the next line to track locally
|
// Uncomment the next line to track locally
|
||||||
// console.log("Track Event", { category, action, label, value });
|
// console.log("Track Event", { category, action, label, value });
|
||||||
|
|
||||||
@@ -12,12 +15,8 @@ export const trackEvent = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID && window.gtag) {
|
if (!ALLOWED_CATEGORIES_TO_TRACK.includes(category)) {
|
||||||
window.gtag("event", action, {
|
return;
|
||||||
event_category: category,
|
|
||||||
event_label: label,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.sa_event) {
|
if (window.sa_event) {
|
||||||
@@ -27,14 +26,6 @@ export const trackEvent = (
|
|||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.fathom) {
|
|
||||||
window.fathom.trackEvent(action, {
|
|
||||||
category,
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("error during analytics", error);
|
console.error("error during analytics", error);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -180,7 +180,7 @@ const commonProps = {
|
|||||||
locked: false,
|
locked: false,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const getChartDimentions = (spreadsheet: Spreadsheet) => {
|
const getChartDimensions = (spreadsheet: Spreadsheet) => {
|
||||||
const chartWidth =
|
const chartWidth =
|
||||||
(BAR_WIDTH + BAR_GAP) * spreadsheet.values.length + BAR_GAP;
|
(BAR_WIDTH + BAR_GAP) * spreadsheet.values.length + BAR_GAP;
|
||||||
const chartHeight = BAR_HEIGHT + BAR_GAP * 2;
|
const chartHeight = BAR_HEIGHT + BAR_GAP * 2;
|
||||||
@@ -250,7 +250,7 @@ const chartLines = (
|
|||||||
groupId: string,
|
groupId: string,
|
||||||
backgroundColor: string,
|
backgroundColor: string,
|
||||||
): ChartElements => {
|
): ChartElements => {
|
||||||
const { chartWidth, chartHeight } = getChartDimentions(spreadsheet);
|
const { chartWidth, chartHeight } = getChartDimensions(spreadsheet);
|
||||||
const xLine = newLinearElement({
|
const xLine = newLinearElement({
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
groupIds: [groupId],
|
groupIds: [groupId],
|
||||||
@@ -313,7 +313,7 @@ const chartBaseElements = (
|
|||||||
backgroundColor: string,
|
backgroundColor: string,
|
||||||
debug?: boolean,
|
debug?: boolean,
|
||||||
): ChartElements => {
|
): ChartElements => {
|
||||||
const { chartWidth, chartHeight } = getChartDimentions(spreadsheet);
|
const { chartWidth, chartHeight } = getChartDimensions(spreadsheet);
|
||||||
|
|
||||||
const title = spreadsheet.title
|
const title = spreadsheet.title
|
||||||
? newTextElement({
|
? newTextElement({
|
||||||
|
|||||||
+83
-29
@@ -245,6 +245,7 @@ import {
|
|||||||
isTransparent,
|
isTransparent,
|
||||||
easeToValuesRAF,
|
easeToValuesRAF,
|
||||||
muteFSAbortError,
|
muteFSAbortError,
|
||||||
|
easeOut,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
@@ -291,13 +292,12 @@ import {
|
|||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
||||||
import {
|
import {
|
||||||
normalizeLink,
|
|
||||||
showHyperlinkTooltip,
|
showHyperlinkTooltip,
|
||||||
hideHyperlinkToolip,
|
hideHyperlinkToolip,
|
||||||
Hyperlink,
|
Hyperlink,
|
||||||
isPointHittingLinkIcon,
|
isPointHittingLinkIcon,
|
||||||
isLocalLink,
|
|
||||||
} from "../element/Hyperlink";
|
} from "../element/Hyperlink";
|
||||||
|
import { isLocalLink, normalizeLink } from "../data/url";
|
||||||
import { shouldShowBoundingBox } from "../element/transformHandles";
|
import { shouldShowBoundingBox } from "../element/transformHandles";
|
||||||
import { actionUnlockAllElements } from "../actions/actionElementLock";
|
import { actionUnlockAllElements } from "../actions/actionElementLock";
|
||||||
import { Fonts } from "../scene/Fonts";
|
import { Fonts } from "../scene/Fonts";
|
||||||
@@ -321,10 +321,7 @@ import {
|
|||||||
actionRemoveAllElementsFromFrame,
|
actionRemoveAllElementsFromFrame,
|
||||||
actionSelectAllElementsInFrame,
|
actionSelectAllElementsInFrame,
|
||||||
} from "../actions/actionFrame";
|
} from "../actions/actionFrame";
|
||||||
import {
|
import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas";
|
||||||
actionToggleHandTool,
|
|
||||||
zoomToFitElements,
|
|
||||||
} from "../actions/actionCanvas";
|
|
||||||
import { jotaiStore } from "../jotai";
|
import { jotaiStore } from "../jotai";
|
||||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||||
@@ -1720,7 +1717,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
theme: this.state.theme,
|
theme: this.state.theme,
|
||||||
imageCache: this.imageCache,
|
imageCache: this.imageCache,
|
||||||
isExporting: false,
|
isExporting: false,
|
||||||
renderScrollbars: !this.device.isMobile,
|
renderScrollbars: false,
|
||||||
},
|
},
|
||||||
callback: ({ atLeastOneVisibleElement, scrollBars }) => {
|
callback: ({ atLeastOneVisibleElement, scrollBars }) => {
|
||||||
if (scrollBars) {
|
if (scrollBars) {
|
||||||
@@ -2240,27 +2237,51 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
target:
|
target:
|
||||||
| ExcalidrawElement
|
| ExcalidrawElement
|
||||||
| readonly ExcalidrawElement[] = this.scene.getNonDeletedElements(),
|
| readonly ExcalidrawElement[] = this.scene.getNonDeletedElements(),
|
||||||
opts?: { fitToContent?: boolean; animate?: boolean; duration?: number },
|
opts?:
|
||||||
|
| {
|
||||||
|
fitToContent?: boolean;
|
||||||
|
fitToViewport?: never;
|
||||||
|
viewportZoomFactor?: never;
|
||||||
|
animate?: boolean;
|
||||||
|
duration?: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
fitToContent?: never;
|
||||||
|
fitToViewport?: boolean;
|
||||||
|
/** when fitToViewport=true, how much screen should the content cover,
|
||||||
|
* between 0.1 (10%) and 1 (100%)
|
||||||
|
*/
|
||||||
|
viewportZoomFactor?: number;
|
||||||
|
animate?: boolean;
|
||||||
|
duration?: number;
|
||||||
|
},
|
||||||
) => {
|
) => {
|
||||||
this.cancelInProgresAnimation?.();
|
this.cancelInProgresAnimation?.();
|
||||||
|
|
||||||
// convert provided target into ExcalidrawElement[] if necessary
|
// convert provided target into ExcalidrawElement[] if necessary
|
||||||
const targets = Array.isArray(target) ? target : [target];
|
const targetElements = Array.isArray(target) ? target : [target];
|
||||||
|
|
||||||
let zoom = this.state.zoom;
|
let zoom = this.state.zoom;
|
||||||
let scrollX = this.state.scrollX;
|
let scrollX = this.state.scrollX;
|
||||||
let scrollY = this.state.scrollY;
|
let scrollY = this.state.scrollY;
|
||||||
|
|
||||||
if (opts?.fitToContent) {
|
if (opts?.fitToContent || opts?.fitToViewport) {
|
||||||
// compute an appropriate viewport location (scroll X, Y) and zoom level
|
const { appState } = zoomToFit({
|
||||||
// that fit the target elements on the scene
|
targetElements,
|
||||||
const { appState } = zoomToFitElements(targets, this.state, false);
|
appState: this.state,
|
||||||
|
fitToViewport: !!opts?.fitToViewport,
|
||||||
|
viewportZoomFactor: opts?.viewportZoomFactor,
|
||||||
|
});
|
||||||
zoom = appState.zoom;
|
zoom = appState.zoom;
|
||||||
scrollX = appState.scrollX;
|
scrollX = appState.scrollX;
|
||||||
scrollY = appState.scrollY;
|
scrollY = appState.scrollY;
|
||||||
} else {
|
} else {
|
||||||
// compute only the viewport location, without any zoom adjustment
|
// compute only the viewport location, without any zoom adjustment
|
||||||
const scroll = calculateScrollCenter(targets, this.state, this.canvas);
|
const scroll = calculateScrollCenter(
|
||||||
|
targetElements,
|
||||||
|
this.state,
|
||||||
|
this.canvas,
|
||||||
|
);
|
||||||
scrollX = scroll.scrollX;
|
scrollX = scroll.scrollX;
|
||||||
scrollY = scroll.scrollY;
|
scrollY = scroll.scrollY;
|
||||||
}
|
}
|
||||||
@@ -2270,19 +2291,42 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (opts?.animate) {
|
if (opts?.animate) {
|
||||||
const origScrollX = this.state.scrollX;
|
const origScrollX = this.state.scrollX;
|
||||||
const origScrollY = this.state.scrollY;
|
const origScrollY = this.state.scrollY;
|
||||||
|
const origZoom = this.state.zoom.value;
|
||||||
|
|
||||||
// zoom animation could become problematic on scenes with large number
|
const cancel = easeToValuesRAF({
|
||||||
// of elements, setting it to its final value to improve user experience.
|
fromValues: {
|
||||||
//
|
scrollX: origScrollX,
|
||||||
// using zoomCanvas() to zoom on current viewport center
|
scrollY: origScrollY,
|
||||||
this.zoomCanvas(zoom.value);
|
zoom: origZoom,
|
||||||
|
},
|
||||||
|
toValues: { scrollX, scrollY, zoom: zoom.value },
|
||||||
|
interpolateValue: (from, to, progress, key) => {
|
||||||
|
// for zoom, use different easing
|
||||||
|
if (key === "zoom") {
|
||||||
|
return from * Math.pow(to / from, easeOut(progress));
|
||||||
|
}
|
||||||
|
// handle using default
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
onStep: ({ scrollX, scrollY, zoom }) => {
|
||||||
|
this.setState({
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
zoom: { value: zoom },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onStart: () => {
|
||||||
|
this.setState({ shouldCacheIgnoreZoom: true });
|
||||||
|
},
|
||||||
|
onEnd: () => {
|
||||||
|
this.setState({ shouldCacheIgnoreZoom: false });
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
this.setState({ shouldCacheIgnoreZoom: false });
|
||||||
|
},
|
||||||
|
duration: opts?.duration ?? 500,
|
||||||
|
});
|
||||||
|
|
||||||
const cancel = easeToValuesRAF(
|
|
||||||
[origScrollX, origScrollY],
|
|
||||||
[scrollX, scrollY],
|
|
||||||
(scrollX, scrollY) => this.setState({ scrollX, scrollY }),
|
|
||||||
{ duration: opts?.duration ?? 500 },
|
|
||||||
);
|
|
||||||
this.cancelInProgresAnimation = () => {
|
this.cancelInProgresAnimation = () => {
|
||||||
cancel();
|
cancel();
|
||||||
this.cancelInProgresAnimation = null;
|
this.cancelInProgresAnimation = null;
|
||||||
@@ -3054,7 +3098,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
container,
|
container,
|
||||||
);
|
);
|
||||||
if (container && parentCenterPosition) {
|
if (container && parentCenterPosition) {
|
||||||
shouldBindToContainer = true;
|
const boundTextElementToContainer = getBoundTextElement(container);
|
||||||
|
if (!boundTextElementToContainer) {
|
||||||
|
shouldBindToContainer = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let existingTextElement: NonDeleted<ExcalidrawTextElement> | null = null;
|
let existingTextElement: NonDeleted<ExcalidrawTextElement> | null = null;
|
||||||
|
|
||||||
@@ -3349,12 +3396,19 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.device.isMobile,
|
this.device.isMobile,
|
||||||
);
|
);
|
||||||
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
||||||
const url = this.hitLinkElement.link;
|
let url = this.hitLinkElement.link;
|
||||||
if (url) {
|
if (url) {
|
||||||
|
url = normalizeLink(url);
|
||||||
let customEvent;
|
let customEvent;
|
||||||
if (this.props.onLinkOpen) {
|
if (this.props.onLinkOpen) {
|
||||||
customEvent = wrapEvent(EVENT.EXCALIDRAW_LINK, event.nativeEvent);
|
customEvent = wrapEvent(EVENT.EXCALIDRAW_LINK, event.nativeEvent);
|
||||||
this.props.onLinkOpen(this.hitLinkElement, customEvent);
|
this.props.onLinkOpen(
|
||||||
|
{
|
||||||
|
...this.hitLinkElement,
|
||||||
|
link: url,
|
||||||
|
},
|
||||||
|
customEvent,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!customEvent?.defaultPrevented) {
|
if (!customEvent?.defaultPrevented) {
|
||||||
const target = isLocalLink(url) ? "_self" : "_blank";
|
const target = isLocalLink(url) ? "_self" : "_blank";
|
||||||
@@ -3362,7 +3416,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// https://mathiasbynens.github.io/rel-noopener/
|
// https://mathiasbynens.github.io/rel-noopener/
|
||||||
if (newWindow) {
|
if (newWindow) {
|
||||||
newWindow.opener = null;
|
newWindow.opener = null;
|
||||||
newWindow.location = normalizeLink(url);
|
newWindow.location = url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,34 @@ import { useSetAtom } from "jotai";
|
|||||||
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||||
import { jotaiScope } from "../jotai";
|
import { jotaiScope } from "../jotai";
|
||||||
|
|
||||||
|
export type DialogSize = number | "small" | "regular" | "wide" | undefined;
|
||||||
|
|
||||||
export interface DialogProps {
|
export interface DialogProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: "small" | "regular" | "wide";
|
size?: DialogSize;
|
||||||
onCloseRequest(): void;
|
onCloseRequest(): void;
|
||||||
title: React.ReactNode | false;
|
title: React.ReactNode | false;
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
closeOnClickOutside?: boolean;
|
closeOnClickOutside?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDialogSize(size: DialogSize): number {
|
||||||
|
if (size && typeof size === "number") {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (size) {
|
||||||
|
case "small":
|
||||||
|
return 550;
|
||||||
|
case "wide":
|
||||||
|
return 1024;
|
||||||
|
case "regular":
|
||||||
|
default:
|
||||||
|
return 800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const Dialog = (props: DialogProps) => {
|
export const Dialog = (props: DialogProps) => {
|
||||||
const [islandNode, setIslandNode] = useCallbackRefState<HTMLDivElement>();
|
const [islandNode, setIslandNode] = useCallbackRefState<HTMLDivElement>();
|
||||||
const [lastActiveElement] = useState(document.activeElement);
|
const [lastActiveElement] = useState(document.activeElement);
|
||||||
@@ -85,9 +103,7 @@ export const Dialog = (props: DialogProps) => {
|
|||||||
<Modal
|
<Modal
|
||||||
className={clsx("Dialog", props.className)}
|
className={clsx("Dialog", props.className)}
|
||||||
labelledBy="dialog-title"
|
labelledBy="dialog-title"
|
||||||
maxWidth={
|
maxWidth={getDialogSize(props.size)}
|
||||||
props.size === "wide" ? 1024 : props.size === "small" ? 550 : 800
|
|
||||||
}
|
|
||||||
onCloseRequest={onClose}
|
onCloseRequest={onClose}
|
||||||
closeOnClickOutside={props.closeOnClickOutside}
|
closeOnClickOutside={props.closeOnClickOutside}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,20 +2,140 @@
|
|||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.ExcButton {
|
.ExcButton {
|
||||||
&--color-primary {
|
--text-color: transparent;
|
||||||
color: var(--input-bg-color);
|
--border-color: transparent;
|
||||||
|
--back-color: transparent;
|
||||||
|
|
||||||
--accent-color: var(--color-primary);
|
color: var(--text-color);
|
||||||
--accent-color-hover: var(--color-primary-darker);
|
background-color: var(--back-color);
|
||||||
--accent-color-active: var(--color-primary-darkest);
|
border-color: var(--border-color);
|
||||||
|
|
||||||
|
&--color-primary {
|
||||||
|
&.ExcButton--variant-filled {
|
||||||
|
--text-color: var(--input-bg-color);
|
||||||
|
--back-color: var(--color-primary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--back-color: var(--color-primary-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
--back-color: var(--color-primary-darkest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ExcButton--variant-outlined,
|
||||||
|
&.ExcButton--variant-icon {
|
||||||
|
--text-color: var(--color-primary);
|
||||||
|
--border-color: var(--color-primary);
|
||||||
|
--back-color: var(--input-bg-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--text-color: var(--color-primary-darker);
|
||||||
|
--border-color: var(--color-primary-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
--text-color: var(--color-primary-darkest);
|
||||||
|
--border-color: var(--color-primary-darkest);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--color-danger {
|
&--color-danger {
|
||||||
color: var(--input-bg-color);
|
&.ExcButton--variant-filled {
|
||||||
|
--text-color: var(--color-danger-text);
|
||||||
|
--back-color: var(--color-danger-dark);
|
||||||
|
|
||||||
--accent-color: var(--color-danger);
|
&:hover {
|
||||||
--accent-color-hover: #d65550;
|
--back-color: var(--color-danger-darker);
|
||||||
--accent-color-active: #d1413c;
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
--back-color: var(--color-danger-darkest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ExcButton--variant-outlined,
|
||||||
|
&.ExcButton--variant-icon {
|
||||||
|
--text-color: var(--color-danger);
|
||||||
|
--border-color: var(--color-danger);
|
||||||
|
--back-color: transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--text-color: var(--color-danger-darkest);
|
||||||
|
--border-color: var(--color-danger-darkest);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
--text-color: var(--color-danger-darker);
|
||||||
|
--border-color: var(--color-danger-darker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--color-muted {
|
||||||
|
&.ExcButton--variant-filled {
|
||||||
|
--text-color: var(--island-bg-color);
|
||||||
|
--back-color: var(--color-gray-50);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--back-color: var(--color-gray-60);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
--back-color: var(--color-gray-80);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ExcButton--variant-outlined,
|
||||||
|
&.ExcButton--variant-icon {
|
||||||
|
--text-color: var(--color-muted-background);
|
||||||
|
--border-color: var(--color-muted);
|
||||||
|
--back-color: var(--island-bg-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--text-color: var(--color-muted-background-darker);
|
||||||
|
--border-color: var(--color-muted-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
--text-color: var(--color-muted-background-darker);
|
||||||
|
--border-color: var(--color-muted-darkest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--color-warning {
|
||||||
|
&.ExcButton--variant-filled {
|
||||||
|
--text-color: black;
|
||||||
|
--back-color: var(--color-warning-dark);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--back-color: var(--color-warning-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
--back-color: var(--color-warning-darkest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ExcButton--variant-outlined,
|
||||||
|
&.ExcButton--variant-icon {
|
||||||
|
--text-color: var(--color-warning-dark);
|
||||||
|
--border-color: var(--color-warning-dark);
|
||||||
|
--back-color: var(--input-bg-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--text-color: var(--color-warning-darker);
|
||||||
|
--border-color: var(--color-warning-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
--text-color: var(--color-warning-darkest);
|
||||||
|
--border-color: var(--color-warning-darkest);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -25,6 +145,8 @@
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
|
||||||
font-family: "Assistant";
|
font-family: "Assistant";
|
||||||
|
|
||||||
@@ -33,9 +155,9 @@
|
|||||||
transition: all 150ms ease-out;
|
transition: all 150ms ease-out;
|
||||||
|
|
||||||
&--size-large {
|
&--size-large {
|
||||||
font-weight: 400;
|
font-weight: 600;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
height: 3rem;
|
min-height: 3rem;
|
||||||
padding: 0.5rem 1.5rem;
|
padding: 0.5rem 1.5rem;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
|
||||||
@@ -45,48 +167,22 @@
|
|||||||
&--size-medium {
|
&--size-medium {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
height: 2.5rem;
|
min-height: 2.5rem;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--variant-filled {
|
|
||||||
background: var(--accent-color);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--accent-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: var(--accent-color-active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--variant-outlined,
|
|
||||||
&--variant-icon {
|
|
||||||
border: 1px solid var(--accent-color);
|
|
||||||
color: var(--accent-color);
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid var(--accent-color-hover);
|
|
||||||
color: var(--accent-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
border: 1px solid var(--accent-color-active);
|
|
||||||
color: var(--accent-color-active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--variant-icon {
|
&--variant-icon {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--fullWidth {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
width: 1.25rem;
|
width: 1.25rem;
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import clsx from "clsx";
|
|||||||
import "./FilledButton.scss";
|
import "./FilledButton.scss";
|
||||||
|
|
||||||
export type ButtonVariant = "filled" | "outlined" | "icon";
|
export type ButtonVariant = "filled" | "outlined" | "icon";
|
||||||
export type ButtonColor = "primary" | "danger";
|
export type ButtonColor = "primary" | "danger" | "warning" | "muted";
|
||||||
export type ButtonSize = "medium" | "large";
|
export type ButtonSize = "medium" | "large";
|
||||||
|
|
||||||
export type FilledButtonProps = {
|
export type FilledButtonProps = {
|
||||||
@@ -17,6 +17,7 @@ export type FilledButtonProps = {
|
|||||||
color?: ButtonColor;
|
color?: ButtonColor;
|
||||||
size?: ButtonSize;
|
size?: ButtonSize;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
fullWidth?: boolean;
|
||||||
|
|
||||||
startIcon?: React.ReactNode;
|
startIcon?: React.ReactNode;
|
||||||
};
|
};
|
||||||
@@ -31,6 +32,7 @@ export const FilledButton = forwardRef<HTMLButtonElement, FilledButtonProps>(
|
|||||||
variant = "filled",
|
variant = "filled",
|
||||||
color = "primary",
|
color = "primary",
|
||||||
size = "medium",
|
size = "medium",
|
||||||
|
fullWidth,
|
||||||
className,
|
className,
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@@ -42,6 +44,7 @@ export const FilledButton = forwardRef<HTMLButtonElement, FilledButtonProps>(
|
|||||||
`ExcButton--color-${color}`,
|
`ExcButton--color-${color}`,
|
||||||
`ExcButton--variant-${variant}`,
|
`ExcButton--variant-${variant}`,
|
||||||
`ExcButton--size-${size}`,
|
`ExcButton--size-${size}`,
|
||||||
|
{ "ExcButton--fullWidth": fullWidth },
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const Header = () => (
|
|||||||
<div className="HelpDialog__header">
|
<div className="HelpDialog__header">
|
||||||
<a
|
<a
|
||||||
className="HelpDialog__btn"
|
className="HelpDialog__btn"
|
||||||
href="https://github.com/excalidraw/excalidraw#documentation"
|
href="https://docs.excalidraw.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { jotaiScope } from "../jotai";
|
|||||||
import { Provider, useAtom, useAtomValue } from "jotai";
|
import { Provider, useAtom, useAtomValue } from "jotai";
|
||||||
import MainMenu from "./main-menu/MainMenu";
|
import MainMenu from "./main-menu/MainMenu";
|
||||||
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
||||||
|
import { OverwriteConfirmDialog } from "./OverwriteConfirm/OverwriteConfirm";
|
||||||
import { HandButton } from "./HandButton";
|
import { HandButton } from "./HandButton";
|
||||||
import { isHandToolActive } from "../appState";
|
import { isHandToolActive } from "../appState";
|
||||||
import { TunnelsContext, useInitializeTunnels } from "../context/tunnels";
|
import { TunnelsContext, useInitializeTunnels } from "../context/tunnels";
|
||||||
@@ -99,6 +100,15 @@ const DefaultMainMenu: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DefaultOverwriteConfirmDialog = () => {
|
||||||
|
return (
|
||||||
|
<OverwriteConfirmDialog __fallback>
|
||||||
|
<OverwriteConfirmDialog.Actions.SaveToDisk />
|
||||||
|
<OverwriteConfirmDialog.Actions.ExportToImage />
|
||||||
|
</OverwriteConfirmDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const LayerUI = ({
|
const LayerUI = ({
|
||||||
actionManager,
|
actionManager,
|
||||||
appState,
|
appState,
|
||||||
@@ -343,6 +353,7 @@ const LayerUI = ({
|
|||||||
>
|
>
|
||||||
{t("toolBar.library")}
|
{t("toolBar.library")}
|
||||||
</DefaultSidebar.Trigger>
|
</DefaultSidebar.Trigger>
|
||||||
|
<DefaultOverwriteConfirmDialog />
|
||||||
{/* ------------------------------------------------------------------ */}
|
{/* ------------------------------------------------------------------ */}
|
||||||
|
|
||||||
{appState.isLoading && <LoadingMessage delay={250} />}
|
{appState.isLoading && <LoadingMessage delay={250} />}
|
||||||
@@ -374,6 +385,7 @@ const LayerUI = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ActiveConfirmDialog />
|
<ActiveConfirmDialog />
|
||||||
|
<tunnels.OverwriteConfirmDialogTunnel.Out />
|
||||||
{renderImageExportDialog()}
|
{renderImageExportDialog()}
|
||||||
{renderJSONExportDialog()}
|
{renderJSONExportDialog()}
|
||||||
{appState.pasteDialog.shown && (
|
{appState.pasteDialog.shown && (
|
||||||
@@ -387,7 +399,7 @@ const LayerUI = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{device.isMobile && !eyeDropperState && (
|
{device.isMobile && (
|
||||||
<MobileMenu
|
<MobileMenu
|
||||||
appState={appState}
|
appState={appState}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
.excalidraw {
|
.excalidraw {
|
||||||
&.excalidraw-modal-container {
|
&.excalidraw-modal-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 10;
|
z-index: var(--zIndex-modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Modal {
|
.Modal {
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
@import "../../css/variables.module";
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.OverwriteConfirm {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
isolation: isolate;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.3125rem;
|
||||||
|
line-height: 130%;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__Description {
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
@include isMobile {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: 2.5rem;
|
||||||
|
|
||||||
|
background: var(--color-danger-background);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
|
font-family: "Assistant";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 150%;
|
||||||
|
|
||||||
|
color: var(--color-danger-color);
|
||||||
|
|
||||||
|
&__spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 2.5rem;
|
||||||
|
background: var(--color-danger-icon-background);
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 3.5rem;
|
||||||
|
|
||||||
|
padding: 0.75rem;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: var(--color-danger-icon-color);
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.OverwriteConfirm__Description--color-warning {
|
||||||
|
background: var(--color-warning-background);
|
||||||
|
color: var(--color-warning-color);
|
||||||
|
|
||||||
|
.OverwriteConfirm__Description__icon {
|
||||||
|
background: var(--color-warning-icon-background);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: var(--color-warning-icon-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__Actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-items: stretch;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
|
||||||
|
@include isMobile {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__Action {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-basis: 50%;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
height: 100%;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 130%;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
|
||||||
|
import { useTunnels } from "../../context/tunnels";
|
||||||
|
import { jotaiScope } from "../../jotai";
|
||||||
|
import { Dialog } from "../Dialog";
|
||||||
|
import { withInternalFallback } from "../hoc/withInternalFallback";
|
||||||
|
import { overwriteConfirmStateAtom } from "./OverwriteConfirmState";
|
||||||
|
|
||||||
|
import { FilledButton } from "../FilledButton";
|
||||||
|
import { alertTriangleIcon } from "../icons";
|
||||||
|
import { Actions, Action } from "./OverwriteConfirmActions";
|
||||||
|
import "./OverwriteConfirm.scss";
|
||||||
|
|
||||||
|
export type OverwriteConfirmDialogProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OverwriteConfirmDialog = Object.assign(
|
||||||
|
withInternalFallback(
|
||||||
|
"OverwriteConfirmDialog",
|
||||||
|
({ children }: OverwriteConfirmDialogProps) => {
|
||||||
|
const { OverwriteConfirmDialogTunnel } = useTunnels();
|
||||||
|
const [overwriteConfirmState, setState] = useAtom(
|
||||||
|
overwriteConfirmStateAtom,
|
||||||
|
jotaiScope,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!overwriteConfirmState.active) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
overwriteConfirmState.onClose();
|
||||||
|
setState((state) => ({ ...state, active: false }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
overwriteConfirmState.onConfirm();
|
||||||
|
setState((state) => ({ ...state, active: false }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OverwriteConfirmDialogTunnel.In>
|
||||||
|
<Dialog onCloseRequest={handleClose} title={false} size={916}>
|
||||||
|
<div className="OverwriteConfirm">
|
||||||
|
<h3>{overwriteConfirmState.title}</h3>
|
||||||
|
<div
|
||||||
|
className={`OverwriteConfirm__Description OverwriteConfirm__Description--color-${overwriteConfirmState.color}`}
|
||||||
|
>
|
||||||
|
<div className="OverwriteConfirm__Description__icon">
|
||||||
|
{alertTriangleIcon}
|
||||||
|
</div>
|
||||||
|
<div>{overwriteConfirmState.description}</div>
|
||||||
|
<div className="OverwriteConfirm__Description__spacer"></div>
|
||||||
|
<FilledButton
|
||||||
|
color={overwriteConfirmState.color}
|
||||||
|
size="large"
|
||||||
|
label={overwriteConfirmState.actionLabel}
|
||||||
|
onClick={handleConfirm}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Actions>{children}</Actions>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</OverwriteConfirmDialogTunnel.In>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
Actions,
|
||||||
|
Action,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export { OverwriteConfirmDialog };
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { FilledButton } from "../FilledButton";
|
||||||
|
import { useExcalidrawActionManager, useExcalidrawSetAppState } from "../App";
|
||||||
|
import { actionSaveFileToDisk } from "../../actions";
|
||||||
|
import { useI18n } from "../../i18n";
|
||||||
|
import { actionChangeExportEmbedScene } from "../../actions/actionExport";
|
||||||
|
|
||||||
|
export type ActionProps = {
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
actionLabel: string;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Action = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
actionLabel,
|
||||||
|
onClick,
|
||||||
|
}: ActionProps) => {
|
||||||
|
return (
|
||||||
|
<div className="OverwriteConfirm__Actions__Action">
|
||||||
|
<h4>{title}</h4>
|
||||||
|
<div className="OverwriteConfirm__Actions__Action__content">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<FilledButton
|
||||||
|
variant="outlined"
|
||||||
|
color="muted"
|
||||||
|
label={actionLabel}
|
||||||
|
size="large"
|
||||||
|
fullWidth
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExportToImage = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
const setAppState = useExcalidrawSetAppState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Action
|
||||||
|
title={t("overwriteConfirm.action.exportToImage.title")}
|
||||||
|
actionLabel={t("overwriteConfirm.action.exportToImage.button")}
|
||||||
|
onClick={() => {
|
||||||
|
actionManager.executeAction(actionChangeExportEmbedScene, "ui", true);
|
||||||
|
setAppState({ openDialog: "imageExport" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("overwriteConfirm.action.exportToImage.description")}
|
||||||
|
</Action>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaveToDisk = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Action
|
||||||
|
title={t("overwriteConfirm.action.saveToDisk.title")}
|
||||||
|
actionLabel={t("overwriteConfirm.action.saveToDisk.button")}
|
||||||
|
onClick={() => {
|
||||||
|
actionManager.executeAction(actionSaveFileToDisk, "ui");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("overwriteConfirm.action.saveToDisk.description")}
|
||||||
|
</Action>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Actions = Object.assign(
|
||||||
|
({ children }: { children: React.ReactNode }) => {
|
||||||
|
return <div className="OverwriteConfirm__Actions">{children}</div>;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ExportToImage,
|
||||||
|
SaveToDisk,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export { Actions };
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { atom } from "jotai";
|
||||||
|
import { jotaiStore } from "../../jotai";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export type OverwriteConfirmState =
|
||||||
|
| {
|
||||||
|
active: true;
|
||||||
|
title: string;
|
||||||
|
description: React.ReactNode;
|
||||||
|
actionLabel: string;
|
||||||
|
color: "danger" | "warning";
|
||||||
|
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onReject: () => void;
|
||||||
|
}
|
||||||
|
| { active: false };
|
||||||
|
|
||||||
|
export const overwriteConfirmStateAtom = atom<OverwriteConfirmState>({
|
||||||
|
active: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function openConfirmModal({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
actionLabel,
|
||||||
|
color,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
description: React.ReactNode;
|
||||||
|
actionLabel: string;
|
||||||
|
color: "danger" | "warning";
|
||||||
|
}) {
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
jotaiStore.set(overwriteConfirmStateAtom, {
|
||||||
|
active: true,
|
||||||
|
onConfirm: () => resolve(true),
|
||||||
|
onClose: () => resolve(false),
|
||||||
|
onReject: () => resolve(false),
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
actionLabel,
|
||||||
|
color,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
@import "../css/variables.module";
|
||||||
|
|
||||||
|
.excalidraw {
|
||||||
|
.ShareableLinkDialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: var(--color-primary-light-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-family: "Assistant";
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.313rem;
|
||||||
|
line-height: 130%;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__popover {
|
||||||
|
@keyframes RoomDialog__popover__scaleIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0.125rem 0.5rem;
|
||||||
|
gap: 0.125rem;
|
||||||
|
|
||||||
|
height: 1.125rem;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.6875rem;
|
||||||
|
|
||||||
|
font-family: "Assistant";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 110%;
|
||||||
|
|
||||||
|
background: var(--color-success-lighter);
|
||||||
|
color: var(--color-success);
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
width: 0.875rem;
|
||||||
|
height: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform-origin: var(--radix-popover-content-transform-origin);
|
||||||
|
animation: RoomDialog__popover__scaleIn 150ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__linkRow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
border-top: 1px solid var(--color-gray-20);
|
||||||
|
|
||||||
|
padding: 0.5rem 0.5rem 0;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 150%;
|
||||||
|
|
||||||
|
& p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p + p {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { useRef, useState } from "react";
|
||||||
|
import * as Popover from "@radix-ui/react-popover";
|
||||||
|
|
||||||
|
import { copyTextToSystemClipboard } from "../clipboard";
|
||||||
|
import { useI18n } from "../i18n";
|
||||||
|
|
||||||
|
import { Dialog } from "./Dialog";
|
||||||
|
import { TextField } from "./TextField";
|
||||||
|
import { FilledButton } from "./FilledButton";
|
||||||
|
import { copyIcon, tablerCheckIcon } from "./icons";
|
||||||
|
|
||||||
|
import "./ShareableLinkDialog.scss";
|
||||||
|
|
||||||
|
export type ShareableLinkDialogProps = {
|
||||||
|
link: string;
|
||||||
|
|
||||||
|
onCloseRequest: () => void;
|
||||||
|
setErrorMessage: (error: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShareableLinkDialog = ({
|
||||||
|
link,
|
||||||
|
onCloseRequest,
|
||||||
|
setErrorMessage,
|
||||||
|
}: ShareableLinkDialogProps) => {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const [justCopied, setJustCopied] = useState(false);
|
||||||
|
const timerRef = useRef<number>(0);
|
||||||
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const copyRoomLink = async () => {
|
||||||
|
try {
|
||||||
|
await copyTextToSystemClipboard(link);
|
||||||
|
|
||||||
|
setJustCopied(true);
|
||||||
|
|
||||||
|
if (timerRef.current) {
|
||||||
|
window.clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
timerRef.current = window.setTimeout(() => {
|
||||||
|
setJustCopied(false);
|
||||||
|
}, 3000);
|
||||||
|
} catch (error: any) {
|
||||||
|
setErrorMessage(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.current?.select();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog onCloseRequest={onCloseRequest} title={false} size="small">
|
||||||
|
<div className="ShareableLinkDialog">
|
||||||
|
<h3>Shareable link</h3>
|
||||||
|
<div className="ShareableLinkDialog__linkRow">
|
||||||
|
<TextField
|
||||||
|
ref={ref}
|
||||||
|
label="Link"
|
||||||
|
readonly
|
||||||
|
fullWidth
|
||||||
|
value={link}
|
||||||
|
selectOnRender
|
||||||
|
/>
|
||||||
|
<Popover.Root open={justCopied}>
|
||||||
|
<Popover.Trigger asChild>
|
||||||
|
<FilledButton
|
||||||
|
size="large"
|
||||||
|
label="Copy link"
|
||||||
|
startIcon={copyIcon}
|
||||||
|
onClick={copyRoomLink}
|
||||||
|
/>
|
||||||
|
</Popover.Trigger>
|
||||||
|
<Popover.Content
|
||||||
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
|
onCloseAutoFocus={(event) => event.preventDefault()}
|
||||||
|
className="ShareableLinkDialog__popover"
|
||||||
|
side="top"
|
||||||
|
align="end"
|
||||||
|
sideOffset={5.5}
|
||||||
|
>
|
||||||
|
{tablerCheckIcon} copied
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Root>
|
||||||
|
</div>
|
||||||
|
<div className="ShareableLinkDialog__description">
|
||||||
|
🔒 {t("alerts.uploadedSecurly")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import { forwardRef, useRef, useImperativeHandle, KeyboardEvent } from "react";
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useRef,
|
||||||
|
useImperativeHandle,
|
||||||
|
KeyboardEvent,
|
||||||
|
useLayoutEffect,
|
||||||
|
} from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import "./TextField.scss";
|
import "./TextField.scss";
|
||||||
@@ -12,6 +18,7 @@ export type TextFieldProps = {
|
|||||||
|
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
|
selectOnRender?: boolean;
|
||||||
|
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@@ -19,13 +26,28 @@ export type TextFieldProps = {
|
|||||||
|
|
||||||
export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
||||||
(
|
(
|
||||||
{ value, onChange, label, fullWidth, placeholder, readonly, onKeyDown },
|
{
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
label,
|
||||||
|
fullWidth,
|
||||||
|
placeholder,
|
||||||
|
readonly,
|
||||||
|
selectOnRender,
|
||||||
|
onKeyDown,
|
||||||
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const innerRef = useRef<HTMLInputElement | null>(null);
|
const innerRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => innerRef.current!);
|
useImperativeHandle(ref, () => innerRef.current!);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (selectOnRender) {
|
||||||
|
innerRef.current?.select();
|
||||||
|
}
|
||||||
|
}, [selectOnRender]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx("ExcTextField", {
|
className={clsx("ExcTextField", {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
Roboto, Helvetica, Arial, sans-serif;
|
Roboto, Helvetica, Arial, sans-serif;
|
||||||
font-family: var(--ui-font);
|
font-family: var(--ui-font);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1000;
|
z-index: var(--zIndex-popup);
|
||||||
|
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|||||||
@@ -1608,13 +1608,30 @@ export const tablerCheckIcon = createIcon(
|
|||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const alertTriangleIcon = createIcon(
|
||||||
|
<>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M10.24 3.957l-8.422 14.06a1.989 1.989 0 0 0 1.7 2.983h16.845a1.989 1.989 0 0 0 1.7 -2.983l-8.423 -14.06a1.989 1.989 0 0 0 -3.4 0z" />
|
||||||
|
<path d="M12 9v4" />
|
||||||
|
<path d="M12 17h.01" />
|
||||||
|
</>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|
||||||
export const eyeDropperIcon = createIcon(
|
export const eyeDropperIcon = createIcon(
|
||||||
<g strokeWidth={1.25}>
|
<g strokeWidth={1.25}>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path d="m9.168 5.833 5 5M9.999 6.667l-6.667 6.666v3.334h3.333L13.332 10M9.999 6.667l3.083-3.084a.833.833 0 0 1 1.167 0l2.166 2.167a.833.833 0 0 1 0 1.167L13.332 10M9.999 6.667 13.332 10" />
|
||||||
<path d="M11 7l6 6"></path>
|
<rect
|
||||||
<path d="M4 16l11.7 -11.7a1 1 0 0 1 1.4 0l2.6 2.6a1 1 0 0 1 0 1.4l-11.7 11.7h-4v-4z"></path>
|
x="13.684"
|
||||||
|
y="2.976"
|
||||||
|
width="4.751"
|
||||||
|
height="5.106"
|
||||||
|
rx="1"
|
||||||
|
transform="rotate(45 13.684 2.976)"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
modifiedTablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const extraToolsIcon = createIcon(
|
export const extraToolsIcon = createIcon(
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { useI18n } from "../../i18n";
|
import { useI18n } from "../../i18n";
|
||||||
import { useExcalidrawSetAppState, useExcalidrawActionManager } from "../App";
|
import {
|
||||||
|
useExcalidrawSetAppState,
|
||||||
|
useExcalidrawActionManager,
|
||||||
|
useExcalidrawElements,
|
||||||
|
} from "../App";
|
||||||
import {
|
import {
|
||||||
ExportIcon,
|
ExportIcon,
|
||||||
ExportImageIcon,
|
ExportImageIcon,
|
||||||
@@ -29,19 +33,42 @@ import { useSetAtom } from "jotai";
|
|||||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||||
import { jotaiScope } from "../../jotai";
|
import { jotaiScope } from "../../jotai";
|
||||||
import { useUIAppState } from "../../context/ui-appState";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
|
||||||
|
import Trans from "../Trans";
|
||||||
|
|
||||||
export const LoadScene = () => {
|
export const LoadScene = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
const elements = useExcalidrawElements();
|
||||||
|
|
||||||
if (!actionManager.isActionEnabled(actionLoadScene)) {
|
if (!actionManager.isActionEnabled(actionLoadScene)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSelect = async () => {
|
||||||
|
if (
|
||||||
|
!elements.length ||
|
||||||
|
(await openConfirmModal({
|
||||||
|
title: t("overwriteConfirm.modal.loadFromFile.title"),
|
||||||
|
actionLabel: t("overwriteConfirm.modal.loadFromFile.button"),
|
||||||
|
color: "warning",
|
||||||
|
description: (
|
||||||
|
<Trans
|
||||||
|
i18nKey="overwriteConfirm.modal.loadFromFile.description"
|
||||||
|
bold={(text) => <strong>{text}</strong>}
|
||||||
|
br={() => <br />}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
actionManager.executeAction(actionLoadScene);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
icon={LoadIcon}
|
icon={LoadIcon}
|
||||||
onSelect={() => actionManager.executeAction(actionLoadScene)}
|
onSelect={handleSelect}
|
||||||
data-testid="load-button"
|
data-testid="load-button"
|
||||||
shortcut={getShortcutFromShortcutName("loadScene")}
|
shortcut={getShortcutFromShortcutName("loadScene")}
|
||||||
aria-label={t("buttons.load")}
|
aria-label={t("buttons.load")}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type TunnelsContextValue = {
|
|||||||
FooterCenterTunnel: Tunnel;
|
FooterCenterTunnel: Tunnel;
|
||||||
DefaultSidebarTriggerTunnel: Tunnel;
|
DefaultSidebarTriggerTunnel: Tunnel;
|
||||||
DefaultSidebarTabTriggersTunnel: Tunnel;
|
DefaultSidebarTabTriggersTunnel: Tunnel;
|
||||||
|
OverwriteConfirmDialogTunnel: Tunnel;
|
||||||
jotaiScope: symbol;
|
jotaiScope: symbol;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ export const useInitializeTunnels = () => {
|
|||||||
FooterCenterTunnel: tunnel(),
|
FooterCenterTunnel: tunnel(),
|
||||||
DefaultSidebarTriggerTunnel: tunnel(),
|
DefaultSidebarTriggerTunnel: tunnel(),
|
||||||
DefaultSidebarTabTriggersTunnel: tunnel(),
|
DefaultSidebarTabTriggersTunnel: tunnel(),
|
||||||
|
OverwriteConfirmDialogTunnel: tunnel(),
|
||||||
jotaiScope: Symbol(),
|
jotaiScope: Symbol(),
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
--zIndex-canvas: 1;
|
--zIndex-canvas: 1;
|
||||||
--zIndex-wysiwyg: 2;
|
--zIndex-wysiwyg: 2;
|
||||||
--zIndex-layerUI: 3;
|
--zIndex-layerUI: 3;
|
||||||
|
|
||||||
|
--zIndex-modal: 1000;
|
||||||
|
--zIndex-popup: 1001;
|
||||||
|
--zIndex-toast: 999999;
|
||||||
|
|
||||||
|
--sab: env(safe-area-inset-bottom);
|
||||||
|
--sal: env(safe-area-inset-left);
|
||||||
|
--sar: env(safe-area-inset-right);
|
||||||
|
--sat: env(safe-area-inset-top);
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
|
|||||||
+45
-4
@@ -27,10 +27,6 @@
|
|||||||
--popup-secondary-bg-color: #{$oc-gray-1};
|
--popup-secondary-bg-color: #{$oc-gray-1};
|
||||||
--popup-text-color: #{$oc-black};
|
--popup-text-color: #{$oc-black};
|
||||||
--popup-text-inverted-color: #{$oc-white};
|
--popup-text-inverted-color: #{$oc-white};
|
||||||
--sab: env(safe-area-inset-bottom);
|
|
||||||
--sal: env(safe-area-inset-left);
|
|
||||||
--sar: env(safe-area-inset-right);
|
|
||||||
--sat: env(safe-area-inset-top);
|
|
||||||
--select-highlight-color: #{$oc-blue-5};
|
--select-highlight-color: #{$oc-blue-5};
|
||||||
--shadow-island: 0px 7px 14px rgba(0, 0, 0, 0.05),
|
--shadow-island: 0px 7px 14px rgba(0, 0, 0, 0.05),
|
||||||
0px 0px 3.12708px rgba(0, 0, 0, 0.0798),
|
0px 0px 3.12708px rgba(0, 0, 0, 0.0798),
|
||||||
@@ -99,9 +95,33 @@
|
|||||||
--color-gray-100: #121212;
|
--color-gray-100: #121212;
|
||||||
|
|
||||||
--color-warning: #fceeca;
|
--color-warning: #fceeca;
|
||||||
|
--color-warning-dark: #f5c354;
|
||||||
|
--color-warning-darker: #f3ab2c;
|
||||||
|
--color-warning-darkest: #ec8b14;
|
||||||
--color-text-warning: var(--text-primary-color);
|
--color-text-warning: var(--text-primary-color);
|
||||||
|
|
||||||
--color-danger: #db6965;
|
--color-danger: #db6965;
|
||||||
|
--color-danger-dark: #db6965;
|
||||||
|
--color-danger-darker: #d65550;
|
||||||
|
--color-danger-darkest: #d1413c;
|
||||||
|
--color-danger-text: black;
|
||||||
|
|
||||||
|
--color-danger-background: #fff0f0;
|
||||||
|
--color-danger-icon-background: #ffdad6;
|
||||||
|
--color-danger-color: #700000;
|
||||||
|
--color-danger-icon-color: #700000;
|
||||||
|
|
||||||
|
--color-warning-background: var(--color-warning);
|
||||||
|
--color-warning-icon-background: var(--color-warning-dark);
|
||||||
|
--color-warning-color: var(--text-primary-color);
|
||||||
|
--color-warning-icon-color: var(--text-primary-color);
|
||||||
|
|
||||||
|
--color-muted: var(--color-gray-30);
|
||||||
|
--color-muted-darker: var(--color-gray-60);
|
||||||
|
--color-muted-darkest: var(--color-gray-100);
|
||||||
|
--color-muted-background: var(--color-gray-80);
|
||||||
|
--color-muted-background-darker: var(--color-gray-100);
|
||||||
|
|
||||||
--color-promo: #e70078;
|
--color-promo: #e70078;
|
||||||
--color-success: #268029;
|
--color-success: #268029;
|
||||||
--color-success-lighter: #cafccc;
|
--color-success-lighter: #cafccc;
|
||||||
@@ -177,6 +197,27 @@
|
|||||||
--color-text-warning: var(--color-gray-80);
|
--color-text-warning: var(--color-gray-80);
|
||||||
|
|
||||||
--color-danger: #ffa8a5;
|
--color-danger: #ffa8a5;
|
||||||
|
--color-danger-dark: #672120;
|
||||||
|
--color-danger-darker: #8f2625;
|
||||||
|
--color-danger-darkest: #ac2b29;
|
||||||
|
--color-danger-text: #fbcbcc;
|
||||||
|
|
||||||
|
--color-danger-background: #fbcbcc;
|
||||||
|
--color-danger-icon-background: #672120;
|
||||||
|
--color-danger-color: #261919;
|
||||||
|
--color-danger-icon-color: #fbcbcc;
|
||||||
|
|
||||||
|
--color-warning-background: var(--color-warning);
|
||||||
|
--color-warning-icon-background: var(--color-warning-dark);
|
||||||
|
--color-warning-color: var(--color-gray-80);
|
||||||
|
--color-warning-icon-color: var(--color-gray-80);
|
||||||
|
|
||||||
|
--color-muted: var(--color-gray-80);
|
||||||
|
--color-muted-darker: var(--color-gray-60);
|
||||||
|
--color-muted-darkest: var(--color-gray-20);
|
||||||
|
--color-muted-background: var(--color-gray-40);
|
||||||
|
--color-muted-background-darker: var(--color-gray-20);
|
||||||
|
|
||||||
--color-promo: #d297ff;
|
--color-promo: #d297ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-1
@@ -41,6 +41,7 @@ import {
|
|||||||
measureBaseline,
|
measureBaseline,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import { COLOR_PALETTE } from "../colors";
|
import { COLOR_PALETTE } from "../colors";
|
||||||
|
import { normalizeLink } from "./url";
|
||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
AppState,
|
AppState,
|
||||||
@@ -142,7 +143,7 @@ const restoreElementWithProperties = <
|
|||||||
? element.boundElementIds.map((id) => ({ type: "arrow", id }))
|
? element.boundElementIds.map((id) => ({ type: "arrow", id }))
|
||||||
: element.boundElements ?? [],
|
: element.boundElements ?? [],
|
||||||
updated: element.updated ?? getUpdatedTimestamp(),
|
updated: element.updated ?? getUpdatedTimestamp(),
|
||||||
link: element.link ?? null,
|
link: element.link ? normalizeLink(element.link) : null,
|
||||||
locked: element.locked ?? false,
|
locked: element.locked ?? false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -370,6 +371,24 @@ const repairBoundElement = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an element's frameId if its containing frame is non-existent
|
||||||
|
*
|
||||||
|
* NOTE mutates elements.
|
||||||
|
*/
|
||||||
|
const repairFrameMembership = (
|
||||||
|
element: Mutable<ExcalidrawElement>,
|
||||||
|
elementsMap: Map<string, Mutable<ExcalidrawElement>>,
|
||||||
|
) => {
|
||||||
|
if (element.frameId) {
|
||||||
|
const containingFrame = elementsMap.get(element.frameId);
|
||||||
|
|
||||||
|
if (!containingFrame) {
|
||||||
|
element.frameId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const restoreElements = (
|
export const restoreElements = (
|
||||||
elements: ImportedDataState["elements"],
|
elements: ImportedDataState["elements"],
|
||||||
/** NOTE doesn't serve for reconciliation */
|
/** NOTE doesn't serve for reconciliation */
|
||||||
@@ -410,6 +429,10 @@ export const restoreElements = (
|
|||||||
// repair binding. Mutates elements.
|
// repair binding. Mutates elements.
|
||||||
const restoredElementsMap = arrayToMap(restoredElements);
|
const restoredElementsMap = arrayToMap(restoredElements);
|
||||||
for (const element of restoredElements) {
|
for (const element of restoredElements) {
|
||||||
|
if (element.frameId) {
|
||||||
|
repairFrameMembership(element, restoredElementsMap);
|
||||||
|
}
|
||||||
|
|
||||||
if (isTextElement(element) && element.containerId) {
|
if (isTextElement(element) && element.containerId) {
|
||||||
repairBoundElement(element, restoredElementsMap);
|
repairBoundElement(element, restoredElementsMap);
|
||||||
} else if (element.boundElements) {
|
} else if (element.boundElements) {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { normalizeLink } from "./url";
|
||||||
|
|
||||||
|
describe("normalizeLink", () => {
|
||||||
|
// NOTE not an extensive XSS test suite, just to check if we're not
|
||||||
|
// regressing in sanitization
|
||||||
|
it("should sanitize links", () => {
|
||||||
|
expect(
|
||||||
|
// eslint-disable-next-line no-script-url
|
||||||
|
normalizeLink(`javascript://%0aalert(document.domain)`).startsWith(
|
||||||
|
// eslint-disable-next-line no-script-url
|
||||||
|
`javascript:`,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(normalizeLink("ola")).toBe("ola");
|
||||||
|
expect(normalizeLink(" ola")).toBe("ola");
|
||||||
|
|
||||||
|
expect(normalizeLink("https://www.excalidraw.com")).toBe(
|
||||||
|
"https://www.excalidraw.com",
|
||||||
|
);
|
||||||
|
expect(normalizeLink("www.excalidraw.com")).toBe("www.excalidraw.com");
|
||||||
|
expect(normalizeLink("/ola")).toBe("/ola");
|
||||||
|
expect(normalizeLink("http://test")).toBe("http://test");
|
||||||
|
expect(normalizeLink("ftp://test")).toBe("ftp://test");
|
||||||
|
expect(normalizeLink("file://")).toBe("file://");
|
||||||
|
expect(normalizeLink("file://")).toBe("file://");
|
||||||
|
expect(normalizeLink("[test](https://test)")).toBe("[test](https://test)");
|
||||||
|
expect(normalizeLink("[[test]]")).toBe("[[test]]");
|
||||||
|
expect(normalizeLink("<test>")).toBe("<test>");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { sanitizeUrl } from "@braintree/sanitize-url";
|
||||||
|
|
||||||
|
export const normalizeLink = (link: string) => {
|
||||||
|
return sanitizeUrl(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isLocalLink = (link: string | null) => {
|
||||||
|
return !!(link?.includes(location.origin) || link?.startsWith("/"));
|
||||||
|
};
|
||||||
@@ -29,6 +29,7 @@ import { getTooltipDiv, updateTooltipPosition } from "../components/Tooltip";
|
|||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
import { isPointHittingElementBoundingBox } from "./collision";
|
import { isPointHittingElementBoundingBox } from "./collision";
|
||||||
import { getElementAbsoluteCoords } from "./";
|
import { getElementAbsoluteCoords } from "./";
|
||||||
|
import { isLocalLink, normalizeLink } from "../data/url";
|
||||||
|
|
||||||
import "./Hyperlink.scss";
|
import "./Hyperlink.scss";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
@@ -166,7 +167,7 @@ export const Hyperlink = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<a
|
<a
|
||||||
href={element.link || ""}
|
href={normalizeLink(element.link || "")}
|
||||||
className={clsx("excalidraw-hyperlinkContainer-link", {
|
className={clsx("excalidraw-hyperlinkContainer-link", {
|
||||||
"d-none": isEditing,
|
"d-none": isEditing,
|
||||||
})}
|
})}
|
||||||
@@ -177,7 +178,13 @@ export const Hyperlink = ({
|
|||||||
EVENT.EXCALIDRAW_LINK,
|
EVENT.EXCALIDRAW_LINK,
|
||||||
event.nativeEvent,
|
event.nativeEvent,
|
||||||
);
|
);
|
||||||
onLinkOpen(element, customEvent);
|
onLinkOpen(
|
||||||
|
{
|
||||||
|
...element,
|
||||||
|
link: normalizeLink(element.link),
|
||||||
|
},
|
||||||
|
customEvent,
|
||||||
|
);
|
||||||
if (customEvent.defaultPrevented) {
|
if (customEvent.defaultPrevented) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -231,21 +238,6 @@ const getCoordsForPopover = (
|
|||||||
return { x, y };
|
return { x, y };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const normalizeLink = (link: string) => {
|
|
||||||
link = link.trim();
|
|
||||||
if (link) {
|
|
||||||
// prefix with protocol if not fully-qualified
|
|
||||||
if (!link.includes("://") && !/^[[\\/]/.test(link)) {
|
|
||||||
link = `https://${link}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return link;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isLocalLink = (link: string | null) => {
|
|
||||||
return !!(link?.includes(location.origin) || link?.startsWith("/"));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const actionLink = register({
|
export const actionLink = register({
|
||||||
name: "hyperlink",
|
name: "hyperlink",
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
|
|||||||
@@ -862,7 +862,6 @@ const VALID_CONTAINER_TYPES = new Set([
|
|||||||
"rectangle",
|
"rectangle",
|
||||||
"ellipse",
|
"ellipse",
|
||||||
"diamond",
|
"diamond",
|
||||||
"image",
|
|
||||||
"arrow",
|
"arrow",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1459,5 +1459,54 @@ describe("textWysiwyg", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shouldn't bind to container if container has bound text not centered and text tool is used", async () => {
|
||||||
|
expect(h.elements.length).toBe(1);
|
||||||
|
|
||||||
|
Keyboard.keyPress(KEYS.ENTER);
|
||||||
|
|
||||||
|
expect(h.elements.length).toBe(2);
|
||||||
|
|
||||||
|
// Bind first text
|
||||||
|
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||||
|
expect(text.containerId).toBe(rectangle.id);
|
||||||
|
let editor = getTextEditor();
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
updateTextEditor(editor, "Hello!");
|
||||||
|
expect(
|
||||||
|
(h.elements[1] as ExcalidrawTextElementWithContainer).verticalAlign,
|
||||||
|
).toBe(VERTICAL_ALIGN.MIDDLE);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTitle("Align bottom"));
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
|
||||||
|
editor.blur();
|
||||||
|
|
||||||
|
expect(rectangle.boundElements).toStrictEqual([
|
||||||
|
{ id: text.id, type: "text" },
|
||||||
|
]);
|
||||||
|
expect(
|
||||||
|
(h.elements[1] as ExcalidrawTextElementWithContainer).verticalAlign,
|
||||||
|
).toBe(VERTICAL_ALIGN.BOTTOM);
|
||||||
|
|
||||||
|
// Attempt to Bind 2nd text using text tool
|
||||||
|
UI.clickTool("text");
|
||||||
|
mouse.clickAt(
|
||||||
|
rectangle.x + rectangle.width / 2,
|
||||||
|
rectangle.y + rectangle.height / 2,
|
||||||
|
);
|
||||||
|
editor = getTextEditor();
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
updateTextEditor(editor, "Excalidraw");
|
||||||
|
editor.blur();
|
||||||
|
|
||||||
|
expect(h.elements.length).toBe(3);
|
||||||
|
expect(rectangle.boundElements).toStrictEqual([
|
||||||
|
{ id: h.elements[1].id, type: "text" },
|
||||||
|
]);
|
||||||
|
text = h.elements[2] as ExcalidrawTextElementWithContainer;
|
||||||
|
expect(text.containerId).toBe(null);
|
||||||
|
expect(text.text).toBe("Excalidraw");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,15 +30,6 @@ describe("Test TypeChecks", () => {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
expect(
|
|
||||||
hasBoundTextElement(
|
|
||||||
API.createElement({
|
|
||||||
type: "image",
|
|
||||||
boundElements: [{ type: "text", id: "text-id" }],
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return false for text bindable containers without bound text", () => {
|
it("should return false for text bindable containers without bound text", () => {
|
||||||
@@ -62,5 +53,14 @@ describe("Test TypeChecks", () => {
|
|||||||
),
|
),
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
hasBoundTextElement(
|
||||||
|
API.createElement({
|
||||||
|
type: "image",
|
||||||
|
boundElements: [{ type: "text", id: "text-id" }],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ export const isTextBindableContainer = (
|
|||||||
(element.type === "rectangle" ||
|
(element.type === "rectangle" ||
|
||||||
element.type === "diamond" ||
|
element.type === "diamond" ||
|
||||||
element.type === "ellipse" ||
|
element.type === "ellipse" ||
|
||||||
element.type === "image" ||
|
|
||||||
isArrowElement(element))
|
isArrowElement(element))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -162,7 +162,6 @@ export type ExcalidrawTextContainer =
|
|||||||
| ExcalidrawRectangleElement
|
| ExcalidrawRectangleElement
|
||||||
| ExcalidrawDiamondElement
|
| ExcalidrawDiamondElement
|
||||||
| ExcalidrawEllipseElement
|
| ExcalidrawEllipseElement
|
||||||
| ExcalidrawImageElement
|
|
||||||
| ExcalidrawArrowElement;
|
| ExcalidrawArrowElement;
|
||||||
|
|
||||||
export type ExcalidrawTextElementWithContainer = {
|
export type ExcalidrawTextElementWithContainer = {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { MIME_TYPES } from "../../constants";
|
|||||||
import { trackEvent } from "../../analytics";
|
import { trackEvent } from "../../analytics";
|
||||||
import { getFrame } from "../../utils";
|
import { getFrame } from "../../utils";
|
||||||
|
|
||||||
const exportToExcalidrawPlus = async (
|
export const exportToExcalidrawPlus = async (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: Partial<AppState>,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
|
|||||||
@@ -282,11 +282,15 @@ export const loadScene = async (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ExportToBackendResult =
|
||||||
|
| { url: null; errorMessage: string }
|
||||||
|
| { url: string; errorMessage: null };
|
||||||
|
|
||||||
export const exportToBackend = async (
|
export const exportToBackend = async (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: Partial<AppState>,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => {
|
): Promise<ExportToBackendResult> => {
|
||||||
const encryptionKey = await generateEncryptionKey("string");
|
const encryptionKey = await generateEncryptionKey("string");
|
||||||
|
|
||||||
const payload = await compressData(
|
const payload = await compressData(
|
||||||
@@ -327,14 +331,18 @@ export const exportToBackend = async (
|
|||||||
files: filesToUpload,
|
files: filesToUpload,
|
||||||
});
|
});
|
||||||
|
|
||||||
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
|
return { url: urlString, errorMessage: null };
|
||||||
} else if (json.error_class === "RequestTooLargeError") {
|
} else if (json.error_class === "RequestTooLargeError") {
|
||||||
window.alert(t("alerts.couldNotCreateShareableLinkTooBig"));
|
return {
|
||||||
} else {
|
url: null,
|
||||||
window.alert(t("alerts.couldNotCreateShareableLink"));
|
errorMessage: t("alerts.couldNotCreateShareableLinkTooBig"),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { url: null, errorMessage: t("alerts.couldNotCreateShareableLink") };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
window.alert(t("alerts.couldNotCreateShareableLink"));
|
|
||||||
|
return { url: null, errorMessage: t("alerts.couldNotCreateShareableLink") };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ import {
|
|||||||
} from "./data/localStorage";
|
} from "./data/localStorage";
|
||||||
import CustomStats from "./CustomStats";
|
import CustomStats from "./CustomStats";
|
||||||
import { restore, restoreAppState, RestoredDataState } from "../data/restore";
|
import { restore, restoreAppState, RestoredDataState } from "../data/restore";
|
||||||
import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus";
|
import {
|
||||||
|
ExportToExcalidrawPlus,
|
||||||
|
exportToExcalidrawPlus,
|
||||||
|
} from "./components/ExportToExcalidrawPlus";
|
||||||
import { updateStaleImageStatuses } from "./data/FileManager";
|
import { updateStaleImageStatuses } from "./data/FileManager";
|
||||||
import { newElementWith } from "../element/mutateElement";
|
import { newElementWith } from "../element/mutateElement";
|
||||||
import { isInitializedImageElement } from "../element/typeChecks";
|
import { isInitializedImageElement } from "../element/typeChecks";
|
||||||
@@ -88,6 +91,10 @@ import { appJotaiStore } from "./app-jotai";
|
|||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { ResolutionType } from "../utility-types";
|
import { ResolutionType } from "../utility-types";
|
||||||
|
import { ShareableLinkDialog } from "../components/ShareableLinkDialog";
|
||||||
|
import { openConfirmModal } from "../components/OverwriteConfirm/OverwriteConfirmState";
|
||||||
|
import { OverwriteConfirmDialog } from "../components/OverwriteConfirm/OverwriteConfirm";
|
||||||
|
import Trans from "../components/Trans";
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
@@ -98,6 +105,19 @@ languageDetector.init({
|
|||||||
languageUtils: {},
|
languageUtils: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shareableLinkConfirmDialog = {
|
||||||
|
title: t("overwriteConfirm.modal.shareableLink.title"),
|
||||||
|
description: (
|
||||||
|
<Trans
|
||||||
|
i18nKey="overwriteConfirm.modal.shareableLink.description"
|
||||||
|
bold={(text) => <strong>{text}</strong>}
|
||||||
|
br={() => <br />}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
actionLabel: t("overwriteConfirm.modal.shareableLink.button"),
|
||||||
|
color: "danger",
|
||||||
|
} as const;
|
||||||
|
|
||||||
const initializeScene = async (opts: {
|
const initializeScene = async (opts: {
|
||||||
collabAPI: CollabAPI | null;
|
collabAPI: CollabAPI | null;
|
||||||
excalidrawAPI: ExcalidrawImperativeAPI;
|
excalidrawAPI: ExcalidrawImperativeAPI;
|
||||||
@@ -129,7 +149,7 @@ const initializeScene = async (opts: {
|
|||||||
// don't prompt for collab scenes because we don't override local storage
|
// don't prompt for collab scenes because we don't override local storage
|
||||||
roomLinkData ||
|
roomLinkData ||
|
||||||
// otherwise, prompt whether user wants to override current scene
|
// otherwise, prompt whether user wants to override current scene
|
||||||
window.confirm(t("alerts.loadSceneOverridePrompt"))
|
(await openConfirmModal(shareableLinkConfirmDialog))
|
||||||
) {
|
) {
|
||||||
if (jsonBackendMatch) {
|
if (jsonBackendMatch) {
|
||||||
scene = await loadScene(
|
scene = await loadScene(
|
||||||
@@ -168,7 +188,7 @@ const initializeScene = async (opts: {
|
|||||||
const data = await loadFromBlob(await request.blob(), null, null);
|
const data = await loadFromBlob(await request.blob(), null, null);
|
||||||
if (
|
if (
|
||||||
!scene.elements.length ||
|
!scene.elements.length ||
|
||||||
window.confirm(t("alerts.loadSceneOverridePrompt"))
|
(await openConfirmModal(shareableLinkConfirmDialog))
|
||||||
) {
|
) {
|
||||||
return { scene: data, isExternalScene };
|
return { scene: data, isExternalScene };
|
||||||
}
|
}
|
||||||
@@ -554,6 +574,10 @@ const ExcalidrawWrapper = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [latestShareableLink, setLatestShareableLink] = useState<string | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
const onExportToBackend = async (
|
const onExportToBackend = async (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: Partial<AppState>,
|
appState: Partial<AppState>,
|
||||||
@@ -565,7 +589,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
}
|
}
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
try {
|
try {
|
||||||
await exportToBackend(
|
const { url, errorMessage } = await exportToBackend(
|
||||||
exportedElements,
|
exportedElements,
|
||||||
{
|
{
|
||||||
...appState,
|
...appState,
|
||||||
@@ -575,6 +599,14 @@ const ExcalidrawWrapper = () => {
|
|||||||
},
|
},
|
||||||
files,
|
files,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (errorMessage) {
|
||||||
|
setErrorMessage(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
setLatestShareableLink(url);
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.name !== "AbortError") {
|
if (error.name !== "AbortError") {
|
||||||
const { width, height } = canvas;
|
const { width, height } = canvas;
|
||||||
@@ -674,21 +706,47 @@ const ExcalidrawWrapper = () => {
|
|||||||
setCollabDialogShown={setCollabDialogShown}
|
setCollabDialogShown={setCollabDialogShown}
|
||||||
isCollabEnabled={!isCollabDisabled}
|
isCollabEnabled={!isCollabDisabled}
|
||||||
/>
|
/>
|
||||||
|
<OverwriteConfirmDialog>
|
||||||
|
<OverwriteConfirmDialog.Actions.ExportToImage />
|
||||||
|
<OverwriteConfirmDialog.Actions.SaveToDisk />
|
||||||
|
{excalidrawAPI && (
|
||||||
|
<OverwriteConfirmDialog.Action
|
||||||
|
title={t("overwriteConfirm.action.excalidrawPlus.title")}
|
||||||
|
actionLabel={t("overwriteConfirm.action.excalidrawPlus.button")}
|
||||||
|
onClick={() => {
|
||||||
|
exportToExcalidrawPlus(
|
||||||
|
excalidrawAPI.getSceneElements(),
|
||||||
|
excalidrawAPI.getAppState(),
|
||||||
|
excalidrawAPI.getFiles(),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("overwriteConfirm.action.excalidrawPlus.description")}
|
||||||
|
</OverwriteConfirmDialog.Action>
|
||||||
|
)}
|
||||||
|
</OverwriteConfirmDialog>
|
||||||
<AppFooter />
|
<AppFooter />
|
||||||
{isCollaborating && isOffline && (
|
{isCollaborating && isOffline && (
|
||||||
<div className="collab-offline-warning">
|
<div className="collab-offline-warning">
|
||||||
{t("alerts.collabOfflineWarning")}
|
{t("alerts.collabOfflineWarning")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{latestShareableLink && (
|
||||||
|
<ShareableLinkDialog
|
||||||
|
link={latestShareableLink}
|
||||||
|
onCloseRequest={() => setLatestShareableLink(null)}
|
||||||
|
setErrorMessage={setErrorMessage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{excalidrawAPI && !isCollabDisabled && (
|
{excalidrawAPI && !isCollabDisabled && (
|
||||||
<Collab excalidrawAPI={excalidrawAPI} />
|
<Collab excalidrawAPI={excalidrawAPI} />
|
||||||
)}
|
)}
|
||||||
|
{errorMessage && (
|
||||||
|
<ErrorDialog onClose={() => setErrorMessage("")}>
|
||||||
|
{errorMessage}
|
||||||
|
</ErrorDialog>
|
||||||
|
)}
|
||||||
</Excalidraw>
|
</Excalidraw>
|
||||||
{errorMessage && (
|
|
||||||
<ErrorDialog onClose={() => setErrorMessage("")}>
|
|
||||||
{errorMessage}
|
|
||||||
</ErrorDialog>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-2
@@ -304,7 +304,7 @@ export const groupsAreCompletelyOutOfFrame = (
|
|||||||
/**
|
/**
|
||||||
* Returns a map of frameId to frame elements. Includes empty frames.
|
* Returns a map of frameId to frame elements. Includes empty frames.
|
||||||
*/
|
*/
|
||||||
export const groupByFrames = (elements: ExcalidrawElementsIncludingDeleted) => {
|
export const groupByFrames = (elements: readonly ExcalidrawElement[]) => {
|
||||||
const frameElementsMap = new Map<
|
const frameElementsMap = new Map<
|
||||||
ExcalidrawElement["id"],
|
ExcalidrawElement["id"],
|
||||||
ExcalidrawElement[]
|
ExcalidrawElement[]
|
||||||
@@ -591,6 +591,7 @@ export const updateFrameMembershipOfSelectedElements = (
|
|||||||
|
|
||||||
elementsToFilter.forEach((element) => {
|
elementsToFilter.forEach((element) => {
|
||||||
if (
|
if (
|
||||||
|
element.frameId &&
|
||||||
!isFrameElement(element) &&
|
!isFrameElement(element) &&
|
||||||
!isElementInFrame(element, allElements, appState)
|
!isElementInFrame(element, allElements, appState)
|
||||||
) {
|
) {
|
||||||
@@ -598,7 +599,9 @@ export const updateFrameMembershipOfSelectedElements = (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return removeElementsFromFrame(allElements, [...elementsToRemove], appState);
|
return elementsToRemove.size > 0
|
||||||
|
? removeElementsFromFrame(allElements, [...elementsToRemove], appState)
|
||||||
|
: allElements;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const CODES = {
|
|||||||
BRACKET_LEFT: "BracketLeft",
|
BRACKET_LEFT: "BracketLeft",
|
||||||
ONE: "Digit1",
|
ONE: "Digit1",
|
||||||
TWO: "Digit2",
|
TWO: "Digit2",
|
||||||
|
THREE: "Digit3",
|
||||||
NINE: "Digit9",
|
NINE: "Digit9",
|
||||||
QUOTE: "Quote",
|
QUOTE: "Quote",
|
||||||
ZERO: "Digit0",
|
ZERO: "Digit0",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "نُشر",
|
"statusPublished": "نُشر",
|
||||||
"sidebarLock": "إبقاء الشريط الجانبي مفتوح",
|
"sidebarLock": "إبقاء الشريط الجانبي مفتوح",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "وضع القلم - امنع اللمس",
|
"penMode": "وضع القلم - امنع اللمس",
|
||||||
"link": "إضافة/تحديث الرابط للشكل المحدد",
|
"link": "إضافة/تحديث الرابط للشكل المحدد",
|
||||||
"eraser": "ممحاة",
|
"eraser": "ممحاة",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "إجراءات اللوحة",
|
"canvasActions": "إجراءات اللوحة",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "",
|
"eraser": "",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "",
|
"canvasActions": "",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "",
|
"eraser": "",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Действия по платното",
|
"canvasActions": "Действия по платното",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "প্রকাশিত",
|
"statusPublished": "প্রকাশিত",
|
||||||
"sidebarLock": "লক",
|
"sidebarLock": "লক",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "একটি নির্বাচিত আকৃতির জন্য লিঙ্ক যোগ বা আপডেট করুন",
|
"link": "একটি নির্বাচিত আকৃতির জন্য লিঙ্ক যোগ বা আপডেট করুন",
|
||||||
"eraser": "ঝাড়ন",
|
"eraser": "ঝাড়ন",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "ক্যানভাস কার্যকলাপ",
|
"canvasActions": "ক্যানভাস কার্যকলাপ",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicat",
|
"statusPublished": "Publicat",
|
||||||
"sidebarLock": "Manté la barra lateral oberta",
|
"sidebarLock": "Manté la barra lateral oberta",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Mode de llapis - evita tocar",
|
"penMode": "Mode de llapis - evita tocar",
|
||||||
"link": "Afegeix / actualitza l'enllaç per a la forma seleccionada",
|
"link": "Afegeix / actualitza l'enllaç per a la forma seleccionada",
|
||||||
"eraser": "Esborrador",
|
"eraser": "Esborrador",
|
||||||
"hand": "Mà (eina de desplaçament)"
|
"frame": "",
|
||||||
|
"hand": "Mà (eina de desplaçament)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Accions del llenç",
|
"canvasActions": "Accions del llenç",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Zveřejněno",
|
"statusPublished": "Zveřejněno",
|
||||||
"sidebarLock": "Ponechat postranní panel otevřený",
|
"sidebarLock": "Ponechat postranní panel otevřený",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": "Vyberte barvu z plátna"
|
"eyeDropper": "Vyberte barvu z plátna"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Režim Pera - zabránit dotyku",
|
"penMode": "Režim Pera - zabránit dotyku",
|
||||||
"link": "Přidat/aktualizovat odkaz pro vybraný tvar",
|
"link": "Přidat/aktualizovat odkaz pro vybraný tvar",
|
||||||
"eraser": "Guma",
|
"eraser": "Guma",
|
||||||
"hand": "Ruka (nástroj pro posouvání)"
|
"frame": "",
|
||||||
|
"hand": "Ruka (nástroj pro posouvání)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Akce plátna",
|
"canvasActions": "Akce plátna",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "",
|
"eraser": "",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "",
|
"canvasActions": "",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Veröffentlicht",
|
"statusPublished": "Veröffentlicht",
|
||||||
"sidebarLock": "Seitenleiste offen lassen",
|
"sidebarLock": "Seitenleiste offen lassen",
|
||||||
|
"selectAllElementsInFrame": "Alle Elemente im Rahmen auswählen",
|
||||||
|
"removeAllElementsFromFrame": "Alle Elemente aus dem Rahmen entfernen",
|
||||||
"eyeDropper": "Farbe von der Zeichenfläche auswählen"
|
"eyeDropper": "Farbe von der Zeichenfläche auswählen"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Stift-Modus - Berührung verhindern",
|
"penMode": "Stift-Modus - Berührung verhindern",
|
||||||
"link": "Link für ausgewählte Form hinzufügen / aktualisieren",
|
"link": "Link für ausgewählte Form hinzufügen / aktualisieren",
|
||||||
"eraser": "Radierer",
|
"eraser": "Radierer",
|
||||||
"hand": "Hand (Schwenkwerkzeug)"
|
"frame": "Rahmenwerkzeug",
|
||||||
|
"hand": "Hand (Schwenkwerkzeug)",
|
||||||
|
"extraTools": "Weitere Werkzeuge"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Aktionen für Zeichenfläche",
|
"canvasActions": "Aktionen für Zeichenfläche",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Δημοσιευμένο",
|
"statusPublished": "Δημοσιευμένο",
|
||||||
"sidebarLock": "Κρατήστε την πλαϊνή μπάρα ανοιχτή",
|
"sidebarLock": "Κρατήστε την πλαϊνή μπάρα ανοιχτή",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Λειτουργία μολυβιού - αποτροπή αφής",
|
"penMode": "Λειτουργία μολυβιού - αποτροπή αφής",
|
||||||
"link": "Προσθήκη/ Ενημέρωση συνδέσμου για ένα επιλεγμένο σχήμα",
|
"link": "Προσθήκη/ Ενημέρωση συνδέσμου για ένα επιλεγμένο σχήμα",
|
||||||
"eraser": "Γόμα",
|
"eraser": "Γόμα",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Ενέργειες καμβά",
|
"canvasActions": "Ενέργειες καμβά",
|
||||||
|
|||||||
@@ -449,5 +449,36 @@
|
|||||||
"shades": "Shades",
|
"shades": "Shades",
|
||||||
"hexCode": "Hex code",
|
"hexCode": "Hex code",
|
||||||
"noShades": "No shades available for this color"
|
"noShades": "No shades available for this color"
|
||||||
|
},
|
||||||
|
"overwriteConfirm": {
|
||||||
|
"action": {
|
||||||
|
"exportToImage": {
|
||||||
|
"title": "Export as image",
|
||||||
|
"button": "Export as image",
|
||||||
|
"description": "Export the scene data as an image from which you can import later."
|
||||||
|
},
|
||||||
|
"saveToDisk": {
|
||||||
|
"title": "Save to disk",
|
||||||
|
"button": "Save to disk",
|
||||||
|
"description": "Export the scene data to a file from which you can import later."
|
||||||
|
},
|
||||||
|
"excalidrawPlus": {
|
||||||
|
"title": "Excalidraw+",
|
||||||
|
"button": "Export to Excalidraw+",
|
||||||
|
"description": "Save the scene to your Excalidraw+ workspace."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"loadFromFile": {
|
||||||
|
"title": "Load from file",
|
||||||
|
"button": "Load from file",
|
||||||
|
"description": "Loading from a file will <bold>replace your existing content</bold>.<br></br>You can back up your drawing first using one of the options below."
|
||||||
|
},
|
||||||
|
"shareableLink": {
|
||||||
|
"title": "Load from link",
|
||||||
|
"button": "Replace my content",
|
||||||
|
"description": "Loading external drawing will <bold>replace your existing content</bold>.<br></br>You can back up your drawing first by using one of the options below."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicado",
|
"statusPublished": "Publicado",
|
||||||
"sidebarLock": "Mantener barra lateral abierta",
|
"sidebarLock": "Mantener barra lateral abierta",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Modo Lápiz - previene toque",
|
"penMode": "Modo Lápiz - previene toque",
|
||||||
"link": "Añadir/Actualizar enlace para una forma seleccionada",
|
"link": "Añadir/Actualizar enlace para una forma seleccionada",
|
||||||
"eraser": "Borrar",
|
"eraser": "Borrar",
|
||||||
"hand": "Mano (herramienta de panoramización)"
|
"frame": "",
|
||||||
|
"hand": "Mano (herramienta de panoramización)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Acciones del lienzo",
|
"canvasActions": "Acciones del lienzo",
|
||||||
|
|||||||
+39
-35
@@ -124,7 +124,9 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Argitaratua",
|
"statusPublished": "Argitaratua",
|
||||||
"sidebarLock": "Mantendu alboko barra irekita",
|
"sidebarLock": "Mantendu alboko barra irekita",
|
||||||
"eyeDropper": ""
|
"selectAllElementsInFrame": "Hautatu markoko elementu guztiak",
|
||||||
|
"removeAllElementsFromFrame": "Kendu markoko elementu guztiak",
|
||||||
|
"eyeDropper": "Aukeratu kolorea oihaletik"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"noItems": "Oraindik ez da elementurik gehitu...",
|
"noItems": "Oraindik ez da elementurik gehitu...",
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Luma modua - ukipena saihestu",
|
"penMode": "Luma modua - ukipena saihestu",
|
||||||
"link": "Gehitu / Eguneratu esteka hautatutako forma baterako",
|
"link": "Gehitu / Eguneratu esteka hautatutako forma baterako",
|
||||||
"eraser": "Borragoma",
|
"eraser": "Borragoma",
|
||||||
"hand": "Eskua (panoratze tresna)"
|
"frame": "Marko tresna",
|
||||||
|
"hand": "Eskua (panoratze tresna)",
|
||||||
|
"extraTools": "Tresna gehiago"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Canvas ekintzak",
|
"canvasActions": "Canvas ekintzak",
|
||||||
@@ -356,27 +360,27 @@
|
|||||||
"removeItemsFromLib": "Kendu hautatutako elementuak liburutegitik"
|
"removeItemsFromLib": "Kendu hautatutako elementuak liburutegitik"
|
||||||
},
|
},
|
||||||
"imageExportDialog": {
|
"imageExportDialog": {
|
||||||
"header": "",
|
"header": "Esportatu irudia",
|
||||||
"label": {
|
"label": {
|
||||||
"withBackground": "",
|
"withBackground": "Atzeko planoa",
|
||||||
"onlySelected": "",
|
"onlySelected": "Hautapena soilik",
|
||||||
"darkMode": "",
|
"darkMode": "Modu iluna",
|
||||||
"embedScene": "",
|
"embedScene": "Txertatu eszena",
|
||||||
"scale": "",
|
"scale": "Eskala",
|
||||||
"padding": ""
|
"padding": "Betegarria"
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"embedScene": ""
|
"embedScene": "Eszenaren datuak esportatutako PNG/SVG fitxategian gordeko dira, eszena bertatik berrezartzeko.\nEsportatutako fitxategien tamaina handituko da."
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"exportToPng": "",
|
"exportToPng": "Esportatu PNG gisa",
|
||||||
"exportToSvg": "",
|
"exportToSvg": "Esportatu SVG gisa",
|
||||||
"copyPngToClipboard": ""
|
"copyPngToClipboard": "Kopiatu PNG arbelera"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"exportToPng": "",
|
"exportToPng": "PNG",
|
||||||
"exportToSvg": "",
|
"exportToSvg": "SVG",
|
||||||
"copyPngToClipboard": ""
|
"copyPngToClipboard": "Kopiatu arbelean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
@@ -411,20 +415,20 @@
|
|||||||
},
|
},
|
||||||
"colors": {
|
"colors": {
|
||||||
"transparent": "Gardena",
|
"transparent": "Gardena",
|
||||||
"black": "",
|
"black": "Beltza",
|
||||||
"white": "",
|
"white": "Zuria",
|
||||||
"red": "",
|
"red": "Gorria",
|
||||||
"pink": "",
|
"pink": "Arrosa",
|
||||||
"grape": "",
|
"grape": "Mahats kolorea",
|
||||||
"violet": "",
|
"violet": "Bioleta",
|
||||||
"gray": "",
|
"gray": "Grisa",
|
||||||
"blue": "",
|
"blue": "Urdina",
|
||||||
"cyan": "",
|
"cyan": "Ziana",
|
||||||
"teal": "",
|
"teal": "Berde urdinxka",
|
||||||
"green": "",
|
"green": "Berdea",
|
||||||
"yellow": "",
|
"yellow": "Horia",
|
||||||
"orange": "",
|
"orange": "Laranja",
|
||||||
"bronze": ""
|
"bronze": "Brontzea"
|
||||||
},
|
},
|
||||||
"welcomeScreen": {
|
"welcomeScreen": {
|
||||||
"app": {
|
"app": {
|
||||||
@@ -440,10 +444,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"colorPicker": {
|
"colorPicker": {
|
||||||
"mostUsedCustomColors": "",
|
"mostUsedCustomColors": "Gehien erabilitako kolore pertsonalizatuak",
|
||||||
"colors": "",
|
"colors": "Koloreak",
|
||||||
"shades": "",
|
"shades": "Ñabardurak",
|
||||||
"hexCode": "",
|
"hexCode": "Hez kodea",
|
||||||
"noShades": ""
|
"noShades": "Kolore honetarako ez dago ñabardurarik eskuragarri"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "منتشر شده",
|
"statusPublished": "منتشر شده",
|
||||||
"sidebarLock": "باز نگه داشتن سایدبار",
|
"sidebarLock": "باز نگه داشتن سایدبار",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "حالت قلم - جلوگیری از تماس",
|
"penMode": "حالت قلم - جلوگیری از تماس",
|
||||||
"link": "افزودن/بهروزرسانی پیوند برای شکل انتخابی",
|
"link": "افزودن/بهروزرسانی پیوند برای شکل انتخابی",
|
||||||
"eraser": "پاک کن",
|
"eraser": "پاک کن",
|
||||||
"hand": "دست (ابزار پانینگ)"
|
"frame": "",
|
||||||
|
"hand": "دست (ابزار پانینگ)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "عملیات روی بوم",
|
"canvasActions": "عملیات روی بوم",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Julkaistu",
|
"statusPublished": "Julkaistu",
|
||||||
"sidebarLock": "Pidä sivupalkki avoinna",
|
"sidebarLock": "Pidä sivupalkki avoinna",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Kynätila - estä kosketus",
|
"penMode": "Kynätila - estä kosketus",
|
||||||
"link": "Lisää/päivitä linkki valitulle muodolle",
|
"link": "Lisää/päivitä linkki valitulle muodolle",
|
||||||
"eraser": "Poistotyökalu",
|
"eraser": "Poistotyökalu",
|
||||||
"hand": "Käsi (panning-työkalu)"
|
"frame": "",
|
||||||
|
"hand": "Käsi (panning-työkalu)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Piirtoalueen toiminnot",
|
"canvasActions": "Piirtoalueen toiminnot",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publié",
|
"statusPublished": "Publié",
|
||||||
"sidebarLock": "Maintenir la barre latérale ouverte",
|
"sidebarLock": "Maintenir la barre latérale ouverte",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Mode stylo - évite le toucher",
|
"penMode": "Mode stylo - évite le toucher",
|
||||||
"link": "Ajouter/mettre à jour le lien pour une forme sélectionnée",
|
"link": "Ajouter/mettre à jour le lien pour une forme sélectionnée",
|
||||||
"eraser": "Gomme",
|
"eraser": "Gomme",
|
||||||
"hand": "Mains (outil de déplacement de la vue)"
|
"frame": "",
|
||||||
|
"hand": "Mains (outil de déplacement de la vue)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Actions du canevas",
|
"canvasActions": "Actions du canevas",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicado",
|
"statusPublished": "Publicado",
|
||||||
"sidebarLock": "Manter a barra lateral aberta",
|
"sidebarLock": "Manter a barra lateral aberta",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Modo lapis - evitar o contacto",
|
"penMode": "Modo lapis - evitar o contacto",
|
||||||
"link": "Engadir/ Actualizar ligazón para a forma seleccionada",
|
"link": "Engadir/ Actualizar ligazón para a forma seleccionada",
|
||||||
"eraser": "Goma de borrar",
|
"eraser": "Goma de borrar",
|
||||||
"hand": "Man (ferramenta de desprazamento)"
|
"frame": "",
|
||||||
|
"hand": "Man (ferramenta de desprazamento)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Accións do lenzo",
|
"canvasActions": "Accións do lenzo",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "פורסם",
|
"statusPublished": "פורסם",
|
||||||
"sidebarLock": "שמור את סרגל הצד פתוח",
|
"sidebarLock": "שמור את סרגל הצד פתוח",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "מצב עט - מנע נגיעה",
|
"penMode": "מצב עט - מנע נגיעה",
|
||||||
"link": "הוספה/עדכון קישור של הצורה שנבחרה",
|
"link": "הוספה/עדכון קישור של הצורה שנבחרה",
|
||||||
"eraser": "מחק",
|
"eraser": "מחק",
|
||||||
"hand": "יד (כלי הזזה)"
|
"frame": "",
|
||||||
|
"hand": "יד (כלי הזזה)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "פעולות קנבאס",
|
"canvasActions": "פעולות קנבאס",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "प्रकाशित",
|
"statusPublished": "प्रकाशित",
|
||||||
"sidebarLock": "साइडबार खुला रखे.",
|
"sidebarLock": "साइडबार खुला रखे.",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": "चित्रफलक से रंग चुने"
|
"eyeDropper": "चित्रफलक से रंग चुने"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "पेन का मोड - स्पर्श टाले",
|
"penMode": "पेन का मोड - स्पर्श टाले",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "रबड़",
|
"eraser": "रबड़",
|
||||||
"hand": "हाथ ( खिसकाने का औज़ार)"
|
"frame": "",
|
||||||
|
"hand": "हाथ ( खिसकाने का औज़ार)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "कैनवास क्रिया",
|
"canvasActions": "कैनवास क्रिया",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "Hivatkozás hozzáadása/frissítése a kiválasztott alakzathoz",
|
"link": "Hivatkozás hozzáadása/frissítése a kiválasztott alakzathoz",
|
||||||
"eraser": "",
|
"eraser": "",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Vászon műveletek",
|
"canvasActions": "Vászon műveletek",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Telah terbit",
|
"statusPublished": "Telah terbit",
|
||||||
"sidebarLock": "Biarkan sidebar tetap terbuka",
|
"sidebarLock": "Biarkan sidebar tetap terbuka",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Mode pena - mencegah sentuhan",
|
"penMode": "Mode pena - mencegah sentuhan",
|
||||||
"link": "Tambah/Perbarui tautan untuk bentuk yang dipilih",
|
"link": "Tambah/Perbarui tautan untuk bentuk yang dipilih",
|
||||||
"eraser": "Penghapus",
|
"eraser": "Penghapus",
|
||||||
"hand": "Tangan (alat panning)"
|
"frame": "",
|
||||||
|
"hand": "Tangan (alat panning)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Opsi Kanvas",
|
"canvasActions": "Opsi Kanvas",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Pubblicato",
|
"statusPublished": "Pubblicato",
|
||||||
"sidebarLock": "Mantieni aperta la barra laterale",
|
"sidebarLock": "Mantieni aperta la barra laterale",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Modalità penna - previene il tocco",
|
"penMode": "Modalità penna - previene il tocco",
|
||||||
"link": "Aggiungi/ aggiorna il link per una forma selezionata",
|
"link": "Aggiungi/ aggiorna il link per una forma selezionata",
|
||||||
"eraser": "Gomma",
|
"eraser": "Gomma",
|
||||||
"hand": "Mano (strumento di panoramica)"
|
"frame": "",
|
||||||
|
"hand": "Mano (strumento di panoramica)",
|
||||||
|
"extraTools": "Altri strumenti"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Azioni sulla Tela",
|
"canvasActions": "Azioni sulla Tela",
|
||||||
@@ -361,12 +365,12 @@
|
|||||||
"withBackground": "Sfondo",
|
"withBackground": "Sfondo",
|
||||||
"onlySelected": "Solo selezionato",
|
"onlySelected": "Solo selezionato",
|
||||||
"darkMode": "Tema scuro",
|
"darkMode": "Tema scuro",
|
||||||
"embedScene": "",
|
"embedScene": "Includi scena",
|
||||||
"scale": "Scala",
|
"scale": "Scala",
|
||||||
"padding": ""
|
"padding": ""
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"embedScene": ""
|
"embedScene": "I dati della scena saranno salvati nel file PNG/SVG esportato in modo che la scena possa essere ripristinata da esso.\nQuesto aumenterà la dimensione del file esportato."
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"exportToPng": "Esporta come PNG",
|
"exportToPng": "Esporta come PNG",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "公開済み",
|
"statusPublished": "公開済み",
|
||||||
"sidebarLock": "サイドバーを開いたままにする",
|
"sidebarLock": "サイドバーを開いたままにする",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "ペンモード - タッチ防止",
|
"penMode": "ペンモード - タッチ防止",
|
||||||
"link": "選択した図形のリンクを追加/更新",
|
"link": "選択した図形のリンクを追加/更新",
|
||||||
"eraser": "消しゴム",
|
"eraser": "消しゴム",
|
||||||
"hand": "手 (パンニングツール)"
|
"frame": "",
|
||||||
|
"hand": "手 (パンニングツール)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "キャンバス操作",
|
"canvasActions": "キャンバス操作",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "Óshirgish",
|
"eraser": "Óshirgish",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "",
|
"canvasActions": "",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Yeffeɣ-d",
|
"statusPublished": "Yeffeɣ-d",
|
||||||
"sidebarLock": "Eǧǧ afeggag n yidis yeldi",
|
"sidebarLock": "Eǧǧ afeggag n yidis yeldi",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Askar n yimru - gdel tanalit",
|
"penMode": "Askar n yimru - gdel tanalit",
|
||||||
"link": "Rnu/leqqem aseɣwen i talɣa yettwafernen",
|
"link": "Rnu/leqqem aseɣwen i talɣa yettwafernen",
|
||||||
"eraser": "Sfeḍ",
|
"eraser": "Sfeḍ",
|
||||||
"hand": "Afus (afecku n usmutti n tmuɣli)"
|
"frame": "",
|
||||||
|
"hand": "Afus (afecku n usmutti n tmuɣli)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Tigawin n teɣzut n usuneɣ",
|
"canvasActions": "Tigawin n teɣzut n usuneɣ",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "",
|
"eraser": "",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "",
|
"canvasActions": "",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "ត្រូវបានបោះពុម្ពផ្សាយ",
|
"statusPublished": "ត្រូវបានបោះពុម្ពផ្សាយ",
|
||||||
"sidebarLock": "ទុករបារចំហៀងបើក",
|
"sidebarLock": "ទុករបារចំហៀងបើក",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "របៀបប៊ិច - ជៀសវាងការប៉ះ",
|
"penMode": "របៀបប៊ិច - ជៀសវាងការប៉ះ",
|
||||||
"link": "បន្ថែម/ធ្វើបច្ចុប្បន្នភាពតំណភ្ជាប់សម្រាប់រូបរាងដែលបានជ្រើសរើស",
|
"link": "បន្ថែម/ធ្វើបច្ចុប្បន្នភាពតំណភ្ជាប់សម្រាប់រូបរាងដែលបានជ្រើសរើស",
|
||||||
"eraser": "ជ័រលុប",
|
"eraser": "ជ័រលុប",
|
||||||
"hand": "ដៃ (panning tool)"
|
"frame": "",
|
||||||
|
"hand": "ដៃ (panning tool)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "សកម្មភាពបាវ",
|
"canvasActions": "សកម្មភាពបាវ",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "게시됨",
|
"statusPublished": "게시됨",
|
||||||
"sidebarLock": "사이드바 유지",
|
"sidebarLock": "사이드바 유지",
|
||||||
|
"selectAllElementsInFrame": "프레임의 모든 요소 선택",
|
||||||
|
"removeAllElementsFromFrame": "프레임의 모든 요소 삭제",
|
||||||
"eyeDropper": "캔버스에서 색상 고르기"
|
"eyeDropper": "캔버스에서 색상 고르기"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "펜 모드 - 터치 방지",
|
"penMode": "펜 모드 - 터치 방지",
|
||||||
"link": "선택한 도형에 대해서 링크를 추가/업데이트",
|
"link": "선택한 도형에 대해서 링크를 추가/업데이트",
|
||||||
"eraser": "지우개",
|
"eraser": "지우개",
|
||||||
"hand": "손 (패닝 도구)"
|
"frame": "프레임 도구",
|
||||||
|
"hand": "손 (패닝 도구)",
|
||||||
|
"extraTools": "다른 도구"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "캔버스 동작",
|
"canvasActions": "캔버스 동작",
|
||||||
|
|||||||
+40
-36
@@ -71,7 +71,7 @@
|
|||||||
"language": "زمان",
|
"language": "زمان",
|
||||||
"liveCollaboration": "هاوکاریکردنی زیندو...",
|
"liveCollaboration": "هاوکاریکردنی زیندو...",
|
||||||
"duplicateSelection": "لەبەرگرتنەوە",
|
"duplicateSelection": "لەبەرگرتنەوە",
|
||||||
"untitled": "بێ-ناو",
|
"untitled": "بێ ناونیشان",
|
||||||
"name": "ناو",
|
"name": "ناو",
|
||||||
"yourName": "ناوەکەت",
|
"yourName": "ناوەکەت",
|
||||||
"madeWithExcalidraw": "دروستکراوە بە Excalidraw",
|
"madeWithExcalidraw": "دروستکراوە بە Excalidraw",
|
||||||
@@ -124,7 +124,9 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "بڵاوکراوەتەوە",
|
"statusPublished": "بڵاوکراوەتەوە",
|
||||||
"sidebarLock": "هێشتنەوەی شریتی لا بە کراوەیی",
|
"sidebarLock": "هێشتنەوەی شریتی لا بە کراوەیی",
|
||||||
"eyeDropper": ""
|
"selectAllElementsInFrame": "هەموو توخمەکانی ناو چوارچێوەکە دیاری بکە",
|
||||||
|
"removeAllElementsFromFrame": "هەموو توخمەکانی ناو چوارچێوەکە لابەرە",
|
||||||
|
"eyeDropper": "ڕەنگێک لەسەر تابلۆکە هەڵبژێرە"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"noItems": "هێشتا هیچ بڕگەیەک زیاد نەکراوە...",
|
"noItems": "هێشتا هیچ بڕگەیەک زیاد نەکراوە...",
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "شێوازی قەڵەم - دەست لێدان ڕابگرە",
|
"penMode": "شێوازی قەڵەم - دەست لێدان ڕابگرە",
|
||||||
"link": "زیادکردن/ نوێکردنەوەی لینک بۆ شێوەی دیاریکراو",
|
"link": "زیادکردن/ نوێکردنەوەی لینک بۆ شێوەی دیاریکراو",
|
||||||
"eraser": "سڕەر",
|
"eraser": "سڕەر",
|
||||||
"hand": "دەست (ئامرازی پانکردن)"
|
"frame": "ئامرازی چوارچێوە",
|
||||||
|
"hand": "دەست (ئامرازی پانکردن)",
|
||||||
|
"extraTools": "ئامرازی زیاتر"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "کردارەکانی تابلۆ",
|
"canvasActions": "کردارەکانی تابلۆ",
|
||||||
@@ -356,27 +360,27 @@
|
|||||||
"removeItemsFromLib": "لابردنی ئایتمە دیاریکراوەکان لە کتێبخانە"
|
"removeItemsFromLib": "لابردنی ئایتمە دیاریکراوەکان لە کتێبخانە"
|
||||||
},
|
},
|
||||||
"imageExportDialog": {
|
"imageExportDialog": {
|
||||||
"header": "",
|
"header": "وێنە هەناردە بکە",
|
||||||
"label": {
|
"label": {
|
||||||
"withBackground": "",
|
"withBackground": "پاشبنەما",
|
||||||
"onlySelected": "",
|
"onlySelected": "تەنها دیاریکراوەکان",
|
||||||
"darkMode": "",
|
"darkMode": "دۆخی تاریک",
|
||||||
"embedScene": "",
|
"embedScene": "دیمەنەکە بەکاربهێنەرەوە",
|
||||||
"scale": "",
|
"scale": "قەبارە",
|
||||||
"padding": ""
|
"padding": "بۆشایی"
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"embedScene": ""
|
"embedScene": "داتاکانی دیمەنەکە لە فایلە هەناردەکراوەکەی PNG/SVG هەڵدەگیرێن بۆ ئەوەی دیمەنەکە لێیەوە بگەڕێتەوە.\nقەبارەی پەڕگەی هەناردەکراو زیاد دەکات."
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"exportToPng": "",
|
"exportToPng": "هەناردە بکە وەک PNG",
|
||||||
"exportToSvg": "",
|
"exportToSvg": "هەناردە بکە وەک SVG",
|
||||||
"copyPngToClipboard": ""
|
"copyPngToClipboard": "لەبەربگرەوە بۆ سەر تەختەنوس"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"exportToPng": "",
|
"exportToPng": "PNG",
|
||||||
"exportToSvg": "",
|
"exportToSvg": "SVG",
|
||||||
"copyPngToClipboard": ""
|
"copyPngToClipboard": "لهبهری بگرهوه بۆ تهختهنووس"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
@@ -411,20 +415,20 @@
|
|||||||
},
|
},
|
||||||
"colors": {
|
"colors": {
|
||||||
"transparent": "ڕوون",
|
"transparent": "ڕوون",
|
||||||
"black": "",
|
"black": "ڕەش",
|
||||||
"white": "",
|
"white": "سپی",
|
||||||
"red": "",
|
"red": "سور",
|
||||||
"pink": "",
|
"pink": "پەمەیی",
|
||||||
"grape": "",
|
"grape": "مێوژی",
|
||||||
"violet": "",
|
"violet": "مۆری کاڵ",
|
||||||
"gray": "",
|
"gray": "خۆڵەمێشی",
|
||||||
"blue": "",
|
"blue": "شین",
|
||||||
"cyan": "",
|
"cyan": "شینی ئاسمانی",
|
||||||
"teal": "",
|
"teal": "شەدری",
|
||||||
"green": "",
|
"green": "سهوز",
|
||||||
"yellow": "",
|
"yellow": "زەرد",
|
||||||
"orange": "",
|
"orange": "پرتەقاڵی",
|
||||||
"bronze": ""
|
"bronze": "برۆنزی"
|
||||||
},
|
},
|
||||||
"welcomeScreen": {
|
"welcomeScreen": {
|
||||||
"app": {
|
"app": {
|
||||||
@@ -440,10 +444,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"colorPicker": {
|
"colorPicker": {
|
||||||
"mostUsedCustomColors": "",
|
"mostUsedCustomColors": "زۆرترین ڕەنگە باوە بەکارهاتووەکان",
|
||||||
"colors": "",
|
"colors": "ڕەنگەکان",
|
||||||
"shades": "",
|
"shades": "سێبەرەکان",
|
||||||
"hexCode": "",
|
"hexCode": "کۆدی هێکس",
|
||||||
"noShades": ""
|
"noShades": "هیچ سێبەرێک بۆ ئەم ڕەنگە بەردەست نییە"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Rašyklio režimas - neleisti prisilietimų",
|
"penMode": "Rašyklio režimas - neleisti prisilietimų",
|
||||||
"link": "Pridėti / Atnaujinti pasirinktos figūros nuorodą",
|
"link": "Pridėti / Atnaujinti pasirinktos figūros nuorodą",
|
||||||
"eraser": "Trintukas",
|
"eraser": "Trintukas",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Veiksmai su drobe",
|
"canvasActions": "Veiksmai su drobe",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicēts",
|
"statusPublished": "Publicēts",
|
||||||
"sidebarLock": "Paturēt atvērtu sānjoslu",
|
"sidebarLock": "Paturēt atvērtu sānjoslu",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Pildspalvas režīms – novērst pieskaršanos",
|
"penMode": "Pildspalvas režīms – novērst pieskaršanos",
|
||||||
"link": "Pievienot/rediģēt atlasītās figūras saiti",
|
"link": "Pievienot/rediģēt atlasītās figūras saiti",
|
||||||
"eraser": "Dzēšgumija",
|
"eraser": "Dzēšgumija",
|
||||||
"hand": "Roka (panoramēšanas rīks)"
|
"frame": "",
|
||||||
|
"hand": "Roka (panoramēšanas rīks)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Tāfeles darbības",
|
"canvasActions": "Tāfeles darbības",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "प्रकाशित करा",
|
"statusPublished": "प्रकाशित करा",
|
||||||
"sidebarLock": "साइडबार उघडं ठेवा",
|
"sidebarLock": "साइडबार उघडं ठेवा",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": "चित्रफलकातून रंग निवडा"
|
"eyeDropper": "चित्रफलकातून रंग निवडा"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "पेन चा मोड - स्पर्श टाळा",
|
"penMode": "पेन चा मोड - स्पर्श टाळा",
|
||||||
"link": "निवडलेल्या आकारासाठी दुवा जोडा/बदल करा",
|
"link": "निवडलेल्या आकारासाठी दुवा जोडा/बदल करा",
|
||||||
"eraser": "खोड रबर",
|
"eraser": "खोड रबर",
|
||||||
"hand": "हात ( सरकवण्या चे उपकरण)"
|
"frame": "",
|
||||||
|
"hand": "हात ( सरकवण्या चे उपकरण)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "पटल क्रिया",
|
"canvasActions": "पटल क्रिया",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "",
|
"eraser": "",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "ကားချပ်လုပ်ဆောင်ချက်",
|
"canvasActions": "ကားချပ်လုပ်ဆောင်ချက်",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publisert",
|
"statusPublished": "Publisert",
|
||||||
"sidebarLock": "Holde sidemenyen åpen",
|
"sidebarLock": "Holde sidemenyen åpen",
|
||||||
|
"selectAllElementsInFrame": "Velg alle elementene i rammen",
|
||||||
|
"removeAllElementsFromFrame": "Fjern alle elementer fra rammen",
|
||||||
"eyeDropper": "Velg farge fra lerretet"
|
"eyeDropper": "Velg farge fra lerretet"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Pennemodus - forhindre berøring",
|
"penMode": "Pennemodus - forhindre berøring",
|
||||||
"link": "Legg til / oppdater link for en valgt figur",
|
"link": "Legg til / oppdater link for en valgt figur",
|
||||||
"eraser": "Viskelær",
|
"eraser": "Viskelær",
|
||||||
"hand": "Hånd (panoreringsverktøy)"
|
"frame": "Rammeverktøy",
|
||||||
|
"hand": "Hånd (panoreringsverktøy)",
|
||||||
|
"extraTools": "Flere verktøy"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Handlinger: lerret",
|
"canvasActions": "Handlinger: lerret",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Gepubliceerd",
|
"statusPublished": "Gepubliceerd",
|
||||||
"sidebarLock": "Zijbalk open houden",
|
"sidebarLock": "Zijbalk open houden",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Pen modus - Blokkeer aanraken",
|
"penMode": "Pen modus - Blokkeer aanraken",
|
||||||
"link": "Link toevoegen / bijwerken voor een geselecteerde vorm",
|
"link": "Link toevoegen / bijwerken voor een geselecteerde vorm",
|
||||||
"eraser": "Gum",
|
"eraser": "Gum",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Canvasacties",
|
"canvasActions": "Canvasacties",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "Legg til/ oppdater lenke til valt figur",
|
"link": "Legg til/ oppdater lenke til valt figur",
|
||||||
"eraser": "Viskelêr",
|
"eraser": "Viskelêr",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Handlingar: lerret",
|
"canvasActions": "Handlingar: lerret",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicat",
|
"statusPublished": "Publicat",
|
||||||
"sidebarLock": "Gardar la barra laterala dobèrta",
|
"sidebarLock": "Gardar la barra laterala dobèrta",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Mòde estilo - empachar lo contact",
|
"penMode": "Mòde estilo - empachar lo contact",
|
||||||
"link": "Apondre/Actualizar lo ligam per una fòrma seleccionada",
|
"link": "Apondre/Actualizar lo ligam per una fòrma seleccionada",
|
||||||
"eraser": "Goma",
|
"eraser": "Goma",
|
||||||
"hand": "Man (aisina de desplaçament de la vista)"
|
"frame": "",
|
||||||
|
"hand": "Man (aisina de desplaçament de la vista)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Accions del canabàs",
|
"canvasActions": "Accions del canabàs",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "ਪ੍ਰਕਾਸ਼ਤ ਹੈ",
|
"statusPublished": "ਪ੍ਰਕਾਸ਼ਤ ਹੈ",
|
||||||
"sidebarLock": "ਸਾਈਡਬਾਰ ਨੂੰ ਖੁੱਲ੍ਹਾ ਰੱਖੋ",
|
"sidebarLock": "ਸਾਈਡਬਾਰ ਨੂੰ ਖੁੱਲ੍ਹਾ ਰੱਖੋ",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "ਪੈੱਨ ਮੋਡ - ਟੱਚ ਤੋਂ ਬਚਾਉਂਦਾ ਹੈ",
|
"penMode": "ਪੈੱਨ ਮੋਡ - ਟੱਚ ਤੋਂ ਬਚਾਉਂਦਾ ਹੈ",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "ਰਬੜ",
|
"eraser": "ਰਬੜ",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "ਕੈਨਵਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ",
|
"canvasActions": "ਕੈਨਵਸ ਦੀਆਂ ਕਾਰਵਾਈਆਂ",
|
||||||
|
|||||||
@@ -1,56 +1,56 @@
|
|||||||
{
|
{
|
||||||
"ar-SA": 80,
|
"ar-SA": 79,
|
||||||
"az-AZ": 20,
|
"az-AZ": 20,
|
||||||
"bg-BG": 54,
|
"bg-BG": 54,
|
||||||
"bn-BD": 60,
|
"bn-BD": 59,
|
||||||
"ca-ES": 88,
|
"ca-ES": 87,
|
||||||
"cs-CZ": 100,
|
"cs-CZ": 98,
|
||||||
"da-DK": 33,
|
"da-DK": 33,
|
||||||
"de-DE": 100,
|
"de-DE": 100,
|
||||||
"el-GR": 93,
|
"el-GR": 92,
|
||||||
"en": 100,
|
"en": 100,
|
||||||
"es-ES": 89,
|
"es-ES": 88,
|
||||||
"eu-ES": 90,
|
"eu-ES": 100,
|
||||||
"fa-IR": 89,
|
"fa-IR": 88,
|
||||||
"fi-FI": 88,
|
"fi-FI": 87,
|
||||||
"fr-FR": 89,
|
"fr-FR": 88,
|
||||||
"gl-ES": 89,
|
"gl-ES": 88,
|
||||||
"he-IL": 87,
|
"he-IL": 86,
|
||||||
"hi-IN": 78,
|
"hi-IN": 77,
|
||||||
"hu-HU": 76,
|
"hu-HU": 76,
|
||||||
"id-ID": 95,
|
"id-ID": 94,
|
||||||
"it-IT": 98,
|
"it-IT": 98,
|
||||||
"ja-JP": 89,
|
"ja-JP": 88,
|
||||||
"kaa": 18,
|
"kaa": 18,
|
||||||
"kab-KAB": 88,
|
"kab-KAB": 87,
|
||||||
"kk-KZ": 21,
|
"kk-KZ": 21,
|
||||||
"km-KH": 95,
|
"km-KH": 94,
|
||||||
"ko-KR": 100,
|
"ko-KR": 100,
|
||||||
"ku-TR": 90,
|
"ku-TR": 100,
|
||||||
"lt-LT": 56,
|
"lt-LT": 55,
|
||||||
"lv-LV": 89,
|
"lv-LV": 88,
|
||||||
"mr-IN": 96,
|
"mr-IN": 95,
|
||||||
"my-MM": 41,
|
"my-MM": 40,
|
||||||
"nb-NO": 100,
|
"nb-NO": 100,
|
||||||
"nl-NL": 83,
|
"nl-NL": 83,
|
||||||
"nn-NO": 77,
|
"nn-NO": 76,
|
||||||
"oc-FR": 87,
|
"oc-FR": 86,
|
||||||
"pa-IN": 90,
|
"pa-IN": 89,
|
||||||
"pl-PL": 100,
|
"pl-PL": 98,
|
||||||
"pt-BR": 99,
|
"pt-BR": 98,
|
||||||
"pt-PT": 95,
|
"pt-PT": 94,
|
||||||
"ro-RO": 100,
|
"ro-RO": 98,
|
||||||
"ru-RU": 100,
|
"ru-RU": 98,
|
||||||
"si-LK": 9,
|
"si-LK": 9,
|
||||||
"sk-SK": 100,
|
"sk-SK": 98,
|
||||||
"sl-SI": 100,
|
"sl-SI": 100,
|
||||||
"sv-SE": 100,
|
"sv-SE": 100,
|
||||||
"ta-IN": 85,
|
"ta-IN": 84,
|
||||||
"th-TH": 39,
|
"th-TH": 38,
|
||||||
"tr-TR": 87,
|
"tr-TR": 86,
|
||||||
"uk-UA": 97,
|
"uk-UA": 96,
|
||||||
"vi-VN": 56,
|
"vi-VN": 56,
|
||||||
"zh-CN": 100,
|
"zh-CN": 100,
|
||||||
"zh-HK": 26,
|
"zh-HK": 25,
|
||||||
"zh-TW": 100
|
"zh-TW": 100
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Opublikowano",
|
"statusPublished": "Opublikowano",
|
||||||
"sidebarLock": "Panel boczny zawsze otwarty",
|
"sidebarLock": "Panel boczny zawsze otwarty",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": "Wybierz kolor z płótna"
|
"eyeDropper": "Wybierz kolor z płótna"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Tryb pióra — zapobiegaj dotknięciom",
|
"penMode": "Tryb pióra — zapobiegaj dotknięciom",
|
||||||
"link": "Dodaj/aktualizuj link dla wybranego kształtu",
|
"link": "Dodaj/aktualizuj link dla wybranego kształtu",
|
||||||
"eraser": "Gumka",
|
"eraser": "Gumka",
|
||||||
"hand": "Ręka (narzędzie do przesuwania)"
|
"frame": "",
|
||||||
|
"hand": "Ręka (narzędzie do przesuwania)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Narzędzia",
|
"canvasActions": "Narzędzia",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicado",
|
"statusPublished": "Publicado",
|
||||||
"sidebarLock": "Manter barra lateral aberta",
|
"sidebarLock": "Manter barra lateral aberta",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Modo caneta — impede o toque",
|
"penMode": "Modo caneta — impede o toque",
|
||||||
"link": "Adicionar/Atualizar link para uma forma selecionada",
|
"link": "Adicionar/Atualizar link para uma forma selecionada",
|
||||||
"eraser": "Borracha",
|
"eraser": "Borracha",
|
||||||
"hand": "Mão (ferramenta de rolagem)"
|
"frame": "",
|
||||||
|
"hand": "Mão (ferramenta de rolagem)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Ações da tela",
|
"canvasActions": "Ações da tela",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicado",
|
"statusPublished": "Publicado",
|
||||||
"sidebarLock": "Manter a barra lateral aberta",
|
"sidebarLock": "Manter a barra lateral aberta",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Modo caneta - impedir toque",
|
"penMode": "Modo caneta - impedir toque",
|
||||||
"link": "Acrescentar/ Adicionar ligação para uma forma seleccionada",
|
"link": "Acrescentar/ Adicionar ligação para uma forma seleccionada",
|
||||||
"eraser": "Borracha",
|
"eraser": "Borracha",
|
||||||
"hand": "Mão (ferramenta de movimento da tela)"
|
"frame": "",
|
||||||
|
"hand": "Mão (ferramenta de movimento da tela)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Ações da área de desenho",
|
"canvasActions": "Ações da área de desenho",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicat",
|
"statusPublished": "Publicat",
|
||||||
"sidebarLock": "Păstrează deschisă bara laterală",
|
"sidebarLock": "Păstrează deschisă bara laterală",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": "Alegere culoare din pânză"
|
"eyeDropper": "Alegere culoare din pânză"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Mod stilou – împiedică atingerea",
|
"penMode": "Mod stilou – împiedică atingerea",
|
||||||
"link": "Adăugare/actualizare URL pentru forma selectată",
|
"link": "Adăugare/actualizare URL pentru forma selectată",
|
||||||
"eraser": "Radieră",
|
"eraser": "Radieră",
|
||||||
"hand": "Mână (instrument de panoramare)"
|
"frame": "",
|
||||||
|
"hand": "Mână (instrument de panoramare)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Acțiuni pentru pânză",
|
"canvasActions": "Acțiuni pentru pânză",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Опубликовано",
|
"statusPublished": "Опубликовано",
|
||||||
"sidebarLock": "Держать боковую панель открытой",
|
"sidebarLock": "Держать боковую панель открытой",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": "Взять образец цвета с холста"
|
"eyeDropper": "Взять образец цвета с холста"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Режим пера - предотвращение касания",
|
"penMode": "Режим пера - предотвращение касания",
|
||||||
"link": "Добавить/обновить ссылку для выбранной фигуры",
|
"link": "Добавить/обновить ссылку для выбранной фигуры",
|
||||||
"eraser": "Ластик",
|
"eraser": "Ластик",
|
||||||
"hand": "Рука (перемещение холста)"
|
"frame": "",
|
||||||
|
"hand": "Рука (перемещение холста)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Операции холста",
|
"canvasActions": "Операции холста",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "",
|
"eraser": "",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "",
|
"canvasActions": "",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Zverejnené",
|
"statusPublished": "Zverejnené",
|
||||||
"sidebarLock": "Nechať bočný panel otvorený",
|
"sidebarLock": "Nechať bočný panel otvorený",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": "Vybrať farbu z plátna"
|
"eyeDropper": "Vybrať farbu z plátna"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Režim pera – zabrániť dotyku",
|
"penMode": "Režim pera – zabrániť dotyku",
|
||||||
"link": "Pridať/ Upraviť odkaz pre vybraný tvar",
|
"link": "Pridať/ Upraviť odkaz pre vybraný tvar",
|
||||||
"eraser": "Guma",
|
"eraser": "Guma",
|
||||||
"hand": "Ruka (nástroj pre pohyb plátna)"
|
"frame": "",
|
||||||
|
"hand": "Ruka (nástroj pre pohyb plátna)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Akcie plátna",
|
"canvasActions": "Akcie plátna",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Objavljeno",
|
"statusPublished": "Objavljeno",
|
||||||
"sidebarLock": "Obdrži stransko vrstico odprto",
|
"sidebarLock": "Obdrži stransko vrstico odprto",
|
||||||
|
"selectAllElementsInFrame": "Izberi vse elemente v okvirju",
|
||||||
|
"removeAllElementsFromFrame": "Izbriši vse elemente v okvirju",
|
||||||
"eyeDropper": "Izberi barvo s platna"
|
"eyeDropper": "Izberi barvo s platna"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Način peresa - prepreči dotik",
|
"penMode": "Način peresa - prepreči dotik",
|
||||||
"link": "Dodaj/posodobi povezavo za izbrano obliko",
|
"link": "Dodaj/posodobi povezavo za izbrano obliko",
|
||||||
"eraser": "Radirka",
|
"eraser": "Radirka",
|
||||||
"hand": "Roka (orodje za premikanje)"
|
"frame": "Okvir",
|
||||||
|
"hand": "Roka (orodje za premikanje)",
|
||||||
|
"extraTools": "Več orodij"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Dejanja za platno",
|
"canvasActions": "Dejanja za platno",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Publicerad",
|
"statusPublished": "Publicerad",
|
||||||
"sidebarLock": "Håll sidofältet öppet",
|
"sidebarLock": "Håll sidofältet öppet",
|
||||||
|
"selectAllElementsInFrame": "Markera alla element i rutan",
|
||||||
|
"removeAllElementsFromFrame": "Ta bort alla element från rutan",
|
||||||
"eyeDropper": "Välj färg från canvas"
|
"eyeDropper": "Välj färg från canvas"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Pennläge - förhindra touch",
|
"penMode": "Pennläge - förhindra touch",
|
||||||
"link": "Lägg till / Uppdatera länk för en vald form",
|
"link": "Lägg till / Uppdatera länk för en vald form",
|
||||||
"eraser": "Radergummi",
|
"eraser": "Radergummi",
|
||||||
"hand": "Hand (panoreringsverktyg)"
|
"frame": "Rutverktyg",
|
||||||
|
"hand": "Hand (panoreringsverktyg)",
|
||||||
|
"extraTools": "Fler verktyg"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Canvas-åtgärder",
|
"canvasActions": "Canvas-åtgärder",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "வெளியிடப்பட்டது",
|
"statusPublished": "வெளியிடப்பட்டது",
|
||||||
"sidebarLock": "பக்கப்பட்டையைத் திறந்தே வை",
|
"sidebarLock": "பக்கப்பட்டையைத் திறந்தே வை",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": "கித்தானிலிருந்து நிறம் தேர்ந்தெடு"
|
"eyeDropper": "கித்தானிலிருந்து நிறம் தேர்ந்தெடு"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "தேர்தெடுத்த வடிவத்திற்குத் தொடுப்பைச் சேர்/ புதுப்பி",
|
"link": "தேர்தெடுத்த வடிவத்திற்குத் தொடுப்பைச் சேர்/ புதுப்பி",
|
||||||
"eraser": "அழிப்பி",
|
"eraser": "அழிப்பி",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "கித்தான் செயல்கள்",
|
"canvasActions": "கித்தான் செயல்கள்",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "เผยแพร่",
|
"statusPublished": "เผยแพร่",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "ยางลบ",
|
"eraser": "ยางลบ",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "",
|
"canvasActions": "",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Yayınlandı",
|
"statusPublished": "Yayınlandı",
|
||||||
"sidebarLock": "Kenar çubuğu açık kalsın",
|
"sidebarLock": "Kenar çubuğu açık kalsın",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Kalem modu - dokunmayı engelle",
|
"penMode": "Kalem modu - dokunmayı engelle",
|
||||||
"link": "Seçilen şekil için bağlantı Ekle/Güncelle",
|
"link": "Seçilen şekil için bağlantı Ekle/Güncelle",
|
||||||
"eraser": "Silgi",
|
"eraser": "Silgi",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Tuval eylemleri",
|
"canvasActions": "Tuval eylemleri",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Опубліковано",
|
"statusPublished": "Опубліковано",
|
||||||
"sidebarLock": "Не закривати бокове меню",
|
"sidebarLock": "Не закривати бокове меню",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Режим пера - запобігання дотику",
|
"penMode": "Режим пера - запобігання дотику",
|
||||||
"link": "Додати/Оновити посилання для вибраної форми",
|
"link": "Додати/Оновити посилання для вибраної форми",
|
||||||
"eraser": "Очищувач",
|
"eraser": "Очищувач",
|
||||||
"hand": "Рука (інструмент для панорамування)"
|
"frame": "",
|
||||||
|
"hand": "Рука (інструмент для панорамування)",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Дії з полотном",
|
"canvasActions": "Дії з полотном",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "Đã đăng tải",
|
"statusPublished": "Đã đăng tải",
|
||||||
"sidebarLock": "Giữ thanh bên luôn mở",
|
"sidebarLock": "Giữ thanh bên luôn mở",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "Chế độ bút vẽ - ngăn ngừa chạm nhầm",
|
"penMode": "Chế độ bút vẽ - ngăn ngừa chạm nhầm",
|
||||||
"link": "Thêm/ Chỉnh sửa liên kết cho hình được chọn",
|
"link": "Thêm/ Chỉnh sửa liên kết cho hình được chọn",
|
||||||
"eraser": "Xóa",
|
"eraser": "Xóa",
|
||||||
"hand": "Tay kéo"
|
"frame": "",
|
||||||
|
"hand": "Tay kéo",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Hành động canvas",
|
"canvasActions": "Hành động canvas",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "已发布",
|
"statusPublished": "已发布",
|
||||||
"sidebarLock": "侧边栏常驻",
|
"sidebarLock": "侧边栏常驻",
|
||||||
|
"selectAllElementsInFrame": "选择画框中的所有元素",
|
||||||
|
"removeAllElementsFromFrame": "删除画框中的所有元素",
|
||||||
"eyeDropper": "从画布上取色"
|
"eyeDropper": "从画布上取色"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "笔模式 – 避免误触",
|
"penMode": "笔模式 – 避免误触",
|
||||||
"link": "为选中的形状添加/更新链接",
|
"link": "为选中的形状添加/更新链接",
|
||||||
"eraser": "橡皮",
|
"eraser": "橡皮",
|
||||||
"hand": "抓手(平移工具)"
|
"frame": "画框工具",
|
||||||
|
"hand": "抓手(平移工具)",
|
||||||
|
"extraTools": "更多工具"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "画布动作",
|
"canvasActions": "画布动作",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "",
|
"statusPublished": "",
|
||||||
"sidebarLock": "",
|
"sidebarLock": "",
|
||||||
|
"selectAllElementsInFrame": "",
|
||||||
|
"removeAllElementsFromFrame": "",
|
||||||
"eyeDropper": ""
|
"eyeDropper": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "",
|
"penMode": "",
|
||||||
"link": "",
|
"link": "",
|
||||||
"eraser": "",
|
"eraser": "",
|
||||||
"hand": ""
|
"frame": "",
|
||||||
|
"hand": "",
|
||||||
|
"extraTools": ""
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "畫布動作",
|
"canvasActions": "畫布動作",
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
},
|
},
|
||||||
"statusPublished": "已發布",
|
"statusPublished": "已發布",
|
||||||
"sidebarLock": "側欄維持開啟",
|
"sidebarLock": "側欄維持開啟",
|
||||||
|
"selectAllElementsInFrame": "選取框架內的所有元素",
|
||||||
|
"removeAllElementsFromFrame": "從框架內移除所有元素",
|
||||||
"eyeDropper": "從畫布中選取顏色"
|
"eyeDropper": "從畫布中選取顏色"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
@@ -221,7 +223,9 @@
|
|||||||
"penMode": "筆模式 - 避免觸摸",
|
"penMode": "筆模式 - 避免觸摸",
|
||||||
"link": "為所選的形狀增加\b/更新連結",
|
"link": "為所選的形狀增加\b/更新連結",
|
||||||
"eraser": "橡皮擦",
|
"eraser": "橡皮擦",
|
||||||
"hand": "手形(平移工具)"
|
"frame": "框架工具",
|
||||||
|
"hand": "手形(平移工具)",
|
||||||
|
"extraTools": "更多工具"
|
||||||
},
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "canvas 動作",
|
"canvasActions": "canvas 動作",
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- Add support for `opts.fitToViewport` and `opts.viewportZoomFactor` in the [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) API. [#6581](https://github.com/excalidraw/excalidraw/pull/6581).
|
||||||
|
- Properly sanitize element `link` urls. [#6728](https://github.com/excalidraw/excalidraw/pull/6728).
|
||||||
- Sidebar component now supports tabs — for more detailed description of new behavior and breaking changes, see the linked PR. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
- Sidebar component now supports tabs — for more detailed description of new behavior and breaking changes, see the linked PR. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
||||||
- Exposed `DefaultSidebar` component to allow modifying the default sidebar, such as adding custom tabs to it. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
- Exposed `DefaultSidebar` component to allow modifying the default sidebar, such as adding custom tabs to it. [#6213](https://github.com/excalidraw/excalidraw/pull/6213)
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- [`ExcalidrawAPI.scrolToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) has new opts object allowing you to fit viewport to content, and animate the scrolling. [#6319](https://github.com/excalidraw/excalidraw/pull/6319)
|
- [`ExcalidrawAPI.scrollToContent`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/ref#scrolltocontent) has new opts object allowing you to fit viewport to content, and animate the scrolling. [#6319](https://github.com/excalidraw/excalidraw/pull/6319)
|
||||||
|
|
||||||
- Expose `useI18n()` hook return an object containing `t()` i18n helper and current `langCode`. You can use this in components you render as `<Excalidraw>` children to render any of our i18n locale strings. [#6224](https://github.com/excalidraw/excalidraw/pull/6224)
|
- Expose `useI18n()` hook return an object containing `t()` i18n helper and current `langCode`. You can use this in components you render as `<Excalidraw>` children to render any of our i18n locale strings. [#6224](https://github.com/excalidraw/excalidraw/pull/6224)
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user