mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-08 23:04:45 +01:00
Compare commits
105 Commits
chore/deps
...
v0.10.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50a7ac431a | ||
|
|
4fe66d5d5e | ||
|
|
39d45afc06 | ||
|
|
00c6940851 | ||
|
|
982cba2035 | ||
|
|
54739cd2df | ||
|
|
75aeaa6c38 | ||
|
|
bea4a1e066 | ||
|
|
e8b462cc31 | ||
|
|
c86c176e10 | ||
|
|
b09c11bb14 | ||
|
|
7199d13f48 | ||
|
|
7d1fddc144 | ||
|
|
5da3207633 | ||
|
|
8c9786e026 | ||
|
|
f0f13ed694 | ||
|
|
850d8eb47e | ||
|
|
f287f9c002 | ||
|
|
78df5bc852 | ||
|
|
f0073c7e26 | ||
|
|
fa7a313412 | ||
|
|
8b3f236cd8 | ||
|
|
621812d0eb | ||
|
|
d607249205 | ||
|
|
df28c3299f | ||
|
|
b00a57b4be | ||
|
|
9277e839db | ||
|
|
0d5d60944f | ||
|
|
489a652d73 | ||
|
|
2b85d96121 | ||
|
|
6ce535d3a4 | ||
|
|
da43cf5635 | ||
|
|
603ecfba34 | ||
|
|
a589708737 | ||
|
|
4df401d012 | ||
|
|
b2c4552416 | ||
|
|
5cae218f1b | ||
|
|
4be726d405 | ||
|
|
99623334d1 | ||
|
|
685abac81a | ||
|
|
9581c45522 | ||
|
|
0749d2c1f3 | ||
|
|
8787f3dc60 | ||
|
|
5fabc57277 | ||
|
|
e7cbb859f0 | ||
|
|
aa860251c7 | ||
|
|
380aaa30e6 | ||
|
|
2e61fec7a6 | ||
|
|
3c295559c7 | ||
|
|
55d3287abf | ||
|
|
e3e967421e | ||
|
|
77aae63006 | ||
|
|
ee64a7e264 | ||
|
|
097362662d | ||
|
|
038e9c13dd | ||
|
|
bc8ba08ad0 | ||
|
|
f861a9fdd0 | ||
|
|
62303b8a22 | ||
|
|
9cc741ab3a | ||
|
|
2d279cbb02 | ||
|
|
57ea4fdf9a | ||
|
|
44402f42bf | ||
|
|
bdead4d164 | ||
|
|
bfc0656475 | ||
|
|
a33a3334f7 | ||
|
|
969d3c694a | ||
|
|
5cd921549a | ||
|
|
437afcbea4 | ||
|
|
6dee02e320 | ||
|
|
74a2f16501 | ||
|
|
fd4460be37 | ||
|
|
e82d0493cf | ||
|
|
083cb4c656 | ||
|
|
d067365c1d | ||
|
|
273cac6b60 | ||
|
|
b9337b8a36 | ||
|
|
0e0025921b | ||
|
|
efc01ddab1 | ||
|
|
7bce22b114 | ||
|
|
aab4965bbb | ||
|
|
486a9a3da8 | ||
|
|
2425c06082 | ||
|
|
79ea844901 | ||
|
|
6690215cd1 | ||
|
|
7f5e783fe8 | ||
|
|
9325109836 | ||
|
|
69b6fbb3f4 | ||
|
|
6b6002baae | ||
|
|
54dcb73701 | ||
|
|
b595d3fcba | ||
|
|
d0867d1c3b | ||
|
|
0d19e9210c | ||
|
|
4249de41d4 | ||
|
|
15f02ba191 | ||
|
|
a2e1199907 | ||
|
|
c08e9c4172 | ||
|
|
abfc58eb24 | ||
|
|
035c7affff | ||
|
|
c819b653bf | ||
|
|
60cea7a0c2 | ||
|
|
d63b6a3469 | ||
|
|
0912fe1c93 | ||
|
|
360310de31 | ||
|
|
716c78e930 | ||
|
|
ba48974351 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,9 +5,11 @@
|
|||||||
.env.test.local
|
.env.test.local
|
||||||
.envrc
|
.envrc
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
.history
|
||||||
.idea
|
.idea
|
||||||
.vercel
|
.vercel
|
||||||
.vscode
|
.vscode
|
||||||
|
.yarn
|
||||||
*.log
|
*.log
|
||||||
*.tgz
|
*.tgz
|
||||||
build
|
build
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ ARG NODE_ENV=production
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN yarn build:app:docker
|
RUN yarn build:app:docker
|
||||||
|
|
||||||
FROM nginx:1.17-alpine
|
FROM nginx:1.21-alpine
|
||||||
|
|
||||||
COPY --from=build /opt/node_app/build /usr/share/nginx/html
|
COPY --from=build /opt/node_app/build /usr/share/nginx/html
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ The first set of digits is the room. This is visible from the server that’s go
|
|||||||
|
|
||||||
The second set of digits is the encryption key. The Excalidraw server doesn’t know about it. This is what all the participants use to encrypt/decrypt the messages.
|
The second set of digits is the encryption key. The Excalidraw server doesn’t know about it. This is what all the participants use to encrypt/decrypt the messages.
|
||||||
|
|
||||||
|
> Note: Please ensure that the encryption key is 22 characters long.
|
||||||
|
|
||||||
## Shape libraries
|
## Shape libraries
|
||||||
|
|
||||||
Find a growing list of libraries containing assets for your drawings at [libraries.excalidraw.com](https://libraries.excalidraw.com).
|
Find a growing list of libraries containing assets for your drawings at [libraries.excalidraw.com](https://libraries.excalidraw.com).
|
||||||
@@ -93,7 +95,7 @@ These instructions will get you a copy of the project up and running on your loc
|
|||||||
#### Requirements
|
#### Requirements
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/)
|
- [Node.js](https://nodejs.org/en/)
|
||||||
- [Yarn](https://yarnpkg.com/getting-started/install)
|
- [Yarn](https://yarnpkg.com/getting-started/install) (v1 or v2.4.2+)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
|
||||||
#### Clone the repo
|
#### Clone the repo
|
||||||
|
|||||||
@@ -2,5 +2,8 @@
|
|||||||
"firestore": {
|
"firestore": {
|
||||||
"rules": "firestore.rules",
|
"rules": "firestore.rules",
|
||||||
"indexes": "firestore.indexes.json"
|
"indexes": "firestore.indexes.json"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"rules": "storage.rules"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
firebase-project/storage.rules
Normal file
12
firebase-project/storage.rules
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
rules_version = '2';
|
||||||
|
service firebase.storage {
|
||||||
|
match /b/{bucket}/o {
|
||||||
|
match /{migrations} {
|
||||||
|
match /{scenes}/{scene} {
|
||||||
|
allow get, write: if true;
|
||||||
|
// redundant, but let's be explicit'
|
||||||
|
allow list: if false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,15 +19,16 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dwelle/browser-fs-access": "0.21.1",
|
||||||
"@sentry/browser": "6.2.5",
|
"@sentry/browser": "6.2.5",
|
||||||
"@sentry/integrations": "6.2.5",
|
"@sentry/integrations": "6.2.5",
|
||||||
"@testing-library/jest-dom": "5.11.10",
|
"@testing-library/jest-dom": "5.11.10",
|
||||||
"@testing-library/react": "11.2.6",
|
"@testing-library/react": "11.2.6",
|
||||||
|
"@tldraw/vec": "0.0.106",
|
||||||
"@types/jest": "26.0.22",
|
"@types/jest": "26.0.22",
|
||||||
"@types/react": "17.0.3",
|
"@types/react": "17.0.3",
|
||||||
"@types/react-dom": "17.0.3",
|
"@types/react-dom": "17.0.3",
|
||||||
"@types/socket.io-client": "1.4.36",
|
"@types/socket.io-client": "1.4.36",
|
||||||
"browser-fs-access": "0.16.4",
|
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"firebase": "8.3.3",
|
"firebase": "8.3.3",
|
||||||
"i18next-browser-languagedetector": "6.1.0",
|
"i18next-browser-languagedetector": "6.1.0",
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
"nanoid": "3.1.22",
|
"nanoid": "3.1.22",
|
||||||
"open-color": "1.8.0",
|
"open-color": "1.8.0",
|
||||||
"pako": "1.0.11",
|
"pako": "1.0.11",
|
||||||
"perfect-freehand": "0.4.8",
|
"perfect-freehand": "1.0.15",
|
||||||
"png-chunk-text": "1.0.0",
|
"png-chunk-text": "1.0.0",
|
||||||
"png-chunks-encode": "1.0.0",
|
"png-chunks-encode": "1.0.0",
|
||||||
"png-chunks-extract": "1.0.0",
|
"png-chunks-extract": "1.0.0",
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"transformIgnorePatterns": [
|
"transformIgnorePatterns": [
|
||||||
"node_modules/(?!(roughjs|points-on-curve|path-data-parser|points-on-path|browser-fs-access)/)"
|
"node_modules/(?!(roughjs|points-on-curve|path-data-parser|points-on-path|@dwelle/browser-fs-access)/)"
|
||||||
],
|
],
|
||||||
"resetMocks": false
|
"resetMocks": false
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
@@ -13,6 +13,18 @@
|
|||||||
|
|
||||||
<meta name="theme-color" content="#000" />
|
<meta name="theme-color" content="#000" />
|
||||||
|
|
||||||
|
<!-- Declarative Link Capturing (https://web.dev/declarative-link-capturing/) -->
|
||||||
|
<meta
|
||||||
|
http-equiv="origin-trial"
|
||||||
|
content="Ak3VyzTheARtX2CnxBZ3ZKxImB0mNyvDakmMxeAChgxrWFMZ3IGN64VP+uj36VxM5OegsbLmrP258b1xvqp7+Q8AAABbeyJvcmlnaW4iOiJodHRwczovL2V4Y2FsaWRyYXcuY29tOjQ0MyIsImZlYXR1cmUiOiJXZWJBcHBMaW5rQ2FwdHVyaW5nIiwiZXhwaXJ5IjoxNjM0MDgzMTk5fQ=="
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- File Handling (https://web.dev/file-handling/) -->
|
||||||
|
<meta
|
||||||
|
http-equiv="origin-trial"
|
||||||
|
content="AkMQsAnFmKfRfPKQHNCv2WmZREqgwkqhyt2M7aOwQiCStB+hPYnGnv+mNbkPDAsGXrwsj/waFi76wPzTDUaEeQ0AAABUeyJvcmlnaW4iOiJodHRwczovL2V4Y2FsaWRyYXcuY29tOjQ0MyIsImZlYXR1cmUiOiJGaWxlSGFuZGxpbmciLCJleHBpcnkiOjE2MzQwODMxOTl9"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- General tags -->
|
<!-- General tags -->
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
@@ -117,6 +129,7 @@
|
|||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visually-hidden {
|
.visually-hidden {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"capture_links": "new_client",
|
"capture_links": "new-client",
|
||||||
"share_target": {
|
"share_target": {
|
||||||
"action": "/web-share-target",
|
"action": "/web-share-target",
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ const crowdinMap = {
|
|||||||
"zh-CN": "en-zhcn",
|
"zh-CN": "en-zhcn",
|
||||||
"zh-TW": "en-zhtw",
|
"zh-TW": "en-zhtw",
|
||||||
"lv-LV": "en-lv",
|
"lv-LV": "en-lv",
|
||||||
"cs-CZ": "cs-cz",
|
"cs-CZ": "en-cs",
|
||||||
|
"kk-KZ": "en-kk",
|
||||||
};
|
};
|
||||||
|
|
||||||
const flags = {
|
const flags = {
|
||||||
@@ -78,6 +79,7 @@ const flags = {
|
|||||||
"zh-TW": "🇹🇼",
|
"zh-TW": "🇹🇼",
|
||||||
"lv-LV": "🇱🇻",
|
"lv-LV": "🇱🇻",
|
||||||
"cs-CZ": "🇨🇿",
|
"cs-CZ": "🇨🇿",
|
||||||
|
"kk-KZ": "🇰🇿",
|
||||||
};
|
};
|
||||||
|
|
||||||
const languages = {
|
const languages = {
|
||||||
@@ -117,6 +119,7 @@ const languages = {
|
|||||||
"zh-TW": "繁體中文",
|
"zh-TW": "繁體中文",
|
||||||
"lv-LV": "Latviešu",
|
"lv-LV": "Latviešu",
|
||||||
"cs-CZ": "Česky",
|
"cs-CZ": "Česky",
|
||||||
|
"kk-KZ": "Қазақ тілі",
|
||||||
};
|
};
|
||||||
|
|
||||||
const percentages = fs.readFileSync(
|
const percentages = fs.readFileSync(
|
||||||
|
|||||||
39
scripts/release.js
Normal file
39
scripts/release.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const util = require("util");
|
||||||
|
const exec = util.promisify(require("child_process").exec);
|
||||||
|
const updateReadme = require("./updateReadme");
|
||||||
|
const updateChangelog = require("./updateChangelog");
|
||||||
|
|
||||||
|
const excalidrawDir = `${__dirname}/../src/packages/excalidraw`;
|
||||||
|
const excalidrawPackage = `${excalidrawDir}/package.json`;
|
||||||
|
|
||||||
|
const updatePackageVersion = (nextVersion) => {
|
||||||
|
const pkg = require(excalidrawPackage);
|
||||||
|
pkg.version = nextVersion;
|
||||||
|
const content = `${JSON.stringify(pkg, null, 2)}\n`;
|
||||||
|
fs.writeFileSync(excalidrawPackage, content, "utf-8");
|
||||||
|
};
|
||||||
|
|
||||||
|
const release = async (nextVersion) => {
|
||||||
|
try {
|
||||||
|
updateReadme();
|
||||||
|
await updateChangelog(nextVersion);
|
||||||
|
updatePackageVersion(nextVersion);
|
||||||
|
await exec(`git add -u`);
|
||||||
|
await exec(
|
||||||
|
`git commit -m "docs: release @excalidraw/excalidraw@${nextVersion} 🎉"`,
|
||||||
|
);
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
console.log("Done!");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextVersion = process.argv.slice(2)[0];
|
||||||
|
if (!nextVersion) {
|
||||||
|
console.error("Pass the next version to release!");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
release(nextVersion);
|
||||||
97
scripts/updateChangelog.js
Normal file
97
scripts/updateChangelog.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const util = require("util");
|
||||||
|
const exec = util.promisify(require("child_process").exec);
|
||||||
|
|
||||||
|
const excalidrawDir = `${__dirname}/../src/packages/excalidraw`;
|
||||||
|
const excalidrawPackage = `${excalidrawDir}/package.json`;
|
||||||
|
const pkg = require(excalidrawPackage);
|
||||||
|
const lastVersion = pkg.version;
|
||||||
|
const existingChangeLog = fs.readFileSync(
|
||||||
|
`${excalidrawDir}/CHANGELOG.md`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
const supportedTypes = ["feat", "fix", "style", "refactor", "perf", "build"];
|
||||||
|
const headerForType = {
|
||||||
|
feat: "Features",
|
||||||
|
fix: "Fixes",
|
||||||
|
style: "Styles",
|
||||||
|
refactor: " Refactor",
|
||||||
|
perf: "Performance",
|
||||||
|
build: "Build",
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCommitHashForLastVersion = async () => {
|
||||||
|
try {
|
||||||
|
const commitMessage = `"release @excalidraw/excalidraw@${lastVersion}"`;
|
||||||
|
const { stdout } = await exec(
|
||||||
|
`git log --format=format:"%H" --grep=${commitMessage}`,
|
||||||
|
);
|
||||||
|
return stdout;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLibraryCommitsSinceLastRelease = async () => {
|
||||||
|
const commitHash = await getCommitHashForLastVersion();
|
||||||
|
const { stdout } = await exec(
|
||||||
|
`git log --pretty=format:%s ${commitHash}...master`,
|
||||||
|
);
|
||||||
|
const commitsSinceLastRelease = stdout.split("\n");
|
||||||
|
const commitList = {};
|
||||||
|
supportedTypes.forEach((type) => {
|
||||||
|
commitList[type] = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
commitsSinceLastRelease.forEach((commit) => {
|
||||||
|
const indexOfColon = commit.indexOf(":");
|
||||||
|
const type = commit.slice(0, indexOfColon);
|
||||||
|
if (!supportedTypes.includes(type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const messageWithoutType = commit.slice(indexOfColon + 1).trim();
|
||||||
|
const messageWithCapitalizeFirst =
|
||||||
|
messageWithoutType.charAt(0).toUpperCase() + messageWithoutType.slice(1);
|
||||||
|
const prNumber = commit.match(/\(#([0-9]*)\)/)[1];
|
||||||
|
|
||||||
|
// return if the changelog already contains the pr number which would happen for package updates
|
||||||
|
if (existingChangeLog.includes(prNumber)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const prMarkdown = `[#${prNumber}](https://github.com/excalidraw/excalidraw/pull/${prNumber})`;
|
||||||
|
const messageWithPRLink = messageWithCapitalizeFirst.replace(
|
||||||
|
/\(#[0-9]*\)/,
|
||||||
|
prMarkdown,
|
||||||
|
);
|
||||||
|
commitList[type].push(messageWithPRLink);
|
||||||
|
});
|
||||||
|
return commitList;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChangelog = async (nextVersion) => {
|
||||||
|
const commitList = await getLibraryCommitsSinceLastRelease();
|
||||||
|
let changelogForLibrary =
|
||||||
|
"## Excalidraw Library\n\n**_This section lists the updates made to the excalidraw library and will not affect the integration._**\n\n";
|
||||||
|
supportedTypes.forEach((type) => {
|
||||||
|
if (commitList[type].length) {
|
||||||
|
changelogForLibrary += `### ${headerForType[type]}\n\n`;
|
||||||
|
const commits = commitList[type];
|
||||||
|
commits.forEach((commit) => {
|
||||||
|
changelogForLibrary += `- ${commit}\n\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
changelogForLibrary += "---\n";
|
||||||
|
const lastVersionIndex = existingChangeLog.indexOf(`## ${lastVersion}`);
|
||||||
|
let updatedContent =
|
||||||
|
existingChangeLog.slice(0, lastVersionIndex) +
|
||||||
|
changelogForLibrary +
|
||||||
|
existingChangeLog.slice(lastVersionIndex);
|
||||||
|
const currentDate = new Date().toISOString().slice(0, 10);
|
||||||
|
const newVersion = `## ${nextVersion} (${currentDate})`;
|
||||||
|
updatedContent = updatedContent.replace(`## Unreleased`, newVersion);
|
||||||
|
fs.writeFileSync(`${excalidrawDir}/CHANGELOG.md`, updatedContent, "utf8");
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = updateChangelog;
|
||||||
27
scripts/updateReadme.js
Normal file
27
scripts/updateReadme.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
const updateReadme = () => {
|
||||||
|
const excalidrawDir = `${__dirname}/../src/packages/excalidraw`;
|
||||||
|
let data = fs.readFileSync(`${excalidrawDir}/README_NEXT.md`, "utf8");
|
||||||
|
|
||||||
|
// remove note for unstable release
|
||||||
|
data = data.replace(
|
||||||
|
/<!-- unstable-readme-start-->[\s\S]*?<!-- unstable-readme-end-->/,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
// replace "excalidraw-next" with "excalidraw"
|
||||||
|
data = data.replace(/excalidraw-next/g, "excalidraw");
|
||||||
|
data = data.trim();
|
||||||
|
|
||||||
|
const demoIndex = data.indexOf("### Demo");
|
||||||
|
const excalidrawNextNote =
|
||||||
|
"#### Note\n\n**If you don't want to wait for the next stable release and try out the unreleased changes you can use [@excalidraw/excalidraw-next](https://www.npmjs.com/package/@excalidraw/excalidraw-next).**\n\n";
|
||||||
|
// Add excalidraw next note to try out for unreleased changes
|
||||||
|
data = data.slice(0, demoIndex) + excalidrawNextNote + data.slice(demoIndex);
|
||||||
|
|
||||||
|
// update readme
|
||||||
|
fs.writeFileSync(`${excalidrawDir}/README.md`, data, "utf8");
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = updateReadme;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { ColorPicker } from "../components/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons";
|
import { trash, zoomIn, zoomOut } from "../components/icons";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { DarkModeToggle } from "../components/DarkModeToggle";
|
import { DarkModeToggle } from "../components/DarkModeToggle";
|
||||||
import { ZOOM_STEP } from "../constants";
|
import { ZOOM_STEP } from "../constants";
|
||||||
@@ -17,6 +17,7 @@ import { getNewZoom } from "../scene/zoom";
|
|||||||
import { AppState, NormalizedZoomValue } from "../types";
|
import { AppState, NormalizedZoomValue } from "../types";
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
|
import { Tooltip } from "../components/Tooltip";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor = register({
|
export const actionChangeViewBackgroundColor = register({
|
||||||
name: "changeViewBackgroundColor",
|
name: "changeViewBackgroundColor",
|
||||||
@@ -34,9 +35,9 @@ export const actionChangeViewBackgroundColor = register({
|
|||||||
type="canvasBackground"
|
type="canvasBackground"
|
||||||
color={appState.viewBackgroundColor}
|
color={appState.viewBackgroundColor}
|
||||||
onChange={(color) => updateData({ viewBackgroundColor: color })}
|
onChange={(color) => updateData({ viewBackgroundColor: color })}
|
||||||
isActive={appState.openMenu === "canvasColorPicker"}
|
isActive={appState.openPopup === "canvasColorPicker"}
|
||||||
setActive={(active) =>
|
setActive={(active) =>
|
||||||
updateData({ openMenu: active ? "canvasColorPicker" : null })
|
updateData({ openPopup: active ? "canvasColorPicker" : null })
|
||||||
}
|
}
|
||||||
data-testid="canvas-background-picker"
|
data-testid="canvas-background-picker"
|
||||||
/>
|
/>
|
||||||
@@ -108,6 +109,7 @@ export const actionZoomIn = register({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateData(null);
|
updateData(null);
|
||||||
}}
|
}}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
@@ -142,6 +144,7 @@ export const actionZoomOut = register({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateData(null);
|
updateData(null);
|
||||||
}}
|
}}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
@@ -168,16 +171,21 @@ export const actionResetZoom = register({
|
|||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData, appState }) => (
|
||||||
<ToolButton
|
<Tooltip label={t("buttons.resetZoom")}>
|
||||||
type="button"
|
<ToolButton
|
||||||
icon={resetZoom}
|
type="button"
|
||||||
title={t("buttons.resetZoom")}
|
className="reset-zoom-button"
|
||||||
aria-label={t("buttons.resetZoom")}
|
title={t("buttons.resetZoom")}
|
||||||
onClick={() => {
|
aria-label={t("buttons.resetZoom")}
|
||||||
updateData(null);
|
onClick={() => {
|
||||||
}}
|
updateData(null);
|
||||||
/>
|
}}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{(appState.zoom.value * 100).toFixed(0)}%
|
||||||
|
</ToolButton>
|
||||||
|
</Tooltip>
|
||||||
),
|
),
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
(event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) &&
|
(event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) &&
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { load, questionCircle, save, saveAs } from "../components/icons";
|
import { load, questionCircle, saveAs } from "../components/icons";
|
||||||
import { ProjectName } from "../components/ProjectName";
|
import { ProjectName } from "../components/ProjectName";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import "../components/ToolIcon.scss";
|
import "../components/ToolIcon.scss";
|
||||||
import { Tooltip } from "../components/Tooltip";
|
import { Tooltip } from "../components/Tooltip";
|
||||||
import { DarkModeToggle, Appearence } from "../components/DarkModeToggle";
|
import { DarkModeToggle, Appearence } from "../components/DarkModeToggle";
|
||||||
import { loadFromJSON, saveAsJSON } from "../data";
|
import { loadFromJSON, saveAsJSON } from "../data";
|
||||||
|
import { resaveAsImageWithScene } from "../data/resave";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useIsMobile } from "../components/App";
|
import { useIsMobile } from "../components/App";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { supported as fsSupported } from "browser-fs-access";
|
|
||||||
import { CheckboxItem } from "../components/CheckboxItem";
|
import { CheckboxItem } from "../components/CheckboxItem";
|
||||||
|
import { getExportSize } from "../scene/export";
|
||||||
|
import { DEFAULT_EXPORT_PADDING, EXPORT_SCALES } from "../constants";
|
||||||
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
|
import { getNonDeletedElements } from "../element";
|
||||||
|
import { ActiveFile } from "../components/ActiveFile";
|
||||||
|
import { isImageFileHandle } from "../data/blob";
|
||||||
|
import { nativeFileSystemSupported } from "../data/filesystem";
|
||||||
|
|
||||||
export const actionChangeProjectName = register({
|
export const actionChangeProjectName = register({
|
||||||
name: "changeProjectName",
|
name: "changeProjectName",
|
||||||
@@ -32,6 +39,54 @@ export const actionChangeProjectName = register({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const actionChangeExportScale = register({
|
||||||
|
name: "changeExportScale",
|
||||||
|
perform: (_elements, appState, value) => {
|
||||||
|
return {
|
||||||
|
appState: { ...appState, exportScale: value },
|
||||||
|
commitToHistory: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
PanelComponent: ({ elements: allElements, appState, updateData }) => {
|
||||||
|
const elements = getNonDeletedElements(allElements);
|
||||||
|
const exportSelected = isSomeElementSelected(elements, appState);
|
||||||
|
const exportedElements = exportSelected
|
||||||
|
? getSelectedElements(elements, appState)
|
||||||
|
: elements;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{EXPORT_SCALES.map((s) => {
|
||||||
|
const [width, height] = getExportSize(
|
||||||
|
exportedElements,
|
||||||
|
DEFAULT_EXPORT_PADDING,
|
||||||
|
s,
|
||||||
|
);
|
||||||
|
|
||||||
|
const scaleButtonTitle = `${t(
|
||||||
|
"buttons.scale",
|
||||||
|
)} ${s}x (${width}x${height})`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolButton
|
||||||
|
key={s}
|
||||||
|
size="small"
|
||||||
|
type="radio"
|
||||||
|
icon={`${s}x`}
|
||||||
|
name="export-canvas-scale"
|
||||||
|
title={scaleButtonTitle}
|
||||||
|
aria-label={scaleButtonTitle}
|
||||||
|
id="export-canvas-scale"
|
||||||
|
checked={s === appState.exportScale}
|
||||||
|
onChange={() => updateData(s)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const actionChangeExportBackground = register({
|
export const actionChangeExportBackground = register({
|
||||||
name: "changeExportBackground",
|
name: "changeExportBackground",
|
||||||
perform: (_elements, appState, value) => {
|
perform: (_elements, appState, value) => {
|
||||||
@@ -65,7 +120,7 @@ export const actionChangeExportEmbedScene = register({
|
|||||||
>
|
>
|
||||||
{t("labels.exportEmbedScene")}
|
{t("labels.exportEmbedScene")}
|
||||||
<Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
|
<Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
|
||||||
<div className="Tooltip-icon">{questionCircle}</div>
|
<div className="excalidraw-tooltip-icon">{questionCircle}</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</CheckboxItem>
|
</CheckboxItem>
|
||||||
),
|
),
|
||||||
@@ -75,15 +130,19 @@ export const actionSaveToActiveFile = register({
|
|||||||
name: "saveToActiveFile",
|
name: "saveToActiveFile",
|
||||||
perform: async (elements, appState, value) => {
|
perform: async (elements, appState, value) => {
|
||||||
const fileHandleExists = !!appState.fileHandle;
|
const fileHandleExists = !!appState.fileHandle;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { fileHandle } = await saveAsJSON(elements, appState);
|
const { fileHandle } = isImageFileHandle(appState.fileHandle)
|
||||||
|
? await resaveAsImageWithScene(elements, appState)
|
||||||
|
: await saveAsJSON(elements, appState);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
fileHandle,
|
fileHandle,
|
||||||
toastMessage: fileHandleExists
|
toastMessage: fileHandleExists
|
||||||
? fileHandle.name
|
? fileHandle?.name
|
||||||
? t("toast.fileSavedToFilename").replace(
|
? t("toast.fileSavedToFilename").replace(
|
||||||
"{filename}",
|
"{filename}",
|
||||||
`"${fileHandle.name}"`,
|
`"${fileHandle.name}"`,
|
||||||
@@ -101,20 +160,16 @@ export const actionSaveToActiveFile = register({
|
|||||||
},
|
},
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
event.key === KEYS.S && event[KEYS.CTRL_OR_CMD] && !event.shiftKey,
|
event.key === KEYS.S && event[KEYS.CTRL_OR_CMD] && !event.shiftKey,
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData, appState }) => (
|
||||||
<ToolButton
|
<ActiveFile
|
||||||
type="icon"
|
onSave={() => updateData(null)}
|
||||||
icon={save}
|
fileName={appState.fileHandle?.name}
|
||||||
title={t("buttons.save")}
|
|
||||||
aria-label={t("buttons.save")}
|
|
||||||
onClick={() => updateData(null)}
|
|
||||||
data-testid="save-button"
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionSaveAsScene = register({
|
export const actionSaveFileToDisk = register({
|
||||||
name: "saveAsScene",
|
name: "saveFileToDisk",
|
||||||
perform: async (elements, appState, value) => {
|
perform: async (elements, appState, value) => {
|
||||||
try {
|
try {
|
||||||
const { fileHandle } = await saveAsJSON(elements, {
|
const { fileHandle } = await saveAsJSON(elements, {
|
||||||
@@ -138,7 +193,7 @@ export const actionSaveAsScene = register({
|
|||||||
title={t("buttons.saveAs")}
|
title={t("buttons.saveAs")}
|
||||||
aria-label={t("buttons.saveAs")}
|
aria-label={t("buttons.saveAs")}
|
||||||
showAriaLabel={useIsMobile()}
|
showAriaLabel={useIsMobile()}
|
||||||
hidden={!fsSupported}
|
hidden={!nativeFileSystemSupported}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
data-testid="save-as-button"
|
data-testid="save-as-button"
|
||||||
/>
|
/>
|
||||||
@@ -152,7 +207,7 @@ export const actionLoadScene = register({
|
|||||||
const {
|
const {
|
||||||
elements: loadedElements,
|
elements: loadedElements,
|
||||||
appState: loadedAppState,
|
appState: loadedAppState,
|
||||||
} = await loadFromJSON(appState);
|
} = await loadFromJSON(appState, elements);
|
||||||
return {
|
return {
|
||||||
elements: loadedElements,
|
elements: loadedElements,
|
||||||
appState: loadedAppState,
|
appState: loadedAppState,
|
||||||
|
|||||||
@@ -69,12 +69,13 @@ export const createUndoAction: ActionCreator = (history) => ({
|
|||||||
event[KEYS.CTRL_OR_CMD] &&
|
event[KEYS.CTRL_OR_CMD] &&
|
||||||
event.key.toLowerCase() === KEYS.Z &&
|
event.key.toLowerCase() === KEYS.Z &&
|
||||||
!event.shiftKey,
|
!event.shiftKey,
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData, data }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={undo}
|
icon={undo}
|
||||||
aria-label={t("buttons.undo")}
|
aria-label={t("buttons.undo")}
|
||||||
onClick={updateData}
|
onClick={updateData}
|
||||||
|
size={data?.size || "medium"}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
commitToHistory: () => false,
|
commitToHistory: () => false,
|
||||||
@@ -89,12 +90,13 @@ export const createRedoAction: ActionCreator = (history) => ({
|
|||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
event.key.toLowerCase() === KEYS.Z) ||
|
event.key.toLowerCase() === KEYS.Z) ||
|
||||||
(isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y),
|
(isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y),
|
||||||
PanelComponent: ({ updateData }) => (
|
PanelComponent: ({ updateData, data }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={redo}
|
icon={redo}
|
||||||
aria-label={t("buttons.redo")}
|
aria-label={t("buttons.redo")}
|
||||||
onClick={updateData}
|
onClick={updateData}
|
||||||
|
size={data?.size || "medium"}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
commitToHistory: () => false,
|
commitToHistory: () => false,
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ export const actionGoToCollaborator = register({
|
|||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData, id }) => {
|
PanelComponent: ({ appState, updateData, data }) => {
|
||||||
const clientId = id;
|
const clientId: string | undefined = data?.id;
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ import {
|
|||||||
FillCrossHatchIcon,
|
FillCrossHatchIcon,
|
||||||
FillHachureIcon,
|
FillHachureIcon,
|
||||||
FillSolidIcon,
|
FillSolidIcon,
|
||||||
|
FontFamilyCodeIcon,
|
||||||
|
FontFamilyHandDrawnIcon,
|
||||||
|
FontFamilyNormalIcon,
|
||||||
|
FontSizeExtraLargeIcon,
|
||||||
|
FontSizeLargeIcon,
|
||||||
|
FontSizeMediumIcon,
|
||||||
|
FontSizeSmallIcon,
|
||||||
SloppinessArchitectIcon,
|
SloppinessArchitectIcon,
|
||||||
SloppinessArtistIcon,
|
SloppinessArtistIcon,
|
||||||
SloppinessCartoonistIcon,
|
SloppinessCartoonistIcon,
|
||||||
@@ -20,18 +27,15 @@ import {
|
|||||||
StrokeStyleDottedIcon,
|
StrokeStyleDottedIcon,
|
||||||
StrokeStyleSolidIcon,
|
StrokeStyleSolidIcon,
|
||||||
StrokeWidthIcon,
|
StrokeWidthIcon,
|
||||||
FontSizeSmallIcon,
|
|
||||||
FontSizeMediumIcon,
|
|
||||||
FontSizeLargeIcon,
|
|
||||||
FontSizeExtraLargeIcon,
|
|
||||||
FontFamilyHandDrawnIcon,
|
|
||||||
FontFamilyNormalIcon,
|
|
||||||
FontFamilyCodeIcon,
|
|
||||||
TextAlignLeftIcon,
|
|
||||||
TextAlignCenterIcon,
|
TextAlignCenterIcon,
|
||||||
|
TextAlignLeftIcon,
|
||||||
TextAlignRightIcon,
|
TextAlignRightIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE } from "../constants";
|
import {
|
||||||
|
DEFAULT_FONT_FAMILY,
|
||||||
|
DEFAULT_FONT_SIZE,
|
||||||
|
FONT_FAMILY,
|
||||||
|
} from "../constants";
|
||||||
import {
|
import {
|
||||||
getNonDeletedElements,
|
getNonDeletedElements,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
@@ -44,7 +48,7 @@ import {
|
|||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
FontFamily,
|
FontFamilyValues,
|
||||||
TextAlign,
|
TextAlign,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { getLanguage, t } from "../i18n";
|
import { getLanguage, t } from "../i18n";
|
||||||
@@ -126,9 +130,9 @@ export const actionChangeStrokeColor = register({
|
|||||||
appState.currentItemStrokeColor,
|
appState.currentItemStrokeColor,
|
||||||
)}
|
)}
|
||||||
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
||||||
isActive={appState.openMenu === "strokeColorPicker"}
|
isActive={appState.openPopup === "strokeColorPicker"}
|
||||||
setActive={(active) =>
|
setActive={(active) =>
|
||||||
updateData({ openMenu: active ? "strokeColorPicker" : null })
|
updateData({ openPopup: active ? "strokeColorPicker" : null })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -166,9 +170,9 @@ export const actionChangeBackgroundColor = register({
|
|||||||
appState.currentItemBackgroundColor,
|
appState.currentItemBackgroundColor,
|
||||||
)}
|
)}
|
||||||
onChange={(color) => updateData({ currentItemBackgroundColor: color })}
|
onChange={(color) => updateData({ currentItemBackgroundColor: color })}
|
||||||
isActive={appState.openMenu === "backgroundColorPicker"}
|
isActive={appState.openPopup === "backgroundColorPicker"}
|
||||||
setActive={(active) =>
|
setActive={(active) =>
|
||||||
updateData({ openMenu: active ? "backgroundColorPicker" : null })
|
updateData({ openPopup: active ? "backgroundColorPicker" : null })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -499,19 +503,23 @@ export const actionChangeFontFamily = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData }) => {
|
PanelComponent: ({ elements, appState, updateData }) => {
|
||||||
const options: { value: FontFamily; text: string; icon: JSX.Element }[] = [
|
const options: {
|
||||||
|
value: FontFamilyValues;
|
||||||
|
text: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
}[] = [
|
||||||
{
|
{
|
||||||
value: 1,
|
value: FONT_FAMILY.Virgil,
|
||||||
text: t("labels.handDrawn"),
|
text: t("labels.handDrawn"),
|
||||||
icon: <FontFamilyHandDrawnIcon theme={appState.theme} />,
|
icon: <FontFamilyHandDrawnIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 2,
|
value: FONT_FAMILY.Helvetica,
|
||||||
text: t("labels.normal"),
|
text: t("labels.normal"),
|
||||||
icon: <FontFamilyNormalIcon theme={appState.theme} />,
|
icon: <FontFamilyNormalIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 3,
|
value: FONT_FAMILY.Cascadia,
|
||||||
text: t("labels.code"),
|
text: t("labels.code"),
|
||||||
icon: <FontFamilyCodeIcon theme={appState.theme} />,
|
icon: <FontFamilyCodeIcon theme={appState.theme} />,
|
||||||
},
|
},
|
||||||
@@ -520,7 +528,7 @@ export const actionChangeFontFamily = register({
|
|||||||
return (
|
return (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.fontFamily")}</legend>
|
<legend>{t("labels.fontFamily")}</legend>
|
||||||
<ButtonIconSelect<FontFamily | false>
|
<ButtonIconSelect<FontFamilyValues | false>
|
||||||
group="font-family"
|
group="font-family"
|
||||||
options={options}
|
options={options}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const actionToggleViewMode = register({
|
|||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
viewModeEnabled: !this.checked!(appState),
|
viewModeEnabled: !this.checked!(appState),
|
||||||
selectedElementIds: {},
|
|
||||||
},
|
},
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export {
|
|||||||
actionChangeProjectName,
|
actionChangeProjectName,
|
||||||
actionChangeExportBackground,
|
actionChangeExportBackground,
|
||||||
actionSaveToActiveFile,
|
actionSaveToActiveFile,
|
||||||
actionSaveAsScene,
|
actionSaveFileToDisk,
|
||||||
actionLoadScene,
|
actionLoadScene,
|
||||||
} from "./actionExport";
|
} from "./actionExport";
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
UpdaterFn,
|
UpdaterFn,
|
||||||
ActionName,
|
ActionName,
|
||||||
ActionResult,
|
ActionResult,
|
||||||
|
PanelComponentProps,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppProps, AppState } from "../types";
|
import { AppProps, AppState } from "../types";
|
||||||
@@ -107,11 +108,10 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id is an attribute that we can use to pass in data like keys.
|
/**
|
||||||
// This is needed for dynamically generated action components
|
* @param data additional data sent to the PanelComponent
|
||||||
// like the user list. We can use this key to extract more
|
*/
|
||||||
// data from app state. This is an alternative to generic prop hell!
|
renderAction = (name: ActionName, data?: PanelComponentProps["data"]) => {
|
||||||
renderAction = (name: ActionName, id?: string) => {
|
|
||||||
const canvasActions = this.app.props.UIOptions.canvasActions;
|
const canvasActions = this.app.props.UIOptions.canvasActions;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -139,8 +139,8 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
elements={this.getElementsIncludingDeleted()}
|
elements={this.getElementsIncludingDeleted()}
|
||||||
appState={this.getAppState()}
|
appState={this.getAppState()}
|
||||||
updateData={updateData}
|
updateData={updateData}
|
||||||
id={id}
|
|
||||||
appProps={this.app.props}
|
appProps={this.app.props}
|
||||||
|
data={data}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { AppState, ExcalidrawProps } from "../types";
|
||||||
import Library from "../data/library";
|
import Library from "../data/library";
|
||||||
|
import { ToolButtonSize } from "../components/ToolButton";
|
||||||
|
|
||||||
/** if false, the action should be prevented */
|
/** if false, the action should be prevented */
|
||||||
export type ActionResult =
|
export type ActionResult =
|
||||||
@@ -66,8 +67,9 @@ export type ActionName =
|
|||||||
| "changeProjectName"
|
| "changeProjectName"
|
||||||
| "changeExportBackground"
|
| "changeExportBackground"
|
||||||
| "changeExportEmbedScene"
|
| "changeExportEmbedScene"
|
||||||
|
| "changeExportScale"
|
||||||
| "saveToActiveFile"
|
| "saveToActiveFile"
|
||||||
| "saveAsScene"
|
| "saveFileToDisk"
|
||||||
| "loadScene"
|
| "loadScene"
|
||||||
| "duplicateSelection"
|
| "duplicateSelection"
|
||||||
| "deleteSelectedElements"
|
| "deleteSelectedElements"
|
||||||
@@ -101,15 +103,17 @@ export type ActionName =
|
|||||||
| "exportWithDarkMode"
|
| "exportWithDarkMode"
|
||||||
| "toggleTheme";
|
| "toggleTheme";
|
||||||
|
|
||||||
|
export type PanelComponentProps = {
|
||||||
|
elements: readonly ExcalidrawElement[];
|
||||||
|
appState: AppState;
|
||||||
|
updateData: (formData?: any) => void;
|
||||||
|
appProps: ExcalidrawProps;
|
||||||
|
data?: Partial<{ id: string; size: ToolButtonSize }>;
|
||||||
|
};
|
||||||
|
|
||||||
export interface Action {
|
export interface Action {
|
||||||
name: ActionName;
|
name: ActionName;
|
||||||
PanelComponent?: React.FC<{
|
PanelComponent?: React.FC<PanelComponentProps>;
|
||||||
elements: readonly ExcalidrawElement[];
|
|
||||||
appState: AppState;
|
|
||||||
updateData: (formData?: any) => void;
|
|
||||||
appProps: ExcalidrawProps;
|
|
||||||
id?: string;
|
|
||||||
}>;
|
|
||||||
perform: ActionFn;
|
perform: ActionFn;
|
||||||
keyPriority?: number;
|
keyPriority?: number;
|
||||||
keyTest?: (
|
keyTest?: (
|
||||||
|
|||||||
@@ -3,11 +3,16 @@ import {
|
|||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
DEFAULT_TEXT_ALIGN,
|
DEFAULT_TEXT_ALIGN,
|
||||||
|
EXPORT_SCALES,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
import { t } from "./i18n";
|
import { t } from "./i18n";
|
||||||
import { AppState, NormalizedZoomValue } from "./types";
|
import { AppState, NormalizedZoomValue } from "./types";
|
||||||
import { getDateTime } from "./utils";
|
import { getDateTime } from "./utils";
|
||||||
|
|
||||||
|
const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
|
||||||
|
? devicePixelRatio
|
||||||
|
: 1;
|
||||||
|
|
||||||
export const getDefaultAppState = (): Omit<
|
export const getDefaultAppState = (): Omit<
|
||||||
AppState,
|
AppState,
|
||||||
"offsetTop" | "offsetLeft" | "width" | "height"
|
"offsetTop" | "offsetLeft" | "width" | "height"
|
||||||
@@ -39,6 +44,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
elementType: "selection",
|
elementType: "selection",
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
exportBackground: true,
|
exportBackground: true,
|
||||||
|
exportScale: defaultExportScale,
|
||||||
exportEmbedScene: false,
|
exportEmbedScene: false,
|
||||||
exportWithDarkMode: false,
|
exportWithDarkMode: false,
|
||||||
fileHandle: null,
|
fileHandle: null,
|
||||||
@@ -52,6 +58,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
multiElement: null,
|
multiElement: null,
|
||||||
name: `${t("labels.untitled")}-${getDateTime()}`,
|
name: `${t("labels.untitled")}-${getDateTime()}`,
|
||||||
openMenu: null,
|
openMenu: null,
|
||||||
|
openPopup: null,
|
||||||
pasteDialog: { shown: false, data: null },
|
pasteDialog: { shown: false, data: null },
|
||||||
previousSelectedElementIds: {},
|
previousSelectedElementIds: {},
|
||||||
resizingElement: null,
|
resizingElement: null,
|
||||||
@@ -116,6 +123,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
errorMessage: { browser: false, export: false },
|
errorMessage: { browser: false, export: false },
|
||||||
exportBackground: { browser: true, export: false },
|
exportBackground: { browser: true, export: false },
|
||||||
exportEmbedScene: { browser: true, export: false },
|
exportEmbedScene: { browser: true, export: false },
|
||||||
|
exportScale: { browser: true, export: false },
|
||||||
exportWithDarkMode: { browser: true, export: false },
|
exportWithDarkMode: { browser: true, export: false },
|
||||||
fileHandle: { browser: false, export: false },
|
fileHandle: { browser: false, export: false },
|
||||||
gridSize: { browser: true, export: true },
|
gridSize: { browser: true, export: true },
|
||||||
@@ -131,6 +139,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
offsetLeft: { browser: false, export: false },
|
offsetLeft: { browser: false, export: false },
|
||||||
offsetTop: { browser: false, export: false },
|
offsetTop: { browser: false, export: false },
|
||||||
openMenu: { browser: true, export: false },
|
openMenu: { browser: true, export: false },
|
||||||
|
openPopup: { browser: false, export: false },
|
||||||
pasteDialog: { browser: false, export: false },
|
pasteDialog: { browser: false, export: false },
|
||||||
previousSelectedElementIds: { browser: true, export: false },
|
previousSelectedElementIds: { browser: true, export: false },
|
||||||
resizingElement: { browser: false, export: false },
|
resizingElement: { browser: false, export: false },
|
||||||
|
|||||||
@@ -151,23 +151,14 @@ export const SelectedShapeActions = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIBRARY_ICON = (
|
|
||||||
// fa-th-large
|
|
||||||
<svg viewBox="0 0 512 512">
|
|
||||||
<path d="M296 32h192c13.255 0 24 10.745 24 24v160c0 13.255-10.745 24-24 24H296c-13.255 0-24-10.745-24-24V56c0-13.255 10.745-24 24-24zm-80 0H24C10.745 32 0 42.745 0 56v160c0 13.255 10.745 24 24 24h192c13.255 0 24-10.745 24-24V56c0-13.255-10.745-24-24-24zM0 296v160c0 13.255 10.745 24 24 24h192c13.255 0 24-10.745 24-24V296c0-13.255-10.745-24-24-24H24c-13.255 0-24 10.745-24 24zm296 184h192c13.255 0 24-10.745 24-24V296c0-13.255-10.745-24-24-24H296c-13.255 0-24 10.745-24 24v160c0 13.255 10.745 24 24 24z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ShapesSwitcher = ({
|
export const ShapesSwitcher = ({
|
||||||
canvas,
|
canvas,
|
||||||
elementType,
|
elementType,
|
||||||
setAppState,
|
setAppState,
|
||||||
isLibraryOpen,
|
|
||||||
}: {
|
}: {
|
||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
elementType: ExcalidrawElement["type"];
|
elementType: ExcalidrawElement["type"];
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
isLibraryOpen: boolean;
|
|
||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
{SHAPES.map(({ value, icon, key }, index) => {
|
{SHAPES.map(({ value, icon, key }, index) => {
|
||||||
@@ -201,19 +192,6 @@ export const ShapesSwitcher = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<ToolButton
|
|
||||||
className="Shape ToolIcon_type_button__library"
|
|
||||||
type="button"
|
|
||||||
icon={LIBRARY_ICON}
|
|
||||||
name="editor-library"
|
|
||||||
keyBindingLabel="9"
|
|
||||||
aria-keyshortcuts="9"
|
|
||||||
title={`${capitalizeString(t("toolBar.library"))} — 9`}
|
|
||||||
aria-label={capitalizeString(t("toolBar.library"))}
|
|
||||||
onClick={() => {
|
|
||||||
setAppState({ isLibraryOpen: !isLibraryOpen });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -226,12 +204,9 @@ export const ZoomActions = ({
|
|||||||
}) => (
|
}) => (
|
||||||
<Stack.Col gap={1}>
|
<Stack.Col gap={1}>
|
||||||
<Stack.Row gap={1} align="center">
|
<Stack.Row gap={1} align="center">
|
||||||
{renderAction("zoomIn")}
|
|
||||||
{renderAction("zoomOut")}
|
{renderAction("zoomOut")}
|
||||||
|
{renderAction("zoomIn")}
|
||||||
{renderAction("resetZoom")}
|
{renderAction("resetZoom")}
|
||||||
<div style={{ marginInlineStart: 4 }}>
|
|
||||||
{(zoom.value * 100).toFixed(0)}%
|
|
||||||
</div>
|
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
);
|
);
|
||||||
|
|||||||
21
src/components/ActiveFile.scss
Normal file
21
src/components/ActiveFile.scss
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.excalidraw {
|
||||||
|
.ActiveFile {
|
||||||
|
.ActiveFile__fileName {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 9.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.15em;
|
||||||
|
margin-inline-end: 0.3em;
|
||||||
|
transform: scaleY(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/components/ActiveFile.tsx
Normal file
29
src/components/ActiveFile.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Stack from "../components/Stack";
|
||||||
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { save, file } from "../components/icons";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
|
||||||
|
import "./ActiveFile.scss";
|
||||||
|
|
||||||
|
type ActiveFileProps = {
|
||||||
|
fileName?: string;
|
||||||
|
onSave: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ActiveFile = ({ fileName, onSave }: ActiveFileProps) => (
|
||||||
|
<Stack.Row className="ActiveFile" gap={1} align="center">
|
||||||
|
<span className="ActiveFile__fileName">
|
||||||
|
{file}
|
||||||
|
<span>{fileName}</span>
|
||||||
|
</span>
|
||||||
|
<ToolButton
|
||||||
|
type="icon"
|
||||||
|
icon={save}
|
||||||
|
title={t("buttons.save")}
|
||||||
|
aria-label={t("buttons.save")}
|
||||||
|
onClick={onSave}
|
||||||
|
data-testid="save-button"
|
||||||
|
/>
|
||||||
|
</Stack.Row>
|
||||||
|
);
|
||||||
@@ -2,7 +2,6 @@ import React, { useContext } from "react";
|
|||||||
import { RoughCanvas } from "roughjs/bin/canvas";
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { supported as fsSupported } from "browser-fs-access";
|
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -111,7 +110,6 @@ import {
|
|||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import { mutateElement } from "../element/mutateElement";
|
import { mutateElement } from "../element/mutateElement";
|
||||||
import { deepCopyElement, newFreeDrawElement } from "../element/newElement";
|
import { deepCopyElement, newFreeDrawElement } from "../element/newElement";
|
||||||
import { MaybeTransformHandleType } from "../element/transformHandles";
|
|
||||||
import {
|
import {
|
||||||
isBindingElement,
|
isBindingElement,
|
||||||
isBindingElementType,
|
isBindingElementType,
|
||||||
@@ -157,6 +155,7 @@ import {
|
|||||||
getElementsWithinSelection,
|
getElementsWithinSelection,
|
||||||
getNormalizedZoom,
|
getNormalizedZoom,
|
||||||
getSelectedElements,
|
getSelectedElements,
|
||||||
|
hasBackground,
|
||||||
isOverScrollBars,
|
isOverScrollBars,
|
||||||
isSomeElementSelected,
|
isSomeElementSelected,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
@@ -167,9 +166,11 @@ import { findShapeByKey } from "../shapes";
|
|||||||
import {
|
import {
|
||||||
AppProps,
|
AppProps,
|
||||||
AppState,
|
AppState,
|
||||||
|
ExcalidrawImperativeAPI,
|
||||||
Gesture,
|
Gesture,
|
||||||
GestureEvent,
|
GestureEvent,
|
||||||
LibraryItems,
|
LibraryItems,
|
||||||
|
PointerDownState,
|
||||||
SceneData,
|
SceneData,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
@@ -180,7 +181,6 @@ import {
|
|||||||
isToolIcon,
|
isToolIcon,
|
||||||
isWritableElement,
|
isWritableElement,
|
||||||
resetCursor,
|
resetCursor,
|
||||||
ResolvablePromise,
|
|
||||||
resolvablePromise,
|
resolvablePromise,
|
||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
setCursor,
|
setCursor,
|
||||||
@@ -194,12 +194,14 @@ import LayerUI from "./LayerUI";
|
|||||||
import { Stats } from "./Stats";
|
import { Stats } from "./Stats";
|
||||||
import { Toast } from "./Toast";
|
import { Toast } from "./Toast";
|
||||||
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
||||||
|
import { nativeFileSystemSupported } from "../data/filesystem";
|
||||||
|
|
||||||
const IsMobileContext = React.createContext(false);
|
const IsMobileContext = React.createContext(false);
|
||||||
export const useIsMobile = () => useContext(IsMobileContext);
|
export const useIsMobile = () => useContext(IsMobileContext);
|
||||||
const ExcalidrawContainerContext = React.createContext<HTMLDivElement | null>(
|
const ExcalidrawContainerContext = React.createContext<{
|
||||||
null,
|
container: HTMLDivElement | null;
|
||||||
);
|
id: string | null;
|
||||||
|
}>({ container: null, id: null });
|
||||||
export const useExcalidrawContainer = () =>
|
export const useExcalidrawContainer = () =>
|
||||||
useContext(ExcalidrawContainerContext);
|
useContext(ExcalidrawContainerContext);
|
||||||
|
|
||||||
@@ -222,83 +224,6 @@ const gesture: Gesture = {
|
|||||||
initialScale: null,
|
initialScale: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PointerDownState = Readonly<{
|
|
||||||
// The first position at which pointerDown happened
|
|
||||||
origin: Readonly<{ x: number; y: number }>;
|
|
||||||
// Same as "origin" but snapped to the grid, if grid is on
|
|
||||||
originInGrid: Readonly<{ x: number; y: number }>;
|
|
||||||
// Scrollbar checks
|
|
||||||
scrollbars: ReturnType<typeof isOverScrollBars>;
|
|
||||||
// The previous pointer position
|
|
||||||
lastCoords: { x: number; y: number };
|
|
||||||
// map of original elements data
|
|
||||||
originalElements: Map<string, NonDeleted<ExcalidrawElement>>;
|
|
||||||
resize: {
|
|
||||||
// Handle when resizing, might change during the pointer interaction
|
|
||||||
handleType: MaybeTransformHandleType;
|
|
||||||
// This is determined on the initial pointer down event
|
|
||||||
isResizing: boolean;
|
|
||||||
// This is determined on the initial pointer down event
|
|
||||||
offset: { x: number; y: number };
|
|
||||||
// This is determined on the initial pointer down event
|
|
||||||
arrowDirection: "origin" | "end";
|
|
||||||
// This is a center point of selected elements determined on the initial pointer down event (for rotation only)
|
|
||||||
center: { x: number; y: number };
|
|
||||||
};
|
|
||||||
hit: {
|
|
||||||
// The element the pointer is "hitting", is determined on the initial
|
|
||||||
// pointer down event
|
|
||||||
element: NonDeleted<ExcalidrawElement> | null;
|
|
||||||
// The elements the pointer is "hitting", is determined on the initial
|
|
||||||
// pointer down event
|
|
||||||
allHitElements: NonDeleted<ExcalidrawElement>[];
|
|
||||||
// This is determined on the initial pointer down event
|
|
||||||
wasAddedToSelection: boolean;
|
|
||||||
// Whether selected element(s) were duplicated, might change during the
|
|
||||||
// pointer interaction
|
|
||||||
hasBeenDuplicated: boolean;
|
|
||||||
hasHitCommonBoundingBoxOfSelectedElements: boolean;
|
|
||||||
};
|
|
||||||
withCmdOrCtrl: boolean;
|
|
||||||
drag: {
|
|
||||||
// Might change during the pointer interation
|
|
||||||
hasOccurred: boolean;
|
|
||||||
// Might change during the pointer interation
|
|
||||||
offset: { x: number; y: number } | null;
|
|
||||||
};
|
|
||||||
// We need to have these in the state so that we can unsubscribe them
|
|
||||||
eventListeners: {
|
|
||||||
// It's defined on the initial pointer down event
|
|
||||||
onMove: null | ((event: PointerEvent) => void);
|
|
||||||
// It's defined on the initial pointer down event
|
|
||||||
onUp: null | ((event: PointerEvent) => void);
|
|
||||||
// It's defined on the initial pointer down event
|
|
||||||
onKeyDown: null | ((event: KeyboardEvent) => void);
|
|
||||||
// It's defined on the initial pointer down event
|
|
||||||
onKeyUp: null | ((event: KeyboardEvent) => void);
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type ExcalidrawImperativeAPI = {
|
|
||||||
updateScene: InstanceType<typeof App>["updateScene"];
|
|
||||||
resetScene: InstanceType<typeof App>["resetScene"];
|
|
||||||
getSceneElementsIncludingDeleted: InstanceType<
|
|
||||||
typeof App
|
|
||||||
>["getSceneElementsIncludingDeleted"];
|
|
||||||
history: {
|
|
||||||
clear: InstanceType<typeof App>["resetHistory"];
|
|
||||||
};
|
|
||||||
scrollToContent: InstanceType<typeof App>["scrollToContent"];
|
|
||||||
getSceneElements: InstanceType<typeof App>["getSceneElements"];
|
|
||||||
getAppState: () => InstanceType<typeof App>["state"];
|
|
||||||
refresh: InstanceType<typeof App>["refresh"];
|
|
||||||
importLibrary: InstanceType<typeof App>["importLibraryFromUrl"];
|
|
||||||
setToastMessage: InstanceType<typeof App>["setToastMessage"];
|
|
||||||
readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
|
|
||||||
ready: true;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
class App extends React.Component<AppProps, AppState> {
|
class App extends React.Component<AppProps, AppState> {
|
||||||
canvas: HTMLCanvasElement | null = null;
|
canvas: HTMLCanvasElement | null = null;
|
||||||
rc: RoughCanvas | null = null;
|
rc: RoughCanvas | null = null;
|
||||||
@@ -321,6 +246,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
public libraryItemsFromStorage: LibraryItems | undefined;
|
public libraryItemsFromStorage: LibraryItems | undefined;
|
||||||
private id: string;
|
private id: string;
|
||||||
private history: History;
|
private history: History;
|
||||||
|
private excalidrawContainerValue: {
|
||||||
|
container: HTMLDivElement | null;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: AppProps) {
|
constructor(props: AppProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -377,6 +306,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
readyPromise.resolve(api);
|
readyPromise.resolve(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.excalidrawContainerValue = {
|
||||||
|
container: this.excalidrawContainerRef.current,
|
||||||
|
id: this.id,
|
||||||
|
};
|
||||||
|
|
||||||
this.scene = new Scene();
|
this.scene = new Scene();
|
||||||
this.library = new Library(this);
|
this.library = new Library(this);
|
||||||
this.history = new History();
|
this.history = new History();
|
||||||
@@ -404,11 +339,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (viewModeEnabled) {
|
if (viewModeEnabled) {
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
id="canvas"
|
className="excalidraw__canvas"
|
||||||
style={{
|
style={{
|
||||||
width: canvasDOMWidth,
|
width: canvasDOMWidth,
|
||||||
height: canvasDOMHeight,
|
height: canvasDOMHeight,
|
||||||
cursor: "grabbing",
|
cursor: CURSOR_TYPE.GRAB,
|
||||||
}}
|
}}
|
||||||
width={canvasWidth}
|
width={canvasWidth}
|
||||||
height={canvasHeight}
|
height={canvasHeight}
|
||||||
@@ -426,7 +361,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
id="canvas"
|
className="excalidraw__canvas"
|
||||||
style={{
|
style={{
|
||||||
width: canvasDOMWidth,
|
width: canvasDOMWidth,
|
||||||
height: canvasDOMHeight,
|
height: canvasDOMHeight,
|
||||||
@@ -452,7 +387,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
onCollabButtonClick,
|
onCollabButtonClick,
|
||||||
onExportToBackend,
|
|
||||||
renderTopRightUI,
|
renderTopRightUI,
|
||||||
renderFooter,
|
renderFooter,
|
||||||
renderCustomStats,
|
renderCustomStats,
|
||||||
@@ -472,7 +406,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ExcalidrawContainerContext.Provider
|
<ExcalidrawContainerContext.Provider
|
||||||
value={this.excalidrawContainerRef.current}
|
value={this.excalidrawContainerValue}
|
||||||
>
|
>
|
||||||
<IsMobileContext.Provider value={this.isMobile}>
|
<IsMobileContext.Provider value={this.isMobile}>
|
||||||
<LayerUI
|
<LayerUI
|
||||||
@@ -493,7 +427,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
toggleZenMode={this.toggleZenMode}
|
toggleZenMode={this.toggleZenMode}
|
||||||
langCode={getLanguage().code}
|
langCode={getLanguage().code}
|
||||||
isCollaborating={this.props.isCollaborating || false}
|
isCollaborating={this.props.isCollaborating || false}
|
||||||
onExportToBackend={onExportToBackend}
|
|
||||||
renderTopRightUI={renderTopRightUI}
|
renderTopRightUI={renderTopRightUI}
|
||||||
renderCustomFooter={renderFooter}
|
renderCustomFooter={renderFooter}
|
||||||
viewModeEnabled={viewModeEnabled}
|
viewModeEnabled={viewModeEnabled}
|
||||||
@@ -536,7 +469,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public focusContainer = () => {
|
public focusContainer = () => {
|
||||||
this.excalidrawContainerRef.current?.focus();
|
if (this.props.autoFocus) {
|
||||||
|
this.excalidrawContainerRef.current?.focus();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public getSceneElementsIncludingDeleted = () => {
|
public getSceneElementsIncludingDeleted = () => {
|
||||||
@@ -720,7 +655,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const fileHandle = launchParams.files[0];
|
const fileHandle = launchParams.files[0];
|
||||||
const blob: Blob = await fileHandle.getFile();
|
const blob: Blob = await fileHandle.getFile();
|
||||||
blob.handle = fileHandle;
|
blob.handle = fileHandle;
|
||||||
loadFromBlob(blob, this.state)
|
loadFromBlob(
|
||||||
|
blob,
|
||||||
|
this.state,
|
||||||
|
this.scene.getElementsIncludingDeleted(),
|
||||||
|
)
|
||||||
.then(({ elements, appState }) =>
|
.then(({ elements, appState }) =>
|
||||||
this.syncActionResult({
|
this.syncActionResult({
|
||||||
elements,
|
elements,
|
||||||
@@ -747,7 +686,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (initialData?.libraryItems) {
|
if (initialData?.libraryItems) {
|
||||||
this.libraryItemsFromStorage = initialData.libraryItems;
|
this.libraryItemsFromStorage = initialData.libraryItems;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
initialData = {
|
initialData = {
|
||||||
appState: {
|
appState: {
|
||||||
@@ -758,7 +697,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const scene = restore(initialData, null);
|
const scene = restore(initialData, null, null);
|
||||||
scene.appState = {
|
scene.appState = {
|
||||||
...scene.appState,
|
...scene.appState,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@@ -802,6 +741,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
|
this.excalidrawContainerValue.container = this.excalidrawContainerRef.current;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV === ENV.TEST ||
|
process.env.NODE_ENV === ENV.TEST ||
|
||||||
process.env.NODE_ENV === ENV.DEVELOPMENT
|
process.env.NODE_ENV === ENV.DEVELOPMENT
|
||||||
@@ -890,6 +831,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
private removeEventListeners() {
|
private removeEventListeners() {
|
||||||
|
document.removeEventListener(EVENT.POINTER_UP, this.removePointer);
|
||||||
document.removeEventListener(EVENT.COPY, this.onCopy);
|
document.removeEventListener(EVENT.COPY, this.onCopy);
|
||||||
document.removeEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
document.removeEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
||||||
document.removeEventListener(EVENT.CUT, this.onCut);
|
document.removeEventListener(EVENT.CUT, this.onCut);
|
||||||
@@ -931,6 +873,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
private addEventListeners() {
|
private addEventListeners() {
|
||||||
this.removeEventListeners();
|
this.removeEventListeners();
|
||||||
|
document.addEventListener(EVENT.POINTER_UP, this.removePointer); // #3553
|
||||||
document.addEventListener(EVENT.COPY, this.onCopy);
|
document.addEventListener(EVENT.COPY, this.onCopy);
|
||||||
if (this.props.handleKeyboardGlobally) {
|
if (this.props.handleKeyboardGlobally) {
|
||||||
document.addEventListener(EVENT.KEYDOWN, this.onKeyDown, false);
|
document.addEventListener(EVENT.KEYDOWN, this.onKeyDown, false);
|
||||||
@@ -986,14 +929,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
|
if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
|
||||||
this.setState(
|
this.setState({ viewModeEnabled: !!this.props.viewModeEnabled });
|
||||||
{ viewModeEnabled: !!this.props.viewModeEnabled },
|
|
||||||
this.addEventListeners,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevState.viewModeEnabled !== this.state.viewModeEnabled) {
|
if (prevState.viewModeEnabled !== this.state.viewModeEnabled) {
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
|
this.deselectElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.zenModeEnabled !== this.props.zenModeEnabled) {
|
if (prevProps.zenModeEnabled !== this.props.zenModeEnabled) {
|
||||||
@@ -1252,8 +1193,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
const data = await parseClipboard(event);
|
const data = await parseClipboard(event);
|
||||||
if (this.props.onPaste) {
|
if (this.props.onPaste) {
|
||||||
if (await this.props.onPaste(data, event)) {
|
try {
|
||||||
return;
|
if ((await this.props.onPaste(data, event)) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.errorMessage) {
|
if (data.errorMessage) {
|
||||||
@@ -1267,7 +1212,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
} else if (data.elements) {
|
} else if (data.elements) {
|
||||||
this.addElementsFromPasteOrLibrary({
|
this.addElementsFromPasteOrLibrary({
|
||||||
elements: restoreElements(data.elements),
|
elements: data.elements,
|
||||||
position: "cursor",
|
position: "cursor",
|
||||||
});
|
});
|
||||||
} else if (data.text) {
|
} else if (data.text) {
|
||||||
@@ -1282,7 +1227,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
position: { clientX: number; clientY: number } | "cursor" | "center";
|
position: { clientX: number; clientY: number } | "cursor" | "center";
|
||||||
}) => {
|
}) => {
|
||||||
const elements = restoreElements(opts.elements);
|
const elements = restoreElements(opts.elements, null);
|
||||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||||
|
|
||||||
const elementsCenterX = distance(minX, maxX) / 2;
|
const elementsCenterX = distance(minX, maxX) / 2;
|
||||||
@@ -1348,6 +1293,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getElements(),
|
this.scene.getElements(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
this.selectShapeTool("selection");
|
||||||
};
|
};
|
||||||
|
|
||||||
private addTextFromPaste(text: any) {
|
private addTextFromPaste(text: any) {
|
||||||
@@ -1388,7 +1334,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.setState(obj);
|
this.setState(obj);
|
||||||
};
|
};
|
||||||
|
|
||||||
removePointer = (event: React.PointerEvent<HTMLElement>) => {
|
removePointer = (event: React.PointerEvent<HTMLElement> | PointerEvent) => {
|
||||||
// remove touch handler for context menu on touch devices
|
// remove touch handler for context menu on touch devices
|
||||||
if (event.pointerType === "touch" && touchTimeout) {
|
if (event.pointerType === "touch" && touchTimeout) {
|
||||||
clearTimeout(touchTimeout);
|
clearTimeout(touchTimeout);
|
||||||
@@ -1454,7 +1400,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
await webShareTargetCache.delete("shared-file");
|
await webShareTargetCache.delete("shared-file");
|
||||||
window.history.replaceState(null, APP_NAME, window.location.pathname);
|
window.history.replaceState(null, APP_NAME, window.location.pathname);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
this.setState({ errorMessage: error.message });
|
this.setState({ errorMessage: error.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1651,13 +1597,22 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getElements(),
|
this.scene.getElements(),
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
if (selectedElements.length) {
|
if (
|
||||||
if (event.key === KEYS.G) {
|
this.state.elementType === "selection" &&
|
||||||
this.setState({ openMenu: "backgroundColorPicker" });
|
!selectedElements.length
|
||||||
}
|
) {
|
||||||
if (event.key === KEYS.S) {
|
return;
|
||||||
this.setState({ openMenu: "strokeColorPicker" });
|
}
|
||||||
}
|
|
||||||
|
if (
|
||||||
|
event.key === KEYS.G &&
|
||||||
|
(hasBackground(this.state.elementType) ||
|
||||||
|
selectedElements.some((element) => hasBackground(element.type)))
|
||||||
|
) {
|
||||||
|
this.setState({ openPopup: "backgroundColorPicker" });
|
||||||
|
}
|
||||||
|
if (event.key === KEYS.S) {
|
||||||
|
this.setState({ openPopup: "strokeColorPicker" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1665,7 +1620,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => {
|
private onKeyUp = withBatchedUpdates((event: KeyboardEvent) => {
|
||||||
if (event.key === KEYS.SPACE) {
|
if (event.key === KEYS.SPACE) {
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.viewModeEnabled) {
|
||||||
|
setCursor(this.canvas, CURSOR_TYPE.GRAB);
|
||||||
|
} else if (this.state.elementType === "selection") {
|
||||||
resetCursor(this.canvas);
|
resetCursor(this.canvas);
|
||||||
} else {
|
} else {
|
||||||
setCursorForShape(this.canvas, this.state.elementType);
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
@@ -1813,7 +1770,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
[element.id]: true,
|
[element.id]: true,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
} else {
|
}
|
||||||
|
if (isDeleted) {
|
||||||
fixBindingsAfterDeletion(this.scene.getElements(), [element]);
|
fixBindingsAfterDeletion(this.scene.getElements(), [element]);
|
||||||
}
|
}
|
||||||
if (!isDeleted || isExistingElement) {
|
if (!isDeleted || isExistingElement) {
|
||||||
@@ -1834,15 +1792,19 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
excalidrawContainer: this.excalidrawContainerRef.current,
|
excalidrawContainer: this.excalidrawContainerRef.current,
|
||||||
});
|
});
|
||||||
// deselect all other elements when inserting text
|
// deselect all other elements when inserting text
|
||||||
|
this.deselectElements();
|
||||||
|
|
||||||
|
// do an initial update to re-initialize element position since we were
|
||||||
|
// modifying element's x/y for sake of editor (case: syncing to remote)
|
||||||
|
updateElement(element.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private deselectElements() {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedElementIds: {},
|
selectedElementIds: {},
|
||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
editingGroupId: null,
|
editingGroupId: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// do an initial update to re-initialize element position since we were
|
|
||||||
// modifying element's x/y for sake of editor (case: syncing to remote)
|
|
||||||
updateElement(element.text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTextElementAtPosition(
|
private getTextElementAtPosition(
|
||||||
@@ -1860,9 +1822,21 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
private getElementAtPosition(
|
private getElementAtPosition(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
opts?: {
|
||||||
|
/** if true, returns the first selected element (with highest z-index)
|
||||||
|
of all hit elements */
|
||||||
|
preferSelected?: boolean;
|
||||||
|
},
|
||||||
): NonDeleted<ExcalidrawElement> | null {
|
): NonDeleted<ExcalidrawElement> | null {
|
||||||
const allHitElements = this.getElementsAtPosition(x, y);
|
const allHitElements = this.getElementsAtPosition(x, y);
|
||||||
if (allHitElements.length > 1) {
|
if (allHitElements.length > 1) {
|
||||||
|
if (opts?.preferSelected) {
|
||||||
|
for (let index = allHitElements.length - 1; index > -1; index--) {
|
||||||
|
if (this.state.selectedElementIds[allHitElements[index].id]) {
|
||||||
|
return allHitElements[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const elementWithHighestZIndex =
|
const elementWithHighestZIndex =
|
||||||
allHitElements[allHitElements.length - 1];
|
allHitElements[allHitElements.length - 1];
|
||||||
// If we're hitting element with highest z-index only on its bounding box
|
// If we're hitting element with highest z-index only on its bounding box
|
||||||
@@ -2273,6 +2247,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.canvas,
|
this.canvas,
|
||||||
isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR,
|
isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR,
|
||||||
);
|
);
|
||||||
|
} else if (this.state.viewModeEnabled) {
|
||||||
|
setCursor(this.canvas, CURSOR_TYPE.GRAB);
|
||||||
} else if (isOverScrollBar) {
|
} else if (isOverScrollBar) {
|
||||||
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
setCursor(this.canvas, CURSOR_TYPE.AUTO);
|
||||||
} else if (
|
} else if (
|
||||||
@@ -2298,8 +2274,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
private handleCanvasPointerDown = (
|
private handleCanvasPointerDown = (
|
||||||
event: React.PointerEvent<HTMLCanvasElement>,
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
) => {
|
) => {
|
||||||
event.persist();
|
|
||||||
|
|
||||||
// remove any active selection when we start to interact with canvas
|
// remove any active selection when we start to interact with canvas
|
||||||
// (mainly, we care about removing selection outside the component which
|
// (mainly, we care about removing selection outside the component which
|
||||||
// would prevent our copy handling otherwise)
|
// would prevent our copy handling otherwise)
|
||||||
@@ -2512,7 +2486,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
lastPointerUp = null;
|
lastPointerUp = null;
|
||||||
isPanning = false;
|
isPanning = false;
|
||||||
if (!isHoldingSpace) {
|
if (!isHoldingSpace) {
|
||||||
setCursorForShape(this.canvas, this.state.elementType);
|
if (this.state.viewModeEnabled) {
|
||||||
|
setCursor(this.canvas, CURSOR_TYPE.GRAB);
|
||||||
|
} else {
|
||||||
|
setCursorForShape(this.canvas, this.state.elementType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
cursorButton: "up",
|
cursorButton: "up",
|
||||||
@@ -3516,6 +3494,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
mutateElement(draggingElement, {
|
mutateElement(draggingElement, {
|
||||||
points: [...points, [dx, dy]],
|
points: [...points, [dx, dy]],
|
||||||
pressures,
|
pressures,
|
||||||
|
lastCommittedPoint: [dx, dy],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.actionManager.executeAction(actionFinalize);
|
this.actionManager.executeAction(actionFinalize);
|
||||||
@@ -3570,7 +3549,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementType: "selection",
|
elementType: "selection",
|
||||||
selectedElementIds: {
|
selectedElementIds: {
|
||||||
...prevState.selectedElementIds,
|
...prevState.selectedElementIds,
|
||||||
[this.state.draggingElement!.id]: true,
|
[draggingElement.id]: true,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
@@ -3578,7 +3557,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
selectedElementIds: {
|
selectedElementIds: {
|
||||||
...prevState.selectedElementIds,
|
...prevState.selectedElementIds,
|
||||||
[this.state.draggingElement!.id]: true,
|
[draggingElement.id]: true,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -3855,7 +3834,22 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
try {
|
try {
|
||||||
const file = event.dataTransfer.files[0];
|
const file = event.dataTransfer.files[0];
|
||||||
if (file?.type === "image/png" || file?.type === "image/svg+xml") {
|
if (file?.type === "image/png" || file?.type === "image/svg+xml") {
|
||||||
const { elements, appState } = await loadFromBlob(file, this.state);
|
if (nativeFileSystemSupported) {
|
||||||
|
try {
|
||||||
|
// This will only work as of Chrome 86,
|
||||||
|
// but can be safely ignored on older releases.
|
||||||
|
const item = event.dataTransfer.items[0];
|
||||||
|
(file as any).handle = await (item as any).getAsFileSystemHandle();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn(error.name, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { elements, appState } = await loadFromBlob(
|
||||||
|
file,
|
||||||
|
this.state,
|
||||||
|
this.scene.getElementsIncludingDeleted(),
|
||||||
|
);
|
||||||
this.syncActionResult({
|
this.syncActionResult({
|
||||||
elements,
|
elements,
|
||||||
appState: {
|
appState: {
|
||||||
@@ -3866,7 +3860,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
return this.setState({
|
return this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: error.message,
|
errorMessage: error.message,
|
||||||
@@ -3900,13 +3894,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// default: assume an Excalidraw file regardless of extension/MimeType
|
// default: assume an Excalidraw file regardless of extension/MimeType
|
||||||
} else {
|
} else {
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
if (fsSupported) {
|
if (nativeFileSystemSupported) {
|
||||||
try {
|
try {
|
||||||
// This will only work as of Chrome 86,
|
// This will only work as of Chrome 86,
|
||||||
// but can be safely ignored on older releases.
|
// but can be safely ignored on older releases.
|
||||||
const item = event.dataTransfer.items[0];
|
const item = event.dataTransfer.items[0];
|
||||||
(file as any).handle = await (item as any).getAsFileSystemHandle();
|
(file as any).handle = await (item as any).getAsFileSystemHandle();
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.warn(error.name, error.message);
|
console.warn(error.name, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3915,7 +3909,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadFileToCanvas = (file: Blob) => {
|
loadFileToCanvas = (file: Blob) => {
|
||||||
loadFromBlob(file, this.state)
|
loadFromBlob(file, this.state, this.scene.getElementsIncludingDeleted())
|
||||||
.then(({ elements, appState }) =>
|
.then(({ elements, appState }) =>
|
||||||
this.syncActionResult({
|
this.syncActionResult({
|
||||||
elements,
|
elements,
|
||||||
@@ -3937,7 +3931,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(event, this.state);
|
const { x, y } = viewportCoordsToSceneCoords(event, this.state);
|
||||||
const element = this.getElementAtPosition(x, y);
|
const element = this.getElementAtPosition(x, y, { preferSelected: true });
|
||||||
|
|
||||||
const type = element ? "element" : "canvas";
|
const type = element ? "element" : "canvas";
|
||||||
|
|
||||||
@@ -4099,116 +4093,112 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
];
|
];
|
||||||
|
|
||||||
ContextMenu.push({
|
|
||||||
options: viewModeOptions,
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
actionManager: this.actionManager,
|
|
||||||
appState: this.state,
|
|
||||||
container: this.excalidrawContainerRef.current!,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.state.viewModeEnabled) {
|
if (this.state.viewModeEnabled) {
|
||||||
return;
|
ContextMenu.push({
|
||||||
}
|
options: viewModeOptions,
|
||||||
|
top,
|
||||||
ContextMenu.push({
|
left,
|
||||||
options: [
|
actionManager: this.actionManager,
|
||||||
this.isMobile &&
|
appState: this.state,
|
||||||
navigator.clipboard && {
|
container: this.excalidrawContainerRef.current!,
|
||||||
name: "paste",
|
});
|
||||||
perform: (elements, appStates) => {
|
} else {
|
||||||
this.pasteFromClipboard(null);
|
ContextMenu.push({
|
||||||
return {
|
options: [
|
||||||
commitToHistory: false,
|
this.isMobile &&
|
||||||
};
|
navigator.clipboard && {
|
||||||
|
name: "paste",
|
||||||
|
perform: (elements, appStates) => {
|
||||||
|
this.pasteFromClipboard(null);
|
||||||
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
contextItemLabel: "labels.paste",
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.paste",
|
this.isMobile && navigator.clipboard && separator,
|
||||||
},
|
probablySupportsClipboardBlob &&
|
||||||
this.isMobile && navigator.clipboard && separator,
|
elements.length > 0 &&
|
||||||
probablySupportsClipboardBlob &&
|
actionCopyAsPng,
|
||||||
elements.length > 0 &&
|
probablySupportsClipboardWriteText &&
|
||||||
actionCopyAsPng,
|
elements.length > 0 &&
|
||||||
probablySupportsClipboardWriteText &&
|
actionCopyAsSvg,
|
||||||
elements.length > 0 &&
|
((probablySupportsClipboardBlob && elements.length > 0) ||
|
||||||
actionCopyAsSvg,
|
(probablySupportsClipboardWriteText && elements.length > 0)) &&
|
||||||
((probablySupportsClipboardBlob && elements.length > 0) ||
|
separator,
|
||||||
(probablySupportsClipboardWriteText && elements.length > 0)) &&
|
actionSelectAll,
|
||||||
separator,
|
separator,
|
||||||
actionSelectAll,
|
typeof this.props.gridModeEnabled === "undefined" &&
|
||||||
separator,
|
actionToggleGridMode,
|
||||||
typeof this.props.gridModeEnabled === "undefined" &&
|
typeof this.props.zenModeEnabled === "undefined" &&
|
||||||
actionToggleGridMode,
|
actionToggleZenMode,
|
||||||
typeof this.props.zenModeEnabled === "undefined" &&
|
typeof this.props.viewModeEnabled === "undefined" &&
|
||||||
actionToggleZenMode,
|
actionToggleViewMode,
|
||||||
typeof this.props.viewModeEnabled === "undefined" &&
|
actionToggleStats,
|
||||||
actionToggleViewMode,
|
],
|
||||||
actionToggleStats,
|
top,
|
||||||
],
|
left,
|
||||||
top,
|
actionManager: this.actionManager,
|
||||||
left,
|
appState: this.state,
|
||||||
actionManager: this.actionManager,
|
container: this.excalidrawContainerRef.current!,
|
||||||
appState: this.state,
|
});
|
||||||
container: this.excalidrawContainerRef.current!,
|
}
|
||||||
});
|
} else if (type === "element") {
|
||||||
return;
|
if (this.state.viewModeEnabled) {
|
||||||
|
ContextMenu.push({
|
||||||
|
options: [navigator.clipboard && actionCopy, ...options],
|
||||||
|
top,
|
||||||
|
left,
|
||||||
|
actionManager: this.actionManager,
|
||||||
|
appState: this.state,
|
||||||
|
container: this.excalidrawContainerRef.current!,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ContextMenu.push({
|
||||||
|
options: [
|
||||||
|
this.isMobile && actionCut,
|
||||||
|
this.isMobile && navigator.clipboard && actionCopy,
|
||||||
|
this.isMobile &&
|
||||||
|
navigator.clipboard && {
|
||||||
|
name: "paste",
|
||||||
|
perform: (elements, appStates) => {
|
||||||
|
this.pasteFromClipboard(null);
|
||||||
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
contextItemLabel: "labels.paste",
|
||||||
|
},
|
||||||
|
this.isMobile && separator,
|
||||||
|
...options,
|
||||||
|
separator,
|
||||||
|
actionCopyStyles,
|
||||||
|
actionPasteStyles,
|
||||||
|
separator,
|
||||||
|
maybeGroupAction && actionGroup,
|
||||||
|
maybeUngroupAction && actionUngroup,
|
||||||
|
(maybeGroupAction || maybeUngroupAction) && separator,
|
||||||
|
actionAddToLibrary,
|
||||||
|
separator,
|
||||||
|
actionSendBackward,
|
||||||
|
actionBringForward,
|
||||||
|
actionSendToBack,
|
||||||
|
actionBringToFront,
|
||||||
|
separator,
|
||||||
|
maybeFlipHorizontal && actionFlipHorizontal,
|
||||||
|
maybeFlipVertical && actionFlipVertical,
|
||||||
|
(maybeFlipHorizontal || maybeFlipVertical) && separator,
|
||||||
|
actionDuplicateSelection,
|
||||||
|
actionDeleteSelected,
|
||||||
|
],
|
||||||
|
top,
|
||||||
|
left,
|
||||||
|
actionManager: this.actionManager,
|
||||||
|
appState: this.state,
|
||||||
|
container: this.excalidrawContainerRef.current!,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.viewModeEnabled) {
|
|
||||||
ContextMenu.push({
|
|
||||||
options: [navigator.clipboard && actionCopy, ...options],
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
actionManager: this.actionManager,
|
|
||||||
appState: this.state,
|
|
||||||
container: this.excalidrawContainerRef.current!,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenu.push({
|
|
||||||
options: [
|
|
||||||
this.isMobile && actionCut,
|
|
||||||
this.isMobile && navigator.clipboard && actionCopy,
|
|
||||||
this.isMobile &&
|
|
||||||
navigator.clipboard && {
|
|
||||||
name: "paste",
|
|
||||||
perform: (elements, appStates) => {
|
|
||||||
this.pasteFromClipboard(null);
|
|
||||||
return {
|
|
||||||
commitToHistory: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
contextItemLabel: "labels.paste",
|
|
||||||
},
|
|
||||||
this.isMobile && separator,
|
|
||||||
...options,
|
|
||||||
separator,
|
|
||||||
actionCopyStyles,
|
|
||||||
actionPasteStyles,
|
|
||||||
separator,
|
|
||||||
maybeGroupAction && actionGroup,
|
|
||||||
maybeUngroupAction && actionUngroup,
|
|
||||||
(maybeGroupAction || maybeUngroupAction) && separator,
|
|
||||||
actionAddToLibrary,
|
|
||||||
separator,
|
|
||||||
actionSendBackward,
|
|
||||||
actionBringForward,
|
|
||||||
actionSendToBack,
|
|
||||||
actionBringToFront,
|
|
||||||
separator,
|
|
||||||
maybeFlipHorizontal && actionFlipHorizontal,
|
|
||||||
maybeFlipVertical && actionFlipVertical,
|
|
||||||
(maybeFlipHorizontal || maybeFlipVertical) && separator,
|
|
||||||
actionDuplicateSelection,
|
|
||||||
actionDeleteSelected,
|
|
||||||
],
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
actionManager: this.actionManager,
|
|
||||||
appState: this.state,
|
|
||||||
container: this.excalidrawContainerRef.current!,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleWheel = withBatchedUpdates((event: WheelEvent) => {
|
private handleWheel = withBatchedUpdates((event: WheelEvent) => {
|
||||||
|
|||||||
@@ -16,10 +16,5 @@ export const BackgroundPickerAndDarkModeToggle = ({
|
|||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
{actionManager.renderAction("changeViewBackgroundColor")}
|
{actionManager.renderAction("changeViewBackgroundColor")}
|
||||||
{showThemeBtn && actionManager.renderAction("toggleTheme")}
|
{showThemeBtn && actionManager.renderAction("toggleTheme")}
|
||||||
{appState.fileHandle && (
|
|
||||||
<div style={{ marginInlineStart: "0.25rem" }}>
|
|
||||||
{actionManager.renderAction("saveToActiveFile")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Tooltip-icon {
|
.excalidraw-tooltip-icon {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { checkIcon } from "./icons";
|
import { checkIcon } from "./icons";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Popover } from "./Popover";
|
import { Popover } from "./Popover";
|
||||||
|
import { isTransparent } from "../utils";
|
||||||
|
|
||||||
import "./ColorPicker.scss";
|
import "./ColorPicker.scss";
|
||||||
import { isArrowKey, KEYS } from "../keys";
|
import { isArrowKey, KEYS } from "../keys";
|
||||||
@@ -14,7 +15,7 @@ const isValidColor = (color: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getColor = (color: string): string | null => {
|
const getColor = (color: string): string | null => {
|
||||||
if (color === "transparent") {
|
if (isTransparent(color)) {
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,36 +138,41 @@ const Picker = ({
|
|||||||
}}
|
}}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
{colors.map((_color, i) => (
|
{colors.map((_color, i) => {
|
||||||
<button
|
const _colorWithoutHash = _color.replace("#", "");
|
||||||
className="color-picker-swatch"
|
return (
|
||||||
onClick={(event) => {
|
<button
|
||||||
(event.currentTarget as HTMLButtonElement).focus();
|
className="color-picker-swatch"
|
||||||
onChange(_color);
|
onClick={(event) => {
|
||||||
}}
|
(event.currentTarget as HTMLButtonElement).focus();
|
||||||
title={`${_color} — ${keyBindings[i].toUpperCase()}`}
|
onChange(_color);
|
||||||
aria-label={_color}
|
}}
|
||||||
aria-keyshortcuts={keyBindings[i]}
|
title={`${t(`colors.${_colorWithoutHash}`)}${
|
||||||
style={{ color: _color }}
|
!isTransparent(_color) ? ` (${_color})` : ""
|
||||||
key={_color}
|
} — ${keyBindings[i].toUpperCase()}`}
|
||||||
ref={(el) => {
|
aria-label={t(`colors.${_colorWithoutHash}`)}
|
||||||
if (el && i === 0) {
|
aria-keyshortcuts={keyBindings[i]}
|
||||||
firstItem.current = el;
|
style={{ color: _color }}
|
||||||
}
|
key={_color}
|
||||||
if (el && _color === color) {
|
ref={(el) => {
|
||||||
activeItem.current = el;
|
if (el && i === 0) {
|
||||||
}
|
firstItem.current = el;
|
||||||
}}
|
}
|
||||||
onFocus={() => {
|
if (el && _color === color) {
|
||||||
onChange(_color);
|
activeItem.current = el;
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
{_color === "transparent" ? (
|
onFocus={() => {
|
||||||
<div className="color-picker-transparent"></div>
|
onChange(_color);
|
||||||
) : undefined}
|
}}
|
||||||
<span className="color-picker-keybinding">{keyBindings[i]}</span>
|
>
|
||||||
</button>
|
{isTransparent(_color) ? (
|
||||||
))}
|
<div className="color-picker-transparent"></div>
|
||||||
|
) : undefined}
|
||||||
|
<span className="color-picker-keybinding">{keyBindings[i]}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
{showInput && (
|
{showInput && (
|
||||||
<ColorInput
|
<ColorInput
|
||||||
color={color}
|
color={color}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import clsx from "clsx";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
import { useCallbackRefState } from "../hooks/useCallbackRefState";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useIsMobile } from "../components/App";
|
import { useExcalidrawContainer, useIsMobile } from "../components/App";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import "./Dialog.scss";
|
import "./Dialog.scss";
|
||||||
import { back, close } from "./icons";
|
import { back, close } from "./icons";
|
||||||
@@ -21,6 +21,7 @@ export const Dialog = (props: {
|
|||||||
}) => {
|
}) => {
|
||||||
const [islandNode, setIslandNode] = useCallbackRefState<HTMLDivElement>();
|
const [islandNode, setIslandNode] = useCallbackRefState<HTMLDivElement>();
|
||||||
const [lastActiveElement] = useState(document.activeElement);
|
const [lastActiveElement] = useState(document.activeElement);
|
||||||
|
const { id } = useExcalidrawContainer();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!islandNode) {
|
if (!islandNode) {
|
||||||
@@ -82,7 +83,7 @@ export const Dialog = (props: {
|
|||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
>
|
>
|
||||||
<Island ref={setIslandNode}>
|
<Island ref={setIslandNode}>
|
||||||
<h2 id="dialog-title" className="Dialog__title">
|
<h2 id={`${id}-dialog-title`} className="Dialog__title">
|
||||||
<span className="Dialog__titleContent">{props.title}</span>
|
<span className="Dialog__titleContent">{props.title}</span>
|
||||||
<button
|
<button
|
||||||
className="Modal__close"
|
className="Modal__close"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const ErrorDialog = ({
|
|||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [modalIsShown, setModalIsShown] = useState(!!message);
|
const [modalIsShown, setModalIsShown] = useState(!!message);
|
||||||
const excalidrawContainer = useExcalidrawContainer();
|
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||||
|
|
||||||
const handleClose = React.useCallback(() => {
|
const handleClose = React.useCallback(() => {
|
||||||
setModalIsShown(false);
|
setModalIsShown(false);
|
||||||
|
|||||||
@@ -97,7 +97,8 @@
|
|||||||
|
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
background-color: var(--button-color);
|
background-color: var(--button-color);
|
||||||
box-shadow: 0 3px 5px -1px rgb(0 0 0 / 28%), 0 6px 10px 0 rgb(0 0 0 / 14%);
|
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.28),
|
||||||
|
0 6px 10px 0 rgba(0, 0, 0, 0.14);
|
||||||
|
|
||||||
font-family: Cascadia;
|
font-family: Cascadia;
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
|
|||||||
@@ -157,6 +157,13 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
shortcuts={["Shift+P", "7"]}
|
shortcuts={["Shift+P", "7"]}
|
||||||
/>
|
/>
|
||||||
<Shortcut label={t("toolBar.text")} shortcuts={["T", "8"]} />
|
<Shortcut label={t("toolBar.text")} shortcuts={["T", "8"]} />
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.editSelectedShape")}
|
||||||
|
shortcuts={[
|
||||||
|
getShortcutKey("Enter"),
|
||||||
|
t("helpDialog.doubleClick"),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("helpDialog.textNewLine")}
|
label={t("helpDialog.textNewLine")}
|
||||||
shortcuts={[
|
shortcuts={[
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { getSelectedElements } from "../scene";
|
|||||||
|
|
||||||
import "./HintViewer.scss";
|
import "./HintViewer.scss";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { isLinearElement } from "../element/typeChecks";
|
import { isLinearElement, isTextElement } from "../element/typeChecks";
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
|
|
||||||
interface Hint {
|
interface Hint {
|
||||||
@@ -57,6 +57,14 @@ const getHints = ({ appState, elements }: Hint) => {
|
|||||||
return t("hints.lineEditor_info");
|
return t("hints.lineEditor_info");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
|
||||||
|
return t("hints.text_selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appState.editingElement && isTextElement(appState.editingElement)) {
|
||||||
|
return t("hints.text_editing");
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,20 +8,17 @@ import { CanvasError } from "../errors";
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useIsMobile } from "./App";
|
import { useIsMobile } from "./App";
|
||||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
import { exportToCanvas, getExportSize } from "../scene/export";
|
import { exportToCanvas } from "../scene/export";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { clipboard, exportImage } from "./icons";
|
import { clipboard, exportImage } from "./icons";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
|
|
||||||
import "./ExportDialog.scss";
|
import "./ExportDialog.scss";
|
||||||
import { supported as fsSupported } from "browser-fs-access";
|
|
||||||
import OpenColor from "open-color";
|
import OpenColor from "open-color";
|
||||||
import { CheckboxItem } from "./CheckboxItem";
|
import { CheckboxItem } from "./CheckboxItem";
|
||||||
|
import { DEFAULT_EXPORT_PADDING } from "../constants";
|
||||||
const scales = [1, 2, 3];
|
import { nativeFileSystemSupported } from "../data/filesystem";
|
||||||
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
|
||||||
|
|
||||||
const supportsContextFilters =
|
const supportsContextFilters =
|
||||||
"filter" in document.createElement("canvas").getContext("2d")!;
|
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||||
@@ -82,7 +79,7 @@ const ExportButton: React.FC<{
|
|||||||
const ImageExportModal = ({
|
const ImageExportModal = ({
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
exportPadding = 10,
|
exportPadding = DEFAULT_EXPORT_PADDING,
|
||||||
actionManager,
|
actionManager,
|
||||||
onExportToPng,
|
onExportToPng,
|
||||||
onExportToSvg,
|
onExportToSvg,
|
||||||
@@ -98,7 +95,6 @@ const ImageExportModal = ({
|
|||||||
onCloseRequest: () => void;
|
onCloseRequest: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
||||||
const [scale, setScale] = useState(defaultScale);
|
|
||||||
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
||||||
const previewRef = useRef<HTMLDivElement>(null);
|
const previewRef = useRef<HTMLDivElement>(null);
|
||||||
const { exportBackground, viewBackgroundColor } = appState;
|
const { exportBackground, viewBackgroundColor } = appState;
|
||||||
@@ -121,7 +117,6 @@ const ImageExportModal = ({
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
scale,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// if converting to blob fails, there's some problem that will
|
// if converting to blob fails, there's some problem that will
|
||||||
@@ -144,7 +139,6 @@ const ImageExportModal = ({
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
scale,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -175,33 +169,8 @@ const ImageExportModal = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", alignItems: "center", marginTop: ".6em" }}>
|
<div style={{ display: "flex", alignItems: "center", marginTop: ".6em" }}>
|
||||||
<Stack.Row gap={2} justifyContent={"center"}>
|
<Stack.Row gap={2}>
|
||||||
{scales.map((_scale) => {
|
{actionManager.renderAction("changeExportScale")}
|
||||||
const [width, height] = getExportSize(
|
|
||||||
exportedElements,
|
|
||||||
exportPadding,
|
|
||||||
_scale,
|
|
||||||
);
|
|
||||||
|
|
||||||
const scaleButtonTitle = `${t(
|
|
||||||
"buttons.scale",
|
|
||||||
)} ${_scale}x (${width}x${height})`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ToolButton
|
|
||||||
key={_scale}
|
|
||||||
size="s"
|
|
||||||
type="radio"
|
|
||||||
icon={`${_scale}x`}
|
|
||||||
name="export-canvas-scale"
|
|
||||||
title={scaleButtonTitle}
|
|
||||||
aria-label={scaleButtonTitle}
|
|
||||||
id="export-canvas-scale"
|
|
||||||
checked={_scale === scale}
|
|
||||||
onChange={() => setScale(_scale)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
<p style={{ marginLeft: "1em", userSelect: "none" }}>Scale</p>
|
<p style={{ marginLeft: "1em", userSelect: "none" }}>Scale</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -213,14 +182,15 @@ const ImageExportModal = ({
|
|||||||
margin: ".6em 0",
|
margin: ".6em 0",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!fsSupported && actionManager.renderAction("changeProjectName")}
|
{!nativeFileSystemSupported &&
|
||||||
|
actionManager.renderAction("changeProjectName")}
|
||||||
</div>
|
</div>
|
||||||
<Stack.Row gap={2} justifyContent="center" style={{ margin: "2em 0" }}>
|
<Stack.Row gap={2} justifyContent="center" style={{ margin: "2em 0" }}>
|
||||||
<ExportButton
|
<ExportButton
|
||||||
color="indigo"
|
color="indigo"
|
||||||
title={t("buttons.exportToPng")}
|
title={t("buttons.exportToPng")}
|
||||||
aria-label={t("buttons.exportToPng")}
|
aria-label={t("buttons.exportToPng")}
|
||||||
onClick={() => onExportToPng(exportedElements, scale)}
|
onClick={() => onExportToPng(exportedElements)}
|
||||||
>
|
>
|
||||||
PNG
|
PNG
|
||||||
</ExportButton>
|
</ExportButton>
|
||||||
@@ -228,14 +198,14 @@ const ImageExportModal = ({
|
|||||||
color="red"
|
color="red"
|
||||||
title={t("buttons.exportToSvg")}
|
title={t("buttons.exportToSvg")}
|
||||||
aria-label={t("buttons.exportToSvg")}
|
aria-label={t("buttons.exportToSvg")}
|
||||||
onClick={() => onExportToSvg(exportedElements, scale)}
|
onClick={() => onExportToSvg(exportedElements)}
|
||||||
>
|
>
|
||||||
SVG
|
SVG
|
||||||
</ExportButton>
|
</ExportButton>
|
||||||
{probablySupportsClipboardBlob && (
|
{probablySupportsClipboardBlob && (
|
||||||
<ExportButton
|
<ExportButton
|
||||||
title={t("buttons.copyPngToClipboard")}
|
title={t("buttons.copyPngToClipboard")}
|
||||||
onClick={() => onExportToClipboard(exportedElements, scale)}
|
onClick={() => onExportToClipboard(exportedElements)}
|
||||||
color="gray"
|
color="gray"
|
||||||
shade={7}
|
shade={7}
|
||||||
>
|
>
|
||||||
@@ -250,7 +220,7 @@ const ImageExportModal = ({
|
|||||||
export const ImageExportDialog = ({
|
export const ImageExportDialog = ({
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
exportPadding = 10,
|
exportPadding = DEFAULT_EXPORT_PADDING,
|
||||||
actionManager,
|
actionManager,
|
||||||
onExportToPng,
|
onExportToPng,
|
||||||
onExportToSvg,
|
onExportToSvg,
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import { ActionsManagerInterface } from "../actions/types";
|
|||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useIsMobile } from "./App";
|
import { useIsMobile } from "./App";
|
||||||
import { AppState } from "../types";
|
import { AppState, ExportOpts } from "../types";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { exportFile, exportToFileIcon, link } from "./icons";
|
import { exportFile, exportToFileIcon, link } from "./icons";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { actionSaveAsScene } from "../actions/actionExport";
|
import { actionSaveFileToDisk } from "../actions/actionExport";
|
||||||
import { Card } from "./Card";
|
import { Card } from "./Card";
|
||||||
|
|
||||||
import "./ExportDialog.scss";
|
import "./ExportDialog.scss";
|
||||||
import { supported as fsSupported } from "browser-fs-access";
|
import { nativeFileSystemSupported } from "../data/filesystem";
|
||||||
|
|
||||||
export type ExportCB = (
|
export type ExportCB = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
@@ -22,35 +22,41 @@ const JSONExportModal = ({
|
|||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
actionManager,
|
actionManager,
|
||||||
onExportToBackend,
|
exportOpts,
|
||||||
|
canvas,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
actionManager: ActionsManagerInterface;
|
actionManager: ActionsManagerInterface;
|
||||||
onExportToBackend?: ExportCB;
|
|
||||||
onCloseRequest: () => void;
|
onCloseRequest: () => void;
|
||||||
|
exportOpts: ExportOpts;
|
||||||
|
canvas: HTMLCanvasElement | null;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { onExportToBackend } = exportOpts;
|
||||||
return (
|
return (
|
||||||
<div className="ExportDialog ExportDialog--json">
|
<div className="ExportDialog ExportDialog--json">
|
||||||
<div className="ExportDialog-cards">
|
<div className="ExportDialog-cards">
|
||||||
<Card color="lime">
|
{exportOpts.saveFileToDisk && (
|
||||||
<div className="Card-icon">{exportToFileIcon}</div>
|
<Card color="lime">
|
||||||
<h2>{t("exportDialog.disk_title")}</h2>
|
<div className="Card-icon">{exportToFileIcon}</div>
|
||||||
<div className="Card-details">
|
<h2>{t("exportDialog.disk_title")}</h2>
|
||||||
{t("exportDialog.disk_details")}
|
<div className="Card-details">
|
||||||
{!fsSupported && actionManager.renderAction("changeProjectName")}
|
{t("exportDialog.disk_details")}
|
||||||
</div>
|
{!nativeFileSystemSupported &&
|
||||||
<ToolButton
|
actionManager.renderAction("changeProjectName")}
|
||||||
className="Card-button"
|
</div>
|
||||||
type="button"
|
<ToolButton
|
||||||
title={t("exportDialog.disk_button")}
|
className="Card-button"
|
||||||
aria-label={t("exportDialog.disk_button")}
|
type="button"
|
||||||
showAriaLabel={true}
|
title={t("exportDialog.disk_button")}
|
||||||
onClick={() => {
|
aria-label={t("exportDialog.disk_button")}
|
||||||
actionManager.executeAction(actionSaveAsScene);
|
showAriaLabel={true}
|
||||||
}}
|
onClick={() => {
|
||||||
/>
|
actionManager.executeAction(actionSaveFileToDisk);
|
||||||
</Card>
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
{onExportToBackend && (
|
{onExportToBackend && (
|
||||||
<Card color="pink">
|
<Card color="pink">
|
||||||
<div className="Card-icon">{link}</div>
|
<div className="Card-icon">{link}</div>
|
||||||
@@ -62,10 +68,12 @@ const JSONExportModal = ({
|
|||||||
title={t("exportDialog.link_button")}
|
title={t("exportDialog.link_button")}
|
||||||
aria-label={t("exportDialog.link_button")}
|
aria-label={t("exportDialog.link_button")}
|
||||||
showAriaLabel={true}
|
showAriaLabel={true}
|
||||||
onClick={() => onExportToBackend(elements)}
|
onClick={() => onExportToBackend(elements, appState, canvas)}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
{exportOpts.renderCustomUI &&
|
||||||
|
exportOpts.renderCustomUI(elements, appState, canvas)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -75,12 +83,14 @@ export const JSONExportDialog = ({
|
|||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
actionManager,
|
actionManager,
|
||||||
onExportToBackend,
|
exportOpts,
|
||||||
|
canvas,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
actionManager: ActionsManagerInterface;
|
actionManager: ActionsManagerInterface;
|
||||||
onExportToBackend?: ExportCB;
|
exportOpts: ExportOpts;
|
||||||
|
canvas: HTMLCanvasElement | null;
|
||||||
}) => {
|
}) => {
|
||||||
const [modalIsShown, setModalIsShown] = useState(false);
|
const [modalIsShown, setModalIsShown] = useState(false);
|
||||||
|
|
||||||
@@ -107,8 +117,9 @@ export const JSONExportDialog = ({
|
|||||||
elements={elements}
|
elements={elements}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
onExportToBackend={onExportToBackend}
|
|
||||||
onCloseRequest={handleClose}
|
onCloseRequest={handleClose}
|
||||||
|
exportOpts={exportOpts}
|
||||||
|
canvas={canvas}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -73,10 +73,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="ltr"] &.layer-ui__wrapper__footer-left--transition-left {
|
:root[dir="ltr"] &.layer-ui__wrapper__footer-left--transition-left {
|
||||||
transform: translate(-92px, 0);
|
transform: translate(-76px, 0);
|
||||||
}
|
}
|
||||||
:root[dir="rtl"] &.layer-ui__wrapper__footer-left--transition-left {
|
:root[dir="rtl"] &.layer-ui__wrapper__footer-left--transition-left {
|
||||||
transform: translate(92px, 0);
|
transform: translate(76px, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.layer-ui__wrapper__footer-left--transition-bottom {
|
&.layer-ui__wrapper__footer-left--transition-bottom {
|
||||||
@@ -116,8 +116,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.layer-ui__wrapper__footer-left,
|
.layer-ui__wrapper__footer-left,
|
||||||
.layer-ui__wrapper__footer-right {
|
.layer-ui__wrapper__footer-right,
|
||||||
|
.disable-zen-mode--visible {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layer-ui__wrapper__footer-left {
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-ui__wrapper__footer-right {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-inline-end: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { Island } from "./Island";
|
|||||||
import "./LayerUI.scss";
|
import "./LayerUI.scss";
|
||||||
import { LibraryUnit } from "./LibraryUnit";
|
import { LibraryUnit } from "./LibraryUnit";
|
||||||
import { LoadingMessage } from "./LoadingMessage";
|
import { LoadingMessage } from "./LoadingMessage";
|
||||||
import { LockIcon } from "./LockIcon";
|
import { LockButton } from "./LockButton";
|
||||||
import { MobileMenu } from "./MobileMenu";
|
import { MobileMenu } from "./MobileMenu";
|
||||||
import { PasteChartDialog } from "./PasteChartDialog";
|
import { PasteChartDialog } from "./PasteChartDialog";
|
||||||
import { Section } from "./Section";
|
import { Section } from "./Section";
|
||||||
@@ -47,6 +47,8 @@ import { Tooltip } from "./Tooltip";
|
|||||||
import { UserList } from "./UserList";
|
import { UserList } from "./UserList";
|
||||||
import Library from "../data/library";
|
import Library from "../data/library";
|
||||||
import { JSONExportDialog } from "./JSONExportDialog";
|
import { JSONExportDialog } from "./JSONExportDialog";
|
||||||
|
import { LibraryButton } from "./LibraryButton";
|
||||||
|
import { isImageFileHandle } from "../data/blob";
|
||||||
|
|
||||||
interface LayerUIProps {
|
interface LayerUIProps {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
@@ -63,11 +65,6 @@ interface LayerUIProps {
|
|||||||
toggleZenMode: () => void;
|
toggleZenMode: () => void;
|
||||||
langCode: Language["code"];
|
langCode: Language["code"];
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
onExportToBackend?: (
|
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
|
||||||
appState: AppState,
|
|
||||||
canvas: HTMLCanvasElement | null,
|
|
||||||
) => void;
|
|
||||||
renderTopRightUI?: (isMobile: boolean, appState: AppState) => JSX.Element;
|
renderTopRightUI?: (isMobile: boolean, appState: AppState) => JSX.Element;
|
||||||
renderCustomFooter?: (isMobile: boolean, appState: AppState) => JSX.Element;
|
renderCustomFooter?: (isMobile: boolean, appState: AppState) => JSX.Element;
|
||||||
viewModeEnabled: boolean;
|
viewModeEnabled: boolean;
|
||||||
@@ -112,6 +109,7 @@ const LibraryMenuItems = ({
|
|||||||
onAddToLibrary,
|
onAddToLibrary,
|
||||||
onInsertShape,
|
onInsertShape,
|
||||||
pendingElements,
|
pendingElements,
|
||||||
|
theme,
|
||||||
setAppState,
|
setAppState,
|
||||||
setLibraryItems,
|
setLibraryItems,
|
||||||
libraryReturnUrl,
|
libraryReturnUrl,
|
||||||
@@ -124,6 +122,7 @@ const LibraryMenuItems = ({
|
|||||||
onRemoveFromLibrary: (index: number) => void;
|
onRemoveFromLibrary: (index: number) => void;
|
||||||
onInsertShape: (elements: LibraryItem) => void;
|
onInsertShape: (elements: LibraryItem) => void;
|
||||||
onAddToLibrary: (elements: LibraryItem) => void;
|
onAddToLibrary: (elements: LibraryItem) => void;
|
||||||
|
theme: AppState["theme"];
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
setLibraryItems: (library: LibraryItems) => void;
|
setLibraryItems: (library: LibraryItems) => void;
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
@@ -197,7 +196,7 @@ const LibraryMenuItems = ({
|
|||||||
<a
|
<a
|
||||||
href={`https://libraries.excalidraw.com?target=${
|
href={`https://libraries.excalidraw.com?target=${
|
||||||
window.name || "_blank"
|
window.name || "_blank"
|
||||||
}&referrer=${referrer}&useHash=true&token=${id}`}
|
}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}`}
|
||||||
target="_excalidraw_libraries"
|
target="_excalidraw_libraries"
|
||||||
>
|
>
|
||||||
{t("labels.libraries")}
|
{t("labels.libraries")}
|
||||||
@@ -251,6 +250,7 @@ const LibraryMenu = ({
|
|||||||
onInsertShape,
|
onInsertShape,
|
||||||
pendingElements,
|
pendingElements,
|
||||||
onAddToLibrary,
|
onAddToLibrary,
|
||||||
|
theme,
|
||||||
setAppState,
|
setAppState,
|
||||||
libraryReturnUrl,
|
libraryReturnUrl,
|
||||||
focusContainer,
|
focusContainer,
|
||||||
@@ -261,6 +261,7 @@ const LibraryMenu = ({
|
|||||||
onClickOutside: (event: MouseEvent) => void;
|
onClickOutside: (event: MouseEvent) => void;
|
||||||
onInsertShape: (elements: LibraryItem) => void;
|
onInsertShape: (elements: LibraryItem) => void;
|
||||||
onAddToLibrary: () => void;
|
onAddToLibrary: () => void;
|
||||||
|
theme: AppState["theme"];
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
focusContainer: () => void;
|
focusContainer: () => void;
|
||||||
@@ -350,6 +351,7 @@ const LibraryMenu = ({
|
|||||||
libraryReturnUrl={libraryReturnUrl}
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
focusContainer={focusContainer}
|
focusContainer={focusContainer}
|
||||||
library={library}
|
library={library}
|
||||||
|
theme={theme}
|
||||||
id={id}
|
id={id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -371,7 +373,6 @@ const LayerUI = ({
|
|||||||
showThemeBtn,
|
showThemeBtn,
|
||||||
toggleZenMode,
|
toggleZenMode,
|
||||||
isCollaborating,
|
isCollaborating,
|
||||||
onExportToBackend,
|
|
||||||
renderTopRightUI,
|
renderTopRightUI,
|
||||||
renderCustomFooter,
|
renderCustomFooter,
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
@@ -393,38 +394,38 @@ const LayerUI = ({
|
|||||||
elements={elements}
|
elements={elements}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
onExportToBackend={
|
exportOpts={UIOptions.canvasActions.export}
|
||||||
onExportToBackend
|
canvas={canvas}
|
||||||
? (elements) => {
|
|
||||||
onExportToBackend &&
|
|
||||||
onExportToBackend(elements, appState, canvas);
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderImageExportDialog = () => {
|
const renderImageExportDialog = () => {
|
||||||
if (!UIOptions.canvasActions.export) {
|
if (!UIOptions.canvasActions.saveAsImage) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createExporter = (type: ExportType): ExportCB => async (
|
const createExporter = (type: ExportType): ExportCB => async (
|
||||||
exportedElements,
|
exportedElements,
|
||||||
scale,
|
|
||||||
) => {
|
) => {
|
||||||
await exportCanvas(type, exportedElements, appState, {
|
const fileHandle = await exportCanvas(type, exportedElements, appState, {
|
||||||
exportBackground: appState.exportBackground,
|
exportBackground: appState.exportBackground,
|
||||||
name: appState.name,
|
name: appState.name,
|
||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
scale,
|
|
||||||
})
|
})
|
||||||
.catch(muteFSAbortError)
|
.catch(muteFSAbortError)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setAppState({ errorMessage: error.message });
|
setAppState({ errorMessage: error.message });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
appState.exportEmbedScene &&
|
||||||
|
fileHandle &&
|
||||||
|
isImageFileHandle(fileHandle)
|
||||||
|
) {
|
||||||
|
setAppState({ fileHandle });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -496,6 +497,9 @@ const LayerUI = ({
|
|||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
showThemeBtn={showThemeBtn}
|
showThemeBtn={showThemeBtn}
|
||||||
/>
|
/>
|
||||||
|
{appState.fileHandle && (
|
||||||
|
<>{actionManager.renderAction("saveToActiveFile")}</>
|
||||||
|
)}
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
</Island>
|
</Island>
|
||||||
</Section>
|
</Section>
|
||||||
@@ -514,7 +518,8 @@ const LayerUI = ({
|
|||||||
style={{
|
style={{
|
||||||
// we want to make sure this doesn't overflow so substracting 200
|
// we want to make sure this doesn't overflow so substracting 200
|
||||||
// which is approximately height of zoom footer and top left menu items with some buffer
|
// which is approximately height of zoom footer and top left menu items with some buffer
|
||||||
maxHeight: `${appState.height - 200}px`,
|
// if active file name is displayed, subtracting 248 to account for its height
|
||||||
|
maxHeight: `${appState.height - (appState.fileHandle ? 248 : 200)}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectedShapeActions
|
<SelectedShapeActions
|
||||||
@@ -551,6 +556,7 @@ const LayerUI = ({
|
|||||||
libraryReturnUrl={libraryReturnUrl}
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
focusContainer={focusContainer}
|
focusContainer={focusContainer}
|
||||||
library={library}
|
library={library}
|
||||||
|
theme={appState.theme}
|
||||||
id={id}
|
id={id}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
@@ -578,6 +584,12 @@ const LayerUI = ({
|
|||||||
{(heading) => (
|
{(heading) => (
|
||||||
<Stack.Col gap={4} align="start">
|
<Stack.Col gap={4} align="start">
|
||||||
<Stack.Row gap={1}>
|
<Stack.Row gap={1}>
|
||||||
|
<LockButton
|
||||||
|
zenModeEnabled={zenModeEnabled}
|
||||||
|
checked={appState.elementLocked}
|
||||||
|
onChange={onLockToggle}
|
||||||
|
title={t("toolBar.lock")}
|
||||||
|
/>
|
||||||
<Island
|
<Island
|
||||||
padding={1}
|
padding={1}
|
||||||
className={clsx({ "zen-mode": zenModeEnabled })}
|
className={clsx({ "zen-mode": zenModeEnabled })}
|
||||||
@@ -589,15 +601,12 @@ const LayerUI = ({
|
|||||||
canvas={canvas}
|
canvas={canvas}
|
||||||
elementType={appState.elementType}
|
elementType={appState.elementType}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
isLibraryOpen={appState.isLibraryOpen}
|
|
||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Island>
|
</Island>
|
||||||
<LockIcon
|
<LibraryButton
|
||||||
zenModeEnabled={zenModeEnabled}
|
appState={appState}
|
||||||
checked={appState.elementLocked}
|
setAppState={setAppState}
|
||||||
onChange={onLockToggle}
|
|
||||||
title={t("toolBar.lock")}
|
|
||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
{libraryMenu}
|
{libraryMenu}
|
||||||
@@ -623,7 +632,9 @@ const LayerUI = ({
|
|||||||
label={client.username || "Unknown user"}
|
label={client.username || "Unknown user"}
|
||||||
key={clientId}
|
key={clientId}
|
||||||
>
|
>
|
||||||
{actionManager.renderAction("goToCollaborator", clientId)}
|
{actionManager.renderAction("goToCollaborator", {
|
||||||
|
id: clientId,
|
||||||
|
})}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</UserList>
|
</UserList>
|
||||||
@@ -656,6 +667,16 @@ const LayerUI = ({
|
|||||||
zoom={appState.zoom}
|
zoom={appState.zoom}
|
||||||
/>
|
/>
|
||||||
</Island>
|
</Island>
|
||||||
|
{!viewModeEnabled && (
|
||||||
|
<div
|
||||||
|
className={clsx("undo-redo-buttons zen-mode-transition", {
|
||||||
|
"layer-ui__wrapper__footer-left--transition-bottom": zenModeEnabled,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{actionManager.renderAction("undo", { size: "small" })}
|
||||||
|
{actionManager.renderAction("redo", { size: "small" })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
46
src/components/LibraryButton.tsx
Normal file
46
src/components/LibraryButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
import { capitalizeString } from "../utils";
|
||||||
|
|
||||||
|
const LIBRARY_ICON = (
|
||||||
|
<svg viewBox="0 0 576 512">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M542.22 32.05c-54.8 3.11-163.72 14.43-230.96 55.59-4.64 2.84-7.27 7.89-7.27 13.17v363.87c0 11.55 12.63 18.85 23.28 13.49 69.18-34.82 169.23-44.32 218.7-46.92 16.89-.89 30.02-14.43 30.02-30.66V62.75c.01-17.71-15.35-31.74-33.77-30.7zM264.73 87.64C197.5 46.48 88.58 35.17 33.78 32.05 15.36 31.01 0 45.04 0 62.75V400.6c0 16.24 13.13 29.78 30.02 30.66 49.49 2.6 149.59 12.11 218.77 46.95 10.62 5.35 23.21-1.94 23.21-13.46V100.63c0-5.29-2.62-10.14-7.27-12.99z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LibraryButton: React.FC<{
|
||||||
|
appState: AppState;
|
||||||
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
}> = ({ appState, setAppState }) => {
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={clsx(
|
||||||
|
"ToolIcon ToolIcon_type_floating ToolIcon__library zen-mode-visibility",
|
||||||
|
`ToolIcon_size_medium`,
|
||||||
|
{
|
||||||
|
"zen-mode-visibility--hidden": appState.zenModeEnabled,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
title={`${capitalizeString(t("toolBar.library"))} — 9`}
|
||||||
|
style={{ marginInlineStart: "var(--space-factor)" }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="ToolIcon_type_checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
name="editor-library"
|
||||||
|
onChange={(event) => {
|
||||||
|
setAppState({ isLibraryOpen: event.target.checked });
|
||||||
|
}}
|
||||||
|
checked={appState.isLibraryOpen}
|
||||||
|
aria-label={capitalizeString(t("toolBar.library"))}
|
||||||
|
aria-keyshortcuts="9"
|
||||||
|
/>
|
||||||
|
<div className="ToolIcon__icon">{LIBRARY_ICON}</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -36,21 +36,27 @@ export const LibraryUnit = ({
|
|||||||
if (!elementsToRender) {
|
if (!elementsToRender) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const svg = exportToSvg(elementsToRender, {
|
let svg: SVGSVGElement;
|
||||||
exportBackground: false,
|
|
||||||
viewBackgroundColor: oc.white,
|
|
||||||
});
|
|
||||||
for (const child of ref.current!.children) {
|
|
||||||
if (child.tagName !== "svg") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ref.current!.removeChild(child);
|
|
||||||
}
|
|
||||||
ref.current!.appendChild(svg);
|
|
||||||
|
|
||||||
const current = ref.current!;
|
const current = ref.current!;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
svg = await exportToSvg(elementsToRender, {
|
||||||
|
exportBackground: false,
|
||||||
|
viewBackgroundColor: oc.white,
|
||||||
|
});
|
||||||
|
for (const child of ref.current!.children) {
|
||||||
|
if (child.tagName !== "svg") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current!.removeChild(child);
|
||||||
|
}
|
||||||
|
current!.appendChild(svg);
|
||||||
|
})();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
current.removeChild(svg);
|
if (svg) {
|
||||||
|
current.removeChild(svg);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [elements, pendingElements]);
|
}, [elements, pendingElements]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,17 @@ import "./ToolIcon.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { ToolButtonSize } from "./ToolButton";
|
||||||
type LockIconSize = "s" | "m";
|
|
||||||
|
|
||||||
type LockIconProps = {
|
type LockIconProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
id?: string;
|
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
onChange?(): void;
|
onChange?(): void;
|
||||||
size?: LockIconSize;
|
|
||||||
zenModeEnabled?: boolean;
|
zenModeEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_SIZE: LockIconSize = "m";
|
const DEFAULT_SIZE: ToolButtonSize = "medium";
|
||||||
|
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
CHECKED: (
|
CHECKED: (
|
||||||
@@ -41,12 +38,12 @@ const ICONS = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LockIcon = (props: LockIconProps) => {
|
export const LockButton = (props: LockIconProps) => {
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"ToolIcon ToolIcon__lock ToolIcon_type_floating zen-mode-visibility",
|
"ToolIcon ToolIcon__lock ToolIcon_type_floating zen-mode-visibility",
|
||||||
`ToolIcon_size_${props.size || DEFAULT_SIZE}`,
|
`ToolIcon_size_${DEFAULT_SIZE}`,
|
||||||
{
|
{
|
||||||
"zen-mode-visibility--hidden": props.zenModeEnabled,
|
"zen-mode-visibility--hidden": props.zenModeEnabled,
|
||||||
},
|
},
|
||||||
@@ -57,7 +54,6 @@ export const LockIcon = (props: LockIconProps) => {
|
|||||||
className="ToolIcon_type_checkbox"
|
className="ToolIcon_type_checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name={props.name}
|
name={props.name}
|
||||||
id={props.id}
|
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
checked={props.checked}
|
checked={props.checked}
|
||||||
aria-label={props.title}
|
aria-label={props.title}
|
||||||
@@ -13,9 +13,10 @@ import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
|||||||
import { Section } from "./Section";
|
import { Section } from "./Section";
|
||||||
import CollabButton from "./CollabButton";
|
import CollabButton from "./CollabButton";
|
||||||
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
||||||
import { LockIcon } from "./LockIcon";
|
import { LockButton } from "./LockButton";
|
||||||
import { UserList } from "./UserList";
|
import { UserList } from "./UserList";
|
||||||
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
||||||
|
import { LibraryButton } from "./LibraryButton";
|
||||||
|
|
||||||
type MobileMenuProps = {
|
type MobileMenuProps = {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@@ -64,15 +65,15 @@ export const MobileMenu = ({
|
|||||||
canvas={canvas}
|
canvas={canvas}
|
||||||
elementType={appState.elementType}
|
elementType={appState.elementType}
|
||||||
setAppState={setAppState}
|
setAppState={setAppState}
|
||||||
isLibraryOpen={appState.isLibraryOpen}
|
|
||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Island>
|
</Island>
|
||||||
<LockIcon
|
<LockButton
|
||||||
checked={appState.elementLocked}
|
checked={appState.elementLocked}
|
||||||
onChange={onLockToggle}
|
onChange={onLockToggle}
|
||||||
title={t("toolBar.lock")}
|
title={t("toolBar.lock")}
|
||||||
/>
|
/>
|
||||||
|
<LibraryButton appState={appState} setAppState={setAppState} />
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
{libraryMenu}
|
{libraryMenu}
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
@@ -167,10 +168,9 @@ export const MobileMenu = ({
|
|||||||
)
|
)
|
||||||
.map(([clientId, client]) => (
|
.map(([clientId, client]) => (
|
||||||
<React.Fragment key={clientId}>
|
<React.Fragment key={clientId}>
|
||||||
{actionManager.renderAction(
|
{actionManager.renderAction("goToCollaborator", {
|
||||||
"goToCollaborator",
|
id: clientId,
|
||||||
clientId,
|
})}
|
||||||
)}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</UserList>
|
</UserList>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const useBodyRoot = (theme: AppState["theme"]) => {
|
|||||||
const isMobileRef = useRef(isMobile);
|
const isMobileRef = useRef(isMobile);
|
||||||
isMobileRef.current = isMobile;
|
isMobileRef.current = isMobile;
|
||||||
|
|
||||||
const excalidrawContainer = useExcalidrawContainer();
|
const { container: excalidrawContainer } = useExcalidrawContainer();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (div) {
|
if (div) {
|
||||||
|
|||||||
@@ -34,19 +34,21 @@ const ChartPreviewBtn = (props: {
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
setChartElements(elements);
|
setChartElements(elements);
|
||||||
|
let svg: SVGSVGElement;
|
||||||
const svg = exportToSvg(elements, {
|
|
||||||
exportBackground: false,
|
|
||||||
viewBackgroundColor: oc.white,
|
|
||||||
});
|
|
||||||
|
|
||||||
const previewNode = previewRef.current!;
|
const previewNode = previewRef.current!;
|
||||||
|
|
||||||
previewNode.appendChild(svg);
|
(async () => {
|
||||||
|
svg = await exportToSvg(elements, {
|
||||||
|
exportBackground: false,
|
||||||
|
viewBackgroundColor: oc.white,
|
||||||
|
});
|
||||||
|
|
||||||
if (props.selected) {
|
previewNode.appendChild(svg);
|
||||||
(previewNode.parentNode as HTMLDivElement).focus();
|
|
||||||
}
|
if (props.selected) {
|
||||||
|
(previewNode.parentNode as HTMLDivElement).focus();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
previewNode.removeChild(svg);
|
previewNode.removeChild(svg);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import "./TextInput.scss";
|
import "./TextInput.scss";
|
||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { useState } from "react";
|
||||||
import { focusNearestParent } from "../utils";
|
import { focusNearestParent } from "../utils";
|
||||||
|
|
||||||
import "./ProjectName.scss";
|
import "./ProjectName.scss";
|
||||||
|
import { useExcalidrawContainer } from "./App";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -12,22 +13,19 @@ type Props = {
|
|||||||
isNameEditable: boolean;
|
isNameEditable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
export const ProjectName = (props: Props) => {
|
||||||
fileName: string;
|
const { id } = useExcalidrawContainer();
|
||||||
};
|
const [fileName, setFileName] = useState<string>(props.value);
|
||||||
export class ProjectName extends Component<Props, State> {
|
|
||||||
state = {
|
const handleBlur = (event: any) => {
|
||||||
fileName: this.props.value,
|
|
||||||
};
|
|
||||||
private handleBlur = (event: any) => {
|
|
||||||
focusNearestParent(event.target);
|
focusNearestParent(event.target);
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
if (value !== this.props.value) {
|
if (value !== props.value) {
|
||||||
this.props.onChange(value);
|
props.onChange(value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (event.nativeEvent.isComposing || event.keyCode === 229) {
|
if (event.nativeEvent.isComposing || event.keyCode === 229) {
|
||||||
@@ -37,29 +35,25 @@ export class ProjectName extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
return (
|
||||||
return (
|
<div className="ProjectName">
|
||||||
<div className="ProjectName">
|
<label className="ProjectName-label" htmlFor="filename">
|
||||||
<label className="ProjectName-label" htmlFor="filename">
|
{`${props.label}${props.isNameEditable ? "" : ":"}`}
|
||||||
{`${this.props.label}${this.props.isNameEditable ? "" : ":"}`}
|
</label>
|
||||||
</label>
|
{props.isNameEditable ? (
|
||||||
{this.props.isNameEditable ? (
|
<input
|
||||||
<input
|
className="TextInput"
|
||||||
className="TextInput"
|
onBlur={handleBlur}
|
||||||
onBlur={this.handleBlur}
|
onKeyDown={handleKeyDown}
|
||||||
onKeyDown={this.handleKeyDown}
|
id={`${id}-filename`}
|
||||||
id="filename"
|
value={fileName}
|
||||||
value={this.state.fileName}
|
onChange={(event) => setFileName(event.target.value)}
|
||||||
onChange={(event) =>
|
/>
|
||||||
this.setState({ fileName: event.target.value })
|
) : (
|
||||||
}
|
<span className="TextInput TextInput--readonly" id={`${id}-filename`}>
|
||||||
/>
|
{props.value}
|
||||||
) : (
|
</span>
|
||||||
<span className="TextInput TextInput--readonly" id="filename">
|
)}
|
||||||
{this.props.value}
|
</div>
|
||||||
</span>
|
);
|
||||||
)}
|
};
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { useExcalidrawContainer } from "./App";
|
||||||
|
|
||||||
interface SectionProps extends React.HTMLProps<HTMLElement> {
|
interface SectionProps extends React.HTMLProps<HTMLElement> {
|
||||||
heading: string;
|
heading: string;
|
||||||
@@ -7,13 +8,14 @@ interface SectionProps extends React.HTMLProps<HTMLElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Section = ({ heading, children, ...props }: SectionProps) => {
|
export const Section = ({ heading, children, ...props }: SectionProps) => {
|
||||||
|
const { id } = useExcalidrawContainer();
|
||||||
const header = (
|
const header = (
|
||||||
<h2 className="visually-hidden" id={`${heading}-title`}>
|
<h2 className="visually-hidden" id={`${id}-${heading}-title`}>
|
||||||
{t(`headings.${heading}`)}
|
{t(`headings.${heading}`)}
|
||||||
</h2>
|
</h2>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<section {...props} aria-labelledby={`${heading}-title`}>
|
<section {...props} aria-labelledby={`${id}-${heading}-title`}>
|
||||||
{typeof children === "function" ? (
|
{typeof children === "function" ? (
|
||||||
children(header)
|
children(header)
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import "./ToolIcon.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { useExcalidrawContainer } from "./App";
|
||||||
|
|
||||||
type ToolIconSize = "s" | "m";
|
export type ToolButtonSize = "small" | "medium";
|
||||||
|
|
||||||
type ToolButtonBaseProps = {
|
type ToolButtonBaseProps = {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
@@ -14,7 +15,7 @@ type ToolButtonBaseProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
size?: ToolIconSize;
|
size?: ToolButtonSize;
|
||||||
keyBindingLabel?: string;
|
keyBindingLabel?: string;
|
||||||
showAriaLabel?: boolean;
|
showAriaLabel?: boolean;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
@@ -40,12 +41,11 @@ type ToolButtonProps =
|
|||||||
onChange?(): void;
|
onChange?(): void;
|
||||||
});
|
});
|
||||||
|
|
||||||
const DEFAULT_SIZE: ToolIconSize = "m";
|
|
||||||
|
|
||||||
export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
||||||
|
const { id: excalId } = useExcalidrawContainer();
|
||||||
const innerRef = React.useRef(null);
|
const innerRef = React.useRef(null);
|
||||||
React.useImperativeHandle(ref, () => innerRef.current);
|
React.useImperativeHandle(ref, () => innerRef.current);
|
||||||
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
|
const sizeCn = `ToolIcon_size_${props.size}`;
|
||||||
|
|
||||||
if (props.type === "button" || props.type === "icon") {
|
if (props.type === "button" || props.type === "icon") {
|
||||||
return (
|
return (
|
||||||
@@ -98,7 +98,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
|||||||
aria-label={props["aria-label"]}
|
aria-label={props["aria-label"]}
|
||||||
aria-keyshortcuts={props["aria-keyshortcuts"]}
|
aria-keyshortcuts={props["aria-keyshortcuts"]}
|
||||||
data-testid={props["data-testid"]}
|
data-testid={props["data-testid"]}
|
||||||
id={props.id}
|
id={`${excalId}-${props.id}`}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
checked={props.checked}
|
checked={props.checked}
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
@@ -116,4 +116,5 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
|
|||||||
ToolButton.defaultProps = {
|
ToolButton.defaultProps = {
|
||||||
visible: true,
|
visible: true,
|
||||||
className: "",
|
className: "",
|
||||||
|
size: "medium",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,10 +8,18 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
font-family: Cascadia;
|
font-family: Cascadia;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--button-gray-1);
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
border-radius: var(--space-factor);
|
border-radius: var(--space-factor);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
background-color: var(--button-gray-1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--button-gray-2);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--button-gray-3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ToolIcon--plain {
|
.ToolIcon--plain {
|
||||||
@@ -52,9 +60,9 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ToolIcon_size_s .ToolIcon__icon {
|
.ToolIcon_size_small .ToolIcon__icon {
|
||||||
width: 1.4rem;
|
width: 2rem;
|
||||||
height: 1.4rem;
|
height: 2rem;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,14 +74,6 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--button-gray-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: var(--button-gray-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0 0 0 2px var(--focus-highlight-color);
|
box-shadow: 0 0 0 2px var(--focus-highlight-color);
|
||||||
}
|
}
|
||||||
@@ -86,6 +86,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--button-gray-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--button-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
&--show {
|
&--show {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
@@ -103,6 +111,9 @@
|
|||||||
|
|
||||||
&:not(.ToolIcon_toggle_opaque):checked + .ToolIcon__icon {
|
&:not(.ToolIcon_toggle_opaque):checked + .ToolIcon__icon {
|
||||||
background-color: var(--button-gray-2);
|
background-color: var(--button-gray-2);
|
||||||
|
&:active {
|
||||||
|
background-color: var(--button-gray-3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus + .ToolIcon__icon {
|
&:focus + .ToolIcon__icon {
|
||||||
@@ -130,12 +141,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ToolIcon__icon {
|
.ToolIcon__icon {
|
||||||
|
background-color: var(--button-gray-1);
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--button-gray-2);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--button-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ToolIcon.ToolIcon__lock {
|
.ToolIcon.ToolIcon__lock {
|
||||||
|
margin-inline-end: var(--space-factor);
|
||||||
&.ToolIcon_type_floating {
|
&.ToolIcon_type_floating {
|
||||||
margin-left: 0.1rem;
|
margin-left: 0.1rem;
|
||||||
}
|
}
|
||||||
@@ -166,10 +186,9 @@
|
|||||||
// move the lock button out of the way on small viewports
|
// move the lock button out of the way on small viewports
|
||||||
// it begins to collide with the GitHub icon before we switch to mobile mode
|
// it begins to collide with the GitHub icon before we switch to mobile mode
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.ToolIcon.ToolIcon__lock {
|
.ToolIcon.ToolIcon_type_floating {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 60px;
|
|
||||||
right: -8px;
|
right: -8px;
|
||||||
|
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@@ -194,6 +213,14 @@
|
|||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ToolIcon.ToolIcon__library {
|
||||||
|
top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolIcon.ToolIcon__lock {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
top: 60px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.unlocked-icon {
|
.unlocked-icon {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
@import "../css/variables.module";
|
@import "../css/variables.module";
|
||||||
|
|
||||||
|
// container in body where the actual tooltip is appended to
|
||||||
.excalidraw-tooltip {
|
.excalidraw-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@@ -24,16 +26,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw {
|
// wraps the element we want to apply the tooltip to
|
||||||
.Tooltip-icon {
|
.excalidraw-tooltip-wrapper {
|
||||||
width: 0.9em;
|
display: flex;
|
||||||
height: 0.9em;
|
height: 100%;
|
||||||
margin-left: 5px;
|
}
|
||||||
margin-top: 1px;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
@include isMobile {
|
.excalidraw-tooltip-icon {
|
||||||
display: none;
|
width: 0.9em;
|
||||||
}
|
height: 0.9em;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 1px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
@include isMobile {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export const Tooltip = ({ children, label, long = false }: TooltipProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
className="excalidraw-tooltip-wrapper"
|
||||||
onPointerEnter={(event) =>
|
onPointerEnter={(event) =>
|
||||||
updateTooltip(
|
updateTooltip(
|
||||||
event.currentTarget as HTMLDivElement,
|
event.currentTarget as HTMLDivElement,
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ type Opts = {
|
|||||||
mirror?: true;
|
mirror?: true;
|
||||||
} & React.SVGProps<SVGSVGElement>;
|
} & React.SVGProps<SVGSVGElement>;
|
||||||
|
|
||||||
const createIcon = (d: string | React.ReactNode, opts: number | Opts = 512) => {
|
export const createIcon = (
|
||||||
|
d: string | React.ReactNode,
|
||||||
|
opts: number | Opts = 512,
|
||||||
|
) => {
|
||||||
const { width = 512, height = width, mirror, style } =
|
const { width = 512, height = width, mirror, style } =
|
||||||
typeof opts === "number" ? ({ width: opts } as Opts) : opts;
|
typeof opts === "number" ? ({ width: opts } as Opts) : opts;
|
||||||
return (
|
return (
|
||||||
@@ -474,6 +477,11 @@ export const shield = createIcon(
|
|||||||
{ width: 24 },
|
{ width: 24 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const file = createIcon(
|
||||||
|
"M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48zm32-48h224V288l-23.5-23.5c-4.7-4.7-12.3-4.7-17 0L176 352l-39.5-39.5c-4.7-4.7-12.3-4.7-17 0L80 352v64zm48-240c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48z",
|
||||||
|
{ width: 384, height: 512 },
|
||||||
|
);
|
||||||
|
|
||||||
export const GroupIcon = React.memo(({ theme }: { theme: "light" | "dark" }) =>
|
export const GroupIcon = React.memo(({ theme }: { theme: "light" | "dark" }) =>
|
||||||
createIcon(
|
createIcon(
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FontFamily } from "./element/types";
|
|
||||||
import cssVariables from "./css/variables.module.scss";
|
import cssVariables from "./css/variables.module.scss";
|
||||||
import { AppProps } from "./types";
|
import { AppProps } from "./types";
|
||||||
|
import { FontFamilyValues } from "./element/types";
|
||||||
|
|
||||||
export const APP_NAME = "Excalidraw";
|
export const APP_NAME = "Excalidraw";
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ export const CURSOR_TYPE = {
|
|||||||
TEXT: "text",
|
TEXT: "text",
|
||||||
CROSSHAIR: "crosshair",
|
CROSSHAIR: "crosshair",
|
||||||
GRABBING: "grabbing",
|
GRABBING: "grabbing",
|
||||||
|
GRAB: "grab",
|
||||||
POINTER: "pointer",
|
POINTER: "pointer",
|
||||||
MOVE: "move",
|
MOVE: "move",
|
||||||
AUTO: "",
|
AUTO: "",
|
||||||
@@ -34,6 +35,7 @@ export enum EVENT {
|
|||||||
MOUSE_MOVE = "mousemove",
|
MOUSE_MOVE = "mousemove",
|
||||||
RESIZE = "resize",
|
RESIZE = "resize",
|
||||||
UNLOAD = "unload",
|
UNLOAD = "unload",
|
||||||
|
FOCUS = "focus",
|
||||||
BLUR = "blur",
|
BLUR = "blur",
|
||||||
DRAG_OVER = "dragover",
|
DRAG_OVER = "dragover",
|
||||||
DROP = "drop",
|
DROP = "drop",
|
||||||
@@ -63,15 +65,15 @@ export const CLASSES = {
|
|||||||
|
|
||||||
// 1-based in case we ever do `if(element.fontFamily)`
|
// 1-based in case we ever do `if(element.fontFamily)`
|
||||||
export const FONT_FAMILY = {
|
export const FONT_FAMILY = {
|
||||||
1: "Virgil",
|
Virgil: 1,
|
||||||
2: "Helvetica",
|
Helvetica: 2,
|
||||||
3: "Cascadia",
|
Cascadia: 3,
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
|
export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
|
||||||
|
|
||||||
export const DEFAULT_FONT_SIZE = 20;
|
export const DEFAULT_FONT_SIZE = 20;
|
||||||
export const DEFAULT_FONT_FAMILY: FontFamily = 1;
|
export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY.Virgil;
|
||||||
export const DEFAULT_TEXT_ALIGN = "left";
|
export const DEFAULT_TEXT_ALIGN = "left";
|
||||||
export const DEFAULT_VERTICAL_ALIGN = "top";
|
export const DEFAULT_VERTICAL_ALIGN = "top";
|
||||||
export const DEFAULT_VERSION = "{version}";
|
export const DEFAULT_VERSION = "{version}";
|
||||||
@@ -83,7 +85,7 @@ export const GRID_SIZE = 20; // TODO make it configurable?
|
|||||||
export const MIME_TYPES = {
|
export const MIME_TYPES = {
|
||||||
excalidraw: "application/vnd.excalidraw+json",
|
excalidraw: "application/vnd.excalidraw+json",
|
||||||
excalidrawlib: "application/vnd.excalidrawlib+json",
|
excalidrawlib: "application/vnd.excalidrawlib+json",
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export const EXPORT_DATA_TYPES = {
|
export const EXPORT_DATA_TYPES = {
|
||||||
excalidraw: "excalidraw",
|
excalidraw: "excalidraw",
|
||||||
@@ -131,11 +133,11 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
|
|||||||
canvasActions: {
|
canvasActions: {
|
||||||
changeViewBackgroundColor: true,
|
changeViewBackgroundColor: true,
|
||||||
clearCanvas: true,
|
clearCanvas: true,
|
||||||
export: true,
|
export: { saveFileToDisk: true },
|
||||||
loadScene: true,
|
loadScene: true,
|
||||||
saveAsScene: true,
|
|
||||||
saveToActiveFile: true,
|
saveToActiveFile: true,
|
||||||
theme: true,
|
theme: true,
|
||||||
|
saveAsImage: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -144,3 +146,6 @@ export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
|
|||||||
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
||||||
|
|
||||||
export const MAX_DECIMALS_FOR_SVG_EXPORT = 2;
|
export const MAX_DECIMALS_FOR_SVG_EXPORT = 2;
|
||||||
|
|
||||||
|
export const EXPORT_SCALES = [1, 2, 3];
|
||||||
|
export const DEFAULT_EXPORT_PADDING = 10; // px
|
||||||
|
|||||||
@@ -51,11 +51,12 @@
|
|||||||
image-rendering: -moz-crisp-edges; // FF
|
image-rendering: -moz-crisp-edges; // FF
|
||||||
|
|
||||||
z-index: var(--zIndex-canvas);
|
z-index: var(--zIndex-canvas);
|
||||||
}
|
|
||||||
|
|
||||||
#canvas {
|
|
||||||
// Remove the main canvas from document flow to avoid resizeObserver
|
// Remove the main canvas from document flow to avoid resizeObserver
|
||||||
// feedback loop (see https://github.com/excalidraw/excalidraw/pull/3379)
|
// feedback loop (see https://github.com/excalidraw/excalidraw/pull/3379)
|
||||||
|
}
|
||||||
|
|
||||||
|
&__canvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,22 +414,6 @@
|
|||||||
&:active {
|
&:active {
|
||||||
background-color: var(--button-gray-2);
|
background-color: var(--button-gray-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dropdown-select--floating {
|
|
||||||
margin: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-select__language.dropdown-select--floating {
|
|
||||||
bottom: 10px;
|
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
|
||||||
right: 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
|
||||||
left: 44px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.zIndexButton {
|
.zIndexButton {
|
||||||
@@ -455,26 +440,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.help-icon {
|
.help-icon {
|
||||||
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
fill: $oc-gray-6;
|
fill: $oc-gray-6;
|
||||||
width: 1.5rem;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 5px;
|
|
||||||
background: none;
|
background: none;
|
||||||
color: var(--icon-fill-color);
|
color: var(--icon-fill-color);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
.reset-zoom-button {
|
||||||
margin-right: 14px;
|
padding: 0.2em;
|
||||||
}
|
background: transparent;
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
font-family: var(--ui-font);
|
||||||
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
.undo-redo-buttons {
|
||||||
margin-left: 14px;
|
display: grid;
|
||||||
}
|
grid-auto-flow: column;
|
||||||
|
gap: 0.4em;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-inline-start: 0.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include isMobile {
|
@include isMobile {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { cleanAppStateForExport } from "../appState";
|
import { cleanAppStateForExport } from "../appState";
|
||||||
import { EXPORT_DATA_TYPES } from "../constants";
|
import { EXPORT_DATA_TYPES } from "../constants";
|
||||||
import { clearElementsForExport } from "../element";
|
import { clearElementsForExport } from "../element";
|
||||||
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { CanvasError } from "../errors";
|
import { CanvasError } from "../errors";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { calculateScrollCenter } from "../scene";
|
import { calculateScrollCenter } from "../scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
import { FileSystemHandle } from "./filesystem";
|
||||||
import { isValidExcalidrawData } from "./json";
|
import { isValidExcalidrawData } from "./json";
|
||||||
import { restore } from "./restore";
|
import { restore } from "./restore";
|
||||||
import { ImportedLibraryData } from "./types";
|
import { ImportedLibraryData } from "./types";
|
||||||
@@ -79,10 +81,30 @@ export const getMimeType = (blob: Blob | string): string => {
|
|||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getFileHandleType = (handle: FileSystemHandle | null) => {
|
||||||
|
if (!handle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle.name.match(/\.(json|excalidraw|png|svg)$/)?.[1] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isImageFileHandleType = (
|
||||||
|
type: string | null,
|
||||||
|
): type is "png" | "svg" => {
|
||||||
|
return type === "png" || type === "svg";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isImageFileHandle = (handle: FileSystemHandle | null) => {
|
||||||
|
const type = getFileHandleType(handle);
|
||||||
|
return type === "png" || type === "svg";
|
||||||
|
};
|
||||||
|
|
||||||
export const loadFromBlob = async (
|
export const loadFromBlob = async (
|
||||||
blob: Blob,
|
blob: Blob,
|
||||||
/** @see restore.localAppState */
|
/** @see restore.localAppState */
|
||||||
localAppState: AppState | null,
|
localAppState: AppState | null,
|
||||||
|
localElements: readonly ExcalidrawElement[] | null,
|
||||||
) => {
|
) => {
|
||||||
const contents = await parseFileContents(blob);
|
const contents = await parseFileContents(blob);
|
||||||
try {
|
try {
|
||||||
@@ -95,7 +117,7 @@ export const loadFromBlob = async (
|
|||||||
elements: clearElementsForExport(data.elements || []),
|
elements: clearElementsForExport(data.elements || []),
|
||||||
appState: {
|
appState: {
|
||||||
theme: localAppState?.theme,
|
theme: localAppState?.theme,
|
||||||
fileHandle: (!blob.type.startsWith("image/") && blob.handle) || null,
|
fileHandle: blob.handle || null,
|
||||||
...cleanAppStateForExport(data.appState || {}),
|
...cleanAppStateForExport(data.appState || {}),
|
||||||
...(localAppState
|
...(localAppState
|
||||||
? calculateScrollCenter(data.elements || [], localAppState, null)
|
? calculateScrollCenter(data.elements || [], localAppState, null)
|
||||||
@@ -103,6 +125,7 @@ export const loadFromBlob = async (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
localAppState,
|
localAppState,
|
||||||
|
localElements,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
122
src/data/filesystem.ts
Normal file
122
src/data/filesystem.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import {
|
||||||
|
FileWithHandle,
|
||||||
|
fileOpen as _fileOpen,
|
||||||
|
fileSave as _fileSave,
|
||||||
|
FileSystemHandle,
|
||||||
|
supported as nativeFileSystemSupported,
|
||||||
|
} from "@dwelle/browser-fs-access";
|
||||||
|
import { EVENT, MIME_TYPES } from "../constants";
|
||||||
|
import { AbortError } from "../errors";
|
||||||
|
import { debounce } from "../utils";
|
||||||
|
|
||||||
|
type FILE_EXTENSION =
|
||||||
|
| "jpg"
|
||||||
|
| "png"
|
||||||
|
| "svg"
|
||||||
|
| "json"
|
||||||
|
| "excalidraw"
|
||||||
|
| "excalidrawlib";
|
||||||
|
|
||||||
|
const FILE_TYPE_TO_MIME_TYPE: Record<FILE_EXTENSION, string> = {
|
||||||
|
jpg: "image/jpeg",
|
||||||
|
png: "image/png",
|
||||||
|
svg: "image/svg+xml",
|
||||||
|
json: "application/json",
|
||||||
|
excalidraw: MIME_TYPES.excalidraw,
|
||||||
|
excalidrawlib: MIME_TYPES.excalidrawlib,
|
||||||
|
};
|
||||||
|
|
||||||
|
const INPUT_CHANGE_INTERVAL_MS = 500;
|
||||||
|
|
||||||
|
export const fileOpen = <M extends boolean | undefined = false>(opts: {
|
||||||
|
extensions?: FILE_EXTENSION[];
|
||||||
|
description?: string;
|
||||||
|
multiple?: M;
|
||||||
|
}): Promise<
|
||||||
|
M extends false | undefined ? FileWithHandle : FileWithHandle[]
|
||||||
|
> => {
|
||||||
|
// an unsafe TS hack, alas not much we can do AFAIK
|
||||||
|
type RetType = M extends false | undefined
|
||||||
|
? FileWithHandle
|
||||||
|
: FileWithHandle[];
|
||||||
|
|
||||||
|
const mimeTypes = opts.extensions?.reduce((mimeTypes, type) => {
|
||||||
|
mimeTypes.push(FILE_TYPE_TO_MIME_TYPE[type]);
|
||||||
|
|
||||||
|
return mimeTypes;
|
||||||
|
}, [] as string[]);
|
||||||
|
|
||||||
|
const extensions = opts.extensions?.reduce((acc, ext) => {
|
||||||
|
if (ext === "jpg") {
|
||||||
|
return acc.concat(".jpg", ".jpeg");
|
||||||
|
}
|
||||||
|
return acc.concat(`.${ext}`);
|
||||||
|
}, [] as string[]);
|
||||||
|
|
||||||
|
return _fileOpen({
|
||||||
|
description: opts.description,
|
||||||
|
extensions,
|
||||||
|
mimeTypes,
|
||||||
|
multiple: opts.multiple ?? false,
|
||||||
|
legacySetup: (resolve, reject, input) => {
|
||||||
|
const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
|
||||||
|
const focusHandler = () => {
|
||||||
|
checkForFile();
|
||||||
|
document.addEventListener(EVENT.KEYUP, scheduleRejection);
|
||||||
|
document.addEventListener(EVENT.POINTER_UP, scheduleRejection);
|
||||||
|
scheduleRejection();
|
||||||
|
};
|
||||||
|
const checkForFile = () => {
|
||||||
|
// this hack might not work when expecting multiple files
|
||||||
|
if (input.files?.length) {
|
||||||
|
const ret = opts.multiple ? [...input.files] : input.files[0];
|
||||||
|
resolve(ret as RetType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
window.addEventListener(EVENT.FOCUS, focusHandler);
|
||||||
|
});
|
||||||
|
const interval = window.setInterval(() => {
|
||||||
|
checkForFile();
|
||||||
|
}, INPUT_CHANGE_INTERVAL_MS);
|
||||||
|
return (rejectPromise) => {
|
||||||
|
clearInterval(interval);
|
||||||
|
scheduleRejection.cancel();
|
||||||
|
window.removeEventListener(EVENT.FOCUS, focusHandler);
|
||||||
|
document.removeEventListener(EVENT.KEYUP, scheduleRejection);
|
||||||
|
document.removeEventListener(EVENT.POINTER_UP, scheduleRejection);
|
||||||
|
if (rejectPromise) {
|
||||||
|
// so that something is shown in console if we need to debug this
|
||||||
|
console.warn("Opening the file was canceled (legacy-fs).");
|
||||||
|
rejectPromise(new AbortError());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}) as Promise<RetType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fileSave = (
|
||||||
|
blob: Blob,
|
||||||
|
opts: {
|
||||||
|
/** supply without the extension */
|
||||||
|
name: string;
|
||||||
|
/** file extension */
|
||||||
|
extension: FILE_EXTENSION;
|
||||||
|
description?: string;
|
||||||
|
/** existing FileSystemHandle */
|
||||||
|
fileHandle?: FileSystemHandle | null;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
return _fileSave(
|
||||||
|
blob,
|
||||||
|
{
|
||||||
|
fileName: `${opts.name}.${opts.extension}`,
|
||||||
|
description: opts.description,
|
||||||
|
extensions: [`.${opts.extension}`],
|
||||||
|
},
|
||||||
|
opts.fileHandle,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { FileSystemHandle };
|
||||||
|
export { nativeFileSystemSupported };
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import { fileSave } from "browser-fs-access";
|
|
||||||
import {
|
import {
|
||||||
copyBlobToClipboardAsPng,
|
copyBlobToClipboardAsPng,
|
||||||
copyTextToSystemClipboard,
|
copyTextToSystemClipboard,
|
||||||
} from "../clipboard";
|
} from "../clipboard";
|
||||||
|
import { DEFAULT_EXPORT_PADDING } from "../constants";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { exportToCanvas, exportToSvg } from "../scene/export";
|
import { exportToCanvas, exportToSvg } from "../scene/export";
|
||||||
import { ExportType } from "../scene/types";
|
import { ExportType } from "../scene/types";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { canvasToBlob } from "./blob";
|
import { canvasToBlob } from "./blob";
|
||||||
|
import { fileSave, FileSystemHandle } from "./filesystem";
|
||||||
import { serializeAsJSON } from "./json";
|
import { serializeAsJSON } from "./json";
|
||||||
|
|
||||||
export { loadFromBlob } from "./blob";
|
export { loadFromBlob } from "./blob";
|
||||||
@@ -20,45 +21,41 @@ export const exportCanvas = async (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
{
|
{
|
||||||
exportBackground,
|
exportBackground,
|
||||||
exportPadding = 10,
|
exportPadding = DEFAULT_EXPORT_PADDING,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
name,
|
name,
|
||||||
scale = 1,
|
fileHandle = null,
|
||||||
}: {
|
}: {
|
||||||
exportBackground: boolean;
|
exportBackground: boolean;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
viewBackgroundColor: string;
|
viewBackgroundColor: string;
|
||||||
name: string;
|
name: string;
|
||||||
scale?: number;
|
fileHandle?: FileSystemHandle | null;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if (elements.length === 0) {
|
if (elements.length === 0) {
|
||||||
throw new Error(t("alerts.cannotExportEmptyCanvas"));
|
throw new Error(t("alerts.cannotExportEmptyCanvas"));
|
||||||
}
|
}
|
||||||
if (type === "svg" || type === "clipboard-svg") {
|
if (type === "svg" || type === "clipboard-svg") {
|
||||||
const tempSvg = exportToSvg(elements, {
|
const tempSvg = await exportToSvg(elements, {
|
||||||
exportBackground,
|
exportBackground,
|
||||||
exportWithDarkMode: appState.exportWithDarkMode,
|
exportWithDarkMode: appState.exportWithDarkMode,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
scale,
|
exportScale: appState.exportScale,
|
||||||
metadata:
|
exportEmbedScene: appState.exportEmbedScene && type === "svg",
|
||||||
appState.exportEmbedScene && type === "svg"
|
|
||||||
? await (
|
|
||||||
await import(/* webpackChunkName: "image" */ "./image")
|
|
||||||
).encodeSvgMetadata({
|
|
||||||
text: serializeAsJSON(elements, appState),
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
if (type === "svg") {
|
if (type === "svg") {
|
||||||
await fileSave(new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }), {
|
return await fileSave(
|
||||||
fileName: `${name}.svg`,
|
new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }),
|
||||||
extensions: [".svg"],
|
{
|
||||||
});
|
name,
|
||||||
return;
|
extension: "svg",
|
||||||
|
fileHandle,
|
||||||
|
},
|
||||||
|
);
|
||||||
} else if (type === "clipboard-svg") {
|
} else if (type === "clipboard-svg") {
|
||||||
copyTextToSystemClipboard(tempSvg.outerHTML);
|
await copyTextToSystemClipboard(tempSvg.outerHTML);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +64,6 @@ export const exportCanvas = async (
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
scale,
|
|
||||||
});
|
});
|
||||||
tempCanvas.style.display = "none";
|
tempCanvas.style.display = "none";
|
||||||
document.body.appendChild(tempCanvas);
|
document.body.appendChild(tempCanvas);
|
||||||
@@ -75,7 +71,6 @@ export const exportCanvas = async (
|
|||||||
tempCanvas.remove();
|
tempCanvas.remove();
|
||||||
|
|
||||||
if (type === "png") {
|
if (type === "png") {
|
||||||
const fileName = `${name}.png`;
|
|
||||||
if (appState.exportEmbedScene) {
|
if (appState.exportEmbedScene) {
|
||||||
blob = await (
|
blob = await (
|
||||||
await import(/* webpackChunkName: "image" */ "./image")
|
await import(/* webpackChunkName: "image" */ "./image")
|
||||||
@@ -85,9 +80,10 @@ export const exportCanvas = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await fileSave(blob, {
|
return await fileSave(blob, {
|
||||||
fileName,
|
name,
|
||||||
extensions: [".png"],
|
extension: "png",
|
||||||
|
fileHandle,
|
||||||
});
|
});
|
||||||
} else if (type === "clipboard") {
|
} else if (type === "clipboard") {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { fileOpen, fileSave } from "browser-fs-access";
|
import { fileOpen, fileSave } from "./filesystem";
|
||||||
import { cleanAppStateForExport } from "../appState";
|
import { cleanAppStateForExport } from "../appState";
|
||||||
import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES } from "../constants";
|
import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES } from "../constants";
|
||||||
import { clearElementsForExport } from "../element";
|
import { clearElementsForExport } from "../element";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { loadFromBlob } from "./blob";
|
import { isImageFileHandle, loadFromBlob } from "./blob";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExportedDataState,
|
ExportedDataState,
|
||||||
@@ -15,7 +15,7 @@ import Library from "./library";
|
|||||||
|
|
||||||
export const serializeAsJSON = (
|
export const serializeAsJSON = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Partial<AppState>,
|
||||||
): string => {
|
): string => {
|
||||||
const data: ExportedDataState = {
|
const data: ExportedDataState = {
|
||||||
type: EXPORT_DATA_TYPES.excalidraw,
|
type: EXPORT_DATA_TYPES.excalidraw,
|
||||||
@@ -37,19 +37,21 @@ export const saveAsJSON = async (
|
|||||||
type: MIME_TYPES.excalidraw,
|
type: MIME_TYPES.excalidraw,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fileHandle = await fileSave(
|
const fileHandle = await fileSave(blob, {
|
||||||
blob,
|
name: appState.name,
|
||||||
{
|
extension: "excalidraw",
|
||||||
fileName: `${appState.name}.excalidraw`,
|
description: "Excalidraw file",
|
||||||
description: "Excalidraw file",
|
fileHandle: isImageFileHandle(appState.fileHandle)
|
||||||
extensions: [".excalidraw"],
|
? null
|
||||||
},
|
: appState.fileHandle,
|
||||||
appState.fileHandle,
|
});
|
||||||
);
|
|
||||||
return { fileHandle };
|
return { fileHandle };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadFromJSON = async (localAppState: AppState) => {
|
export const loadFromJSON = async (
|
||||||
|
localAppState: AppState,
|
||||||
|
localElements: readonly ExcalidrawElement[] | null,
|
||||||
|
) => {
|
||||||
const blob = await fileOpen({
|
const blob = await fileOpen({
|
||||||
description: "Excalidraw files",
|
description: "Excalidraw files",
|
||||||
// ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
|
// ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
|
||||||
@@ -64,7 +66,7 @@ export const loadFromJSON = async (localAppState: AppState) => {
|
|||||||
],
|
],
|
||||||
*/
|
*/
|
||||||
});
|
});
|
||||||
return loadFromBlob(blob, localAppState);
|
return loadFromBlob(blob, localAppState, localElements);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isValidExcalidrawData = (data?: {
|
export const isValidExcalidrawData = (data?: {
|
||||||
@@ -98,15 +100,16 @@ export const saveLibraryAsJSON = async (library: Library) => {
|
|||||||
library: libraryItems,
|
library: libraryItems,
|
||||||
};
|
};
|
||||||
const serialized = JSON.stringify(data, null, 2);
|
const serialized = JSON.stringify(data, null, 2);
|
||||||
const fileName = "library.excalidrawlib";
|
await fileSave(
|
||||||
const blob = new Blob([serialized], {
|
new Blob([serialized], {
|
||||||
type: MIME_TYPES.excalidrawlib,
|
type: MIME_TYPES.excalidrawlib,
|
||||||
});
|
}),
|
||||||
await fileSave(blob, {
|
{
|
||||||
fileName,
|
name: "library",
|
||||||
description: "Excalidraw library file",
|
extension: "excalidrawlib",
|
||||||
extensions: [".excalidrawlib"],
|
description: "Excalidraw library file",
|
||||||
});
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const importLibraryFromJSON = async (library: Library) => {
|
export const importLibraryFromJSON = async (library: Library) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { loadLibraryFromBlob } from "./blob";
|
|||||||
import { LibraryItems, LibraryItem } from "../types";
|
import { LibraryItems, LibraryItem } from "../types";
|
||||||
import { restoreElements } from "./restore";
|
import { restoreElements } from "./restore";
|
||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
import App from "../components/App";
|
import type App from "../components/App";
|
||||||
|
|
||||||
class Library {
|
class Library {
|
||||||
private libraryCache: LibraryItems | null = null;
|
private libraryCache: LibraryItems | null = null;
|
||||||
@@ -18,7 +18,7 @@ class Library {
|
|||||||
};
|
};
|
||||||
|
|
||||||
restoreLibraryItem = (libraryItem: LibraryItem): LibraryItem | null => {
|
restoreLibraryItem = (libraryItem: LibraryItem): LibraryItem | null => {
|
||||||
const elements = getNonDeletedElements(restoreElements(libraryItem));
|
const elements = getNonDeletedElements(restoreElements(libraryItem, null));
|
||||||
return elements.length ? elements : null;
|
return elements.length ? elements : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
38
src/data/resave.ts
Normal file
38
src/data/resave.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
import { exportCanvas } from ".";
|
||||||
|
import { getNonDeletedElements } from "../element";
|
||||||
|
import { getFileHandleType, isImageFileHandleType } from "./blob";
|
||||||
|
|
||||||
|
export const resaveAsImageWithScene = async (
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
appState: AppState,
|
||||||
|
) => {
|
||||||
|
const { exportBackground, viewBackgroundColor, name, fileHandle } = appState;
|
||||||
|
|
||||||
|
const fileHandleType = getFileHandleType(fileHandle);
|
||||||
|
|
||||||
|
if (!fileHandle || !isImageFileHandleType(fileHandleType)) {
|
||||||
|
throw new Error(
|
||||||
|
"fileHandle should exist and should be of type svg or png when resaving",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
appState = {
|
||||||
|
...appState,
|
||||||
|
exportEmbedScene: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
await exportCanvas(
|
||||||
|
fileHandleType,
|
||||||
|
getNonDeletedElements(elements),
|
||||||
|
appState,
|
||||||
|
{
|
||||||
|
exportBackground,
|
||||||
|
viewBackgroundColor,
|
||||||
|
name,
|
||||||
|
fileHandle,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return { fileHandle };
|
||||||
|
};
|
||||||
@@ -1,21 +1,26 @@
|
|||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
FontFamily,
|
|
||||||
ExcalidrawSelectionElement,
|
ExcalidrawSelectionElement,
|
||||||
|
FontFamilyValues,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { AppState, NormalizedZoomValue } from "../types";
|
import { AppState, NormalizedZoomValue } from "../types";
|
||||||
import { ImportedDataState } from "./types";
|
import { ImportedDataState } from "./types";
|
||||||
import { isInvisiblySmallElement, getNormalizedDimensions } from "../element";
|
import {
|
||||||
|
getElementMap,
|
||||||
|
getNormalizedDimensions,
|
||||||
|
isInvisiblySmallElement,
|
||||||
|
} from "../element";
|
||||||
import { isLinearElementType } from "../element/typeChecks";
|
import { isLinearElementType } from "../element/typeChecks";
|
||||||
import { randomId } from "../random";
|
import { randomId } from "../random";
|
||||||
import {
|
import {
|
||||||
FONT_FAMILY,
|
|
||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
DEFAULT_TEXT_ALIGN,
|
DEFAULT_TEXT_ALIGN,
|
||||||
DEFAULT_VERTICAL_ALIGN,
|
DEFAULT_VERTICAL_ALIGN,
|
||||||
|
FONT_FAMILY,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
|
import { bumpVersion } from "../element/mutateElement";
|
||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
AppState,
|
AppState,
|
||||||
@@ -41,11 +46,11 @@ export type RestoredDataState = {
|
|||||||
appState: RestoredAppState;
|
appState: RestoredAppState;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFontFamilyByName = (fontFamilyName: string): FontFamily => {
|
const getFontFamilyByName = (fontFamilyName: string): FontFamilyValues => {
|
||||||
for (const [id, fontFamilyString] of Object.entries(FONT_FAMILY)) {
|
if (Object.keys(FONT_FAMILY).includes(fontFamilyName)) {
|
||||||
if (fontFamilyString.includes(fontFamilyName)) {
|
return FONT_FAMILY[
|
||||||
return parseInt(id) as FontFamily;
|
fontFamilyName as keyof typeof FONT_FAMILY
|
||||||
}
|
] as FontFamilyValues;
|
||||||
}
|
}
|
||||||
return DEFAULT_FONT_FAMILY;
|
return DEFAULT_FONT_FAMILY;
|
||||||
};
|
};
|
||||||
@@ -181,13 +186,20 @@ const restoreElement = (
|
|||||||
|
|
||||||
export const restoreElements = (
|
export const restoreElements = (
|
||||||
elements: ImportedDataState["elements"],
|
elements: ImportedDataState["elements"],
|
||||||
|
/** NOTE doesn't serve for reconciliation */
|
||||||
|
localElements: readonly ExcalidrawElement[] | null | undefined,
|
||||||
): ExcalidrawElement[] => {
|
): ExcalidrawElement[] => {
|
||||||
|
const localElementsMap = localElements ? getElementMap(localElements) : null;
|
||||||
return (elements || []).reduce((elements, element) => {
|
return (elements || []).reduce((elements, element) => {
|
||||||
// filtering out selection, which is legacy, no longer kept in elements,
|
// filtering out selection, which is legacy, no longer kept in elements,
|
||||||
// and causing issues if retained
|
// and causing issues if retained
|
||||||
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
|
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
|
||||||
const migratedElement = restoreElement(element);
|
let migratedElement: ExcalidrawElement = restoreElement(element);
|
||||||
if (migratedElement) {
|
if (migratedElement) {
|
||||||
|
const localElement = localElementsMap?.[element.id];
|
||||||
|
if (localElement && localElement.version > migratedElement.version) {
|
||||||
|
migratedElement = bumpVersion(migratedElement, localElement.version);
|
||||||
|
}
|
||||||
elements.push(migratedElement);
|
elements.push(migratedElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,25 +209,25 @@ export const restoreElements = (
|
|||||||
|
|
||||||
export const restoreAppState = (
|
export const restoreAppState = (
|
||||||
appState: ImportedDataState["appState"],
|
appState: ImportedDataState["appState"],
|
||||||
localAppState: Partial<AppState> | null,
|
localAppState: Partial<AppState> | null | undefined,
|
||||||
): RestoredAppState => {
|
): RestoredAppState => {
|
||||||
appState = appState || {};
|
appState = appState || {};
|
||||||
|
|
||||||
const defaultAppState = getDefaultAppState();
|
const defaultAppState = getDefaultAppState();
|
||||||
const nextAppState = {} as typeof defaultAppState;
|
const nextAppState = {} as typeof defaultAppState;
|
||||||
|
|
||||||
for (const [key, val] of Object.entries(defaultAppState) as [
|
for (const [key, defaultValue] of Object.entries(defaultAppState) as [
|
||||||
keyof typeof defaultAppState,
|
keyof typeof defaultAppState,
|
||||||
any,
|
any,
|
||||||
][]) {
|
][]) {
|
||||||
const restoredValue = appState[key];
|
const suppliedValue = appState[key];
|
||||||
const localValue = localAppState ? localAppState[key] : undefined;
|
const localValue = localAppState ? localAppState[key] : undefined;
|
||||||
(nextAppState as any)[key] =
|
(nextAppState as any)[key] =
|
||||||
restoredValue !== undefined
|
suppliedValue !== undefined
|
||||||
? restoredValue
|
? suppliedValue
|
||||||
: localValue !== undefined
|
: localValue !== undefined
|
||||||
? localValue
|
? localValue
|
||||||
: val;
|
: defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -243,9 +255,10 @@ export const restore = (
|
|||||||
* Supply `null` if you can't get access to it.
|
* Supply `null` if you can't get access to it.
|
||||||
*/
|
*/
|
||||||
localAppState: Partial<AppState> | null | undefined,
|
localAppState: Partial<AppState> | null | undefined,
|
||||||
|
localElements: readonly ExcalidrawElement[] | null | undefined,
|
||||||
): RestoredDataState => {
|
): RestoredDataState => {
|
||||||
return {
|
return {
|
||||||
elements: restoreElements(data?.elements),
|
elements: restoreElements(data?.elements, localElements),
|
||||||
appState: restoreAppState(data?.appState, localAppState || null),
|
appState: restoreAppState(data?.appState, localAppState || null),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -328,15 +328,15 @@ const hitTestFreeDrawElement = (
|
|||||||
let P: readonly [number, number];
|
let P: readonly [number, number];
|
||||||
|
|
||||||
// For freedraw dots
|
// For freedraw dots
|
||||||
if (element.points.length === 2) {
|
if (
|
||||||
return (
|
distance2d(A[0], A[1], x, y) < threshold ||
|
||||||
distance2d(A[0], A[1], x, y) < threshold ||
|
distance2d(B[0], B[1], x, y) < threshold
|
||||||
distance2d(B[0], B[1], x, y) < threshold
|
) {
|
||||||
);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For freedraw lines
|
// For freedraw lines
|
||||||
for (let i = 1; i < element.points.length - 1; i++) {
|
for (let i = 0; i < element.points.length; i++) {
|
||||||
const delta = [B[0] - A[0], B[1] - A[1]];
|
const delta = [B[0] - A[0], B[1] - A[1]];
|
||||||
const length = Math.hypot(delta[1], delta[0]);
|
const length = Math.hypot(delta[1], delta[0]);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { mutateElement } from "./mutateElement";
|
|||||||
import { getPerfectElementSize } from "./sizeHelpers";
|
import { getPerfectElementSize } from "./sizeHelpers";
|
||||||
import Scene from "../scene/Scene";
|
import Scene from "../scene/Scene";
|
||||||
import { NonDeletedExcalidrawElement } from "./types";
|
import { NonDeletedExcalidrawElement } from "./types";
|
||||||
import { PointerDownState } from "../components/App";
|
import { PointerDownState } from "../types";
|
||||||
|
|
||||||
export const dragSelectedElements = (
|
export const dragSelectedElements = (
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
|
|||||||
@@ -120,8 +120,11 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
|
|||||||
*
|
*
|
||||||
* NOTE: does not trigger re-render.
|
* NOTE: does not trigger re-render.
|
||||||
*/
|
*/
|
||||||
export const bumpVersion = (element: Mutable<ExcalidrawElement>) => {
|
export const bumpVersion = (
|
||||||
element.version = element.version + 1;
|
element: Mutable<ExcalidrawElement>,
|
||||||
|
version?: ExcalidrawElement["version"],
|
||||||
|
) => {
|
||||||
|
element.version = (version ?? element.version) + 1;
|
||||||
element.versionNonce = randomInteger();
|
element.versionNonce = randomInteger();
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { duplicateElement } from "./newElement";
|
import { duplicateElement } from "./newElement";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { API } from "../tests/helpers/api";
|
import { API } from "../tests/helpers/api";
|
||||||
|
import { FONT_FAMILY } from "../constants";
|
||||||
|
|
||||||
const isPrimitive = (val: any) => {
|
const isPrimitive = (val: any) => {
|
||||||
const type = typeof val;
|
const type = typeof val;
|
||||||
@@ -79,7 +80,7 @@ it("clones text element", () => {
|
|||||||
opacity: 100,
|
opacity: 100,
|
||||||
text: "hello",
|
text: "hello",
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontFamily: 1,
|
fontFamily: FONT_FAMILY.Virgil,
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
verticalAlign: "top",
|
verticalAlign: "top",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import {
|
|||||||
ExcalidrawGenericElement,
|
ExcalidrawGenericElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
TextAlign,
|
TextAlign,
|
||||||
FontFamily,
|
|
||||||
GroupId,
|
GroupId,
|
||||||
VerticalAlign,
|
VerticalAlign,
|
||||||
Arrowhead,
|
Arrowhead,
|
||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
|
FontFamilyValues,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { measureText, getFontString } from "../utils";
|
import { measureText, getFontString } from "../utils";
|
||||||
import { randomInteger, randomId } from "../random";
|
import { randomInteger, randomId } from "../random";
|
||||||
@@ -109,7 +109,7 @@ export const newTextElement = (
|
|||||||
opts: {
|
opts: {
|
||||||
text: string;
|
text: string;
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
fontFamily: FontFamily;
|
fontFamily: FontFamilyValues;
|
||||||
textAlign: TextAlign;
|
textAlign: TextAlign;
|
||||||
verticalAlign: VerticalAlign;
|
verticalAlign: VerticalAlign;
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
@@ -307,7 +307,19 @@ export const duplicateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
overrides?: Partial<TElement>,
|
overrides?: Partial<TElement>,
|
||||||
): TElement => {
|
): TElement => {
|
||||||
let copy: TElement = deepCopyElement(element);
|
let copy: TElement = deepCopyElement(element);
|
||||||
copy.id = process.env.NODE_ENV === "test" ? `${copy.id}_copy` : randomId();
|
if (process.env.NODE_ENV === "test") {
|
||||||
|
copy.id = `${copy.id}_copy`;
|
||||||
|
// `window.h` may not be defined in some unit tests
|
||||||
|
if (
|
||||||
|
window.h?.app
|
||||||
|
?.getSceneElementsIncludingDeleted()
|
||||||
|
.find((el) => el.id === copy.id)
|
||||||
|
) {
|
||||||
|
copy.id += "_copy";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
copy.id = randomId();
|
||||||
|
}
|
||||||
copy.seed = randomInteger();
|
copy.seed = randomInteger();
|
||||||
copy.groupIds = getNewGroupIdsForDuplication(
|
copy.groupIds = getNewGroupIdsForDuplication(
|
||||||
copy.groupIds,
|
copy.groupIds,
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ import {
|
|||||||
MaybeTransformHandleType,
|
MaybeTransformHandleType,
|
||||||
TransformHandleDirection,
|
TransformHandleDirection,
|
||||||
} from "./transformHandles";
|
} from "./transformHandles";
|
||||||
import { PointerDownState } from "../components/App";
|
import { Point, PointerDownState } from "../types";
|
||||||
import { Point } from "../types";
|
|
||||||
|
|
||||||
export const normalizeAngle = (angle: number): number => {
|
export const normalizeAngle = (angle: number): number => {
|
||||||
if (angle >= 2 * Math.PI) {
|
if (angle >= 2 * Math.PI) {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { FONT_FAMILY } from "../constants";
|
|||||||
|
|
||||||
export type ChartType = "bar" | "line";
|
export type ChartType = "bar" | "line";
|
||||||
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
||||||
export type FontFamily = keyof typeof FONT_FAMILY;
|
export type FontFamilyKeys = keyof typeof FONT_FAMILY;
|
||||||
|
export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys];
|
||||||
export type FontString = string & { _brand: "fontString" };
|
export type FontString = string & { _brand: "fontString" };
|
||||||
export type GroupId = string;
|
export type GroupId = string;
|
||||||
export type PointerType = "mouse" | "pen" | "touch";
|
export type PointerType = "mouse" | "pen" | "touch";
|
||||||
@@ -91,7 +92,7 @@ export type ExcalidrawTextElement = _ExcalidrawElementBase &
|
|||||||
Readonly<{
|
Readonly<{
|
||||||
type: "text";
|
type: "text";
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
fontFamily: FontFamily;
|
fontFamily: FontFamilyValues;
|
||||||
text: string;
|
text: string;
|
||||||
baseline: number;
|
baseline: number;
|
||||||
textAlign: TextAlign;
|
textAlign: TextAlign;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
type CANVAS_ERROR_NAMES = "CANVAS_ERROR" | "CANVAS_POSSIBLY_TOO_BIG";
|
type CANVAS_ERROR_NAMES = "CANVAS_ERROR" | "CANVAS_POSSIBLY_TOO_BIG";
|
||||||
|
|
||||||
export class CanvasError extends Error {
|
export class CanvasError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: string = "Couldn't export canvas.",
|
message: string = "Couldn't export canvas.",
|
||||||
@@ -9,3 +10,9 @@ export class CanvasError extends Error {
|
|||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AbortError extends DOMException {
|
||||||
|
constructor(message: string = "Request Aborted") {
|
||||||
|
super(message, "AbortError");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash.throttle";
|
||||||
import React, { PureComponent } from "react";
|
import React, { PureComponent } from "react";
|
||||||
import { ExcalidrawImperativeAPI } from "../../components/App";
|
import { ExcalidrawImperativeAPI } from "../../types";
|
||||||
import { ErrorDialog } from "../../components/ErrorDialog";
|
import { ErrorDialog } from "../../components/ErrorDialog";
|
||||||
import { APP_NAME, ENV, EVENT } from "../../constants";
|
import { APP_NAME, ENV, EVENT } from "../../constants";
|
||||||
import { ImportedDataState } from "../../data/types";
|
import { ImportedDataState } from "../../data/types";
|
||||||
|
|||||||
92
src/excalidraw-app/components/ExportToExcalidrawPlus.tsx
Normal file
92
src/excalidraw-app/components/ExportToExcalidrawPlus.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Card } from "../../components/Card";
|
||||||
|
import { ToolButton } from "../../components/ToolButton";
|
||||||
|
import { serializeAsJSON } from "../../data/json";
|
||||||
|
import { getImportedKey, createIV, generateEncryptionKey } from "../data";
|
||||||
|
import { loadFirebaseStorage } from "../data/firebase";
|
||||||
|
import { NonDeletedExcalidrawElement } from "../../element/types";
|
||||||
|
import { AppState } from "../../types";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { t } from "../../i18n";
|
||||||
|
import { excalidrawPlusIcon } from "./icons";
|
||||||
|
|
||||||
|
const encryptData = async (
|
||||||
|
key: string,
|
||||||
|
json: string,
|
||||||
|
): Promise<{ blob: Blob; iv: Uint8Array }> => {
|
||||||
|
const importedKey = await getImportedKey(key, "encrypt");
|
||||||
|
const iv = createIV();
|
||||||
|
const encoded = new TextEncoder().encode(json);
|
||||||
|
const ciphertext = await window.crypto.subtle.encrypt(
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv,
|
||||||
|
},
|
||||||
|
importedKey,
|
||||||
|
encoded,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { blob: new Blob([new Uint8Array(ciphertext)]), iv };
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportToExcalidrawPlus = async (
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
appState: AppState,
|
||||||
|
) => {
|
||||||
|
const firebase = await loadFirebaseStorage();
|
||||||
|
|
||||||
|
const id = `${nanoid(12)}`;
|
||||||
|
|
||||||
|
const key = (await generateEncryptionKey())!;
|
||||||
|
const encryptedData = await encryptData(
|
||||||
|
key,
|
||||||
|
serializeAsJSON(elements, appState),
|
||||||
|
);
|
||||||
|
|
||||||
|
const blob = new Blob([encryptedData.iv, encryptedData.blob], {
|
||||||
|
type: "application/octet-stream",
|
||||||
|
});
|
||||||
|
|
||||||
|
await firebase
|
||||||
|
.storage()
|
||||||
|
.ref(`/migrations/scenes/${id}`)
|
||||||
|
.put(blob, {
|
||||||
|
customMetadata: {
|
||||||
|
data: JSON.stringify({ version: 1, name: appState.name }),
|
||||||
|
created: Date.now().toString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
window.open(`https://plus.excalidraw.com/import?excalidraw=${id},${key}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExportToExcalidrawPlus: React.FC<{
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
|
appState: AppState;
|
||||||
|
onError: (error: Error) => void;
|
||||||
|
}> = ({ elements, appState, onError }) => {
|
||||||
|
return (
|
||||||
|
<Card color="indigo">
|
||||||
|
<div className="Card-icon">{excalidrawPlusIcon}</div>
|
||||||
|
<h2>Excalidraw+</h2>
|
||||||
|
<div className="Card-details">
|
||||||
|
{t("exportDialog.excalidrawplus_description")}
|
||||||
|
</div>
|
||||||
|
<ToolButton
|
||||||
|
className="Card-button"
|
||||||
|
type="button"
|
||||||
|
title={t("exportDialog.excalidrawplus_button")}
|
||||||
|
aria-label={t("exportDialog.excalidrawplus_button")}
|
||||||
|
showAriaLabel={true}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await exportToExcalidrawPlus(elements, appState);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
onError(new Error(t("exportDialog.excalidrawplus_exportError")));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
|
||||||
import * as i18n from "../../i18n";
|
import * as i18n from "../../i18n";
|
||||||
|
|
||||||
export const LanguageList = ({
|
export const LanguageList = ({
|
||||||
onChange,
|
onChange,
|
||||||
languages = i18n.languages,
|
languages = i18n.languages,
|
||||||
currentLangCode = i18n.getLanguage().code,
|
currentLangCode = i18n.getLanguage().code,
|
||||||
floating,
|
|
||||||
}: {
|
}: {
|
||||||
languages?: { code: string; label: string }[];
|
languages?: { code: string; label: string }[];
|
||||||
onChange: (langCode: i18n.Language["code"]) => void;
|
onChange: (langCode: i18n.Language["code"]) => void;
|
||||||
currentLangCode?: i18n.Language["code"];
|
currentLangCode?: i18n.Language["code"];
|
||||||
floating?: boolean;
|
|
||||||
}) => (
|
}) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<select
|
<select
|
||||||
className={clsx("dropdown-select dropdown-select__language", {
|
className="dropdown-select dropdown-select__language"
|
||||||
"dropdown-select--floating": floating,
|
|
||||||
})}
|
|
||||||
onChange={({ target }) => onChange(target.value)}
|
onChange={({ target }) => onChange(target.value)}
|
||||||
value={currentLangCode}
|
value={currentLangCode}
|
||||||
aria-label={i18n.t("buttons.selectLanguage")}
|
aria-label={i18n.t("buttons.selectLanguage")}
|
||||||
|
|||||||
19
src/excalidraw-app/components/icons.tsx
Normal file
19
src/excalidraw-app/components/icons.tsx
Normal file
File diff suppressed because one or more lines are too long
@@ -5,15 +5,19 @@ import { getSceneVersion } from "../../element";
|
|||||||
import Portal from "../collab/Portal";
|
import Portal from "../collab/Portal";
|
||||||
import { restoreElements } from "../../data/restore";
|
import { restoreElements } from "../../data/restore";
|
||||||
|
|
||||||
|
// private
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
let firebasePromise: Promise<
|
let firebasePromise: Promise<
|
||||||
typeof import("firebase/app").default
|
typeof import("firebase/app").default
|
||||||
> | null = null;
|
> | null = null;
|
||||||
|
let firestorePromise: Promise<any> | null = null;
|
||||||
|
let firebseStoragePromise: Promise<any> | null = null;
|
||||||
|
|
||||||
const loadFirebase = async () => {
|
const _loadFirebase = async () => {
|
||||||
const firebase = (
|
const firebase = (
|
||||||
await import(/* webpackChunkName: "firebase" */ "firebase/app")
|
await import(/* webpackChunkName: "firebase" */ "firebase/app")
|
||||||
).default;
|
).default;
|
||||||
await import(/* webpackChunkName: "firestore" */ "firebase/firestore");
|
|
||||||
|
|
||||||
const firebaseConfig = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
|
const firebaseConfig = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
|
||||||
firebase.initializeApp(firebaseConfig);
|
firebase.initializeApp(firebaseConfig);
|
||||||
@@ -21,13 +25,37 @@ const loadFirebase = async () => {
|
|||||||
return firebase;
|
return firebase;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFirebase = async (): Promise<
|
const _getFirebase = async (): Promise<
|
||||||
typeof import("firebase/app").default
|
typeof import("firebase/app").default
|
||||||
> => {
|
> => {
|
||||||
if (!firebasePromise) {
|
if (!firebasePromise) {
|
||||||
firebasePromise = loadFirebase();
|
firebasePromise = _loadFirebase();
|
||||||
}
|
}
|
||||||
return await firebasePromise!;
|
return firebasePromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const loadFirestore = async () => {
|
||||||
|
const firebase = await _getFirebase();
|
||||||
|
if (!firestorePromise) {
|
||||||
|
firestorePromise = import(
|
||||||
|
/* webpackChunkName: "firestore" */ "firebase/firestore"
|
||||||
|
);
|
||||||
|
await firestorePromise;
|
||||||
|
}
|
||||||
|
return firebase;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadFirebaseStorage = async () => {
|
||||||
|
const firebase = await _getFirebase();
|
||||||
|
if (!firebseStoragePromise) {
|
||||||
|
firebseStoragePromise = import(
|
||||||
|
/* webpackChunkName: "storage" */ "firebase/storage"
|
||||||
|
);
|
||||||
|
await firebseStoragePromise;
|
||||||
|
}
|
||||||
|
return firebase;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FirebaseStoredScene {
|
interface FirebaseStoredScene {
|
||||||
@@ -108,7 +136,7 @@ export const saveToFirebase = async (
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firebase = await getFirebase();
|
const firebase = await loadFirestore();
|
||||||
const sceneVersion = getSceneVersion(elements);
|
const sceneVersion = getSceneVersion(elements);
|
||||||
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
||||||
|
|
||||||
@@ -150,7 +178,7 @@ export const loadFromFirebase = async (
|
|||||||
roomKey: string,
|
roomKey: string,
|
||||||
socket: SocketIOClient.Socket | null,
|
socket: SocketIOClient.Socket | null,
|
||||||
): Promise<readonly ExcalidrawElement[] | null> => {
|
): Promise<readonly ExcalidrawElement[] | null> => {
|
||||||
const firebase = await getFirebase();
|
const firebase = await loadFirestore();
|
||||||
const db = firebase.firestore();
|
const db = firebase.firestore();
|
||||||
|
|
||||||
const docRef = db.collection("scenes").doc(roomId);
|
const docRef = db.collection("scenes").doc(roomId);
|
||||||
@@ -168,5 +196,5 @@ export const loadFromFirebase = async (
|
|||||||
firebaseSceneVersionCache.set(socket, getSceneVersion(elements));
|
firebaseSceneVersionCache.set(socket, getSceneVersion(elements));
|
||||||
}
|
}
|
||||||
|
|
||||||
return restoreElements(elements);
|
return restoreElements(elements, null);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const generateRandomID = async () => {
|
|||||||
return Array.from(arr, byteToHex).join("");
|
return Array.from(arr, byteToHex).join("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateEncryptionKey = async () => {
|
export const generateEncryptionKey = async () => {
|
||||||
const key = await window.crypto.subtle.generateKey(
|
const key = await window.crypto.subtle.generateKey(
|
||||||
{
|
{
|
||||||
name: "AES-GCM",
|
name: "AES-GCM",
|
||||||
@@ -137,6 +137,10 @@ export const decryptAESGEM = async (
|
|||||||
export const getCollaborationLinkData = (link: string) => {
|
export const getCollaborationLinkData = (link: string) => {
|
||||||
const hash = new URL(link).hash;
|
const hash = new URL(link).hash;
|
||||||
const match = hash.match(/^#room=([a-zA-Z0-9_-]+),([a-zA-Z0-9_-]+)$/);
|
const match = hash.match(/^#room=([a-zA-Z0-9_-]+),([a-zA-Z0-9_-]+)$/);
|
||||||
|
if (match && match[2].length !== 22) {
|
||||||
|
window.alert(t("alerts.invalidEncryptionKey"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return match ? { roomId: match[1], roomKey: match[2] } : null;
|
return match ? { roomId: match[1], roomKey: match[2] } : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,7 +180,7 @@ export const getImportedKey = (key: string, usage: KeyUsage) =>
|
|||||||
[usage],
|
[usage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const decryptImported = async (
|
export const decryptImported = async (
|
||||||
iv: ArrayBuffer,
|
iv: ArrayBuffer,
|
||||||
encrypted: ArrayBuffer,
|
encrypted: ArrayBuffer,
|
||||||
privateKey: string,
|
privateKey: string,
|
||||||
@@ -257,9 +261,10 @@ export const loadScene = async (
|
|||||||
data = restore(
|
data = restore(
|
||||||
await importFromBackend(id, privateKey),
|
await importFromBackend(id, privateKey),
|
||||||
localDataState?.appState,
|
localDataState?.appState,
|
||||||
|
localDataState?.elements,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
data = restore(localDataState || null, null);
|
data = restore(localDataState || null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,12 +2,18 @@
|
|||||||
.layer-ui__wrapper__footer-center {
|
.layer-ui__wrapper__footer-center {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-inline-start: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.encrypted-icon {
|
.encrypted-icon {
|
||||||
border-radius: var(--space-factor);
|
border-radius: var(--space-factor);
|
||||||
color: var(--icon-green-fill-color);
|
color: var(--icon-green-fill-color);
|
||||||
margin-top: 13px;
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
margin-inline-start: auto;
|
||||||
|
margin-inline-end: 0.6em;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 1.2rem;
|
width: 1.2rem;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import React, {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { ExcalidrawImperativeAPI } from "../components/App";
|
|
||||||
import { ErrorDialog } from "../components/ErrorDialog";
|
import { ErrorDialog } from "../components/ErrorDialog";
|
||||||
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
import { TopErrorBoundary } from "../components/TopErrorBoundary";
|
||||||
import {
|
import {
|
||||||
@@ -31,7 +30,7 @@ import Excalidraw, {
|
|||||||
defaultLang,
|
defaultLang,
|
||||||
languages,
|
languages,
|
||||||
} from "../packages/excalidraw/index";
|
} from "../packages/excalidraw/index";
|
||||||
import { AppState, LibraryItems } from "../types";
|
import { AppState, LibraryItems, ExcalidrawImperativeAPI } from "../types";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
getVersion,
|
getVersion,
|
||||||
@@ -56,6 +55,7 @@ import { Tooltip } from "../components/Tooltip";
|
|||||||
import { shield } from "../components/icons";
|
import { shield } from "../components/icons";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
|
import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus";
|
||||||
|
|
||||||
const languageDetector = new LanguageDetector();
|
const languageDetector = new LanguageDetector();
|
||||||
languageDetector.init({
|
languageDetector.init({
|
||||||
@@ -141,7 +141,7 @@ const initializeScene = async (opts: {
|
|||||||
const url = externalUrlMatch[1];
|
const url = externalUrlMatch[1];
|
||||||
try {
|
try {
|
||||||
const request = await fetch(window.decodeURIComponent(url));
|
const request = await fetch(window.decodeURIComponent(url));
|
||||||
const data = await loadFromBlob(await request.blob(), null);
|
const data = await loadFromBlob(await request.blob(), null, null);
|
||||||
if (
|
if (
|
||||||
!scene.elements.length ||
|
!scene.elements.length ||
|
||||||
window.confirm(t("alerts.loadSceneOverridePrompt"))
|
window.confirm(t("alerts.loadSceneOverridePrompt"))
|
||||||
@@ -348,11 +348,8 @@ const ExcalidrawWrapper = () => {
|
|||||||
|
|
||||||
const renderLanguageList = () => (
|
const renderLanguageList = () => (
|
||||||
<LanguageList
|
<LanguageList
|
||||||
onChange={(langCode) => {
|
onChange={(langCode) => setLangCode(langCode)}
|
||||||
setLangCode(langCode);
|
|
||||||
}}
|
|
||||||
languages={languages}
|
languages={languages}
|
||||||
floating={!isMobile}
|
|
||||||
currentLangCode={langCode}
|
currentLangCode={langCode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -424,7 +421,28 @@ const ExcalidrawWrapper = () => {
|
|||||||
onCollabButtonClick={collabAPI?.onCollabButtonClick}
|
onCollabButtonClick={collabAPI?.onCollabButtonClick}
|
||||||
isCollaborating={collabAPI?.isCollaborating()}
|
isCollaborating={collabAPI?.isCollaborating()}
|
||||||
onPointerUpdate={collabAPI?.onPointerUpdate}
|
onPointerUpdate={collabAPI?.onPointerUpdate}
|
||||||
onExportToBackend={onExportToBackend}
|
UIOptions={{
|
||||||
|
canvasActions: {
|
||||||
|
export: {
|
||||||
|
onExportToBackend,
|
||||||
|
renderCustomUI: (elements, appState) => {
|
||||||
|
return (
|
||||||
|
<ExportToExcalidrawPlus
|
||||||
|
elements={elements}
|
||||||
|
appState={appState}
|
||||||
|
onError={(error) => {
|
||||||
|
excalidrawAPI?.updateScene({
|
||||||
|
appState: {
|
||||||
|
errorMessage: error.message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
renderTopRightUI={renderTopRightUI}
|
renderTopRightUI={renderTopRightUI}
|
||||||
renderFooter={renderFooter}
|
renderFooter={renderFooter}
|
||||||
langCode={langCode}
|
langCode={langCode}
|
||||||
@@ -432,6 +450,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
detectScroll={false}
|
detectScroll={false}
|
||||||
handleKeyboardGlobally={true}
|
handleKeyboardGlobally={true}
|
||||||
onLibraryChange={onLibraryChange}
|
onLibraryChange={onLibraryChange}
|
||||||
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
{excalidrawAPI && <CollabWrapper excalidrawAPI={excalidrawAPI} />}
|
{excalidrawAPI && <CollabWrapper excalidrawAPI={excalidrawAPI} />}
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface DehydratedHistoryEntry {
|
|||||||
const clearAppStatePropertiesForHistory = (appState: AppState) => {
|
const clearAppStatePropertiesForHistory = (appState: AppState) => {
|
||||||
return {
|
return {
|
||||||
selectedElementIds: appState.selectedElementIds,
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
selectedGroupIds: appState.selectedGroupIds,
|
||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
editingLinearElement: appState.editingLinearElement,
|
editingLinearElement: appState.editingLinearElement,
|
||||||
editingGroupId: appState.editingGroupId,
|
editingGroupId: appState.editingGroupId,
|
||||||
@@ -169,7 +170,7 @@ class History {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key === "selectedElementIds") {
|
if (key === "selectedElementIds" || key === "selectedGroupIds") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (nextEntry.appState[key] !== lastEntry.appState[key]) {
|
if (nextEntry.appState[key] !== lastEntry.appState[key]) {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const allLanguages: Language[] = [
|
|||||||
{ code: "zh-TW", label: "繁體中文" },
|
{ code: "zh-TW", label: "繁體中文" },
|
||||||
{ code: "lv-LV", label: "Latviešu" },
|
{ code: "lv-LV", label: "Latviešu" },
|
||||||
{ code: "cs-CZ", label: "Česky" },
|
{ code: "cs-CZ", label: "Česky" },
|
||||||
|
{ code: "kk-KZ", label: "Қазақ тілі" },
|
||||||
].concat([defaultLang]);
|
].concat([defaultLang]);
|
||||||
|
|
||||||
export const languages: Language[] = allLanguages
|
export const languages: Language[] = allLanguages
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ const canvas = exportToCanvas(
|
|||||||
{
|
{
|
||||||
exportBackground: true,
|
exportBackground: true,
|
||||||
viewBackgroundColor: "#ffffff",
|
viewBackgroundColor: "#ffffff",
|
||||||
scale: 1,
|
|
||||||
},
|
},
|
||||||
createCanvas,
|
createCanvas,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"pasteCharts": "لصق الرسوم البيانية",
|
"pasteCharts": "لصق الرسوم البيانية",
|
||||||
"selectAll": "تحديد الكل",
|
"selectAll": "تحديد الكل",
|
||||||
"multiSelect": "إضافة عنصر للتحديد",
|
"multiSelect": "إضافة عنصر للتحديد",
|
||||||
"moveCanvas": "نقل لوح رسم",
|
"moveCanvas": "نقل لوح الرسم",
|
||||||
"cut": "قص",
|
"cut": "قص",
|
||||||
"copy": "نسخ",
|
"copy": "نسخ",
|
||||||
"copyAsPng": "نسخ إلى الحافظة بصيغة PNG",
|
"copyAsPng": "نسخ إلى الحافظة بصيغة PNG",
|
||||||
@@ -14,18 +14,18 @@
|
|||||||
"bringToFront": "أحضر للأمام",
|
"bringToFront": "أحضر للأمام",
|
||||||
"sendBackward": "أرسل للخلف",
|
"sendBackward": "أرسل للخلف",
|
||||||
"delete": "حذف",
|
"delete": "حذف",
|
||||||
"copyStyles": "نسخ النمط",
|
"copyStyles": "نسخ الأنماط",
|
||||||
"pasteStyles": "لصق النمط",
|
"pasteStyles": "لصق الأنماط",
|
||||||
"stroke": "الحدود",
|
"stroke": "الخط",
|
||||||
"background": "الخلفية",
|
"background": "الخلفية",
|
||||||
"fill": "التعبئة",
|
"fill": "التعبئة",
|
||||||
"strokeWidth": "حجم الحدود",
|
"strokeWidth": "سُمك الخط",
|
||||||
"strokeShape": "",
|
"strokeShape": "شكل الخط",
|
||||||
"strokeShape_gel": "",
|
"strokeShape_gel": "قلم جل",
|
||||||
"strokeShape_fountain": "",
|
"strokeShape_fountain": "قلم رش",
|
||||||
"strokeShape_brush": "",
|
"strokeShape_brush": "فرشاه",
|
||||||
"strokeStyle": "نمط الحدود",
|
"strokeStyle": "نمط الخط",
|
||||||
"strokeStyle_solid": "صلبة",
|
"strokeStyle_solid": "كامل",
|
||||||
"strokeStyle_dashed": "متقطع",
|
"strokeStyle_dashed": "متقطع",
|
||||||
"strokeStyle_dotted": "منقط",
|
"strokeStyle_dotted": "منقط",
|
||||||
"sloppiness": "الإمالة",
|
"sloppiness": "الإمالة",
|
||||||
@@ -43,12 +43,12 @@
|
|||||||
"fontFamily": "نوع الخط",
|
"fontFamily": "نوع الخط",
|
||||||
"onlySelected": "المحدد فقط",
|
"onlySelected": "المحدد فقط",
|
||||||
"withBackground": "الخلفية",
|
"withBackground": "الخلفية",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "تضمين المشهد",
|
||||||
"exportEmbedScene_details": "سيتم حفظ بيانات المشهد في ملف PNG/SVG المصدّر بحيث يمكن استعادة المشهد منه.\nسيزيد حجم الملف المصدر.",
|
"exportEmbedScene_details": "سيتم حفظ بيانات المشهد في ملف PNG/SVG المصدّر بحيث يمكن استعادة المشهد منه.\nسيزيد حجم الملف المصدر.",
|
||||||
"addWatermark": "إضافة \"مصنوعة بواسطة Excalidraw\"",
|
"addWatermark": "إضافة \"مصنوعة بواسطة Excalidraw\"",
|
||||||
"handDrawn": "رسم باليد",
|
"handDrawn": "رسم باليد",
|
||||||
"normal": "عادي",
|
"normal": "عادي",
|
||||||
"code": "الرمز",
|
"code": "رمز",
|
||||||
"small": "صغير",
|
"small": "صغير",
|
||||||
"medium": "متوسط",
|
"medium": "متوسط",
|
||||||
"large": "كبير",
|
"large": "كبير",
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
"artist": "رسام",
|
"artist": "رسام",
|
||||||
"cartoonist": "كرتوني",
|
"cartoonist": "كرتوني",
|
||||||
"fileTitle": "إسم الملف",
|
"fileTitle": "إسم الملف",
|
||||||
"colorPicker": "اختيار الألوان",
|
"colorPicker": "منتقي اللون",
|
||||||
"canvasBackground": "خلفية اللوحة",
|
"canvasBackground": "خلفية اللوحة",
|
||||||
"drawingCanvas": "لوحة الرسم",
|
"drawingCanvas": "لوحة الرسم",
|
||||||
"layers": "الطبقات",
|
"layers": "الطبقات",
|
||||||
@@ -99,8 +99,10 @@
|
|||||||
"flipHorizontal": "قلب عامودي",
|
"flipHorizontal": "قلب عامودي",
|
||||||
"flipVertical": "قلب أفقي",
|
"flipVertical": "قلب أفقي",
|
||||||
"viewMode": "نمط العرض",
|
"viewMode": "نمط العرض",
|
||||||
"toggleExportColorScheme": "",
|
"toggleExportColorScheme": "تبديل نظام ألوان الصادرات",
|
||||||
"share": "مشاركة",
|
"share": "مشاركة",
|
||||||
|
"showStroke": "إظهار منتقي لون الخط",
|
||||||
|
"showBackground": "إظهار منتقي لون الخلفية",
|
||||||
"toggleTheme": "غير النمط"
|
"toggleTheme": "غير النمط"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -149,13 +151,14 @@
|
|||||||
"loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟",
|
"loadSceneOverridePrompt": "تحميل الرسم الخارجي سيحل محل المحتوى الموجود لديك. هل ترغب في المتابعة؟",
|
||||||
"collabStopOverridePrompt": "إيقاف الجلسة سيؤدي إلى الكتابة فوق رسومك السابقة المخزنة داخليا. هل أنت متأكد؟\n\n(إذا كنت ترغب في الاحتفاظ برسمك المخزن داخليا، ببساطة أغلق علامة تبويب المتصفح بدلاً من ذلك.)",
|
"collabStopOverridePrompt": "إيقاف الجلسة سيؤدي إلى الكتابة فوق رسومك السابقة المخزنة داخليا. هل أنت متأكد؟\n\n(إذا كنت ترغب في الاحتفاظ برسمك المخزن داخليا، ببساطة أغلق علامة تبويب المتصفح بدلاً من ذلك.)",
|
||||||
"errorLoadingLibrary": "حصل خطأ أثناء تحميل مكتبة الطرف الثالث.",
|
"errorLoadingLibrary": "حصل خطأ أثناء تحميل مكتبة الطرف الثالث.",
|
||||||
"errorAddingToLibrary": "",
|
"errorAddingToLibrary": "تعذر إضافة العنصر للمكتبة",
|
||||||
"errorRemovingFromLibrary": "",
|
"errorRemovingFromLibrary": "تعذر إزالة العنصر من المكتبة",
|
||||||
"confirmAddLibrary": "هذا سيضيف {{numShapes}} شكل إلى مكتبتك. هل أنت متأكد؟",
|
"confirmAddLibrary": "هذا سيضيف {{numShapes}} شكل إلى مكتبتك. هل أنت متأكد؟",
|
||||||
"imageDoesNotContainScene": "استيراد الصور غير مدعوم في الوقت الراهن.\n\nهل تريد استيراد مشهد؟ لا يبدو أن هذه الصورة تحتوي على أي بيانات مشهد. هل قمت بسماح هذا أثناء التصدير؟",
|
"imageDoesNotContainScene": "استيراد الصور غير مدعوم في الوقت الراهن.\n\nهل تريد استيراد مشهد؟ لا يبدو أن هذه الصورة تحتوي على أي بيانات مشهد. هل قمت بسماح هذا أثناء التصدير؟",
|
||||||
"cannotRestoreFromImage": "تعذر استعادة المشهد من ملف الصورة",
|
"cannotRestoreFromImage": "تعذر استعادة المشهد من ملف الصورة",
|
||||||
"invalidSceneUrl": "",
|
"invalidSceneUrl": "تعذر استيراد المشهد من عنوان URL المتوفر. إما أنها مشوهة، أو لا تحتوي على بيانات Excalidraw JSON صالحة.",
|
||||||
"resetLibrary": ""
|
"resetLibrary": "هذا سوف يمسح مكتبتك. هل أنت متأكد؟",
|
||||||
|
"invalidEncryptionKey": "مفتاح التشفير يجب أن يكون من 22 حرفاً. التعاون المباشر معطل."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "تحديد",
|
"selection": "تحديد",
|
||||||
@@ -164,7 +167,7 @@
|
|||||||
"ellipse": "دائرة",
|
"ellipse": "دائرة",
|
||||||
"arrow": "سهم",
|
"arrow": "سهم",
|
||||||
"line": "خط",
|
"line": "خط",
|
||||||
"freedraw": "",
|
"freedraw": "رسم",
|
||||||
"text": "نص",
|
"text": "نص",
|
||||||
"library": "مكتبة",
|
"library": "مكتبة",
|
||||||
"lock": "الحفاظ على أداة التحديد نشطة بعد الرسم"
|
"lock": "الحفاظ على أداة التحديد نشطة بعد الرسم"
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "انقر لبدء نقاط متعددة، اسحب لخط واحد",
|
"linearElement": "انقر لبدء نقاط متعددة، اسحب لخط واحد",
|
||||||
"freeDraw": "انقر واسحب، افرج عند الانتهاء",
|
"freeDraw": "انقر واسحب، افرج عند الانتهاء",
|
||||||
"text": "نصيحة: يمكنك أيضًا إضافة نص بالنقر المزدوج في أي مكان بأداة الاختيار",
|
"text": "نصيحة: يمكنك أيضًا إضافة نص بالنقر المزدوج في أي مكان بأداة الاختيار",
|
||||||
|
"text_selected": "انقر نقراً مزدوجاً أو اضغط ادخال لتعديل النص",
|
||||||
|
"text_editing": "اضغط على Esc أو (Ctrl أو Cmd) + Enter لإنهاء التعديل",
|
||||||
"linearElementMulti": "انقر فوق النقطة الأخيرة أو اضغط على Esc أو Enter للإنهاء",
|
"linearElementMulti": "انقر فوق النقطة الأخيرة أو اضغط على Esc أو Enter للإنهاء",
|
||||||
"lockAngle": "يمكنك تقييد الزاوية بالضغط على SHIFT",
|
"lockAngle": "يمكنك تقييد الزاوية بالضغط على SHIFT",
|
||||||
"resize": "يمكنك تقييد النسب بالضغط على SHIFT أثناء تغيير الحجم،\nاضغط على ALT لتغيير الحجم من المركز",
|
"resize": "يمكنك تقييد النسب بالضغط على SHIFT أثناء تغيير الحجم،\nاضغط على ALT لتغيير الحجم من المركز",
|
||||||
@@ -212,18 +217,21 @@
|
|||||||
"desc_inProgressIntro": "تجري الآن المشاركة الحية.",
|
"desc_inProgressIntro": "تجري الآن المشاركة الحية.",
|
||||||
"desc_shareLink": "شارك هذا الرابط مع أي شخص تريده أن يشاركك الجلسة:",
|
"desc_shareLink": "شارك هذا الرابط مع أي شخص تريده أن يشاركك الجلسة:",
|
||||||
"desc_exitSession": "إيقاف الجلسة سيؤدي إلى قطع الاتصال الخاص بك من الغرفة، ولكن ستتمكن من مواصلة العمل مع المشهد، محليا. لاحظ أن هذا لن يؤثر على الأشخاص الآخرين، و سيظلون قادرين على التعاون في إصدارهم.",
|
"desc_exitSession": "إيقاف الجلسة سيؤدي إلى قطع الاتصال الخاص بك من الغرفة، ولكن ستتمكن من مواصلة العمل مع المشهد، محليا. لاحظ أن هذا لن يؤثر على الأشخاص الآخرين، و سيظلون قادرين على التعاون في إصدارهم.",
|
||||||
"shareTitle": ""
|
"shareTitle": "الانضمام إلى جلسة تعاون حية على Excalidraw"
|
||||||
},
|
},
|
||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "خطأ"
|
"title": "خطأ"
|
||||||
},
|
},
|
||||||
"exportDialog": {
|
"exportDialog": {
|
||||||
"disk_title": "حفظ الملف للجهاز",
|
"disk_title": "حفظ الملف للجهاز",
|
||||||
"disk_details": "",
|
"disk_details": "تصدير بيانات المشهد إلى ملف يمكنك الاستيراد منه لاحقاً.",
|
||||||
"disk_button": "إحفظ لملف",
|
"disk_button": "إحفظ لملف",
|
||||||
"link_title": "رابط قابل للمشاركة",
|
"link_title": "رابط قابل للمشاركة",
|
||||||
"link_details": "صدر الملف للمشاهدة فقط.",
|
"link_details": "صدر الملف للمشاهدة فقط.",
|
||||||
"link_button": "التصدير كرابط"
|
"link_button": "التصدير كرابط",
|
||||||
|
"excalidrawplus_description": "حفظ المشهد إلى مساحة العمل +Excalidraw الخاصة بك.",
|
||||||
|
"excalidrawplus_button": "تصدير",
|
||||||
|
"excalidrawplus_exportError": "تعذر التصدير إلى +Excalidraw في الوقت الحالي..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "اقرأ مدونتنا",
|
"blog": "اقرأ مدونتنا",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "سهم مائل",
|
"curvedArrow": "سهم مائل",
|
||||||
"curvedLine": "خط مائل",
|
"curvedLine": "خط مائل",
|
||||||
"documentation": "دليل الاستخدام",
|
"documentation": "دليل الاستخدام",
|
||||||
|
"doubleClick": "انقر مرتين",
|
||||||
"drag": "اسحب",
|
"drag": "اسحب",
|
||||||
"editor": "المحرر",
|
"editor": "المحرر",
|
||||||
|
"editSelectedShape": "تعديل الشكل المحدد (النص/السهم/الخط)",
|
||||||
"github": "عثرت على مشكلة؟ إرسال",
|
"github": "عثرت على مشكلة؟ إرسال",
|
||||||
"howto": "اتبع التعليمات",
|
"howto": "اتبع التعليمات",
|
||||||
"or": "أو",
|
"or": "أو",
|
||||||
"preventBinding": "منع ارتبط السهم",
|
"preventBinding": "منع ارتبط السهم",
|
||||||
"shapes": "أشكال",
|
"shapes": "أشكال",
|
||||||
"shortcuts": "اختصارات لوحة المفاتيح",
|
"shortcuts": "اختصارات لوحة المفاتيح",
|
||||||
"textFinish": "الانتهاء من التحرير (نص)",
|
"textFinish": "إنهاء التعديل (محرر النص)",
|
||||||
"textNewLine": "اضف سطر جديد (نص)",
|
"textNewLine": "أضف سطر جديد (محرر نص)",
|
||||||
"title": "المساعدة",
|
"title": "المساعدة",
|
||||||
"view": "عرض",
|
"view": "عرض",
|
||||||
"zoomToFit": "تكبير للملائمة",
|
"zoomToFit": "تكبير للملائمة",
|
||||||
@@ -266,12 +276,59 @@
|
|||||||
"width": "العرض"
|
"width": "العرض"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "نسخ النمط.",
|
"copyStyles": "نسخت الانماط.",
|
||||||
"copyToClipboard": "نسخ إلى الحافظة.",
|
"copyToClipboard": "نسخ إلى الحافظة.",
|
||||||
"copyToClipboardAsPng": "تم نسخ {{exportSelection}} إلى الحافظة بصيغةPNG\n({{exportColorScheme}})",
|
"copyToClipboardAsPng": "تم نسخ {{exportSelection}} إلى الحافظة بصيغة PNG\n({{exportColorScheme}})",
|
||||||
"fileSaved": "تم حفظ الملف.",
|
"fileSaved": "تم حفظ الملف.",
|
||||||
"fileSavedToFilename": "حفظ باسم {filename}",
|
"fileSavedToFilename": "حفظ باسم {filename}",
|
||||||
"canvas": "لوحة الرسم",
|
"canvas": "لوحة الرسم",
|
||||||
"selection": "العنصر المحدد"
|
"selection": "العنصر المحدد"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "أبيض",
|
||||||
|
"f8f9fa": "رمادي 0",
|
||||||
|
"f1f3f5": "رمادي 1",
|
||||||
|
"fff5f5": "أحمر 0",
|
||||||
|
"fff0f6": "وردي 0",
|
||||||
|
"f8f0fc": "عنبي 0",
|
||||||
|
"f3f0ff": "بنفسجي 0",
|
||||||
|
"edf2ff": "نيلي 0",
|
||||||
|
"e7f5ff": "أزرق 0",
|
||||||
|
"e3fafc": "سماوي 0",
|
||||||
|
"e6fcf5": "تركواز 0",
|
||||||
|
"ebfbee": "أخضر 0",
|
||||||
|
"f4fce3": "ليموني 0",
|
||||||
|
"fff9db": "أصفر 0",
|
||||||
|
"fff4e6": "برتقالي 0",
|
||||||
|
"transparent": "شفاف",
|
||||||
|
"ced4da": "رمادي 4",
|
||||||
|
"868e96": "رمادي 6",
|
||||||
|
"fa5252": "أحمر 6",
|
||||||
|
"e64980": "وردي 6",
|
||||||
|
"be4bdb": "عنبي 6",
|
||||||
|
"7950f2": "بنفسجي 6",
|
||||||
|
"4c6ef5": "نيلي 6",
|
||||||
|
"228be6": "أزرق 6",
|
||||||
|
"15aabf": "سماوي 6",
|
||||||
|
"12b886": "تركواز 6",
|
||||||
|
"40c057": "أخضر 6",
|
||||||
|
"82c91e": "ليموني 6",
|
||||||
|
"fab005": "أصفر 6",
|
||||||
|
"fd7e14": "برتقالي 6",
|
||||||
|
"000000": "أسود",
|
||||||
|
"343a40": "رمادي 8",
|
||||||
|
"495057": "رمادي 7",
|
||||||
|
"c92a2a": "أحمر 9",
|
||||||
|
"a61e4d": "وردي 9",
|
||||||
|
"862e9c": "عنبي 9",
|
||||||
|
"5f3dc4": "بنفسجي 9",
|
||||||
|
"364fc7": "نيلي 9",
|
||||||
|
"1864ab": "أزرق 9",
|
||||||
|
"0b7285": "سماوي 9",
|
||||||
|
"087f5b": "تركواز 9",
|
||||||
|
"2b8a3e": "أخضر 9",
|
||||||
|
"5c940d": "ليموني 9",
|
||||||
|
"e67700": "أصفر 9",
|
||||||
|
"d9480f": "برتقالي 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "Изглед",
|
"viewMode": "Изглед",
|
||||||
"toggleExportColorScheme": "",
|
"toggleExportColorScheme": "",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"showStroke": "",
|
||||||
|
"showBackground": "",
|
||||||
"toggleTheme": ""
|
"toggleTheme": ""
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "Импортирането на картинки не се поддържва в момента.\n\nИскате да импортнете сцена? Тази картинка не съдържа данни от сцена. Разрешили ли сте последното при експортирането?",
|
"imageDoesNotContainScene": "Импортирането на картинки не се поддържва в момента.\n\nИскате да импортнете сцена? Тази картинка не съдържа данни от сцена. Разрешили ли сте последното при експортирането?",
|
||||||
"cannotRestoreFromImage": "Не може да бъде възстановена сцена от този файл",
|
"cannotRestoreFromImage": "Не може да бъде възстановена сцена от този файл",
|
||||||
"invalidSceneUrl": "",
|
"invalidSceneUrl": "",
|
||||||
"resetLibrary": ""
|
"resetLibrary": "",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Селекция",
|
"selection": "Селекция",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Кликнете, за да стартирате няколко точки, плъзнете за една линия",
|
"linearElement": "Кликнете, за да стартирате няколко точки, плъзнете за една линия",
|
||||||
"freeDraw": "Натиснете и влачете, пуснете като сте готови",
|
"freeDraw": "Натиснете и влачете, пуснете като сте готови",
|
||||||
"text": "Подсказка: Можете също да добавите текст като натиснете някъде два път с инструмента за селекция",
|
"text": "Подсказка: Можете също да добавите текст като натиснете някъде два път с инструмента за селекция",
|
||||||
|
"text_selected": "",
|
||||||
|
"text_editing": "",
|
||||||
"linearElementMulti": "Кликнете върху последната точка или натиснете Escape или Enter, за да завършите",
|
"linearElementMulti": "Кликнете върху последната точка или натиснете Escape или Enter, за да завършите",
|
||||||
"lockAngle": "Можете да ограничите ъгъла, като задържите SHIFT",
|
"lockAngle": "Можете да ограничите ъгъла, като задържите SHIFT",
|
||||||
"resize": "Може да ограничите при преоразмеряване като задържите SHIFT,\nзадръжте ALT за преоразмерите през центъра",
|
"resize": "Може да ограничите при преоразмеряване като задържите SHIFT,\nзадръжте ALT за преоразмерите през центъра",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "",
|
"disk_button": "",
|
||||||
"link_title": "",
|
"link_title": "",
|
||||||
"link_details": "",
|
"link_details": "",
|
||||||
"link_button": ""
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Прочетете нашия блог",
|
"blog": "Прочетете нашия блог",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Извита стрелка",
|
"curvedArrow": "Извита стрелка",
|
||||||
"curvedLine": "Извита линия",
|
"curvedLine": "Извита линия",
|
||||||
"documentation": "Документация",
|
"documentation": "Документация",
|
||||||
|
"doubleClick": "",
|
||||||
"drag": "плъзнете",
|
"drag": "плъзнете",
|
||||||
"editor": "Редактор",
|
"editor": "Редактор",
|
||||||
|
"editSelectedShape": "",
|
||||||
"github": "Намерихте проблем? Изпратете",
|
"github": "Намерихте проблем? Изпратете",
|
||||||
"howto": "Следвайте нашите ръководства",
|
"howto": "Следвайте нашите ръководства",
|
||||||
"or": "или",
|
"or": "или",
|
||||||
"preventBinding": "Спри прилепяне на стрелките",
|
"preventBinding": "Спри прилепяне на стрелките",
|
||||||
"shapes": "Фигури",
|
"shapes": "Фигури",
|
||||||
"shortcuts": "Клавиши за бърз достъп",
|
"shortcuts": "Клавиши за бърз достъп",
|
||||||
"textFinish": "Завършете редактирането (текст)",
|
"textFinish": "",
|
||||||
"textNewLine": "Добавяне на нов ред (текст)",
|
"textNewLine": "",
|
||||||
"title": "Помощ",
|
"title": "Помощ",
|
||||||
"view": "Преглед",
|
"view": "Преглед",
|
||||||
"zoomToFit": "Приближи докато се виждат всички елементи",
|
"zoomToFit": "Приближи докато се виждат всички елементи",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "",
|
"fileSavedToFilename": "",
|
||||||
"canvas": "",
|
"canvas": "",
|
||||||
"selection": ""
|
"selection": ""
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "",
|
||||||
|
"fd7e14": "",
|
||||||
|
"000000": "",
|
||||||
|
"343a40": "",
|
||||||
|
"495057": "",
|
||||||
|
"c92a2a": "",
|
||||||
|
"a61e4d": "",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "",
|
||||||
|
"d9480f": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "Enganxar",
|
"paste": "Enganxa",
|
||||||
"pasteCharts": "Enganxar diagrames",
|
"pasteCharts": "Enganxa els diagrames",
|
||||||
"selectAll": "Seleccionar tot",
|
"selectAll": "Selecciona-ho tot",
|
||||||
"multiSelect": "Afegir element a la selecció",
|
"multiSelect": "Afegeix un element a la selecció",
|
||||||
"moveCanvas": "Moure el llenç",
|
"moveCanvas": "Mou el llenç",
|
||||||
"cut": "Tallar",
|
"cut": "Retalla",
|
||||||
"copy": "Copiar",
|
"copy": "Copia",
|
||||||
"copyAsPng": "Copiar al porta-retalls com a PNG",
|
"copyAsPng": "Copia al porta-retalls com a PNG",
|
||||||
"copyAsSvg": "Copiar al porta-retalls com a SVG",
|
"copyAsSvg": "Copia al porta-retalls com a SVG",
|
||||||
"bringForward": "Portar endavant",
|
"bringForward": "Porta endavant",
|
||||||
"sendToBack": "Enviar endarrere",
|
"sendToBack": "Envia enrere",
|
||||||
"bringToFront": "Portar al capdavant",
|
"bringToFront": "Porta al davant",
|
||||||
"sendBackward": "Enviar al fons",
|
"sendBackward": "Envia al fons",
|
||||||
"delete": "Eliminar",
|
"delete": "Elimina",
|
||||||
"copyStyles": "Copiar estils",
|
"copyStyles": "Copia els estils",
|
||||||
"pasteStyles": "Enganxar estils",
|
"pasteStyles": "Enganxa els estils",
|
||||||
"stroke": "Color del traç",
|
"stroke": "Color del traç",
|
||||||
"background": "Color del fons",
|
"background": "Color del fons",
|
||||||
"fill": "Estil del fons",
|
"fill": "Estil del fons",
|
||||||
"strokeWidth": "Amplada del traç",
|
"strokeWidth": "Amplada del traç",
|
||||||
"strokeShape": "",
|
"strokeShape": "Estil del traç",
|
||||||
"strokeShape_gel": "",
|
"strokeShape_gel": "Bolígraf de gel",
|
||||||
"strokeShape_fountain": "",
|
"strokeShape_fountain": "Bolígraf de font",
|
||||||
"strokeShape_brush": "",
|
"strokeShape_brush": "Bolígraf de raspall",
|
||||||
"strokeStyle": "Estil del traç",
|
"strokeStyle": "Estil del traç",
|
||||||
"strokeStyle_solid": "Sòlid",
|
"strokeStyle_solid": "Sòlid",
|
||||||
"strokeStyle_dashed": "Guions",
|
"strokeStyle_dashed": "Guions",
|
||||||
@@ -42,10 +42,10 @@
|
|||||||
"fontSize": "Mida de lletra",
|
"fontSize": "Mida de lletra",
|
||||||
"fontFamily": "Tipus de lletra",
|
"fontFamily": "Tipus de lletra",
|
||||||
"onlySelected": "Només seleccionats",
|
"onlySelected": "Només seleccionats",
|
||||||
"withBackground": "",
|
"withBackground": "Fons",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "Insereix l'escena",
|
||||||
"exportEmbedScene_details": "Les dades de l’escena es desaran al fitxer PNG/SVG de manera que es pugui restaurar l’escena.\nAugmentarà la mida del fitxer exportat.",
|
"exportEmbedScene_details": "Les dades de l’escena es desaran al fitxer PNG/SVG de manera que es pugui restaurar l’escena.\nAugmentarà la mida del fitxer exportat.",
|
||||||
"addWatermark": "Afegir \"Fet amb Excalidraw\"",
|
"addWatermark": "Afegeix-hi «Fet amb Excalidraw»",
|
||||||
"handDrawn": "Dibuixat a mà",
|
"handDrawn": "Dibuixat a mà",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
"code": "Codi",
|
"code": "Codi",
|
||||||
@@ -73,73 +73,75 @@
|
|||||||
"actions": "Accions",
|
"actions": "Accions",
|
||||||
"language": "Llengua",
|
"language": "Llengua",
|
||||||
"liveCollaboration": "Col·laboració en directe",
|
"liveCollaboration": "Col·laboració en directe",
|
||||||
"duplicateSelection": "Duplicar",
|
"duplicateSelection": "Duplica",
|
||||||
"untitled": "Sense títol",
|
"untitled": "Sense títol",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"yourName": "El teu nom",
|
"yourName": "El vostre nom",
|
||||||
"madeWithExcalidraw": "Fet amb Excalidraw",
|
"madeWithExcalidraw": "Fet amb Excalidraw",
|
||||||
"group": "Agrupar la selecció",
|
"group": "Agrupa la selecció",
|
||||||
"ungroup": "Desagrupar la selecció",
|
"ungroup": "Desagrupa la selecció",
|
||||||
"collaborators": "Col·laboradors",
|
"collaborators": "Col·laboradors",
|
||||||
"showGrid": "Mostra la graella",
|
"showGrid": "Mostra la graella",
|
||||||
"addToLibrary": "Afegir a la biblioteca",
|
"addToLibrary": "Afegir a la biblioteca",
|
||||||
"removeFromLibrary": "Eliminar de la biblioteca",
|
"removeFromLibrary": "Eliminar de la biblioteca",
|
||||||
"libraryLoadingMessage": "Carregant la biblioteca…",
|
"libraryLoadingMessage": "S'està carregant la biblioteca…",
|
||||||
"libraries": "Explorar biblioteques",
|
"libraries": "Explora les biblioteques",
|
||||||
"loadingScene": "Carregant escena…",
|
"loadingScene": "S'està carregant l'escena…",
|
||||||
"align": "Alinear",
|
"align": "Alinea",
|
||||||
"alignTop": "Alinear a dalt",
|
"alignTop": "Alinea a la part superior",
|
||||||
"alignBottom": "Alinear a baix",
|
"alignBottom": "Alinea a la part inferior",
|
||||||
"alignLeft": "Alinear a l’esquerra",
|
"alignLeft": "Alinea a l’esquerra",
|
||||||
"alignRight": "Alinear a la dreta",
|
"alignRight": "Alinea a la dreta",
|
||||||
"centerVertically": "Centrar verticalment",
|
"centerVertically": "Centra verticalment",
|
||||||
"centerHorizontally": "Centrar horitzontalment",
|
"centerHorizontally": "Centra horitzontalment",
|
||||||
"distributeHorizontally": "Distribuir horitzontalment",
|
"distributeHorizontally": "Distribueix horitzontalment",
|
||||||
"distributeVertically": "Distribuir verticalment",
|
"distributeVertically": "Distribueix verticalment",
|
||||||
"flipHorizontal": "Capgira horitzontalment",
|
"flipHorizontal": "Capgira horitzontalment",
|
||||||
"flipVertical": "Capgira verticalment",
|
"flipVertical": "Capgira verticalment",
|
||||||
"viewMode": "Mode de visualització",
|
"viewMode": "Mode de visualització",
|
||||||
"toggleExportColorScheme": "Canvia l'esquema de colors de l'exportació",
|
"toggleExportColorScheme": "Canvia l'esquema de colors de l'exportació",
|
||||||
"share": "Compartir",
|
"share": "Comparteix",
|
||||||
"toggleTheme": ""
|
"showStroke": "Mostra el selector de color del traç",
|
||||||
|
"showBackground": "Mostra el selector de color de fons",
|
||||||
|
"toggleTheme": "Activa o desactiva el tema"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Netejar el llenç",
|
"clearReset": "Neteja el llenç",
|
||||||
"exportJSON": "",
|
"exportJSON": "Exporta a un fitxer",
|
||||||
"exportImage": "",
|
"exportImage": "Desa com a imatge",
|
||||||
"export": "Exportar",
|
"export": "Exporta",
|
||||||
"exportToPng": "Exportar a PNG",
|
"exportToPng": "Exporta a PNG",
|
||||||
"exportToSvg": "Exportar a SNG",
|
"exportToSvg": "Exporta a SNG",
|
||||||
"copyToClipboard": "Copiar al porta-retalls",
|
"copyToClipboard": "Copia al porta-retalls",
|
||||||
"copyPngToClipboard": "Copiar PNG al porta-retalls",
|
"copyPngToClipboard": "Copia el PNG al porta-retalls",
|
||||||
"scale": "Escala",
|
"scale": "Escala",
|
||||||
"save": "",
|
"save": "Desa al fitxer actual",
|
||||||
"saveAs": "Desar com",
|
"saveAs": "Anomena i desa",
|
||||||
"load": "Carregar",
|
"load": "Carrega",
|
||||||
"getShareableLink": "Obtenir enllaç per compartir",
|
"getShareableLink": "Obté l'enllaç per a compartir",
|
||||||
"close": "Tancar",
|
"close": "Tanca",
|
||||||
"selectLanguage": "Triar idioma",
|
"selectLanguage": "Trieu la llengua",
|
||||||
"scrollBackToContent": "Tornar al contingut",
|
"scrollBackToContent": "Torna al contingut",
|
||||||
"zoomIn": "Ampliar",
|
"zoomIn": "Apropa't",
|
||||||
"zoomOut": "Reduir",
|
"zoomOut": "Allunya't",
|
||||||
"resetZoom": "Restablir zoom",
|
"resetZoom": "Restableix el zoom",
|
||||||
"menu": "Menú",
|
"menu": "Menú",
|
||||||
"done": "Fet",
|
"done": "Fet",
|
||||||
"edit": "Editar",
|
"edit": "Edita",
|
||||||
"undo": "Desfer",
|
"undo": "Desfés",
|
||||||
"redo": "Refer",
|
"redo": "Refés",
|
||||||
"resetLibrary": "Restablir biblioteca",
|
"resetLibrary": "Restableix la biblioteca",
|
||||||
"createNewRoom": "Crear sala nova",
|
"createNewRoom": "Crea una sala nova",
|
||||||
"fullScreen": "Pantalla completa",
|
"fullScreen": "Pantalla completa",
|
||||||
"darkMode": "Mode fosc",
|
"darkMode": "Mode fosc",
|
||||||
"lightMode": "Mode clar",
|
"lightMode": "Mode clar",
|
||||||
"zenMode": "Mode Zen",
|
"zenMode": "Mode zen",
|
||||||
"exitZenMode": "Sortir de modo zen"
|
"exitZenMode": "Surt de mode zen"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"clearReset": "Tot el llenç s'esborrarà. Estàs segur?",
|
"clearReset": "S'esborrarà tot el llenç. N'esteu segur?",
|
||||||
"couldNotCreateShareableLink": "No s'ha pogut crear un enllaç per compartir.",
|
"couldNotCreateShareableLink": "No s'ha pogut crear un enllaç per a compartir.",
|
||||||
"couldNotCreateShareableLinkTooBig": "No s’ha pogut crear un enllaç per compartir: l’escena és massa gran",
|
"couldNotCreateShareableLinkTooBig": "No s’ha pogut crear un enllaç per a compartir: l’escena és massa gran",
|
||||||
"couldNotLoadInvalidFile": "No s'ha pogut carregar un fitxer no vàlid",
|
"couldNotLoadInvalidFile": "No s'ha pogut carregar un fitxer no vàlid",
|
||||||
"importBackendFailed": "Importació fallida.",
|
"importBackendFailed": "Importació fallida.",
|
||||||
"cannotExportEmptyCanvas": "No es pot exportar un llenç buit.",
|
"cannotExportEmptyCanvas": "No es pot exportar un llenç buit.",
|
||||||
@@ -149,13 +151,14 @@
|
|||||||
"loadSceneOverridePrompt": "Si carregas aquest dibuix extern, substituirá el que tens. Vols continuar?",
|
"loadSceneOverridePrompt": "Si carregas aquest dibuix extern, substituirá el que tens. Vols continuar?",
|
||||||
"collabStopOverridePrompt": "Aturar la sessió provocarà la sobreescriptura del dibuix previ, que hi ha desat en l'emmagatzematge local. N'esteu segur?\n\n(Si voleu conservar el dibuix local, tanqueu la pentanya del navegador en comptes d'aturar la sessió).",
|
"collabStopOverridePrompt": "Aturar la sessió provocarà la sobreescriptura del dibuix previ, que hi ha desat en l'emmagatzematge local. N'esteu segur?\n\n(Si voleu conservar el dibuix local, tanqueu la pentanya del navegador en comptes d'aturar la sessió).",
|
||||||
"errorLoadingLibrary": "S'ha produït un error en carregar la biblioteca de tercers.",
|
"errorLoadingLibrary": "S'ha produït un error en carregar la biblioteca de tercers.",
|
||||||
"errorAddingToLibrary": "",
|
"errorAddingToLibrary": "No s'ha pogut afegir l'element a la biblioteca",
|
||||||
"errorRemovingFromLibrary": "",
|
"errorRemovingFromLibrary": "No s'ha pogut eliminar l'element de la biblioteca",
|
||||||
"confirmAddLibrary": "Això afegirà {{numShapes}} forma(es) a la vostra biblioteca. Estàs segur?",
|
"confirmAddLibrary": "Això afegirà {{numShapes}} forma(es) a la vostra biblioteca. Estàs segur?",
|
||||||
"imageDoesNotContainScene": "En aquest moment no s’admet la importació d’imatges.\n\nVolies importar una escena? Sembla que aquesta imatge no conté cap dada d’escena. Ho has activat durant l'exportació?",
|
"imageDoesNotContainScene": "En aquest moment no s’admet la importació d’imatges.\n\nVolies importar una escena? Sembla que aquesta imatge no conté cap dada d’escena. Ho has activat durant l'exportació?",
|
||||||
"cannotRestoreFromImage": "L’escena no s’ha pogut restaurar des d’aquest fitxer d’imatge",
|
"cannotRestoreFromImage": "L’escena no s’ha pogut restaurar des d’aquest fitxer d’imatge",
|
||||||
"invalidSceneUrl": "No s'ha pogut importar l'escena des de l'adreça URL proporcionada. Està malformada o no conté dades Excalidraw JSON vàlides.",
|
"invalidSceneUrl": "No s'ha pogut importar l'escena des de l'adreça URL proporcionada. Està malformada o no conté dades Excalidraw JSON vàlides.",
|
||||||
"resetLibrary": "Tot el llenç s'esborrarà. Estàs segur?"
|
"resetLibrary": "Això buidarà la biblioteca. N'esteu segur?",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selecció",
|
"selection": "Selecció",
|
||||||
@@ -175,55 +178,60 @@
|
|||||||
"shapes": "Formes"
|
"shapes": "Formes"
|
||||||
},
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "Fer clic per dibuixar múltiples punts; arrossegar per una sola línea",
|
"linearElement": "Feu clic per a dibuixar múltiples punts; arrossegueu per a una sola línia",
|
||||||
"freeDraw": "Fer clic i arrosegar, deixar anar al punt final",
|
"freeDraw": "Feu clic i arrossegueu, deixeu anar per a finalitzar",
|
||||||
"text": "Consell: també pots afegir text fent doble clic a qualsevol lloc amb l'eina de selecció",
|
"text": "Consell: també podeu afegir text fent doble clic en qualsevol lloc amb l'eina de selecció",
|
||||||
"linearElementMulti": "Fer clic a l'ultim punt, o polsar Escape o Enter per acabar",
|
"text_selected": "Feu doble clic o premeu Retorn per a editar el text",
|
||||||
|
"text_editing": "Premeu Escapada o Ctrl+Retorn (o Ordre+Retorn) per a finalitzar l'edició",
|
||||||
|
"linearElementMulti": "Feu clic a l'ultim punt, o pitgeu Esc o Retorn per a finalitzar",
|
||||||
"lockAngle": "Per restringir els angles, mantenir premut el majúscul (SHIFT)",
|
"lockAngle": "Per restringir els angles, mantenir premut el majúscul (SHIFT)",
|
||||||
"resize": "Per restringir les proporcions mentres es canvia la mida, mantenir premut el majúscul (SHIFT); per canviar la mida des del centre, mantenir premut ALT",
|
"resize": "Per restringir les proporcions mentres es canvia la mida, mantenir premut el majúscul (SHIFT); per canviar la mida des del centre, mantenir premut ALT",
|
||||||
"rotate": "Per restringir els angles mentre gira, mantenir premut el majúscul (SHIFT)",
|
"rotate": "Per restringir els angles mentre gira, mantenir premut el majúscul (SHIFT)",
|
||||||
"lineEditor_info": "Fes doble clic o premi Enter per editar punts",
|
"lineEditor_info": "Fes doble clic o premi Enter per editar punts",
|
||||||
"lineEditor_pointSelected": "Premi Suprimir per eliminar el punt, CtrlOrCmd+D per duplicar-lo, o arrosega'l per moure'l",
|
"lineEditor_pointSelected": "Premeu Suprimir per a eliminar el punt, CtrlOrCmd+D per a duplicar-lo, o arrossegueu-lo per a moure'l",
|
||||||
"lineEditor_nothingSelected": "Selecciona un punt per moure o eliminar, o manté premut Alt i fes clic per afegir punts nous"
|
"lineEditor_nothingSelected": "Selecciona un punt per moure o eliminar, o manté premut Alt i fes clic per afegir punts nous"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "No es pot mostrar la vista prèvia",
|
"cannotShowPreview": "No es pot mostrar la previsualització",
|
||||||
"canvasTooBig": "Pot ser que el llenç sigui massa gran.",
|
"canvasTooBig": "Pot ser que el llenç sigui massa gran.",
|
||||||
"canvasTooBigTip": "Consell: prova d’acostar una mica els elements més allunyats."
|
"canvasTooBigTip": "Consell: proveu d’acostar una mica els elements més allunyats."
|
||||||
},
|
},
|
||||||
"errorSplash": {
|
"errorSplash": {
|
||||||
"headingMain_pre": "S'ha produït un error. Intentar ",
|
"headingMain_pre": "S'ha produït un error. Proveu ",
|
||||||
"headingMain_button": "recarregar la pàgina.",
|
"headingMain_button": "recarregar la pàgina.",
|
||||||
"clearCanvasMessage": "Si la recarrega no funciona, intentar ",
|
"clearCanvasMessage": "Si la recàrrega no funciona, proveu ",
|
||||||
"clearCanvasMessage_button": "esborrar el llenç.",
|
"clearCanvasMessage_button": "esborrar el llenç.",
|
||||||
"clearCanvasCaveat": " Això resultarà en pèrdua de feina ",
|
"clearCanvasCaveat": " Això resultarà en la pèrdua de feina ",
|
||||||
"trackedToSentry_pre": "L'error amb l'identificador ",
|
"trackedToSentry_pre": "L'error amb l'identificador ",
|
||||||
"trackedToSentry_post": " s'ha rastrejat en el nostre sistema.",
|
"trackedToSentry_post": " s'ha rastrejat en el nostre sistema.",
|
||||||
"openIssueMessage_pre": "Estàvem molt amb compte de no incloure la teva informació de l'escena en l'error. Si la teva escena no és privada, pots fer el seguiment al nostre ",
|
"openIssueMessage_pre": "Anàvem amb molta cura de no incloure la informació de la vostra escena en l'error. Si l'escena no és privada, podeu fer-ne el seguiment al nostre ",
|
||||||
"openIssueMessage_button": "rastrejador d'errors.",
|
"openIssueMessage_button": "rastrejador d'errors.",
|
||||||
"openIssueMessage_post": " Si us plau incloure la informació a continuació copiant i enganxant a GitHub Issues.",
|
"openIssueMessage_post": " Incloeu la informació a continuació copiant i enganxant a GitHub Issues.",
|
||||||
"sceneContent": "Contingut de l'escena:"
|
"sceneContent": "Contingut de l'escena:"
|
||||||
},
|
},
|
||||||
"roomDialog": {
|
"roomDialog": {
|
||||||
"desc_intro": "Pots convidar persones a la teva escena actual a col·laborar amb tu.",
|
"desc_intro": "Podeu convidar persones a la vostra escena actual a col·laborar amb vós.",
|
||||||
"desc_privacy": "No et preocupis, la sessió utilitza el xifratge de punta a punta, de manera que qualsevol cosa que dibuixis quedarà privada. Ni tan sols el nostre servidor podrà veure el que fas.",
|
"desc_privacy": "No us preocupeu, la sessió utilitza el xifratge de punta a punta, de manera que qualsevol cosa que dibuixeu romandrà privada. Ni tan sols el nostre servidor podrà veure què feu.",
|
||||||
"button_startSession": "Iniciar sessió",
|
"button_startSession": "Inicia la sessió",
|
||||||
"button_stopSession": "Aturar sessió",
|
"button_stopSession": "Atura la sessió",
|
||||||
"desc_inProgressIntro": "La sessió de col·laboració en directe està en marxa.",
|
"desc_inProgressIntro": "La sessió de col·laboració en directe està en marxa.",
|
||||||
"desc_shareLink": "Comparteix aquest enllaç amb qualsevol persona amb qui vulguis col·laborar:",
|
"desc_shareLink": "Comparteix aquest enllaç amb qualsevol persona amb qui vulgueu col·laborar:",
|
||||||
"desc_exitSession": "Si aturas la sessió, et desconectarás de la sala, però podrás continuar treballant amb el dibuix localment. Tingues en compte que això no afectarà a altres persones, i encara podran col·laborar en la seva versió.",
|
"desc_exitSession": "Si atureu la sessió, us desconectareu de la sala, però podreu continuar treballant amb el dibuix localment. Tingues en compte que això no afectarà a altres persones, i encara podran col·laborar en la seva versió.",
|
||||||
"shareTitle": "Uniu-vos a una sessió de col·laboració en directe a Excalidraw"
|
"shareTitle": "Uniu-vos a una sessió de col·laboració en directe a Excalidraw"
|
||||||
},
|
},
|
||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "Error"
|
"title": "Error"
|
||||||
},
|
},
|
||||||
"exportDialog": {
|
"exportDialog": {
|
||||||
"disk_title": "",
|
"disk_title": "Desa al disc",
|
||||||
"disk_details": "",
|
"disk_details": "Exporta les dades de l'escena a un fitxer que després podreu importar.",
|
||||||
"disk_button": "",
|
"disk_button": "Desa en un fitxer",
|
||||||
"link_title": "",
|
"link_title": "Enllaç per a compartir",
|
||||||
"link_details": "",
|
"link_details": "Exporta com a un enllaç de només lectura.",
|
||||||
"link_button": ""
|
"link_button": "Exporta a un enllaç",
|
||||||
|
"excalidrawplus_description": "Desa l'escena en el vostre espai de treball Excalidraw+.",
|
||||||
|
"excalidrawplus_button": "Exporta",
|
||||||
|
"excalidrawplus_exportError": "No és possible exportar a Excalidraw+ ara mateix..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Llegiu el nostre blog",
|
"blog": "Llegiu el nostre blog",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Fletxa corba",
|
"curvedArrow": "Fletxa corba",
|
||||||
"curvedLine": "Línia corba",
|
"curvedLine": "Línia corba",
|
||||||
"documentation": "Documentació",
|
"documentation": "Documentació",
|
||||||
|
"doubleClick": "doble clic",
|
||||||
"drag": "arrossega",
|
"drag": "arrossega",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
|
"editSelectedShape": "Edita la forma seleccionada (text, fletxa o línia)",
|
||||||
"github": "Hi heu trobat un problema? Informeu-ne",
|
"github": "Hi heu trobat un problema? Informeu-ne",
|
||||||
"howto": "Seguiu les nostres guies",
|
"howto": "Seguiu les nostres guies",
|
||||||
"or": "o",
|
"or": "o",
|
||||||
"preventBinding": "Prevenir vinculació de la fletxa",
|
"preventBinding": "Prevenir vinculació de la fletxa",
|
||||||
"shapes": "Formes",
|
"shapes": "Formes",
|
||||||
"shortcuts": "Dreceres de teclat",
|
"shortcuts": "Dreceres de teclat",
|
||||||
"textFinish": "Acaba d'editar (text)",
|
"textFinish": "Finalitza l'edició (editor de text)",
|
||||||
"textNewLine": "Afegeix línea nova (text)",
|
"textNewLine": "Afegeix una línia nova (editor de text)",
|
||||||
"title": "Ajuda",
|
"title": "Ajuda",
|
||||||
"view": "Visualització",
|
"view": "Visualització",
|
||||||
"zoomToFit": "Zoom per veure tots els elements",
|
"zoomToFit": "Zoom per veure tots els elements",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "S'ha desat a {filename}",
|
"fileSavedToFilename": "S'ha desat a {filename}",
|
||||||
"canvas": "el llenç",
|
"canvas": "el llenç",
|
||||||
"selection": "la selecció"
|
"selection": "la selecció"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "",
|
||||||
|
"fd7e14": "",
|
||||||
|
"000000": "",
|
||||||
|
"343a40": "",
|
||||||
|
"495057": "",
|
||||||
|
"c92a2a": "",
|
||||||
|
"a61e4d": "",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "",
|
||||||
|
"d9480f": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "Náhled",
|
"viewMode": "Náhled",
|
||||||
"toggleExportColorScheme": "",
|
"toggleExportColorScheme": "",
|
||||||
"share": "Sdílet",
|
"share": "Sdílet",
|
||||||
|
"showStroke": "",
|
||||||
|
"showBackground": "",
|
||||||
"toggleTheme": "Přepnout tmavý řežim"
|
"toggleTheme": "Přepnout tmavý řežim"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "",
|
"imageDoesNotContainScene": "",
|
||||||
"cannotRestoreFromImage": "",
|
"cannotRestoreFromImage": "",
|
||||||
"invalidSceneUrl": "",
|
"invalidSceneUrl": "",
|
||||||
"resetLibrary": ""
|
"resetLibrary": "",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Výběr",
|
"selection": "Výběr",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "",
|
"linearElement": "",
|
||||||
"freeDraw": "",
|
"freeDraw": "",
|
||||||
"text": "",
|
"text": "",
|
||||||
|
"text_selected": "",
|
||||||
|
"text_editing": "",
|
||||||
"linearElementMulti": "",
|
"linearElementMulti": "",
|
||||||
"lockAngle": "",
|
"lockAngle": "",
|
||||||
"resize": "",
|
"resize": "",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "",
|
"disk_button": "",
|
||||||
"link_title": "",
|
"link_title": "",
|
||||||
"link_details": "",
|
"link_details": "",
|
||||||
"link_button": ""
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "",
|
"blog": "",
|
||||||
@@ -231,8 +239,10 @@
|
|||||||
"curvedArrow": "",
|
"curvedArrow": "",
|
||||||
"curvedLine": "",
|
"curvedLine": "",
|
||||||
"documentation": "",
|
"documentation": "",
|
||||||
|
"doubleClick": "",
|
||||||
"drag": "tažení",
|
"drag": "tažení",
|
||||||
"editor": "",
|
"editor": "",
|
||||||
|
"editSelectedShape": "",
|
||||||
"github": "",
|
"github": "",
|
||||||
"howto": "",
|
"howto": "",
|
||||||
"or": "nebo",
|
"or": "nebo",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "",
|
"fileSavedToFilename": "",
|
||||||
"canvas": "plátno",
|
"canvas": "plátno",
|
||||||
"selection": "výběr"
|
"selection": "výběr"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "",
|
||||||
|
"fd7e14": "",
|
||||||
|
"000000": "",
|
||||||
|
"343a40": "",
|
||||||
|
"495057": "",
|
||||||
|
"c92a2a": "",
|
||||||
|
"a61e4d": "",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "",
|
||||||
|
"d9480f": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
334
src/locales/da-DK.json
Normal file
334
src/locales/da-DK.json
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"paste": "Indsæt",
|
||||||
|
"pasteCharts": "",
|
||||||
|
"selectAll": "Marker alle",
|
||||||
|
"multiSelect": "",
|
||||||
|
"moveCanvas": "",
|
||||||
|
"cut": "",
|
||||||
|
"copy": "Kopier",
|
||||||
|
"copyAsPng": "Kopier til klippebord som PNG",
|
||||||
|
"copyAsSvg": "Kopier til klippebord som SVG",
|
||||||
|
"bringForward": "",
|
||||||
|
"sendToBack": "",
|
||||||
|
"bringToFront": "",
|
||||||
|
"sendBackward": "",
|
||||||
|
"delete": "Fjern",
|
||||||
|
"copyStyles": "",
|
||||||
|
"pasteStyles": "",
|
||||||
|
"stroke": "Linje",
|
||||||
|
"background": "Baggrund",
|
||||||
|
"fill": "",
|
||||||
|
"strokeWidth": "Linjebredde",
|
||||||
|
"strokeShape": "Linjeform",
|
||||||
|
"strokeShape_gel": "",
|
||||||
|
"strokeShape_fountain": "",
|
||||||
|
"strokeShape_brush": "",
|
||||||
|
"strokeStyle": "",
|
||||||
|
"strokeStyle_solid": "",
|
||||||
|
"strokeStyle_dashed": "",
|
||||||
|
"strokeStyle_dotted": "",
|
||||||
|
"sloppiness": "",
|
||||||
|
"opacity": "",
|
||||||
|
"textAlign": "",
|
||||||
|
"edges": "",
|
||||||
|
"sharp": "",
|
||||||
|
"round": "",
|
||||||
|
"arrowheads": "",
|
||||||
|
"arrowhead_none": "",
|
||||||
|
"arrowhead_arrow": "Pil",
|
||||||
|
"arrowhead_bar": "",
|
||||||
|
"arrowhead_dot": "",
|
||||||
|
"fontSize": "",
|
||||||
|
"fontFamily": "",
|
||||||
|
"onlySelected": "",
|
||||||
|
"withBackground": "",
|
||||||
|
"exportEmbedScene": "",
|
||||||
|
"exportEmbedScene_details": "",
|
||||||
|
"addWatermark": "",
|
||||||
|
"handDrawn": "",
|
||||||
|
"normal": "",
|
||||||
|
"code": "",
|
||||||
|
"small": "",
|
||||||
|
"medium": "",
|
||||||
|
"large": "",
|
||||||
|
"veryLarge": "",
|
||||||
|
"solid": "",
|
||||||
|
"hachure": "",
|
||||||
|
"crossHatch": "",
|
||||||
|
"thin": "",
|
||||||
|
"bold": "Fed",
|
||||||
|
"left": "Venstre",
|
||||||
|
"center": "Centrere",
|
||||||
|
"right": "Højre",
|
||||||
|
"extraBold": "Extra fed",
|
||||||
|
"architect": "",
|
||||||
|
"artist": "",
|
||||||
|
"cartoonist": "",
|
||||||
|
"fileTitle": "Filnavn",
|
||||||
|
"colorPicker": "Farvevælger",
|
||||||
|
"canvasBackground": "",
|
||||||
|
"drawingCanvas": "",
|
||||||
|
"layers": "",
|
||||||
|
"actions": "",
|
||||||
|
"language": "Sprog",
|
||||||
|
"liveCollaboration": "Direkte samarbejde",
|
||||||
|
"duplicateSelection": "",
|
||||||
|
"untitled": "",
|
||||||
|
"name": "",
|
||||||
|
"yourName": "Dit navn",
|
||||||
|
"madeWithExcalidraw": "Fremstillet med Excalidraw",
|
||||||
|
"group": "",
|
||||||
|
"ungroup": "",
|
||||||
|
"collaborators": "",
|
||||||
|
"showGrid": "",
|
||||||
|
"addToLibrary": "",
|
||||||
|
"removeFromLibrary": "",
|
||||||
|
"libraryLoadingMessage": "",
|
||||||
|
"libraries": "",
|
||||||
|
"loadingScene": "",
|
||||||
|
"align": "",
|
||||||
|
"alignTop": "",
|
||||||
|
"alignBottom": "",
|
||||||
|
"alignLeft": "",
|
||||||
|
"alignRight": "",
|
||||||
|
"centerVertically": "",
|
||||||
|
"centerHorizontally": "",
|
||||||
|
"distributeHorizontally": "",
|
||||||
|
"distributeVertically": "",
|
||||||
|
"flipHorizontal": "",
|
||||||
|
"flipVertical": "",
|
||||||
|
"viewMode": "",
|
||||||
|
"toggleExportColorScheme": "",
|
||||||
|
"share": "Del",
|
||||||
|
"showStroke": "",
|
||||||
|
"showBackground": "",
|
||||||
|
"toggleTheme": ""
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"clearReset": "",
|
||||||
|
"exportJSON": "",
|
||||||
|
"exportImage": "",
|
||||||
|
"export": "",
|
||||||
|
"exportToPng": "",
|
||||||
|
"exportToSvg": "",
|
||||||
|
"copyToClipboard": "Kopier til klippebord",
|
||||||
|
"copyPngToClipboard": "Kopier PNG til klippebord",
|
||||||
|
"scale": "",
|
||||||
|
"save": "",
|
||||||
|
"saveAs": "",
|
||||||
|
"load": "",
|
||||||
|
"getShareableLink": "",
|
||||||
|
"close": "",
|
||||||
|
"selectLanguage": "Vælg sprog",
|
||||||
|
"scrollBackToContent": "Scroll tilbage til indhold",
|
||||||
|
"zoomIn": "Zoom ind",
|
||||||
|
"zoomOut": "Zoom ud",
|
||||||
|
"resetZoom": "Nulstil zoom",
|
||||||
|
"menu": "Menu",
|
||||||
|
"done": "Færdig",
|
||||||
|
"edit": "Rediger",
|
||||||
|
"undo": "Fortryd",
|
||||||
|
"redo": "Gendan",
|
||||||
|
"resetLibrary": "",
|
||||||
|
"createNewRoom": "Opret nyt rum",
|
||||||
|
"fullScreen": "Fuld skærm",
|
||||||
|
"darkMode": "Mørk tilstand",
|
||||||
|
"lightMode": "Lys baggrund",
|
||||||
|
"zenMode": "",
|
||||||
|
"exitZenMode": ""
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"clearReset": "",
|
||||||
|
"couldNotCreateShareableLink": "",
|
||||||
|
"couldNotCreateShareableLinkTooBig": "",
|
||||||
|
"couldNotLoadInvalidFile": "",
|
||||||
|
"importBackendFailed": "",
|
||||||
|
"cannotExportEmptyCanvas": "",
|
||||||
|
"couldNotCopyToClipboard": "Kunne ikke kopiere til klippebord. Prøv at bruge Chrome browser.",
|
||||||
|
"decryptFailed": "",
|
||||||
|
"uploadedSecurly": "",
|
||||||
|
"loadSceneOverridePrompt": "",
|
||||||
|
"collabStopOverridePrompt": "",
|
||||||
|
"errorLoadingLibrary": "",
|
||||||
|
"errorAddingToLibrary": "",
|
||||||
|
"errorRemovingFromLibrary": "",
|
||||||
|
"confirmAddLibrary": "",
|
||||||
|
"imageDoesNotContainScene": "",
|
||||||
|
"cannotRestoreFromImage": "",
|
||||||
|
"invalidSceneUrl": "",
|
||||||
|
"resetLibrary": "",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
|
},
|
||||||
|
"toolBar": {
|
||||||
|
"selection": "",
|
||||||
|
"rectangle": "",
|
||||||
|
"diamond": "",
|
||||||
|
"ellipse": "",
|
||||||
|
"arrow": "",
|
||||||
|
"line": "",
|
||||||
|
"freedraw": "",
|
||||||
|
"text": "",
|
||||||
|
"library": "",
|
||||||
|
"lock": ""
|
||||||
|
},
|
||||||
|
"headings": {
|
||||||
|
"canvasActions": "",
|
||||||
|
"selectedShapeActions": "",
|
||||||
|
"shapes": ""
|
||||||
|
},
|
||||||
|
"hints": {
|
||||||
|
"linearElement": "",
|
||||||
|
"freeDraw": "Klik og træk, slip når du er færdig",
|
||||||
|
"text": "",
|
||||||
|
"text_selected": "",
|
||||||
|
"text_editing": "",
|
||||||
|
"linearElementMulti": "",
|
||||||
|
"lockAngle": "",
|
||||||
|
"resize": "",
|
||||||
|
"rotate": "",
|
||||||
|
"lineEditor_info": "",
|
||||||
|
"lineEditor_pointSelected": "",
|
||||||
|
"lineEditor_nothingSelected": ""
|
||||||
|
},
|
||||||
|
"canvasError": {
|
||||||
|
"cannotShowPreview": "",
|
||||||
|
"canvasTooBig": "",
|
||||||
|
"canvasTooBigTip": ""
|
||||||
|
},
|
||||||
|
"errorSplash": {
|
||||||
|
"headingMain_pre": "",
|
||||||
|
"headingMain_button": "",
|
||||||
|
"clearCanvasMessage": "",
|
||||||
|
"clearCanvasMessage_button": "",
|
||||||
|
"clearCanvasCaveat": "",
|
||||||
|
"trackedToSentry_pre": "",
|
||||||
|
"trackedToSentry_post": "",
|
||||||
|
"openIssueMessage_pre": "",
|
||||||
|
"openIssueMessage_button": "",
|
||||||
|
"openIssueMessage_post": " Kopiere og indsæt venligst oplysningerne nedenfor i et GitHub problem.",
|
||||||
|
"sceneContent": "Scene indhold:"
|
||||||
|
},
|
||||||
|
"roomDialog": {
|
||||||
|
"desc_intro": "Du kan invitere folk til din nuværende scene, så de kan samarbejde med dig.",
|
||||||
|
"desc_privacy": "Bare rolig, sessionen bruger end-to-end kryptering, så uanset hvad du tegner vil det forblive privat. Ikke engang vores server vil kunne se, hvad du kommer op med.",
|
||||||
|
"button_startSession": "Start session",
|
||||||
|
"button_stopSession": "Stop session",
|
||||||
|
"desc_inProgressIntro": "Live-samarbejde session er nu begyndt.",
|
||||||
|
"desc_shareLink": "Del dette link med enhver, du ønsker at samarbejde med:",
|
||||||
|
"desc_exitSession": "",
|
||||||
|
"shareTitle": ""
|
||||||
|
},
|
||||||
|
"errorDialog": {
|
||||||
|
"title": "Fejl"
|
||||||
|
},
|
||||||
|
"exportDialog": {
|
||||||
|
"disk_title": "Gem til disk",
|
||||||
|
"disk_details": "",
|
||||||
|
"disk_button": "",
|
||||||
|
"link_title": "",
|
||||||
|
"link_details": "",
|
||||||
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
|
},
|
||||||
|
"helpDialog": {
|
||||||
|
"blog": "Læs vores blog",
|
||||||
|
"click": "",
|
||||||
|
"curvedArrow": "",
|
||||||
|
"curvedLine": "",
|
||||||
|
"documentation": "",
|
||||||
|
"doubleClick": "",
|
||||||
|
"drag": "",
|
||||||
|
"editor": "",
|
||||||
|
"editSelectedShape": "",
|
||||||
|
"github": "",
|
||||||
|
"howto": "",
|
||||||
|
"or": "",
|
||||||
|
"preventBinding": "",
|
||||||
|
"shapes": "",
|
||||||
|
"shortcuts": "",
|
||||||
|
"textFinish": "",
|
||||||
|
"textNewLine": "",
|
||||||
|
"title": "",
|
||||||
|
"view": "",
|
||||||
|
"zoomToFit": "",
|
||||||
|
"zoomToSelection": ""
|
||||||
|
},
|
||||||
|
"encrypted": {
|
||||||
|
"tooltip": "",
|
||||||
|
"link": ""
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"angle": "",
|
||||||
|
"element": "",
|
||||||
|
"elements": "",
|
||||||
|
"height": "",
|
||||||
|
"scene": "",
|
||||||
|
"selected": "",
|
||||||
|
"storage": "",
|
||||||
|
"title": "Statistik for nørder",
|
||||||
|
"total": "",
|
||||||
|
"version": "",
|
||||||
|
"versionCopy": "Klik for at kopiere",
|
||||||
|
"versionNotAvailable": "",
|
||||||
|
"width": "Bredde"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"copyStyles": "Kopieret stilarter.",
|
||||||
|
"copyToClipboard": "Kopieret til klippebord.",
|
||||||
|
"copyToClipboardAsPng": "Kopieret {{exportSelection}} til klippebord som PNG\n({{exportColorScheme}})",
|
||||||
|
"fileSaved": "Fil gemt.",
|
||||||
|
"fileSavedToFilename": "Gemt som {filename}",
|
||||||
|
"canvas": "canvas",
|
||||||
|
"selection": "markering"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "",
|
||||||
|
"fd7e14": "",
|
||||||
|
"000000": "",
|
||||||
|
"343a40": "",
|
||||||
|
"495057": "",
|
||||||
|
"c92a2a": "",
|
||||||
|
"a61e4d": "",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "",
|
||||||
|
"d9480f": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "Ansichtsmodus",
|
"viewMode": "Ansichtsmodus",
|
||||||
"toggleExportColorScheme": "Exportfarbschema umschalten",
|
"toggleExportColorScheme": "Exportfarbschema umschalten",
|
||||||
"share": "Teilen",
|
"share": "Teilen",
|
||||||
|
"showStroke": "Auswahl für Strichfarbe anzeigen",
|
||||||
|
"showBackground": "Hintergrundfarbe auswählen",
|
||||||
"toggleTheme": "Design umschalten"
|
"toggleTheme": "Design umschalten"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "Das Importieren von Bildern wird derzeit nicht unterstützt.\n\nMöchtest du eine Szene importieren? Dieses Bild scheint keine Zeichnungsdaten zu enthalten. Hast du dies beim Exportieren aktiviert?",
|
"imageDoesNotContainScene": "Das Importieren von Bildern wird derzeit nicht unterstützt.\n\nMöchtest du eine Szene importieren? Dieses Bild scheint keine Zeichnungsdaten zu enthalten. Hast du dies beim Exportieren aktiviert?",
|
||||||
"cannotRestoreFromImage": "Die Zeichnung konnte aus dieser Bilddatei nicht wiederhergestellt werden",
|
"cannotRestoreFromImage": "Die Zeichnung konnte aus dieser Bilddatei nicht wiederhergestellt werden",
|
||||||
"invalidSceneUrl": "Die Szene konnte nicht von der angegebenen URL importiert werden. Sie ist entweder fehlerhaft oder enthält keine gültigen Excalidraw JSON-Daten.",
|
"invalidSceneUrl": "Die Szene konnte nicht von der angegebenen URL importiert werden. Sie ist entweder fehlerhaft oder enthält keine gültigen Excalidraw JSON-Daten.",
|
||||||
"resetLibrary": "Dieses löscht deine Bibliothek. Bist du sicher?"
|
"resetLibrary": "Dieses löscht deine Bibliothek. Bist du sicher?",
|
||||||
|
"invalidEncryptionKey": "Verschlüsselungsschlüssel muss 22 Zeichen lang sein. Die Live-Zusammenarbeit ist deaktiviert."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Auswahl",
|
"selection": "Auswahl",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Klicken für Linie mit mehreren Punkten, Ziehen für einzelne Linie",
|
"linearElement": "Klicken für Linie mit mehreren Punkten, Ziehen für einzelne Linie",
|
||||||
"freeDraw": "Klicke und ziehe. Lass los, wenn du fertig bist",
|
"freeDraw": "Klicke und ziehe. Lass los, wenn du fertig bist",
|
||||||
"text": "Tipp: Du kannst auch Text hinzufügen, indem du mit dem Auswahlwerkzeug auf eine beliebige Stelle doppelklickst",
|
"text": "Tipp: Du kannst auch Text hinzufügen, indem du mit dem Auswahlwerkzeug auf eine beliebige Stelle doppelklickst",
|
||||||
|
"text_selected": "Doppelklicken oder Eingabetaste drücken, um Text zu bearbeiten",
|
||||||
|
"text_editing": "Drücke Escape oder Strg/Cmd+Eingabetaste, um die Bearbeitung abzuschließen",
|
||||||
"linearElementMulti": "Zum Beenden auf den letzten Punkt klicken oder Escape oder Eingabe drücken",
|
"linearElementMulti": "Zum Beenden auf den letzten Punkt klicken oder Escape oder Eingabe drücken",
|
||||||
"lockAngle": "Du kannst Winkel einschränken, indem du SHIFT gedrückt hältst",
|
"lockAngle": "Du kannst Winkel einschränken, indem du SHIFT gedrückt hältst",
|
||||||
"resize": "Du kannst die Proportionen einschränken, indem du SHIFT während der Größenänderung gedrückt hältst. Halte ALT gedrückt, um die Größe vom Zentrum aus zu ändern",
|
"resize": "Du kannst die Proportionen einschränken, indem du SHIFT während der Größenänderung gedrückt hältst. Halte ALT gedrückt, um die Größe vom Zentrum aus zu ändern",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "In Datei speichern",
|
"disk_button": "In Datei speichern",
|
||||||
"link_title": "Teilbarer Link",
|
"link_title": "Teilbarer Link",
|
||||||
"link_details": "Als schreibgeschützten Link exportieren.",
|
"link_details": "Als schreibgeschützten Link exportieren.",
|
||||||
"link_button": "Als Link exportieren"
|
"link_button": "Als Link exportieren",
|
||||||
|
"excalidrawplus_description": "Speichere die Szene in deinem Excalidraw+ Arbeitsbereich.",
|
||||||
|
"excalidrawplus_button": "Exportieren",
|
||||||
|
"excalidrawplus_exportError": "Konnte nicht nach Excalidraw+ exportieren..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Lies unseren Blog",
|
"blog": "Lies unseren Blog",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Gebogener Pfeil",
|
"curvedArrow": "Gebogener Pfeil",
|
||||||
"curvedLine": "Gebogene Linie",
|
"curvedLine": "Gebogene Linie",
|
||||||
"documentation": "Dokumentation",
|
"documentation": "Dokumentation",
|
||||||
|
"doubleClick": "doppelklicken",
|
||||||
"drag": "ziehen",
|
"drag": "ziehen",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
|
"editSelectedShape": "Ausgewählte Form bearbeiten (Text/Pfeil/Linie)",
|
||||||
"github": "Ein Problem gefunden? Informiere uns",
|
"github": "Ein Problem gefunden? Informiere uns",
|
||||||
"howto": "Folge unseren Anleitungen",
|
"howto": "Folge unseren Anleitungen",
|
||||||
"or": "oder",
|
"or": "oder",
|
||||||
"preventBinding": "Pfeil-Bindung verhindern",
|
"preventBinding": "Pfeil-Bindung verhindern",
|
||||||
"shapes": "Formen",
|
"shapes": "Formen",
|
||||||
"shortcuts": "Tastaturkürzel",
|
"shortcuts": "Tastaturkürzel",
|
||||||
"textFinish": "Bearbeiten beenden (Text)",
|
"textFinish": "Bearbeitung beenden (Texteditor)",
|
||||||
"textNewLine": "Neue Zeile hinzufügen (Text)",
|
"textNewLine": "Neue Zeile hinzufügen (Texteditor)",
|
||||||
"title": "Hilfe",
|
"title": "Hilfe",
|
||||||
"view": "Ansicht",
|
"view": "Ansicht",
|
||||||
"zoomToFit": "Zoomen um alle Elemente einzupassen",
|
"zoomToFit": "Zoomen um alle Elemente einzupassen",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "Als {filename} gespeichert",
|
"fileSavedToFilename": "Als {filename} gespeichert",
|
||||||
"canvas": "Zeichenfläche",
|
"canvas": "Zeichenfläche",
|
||||||
"selection": "Auswahl"
|
"selection": "Auswahl"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "Weiß",
|
||||||
|
"f8f9fa": "Grau 0",
|
||||||
|
"f1f3f5": "Grau 1",
|
||||||
|
"fff5f5": "Rot 0",
|
||||||
|
"fff0f6": "Pink 0",
|
||||||
|
"f8f0fc": "Traube 0",
|
||||||
|
"f3f0ff": "Violett 0",
|
||||||
|
"edf2ff": "Indigo 0",
|
||||||
|
"e7f5ff": "Blau 0",
|
||||||
|
"e3fafc": "Cyan 0",
|
||||||
|
"e6fcf5": "Teal 0",
|
||||||
|
"ebfbee": "Grün 0",
|
||||||
|
"f4fce3": "Limette 0",
|
||||||
|
"fff9db": "Gelb 0",
|
||||||
|
"fff4e6": "Orange 0",
|
||||||
|
"transparent": "Transparent",
|
||||||
|
"ced4da": "Grau 4",
|
||||||
|
"868e96": "Grau 6",
|
||||||
|
"fa5252": "Rot 6",
|
||||||
|
"e64980": "Pink 6",
|
||||||
|
"be4bdb": "Traube 6",
|
||||||
|
"7950f2": "Violett 6",
|
||||||
|
"4c6ef5": "Indigo 6",
|
||||||
|
"228be6": "Blau 6",
|
||||||
|
"15aabf": "Cyan 6",
|
||||||
|
"12b886": "Teal 6",
|
||||||
|
"40c057": "Grün 6",
|
||||||
|
"82c91e": "Limette 6",
|
||||||
|
"fab005": "Gelb 6",
|
||||||
|
"fd7e14": "Orange 6",
|
||||||
|
"000000": "Schwarz",
|
||||||
|
"343a40": "Grau 8",
|
||||||
|
"495057": "Grau 7",
|
||||||
|
"c92a2a": "Rot 9",
|
||||||
|
"a61e4d": "Pink 9",
|
||||||
|
"862e9c": "Traube 9",
|
||||||
|
"5f3dc4": "Violett 9",
|
||||||
|
"364fc7": "Indigo 9",
|
||||||
|
"1864ab": "Blau 9",
|
||||||
|
"0b7285": "Cyan 9",
|
||||||
|
"087f5b": "Teal 9",
|
||||||
|
"2b8a3e": "Grün 9",
|
||||||
|
"5c940d": "Limette 9",
|
||||||
|
"e67700": "Gelb 9",
|
||||||
|
"d9480f": "Orange 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "Λειτουργία προβολής",
|
"viewMode": "Λειτουργία προβολής",
|
||||||
"toggleExportColorScheme": "Εναλλαγή εξαγωγής θέματος χρωμάτων",
|
"toggleExportColorScheme": "Εναλλαγή εξαγωγής θέματος χρωμάτων",
|
||||||
"share": "Κοινοποίηση",
|
"share": "Κοινοποίηση",
|
||||||
|
"showStroke": "",
|
||||||
|
"showBackground": "",
|
||||||
"toggleTheme": ""
|
"toggleTheme": ""
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "Η εισαγωγή εικόνων δεν υποστηρίζεται αυτή τη στιγμή.\n\nΜήπως θέλετε να εισαγάγετε μια σκηνή; Αυτή η εικόνα δεν φαίνεται να περιέχει δεδομένα σκηνής. Έχετε ενεργοποιήσει αυτό κατά την εξαγωγή;",
|
"imageDoesNotContainScene": "Η εισαγωγή εικόνων δεν υποστηρίζεται αυτή τη στιγμή.\n\nΜήπως θέλετε να εισαγάγετε μια σκηνή; Αυτή η εικόνα δεν φαίνεται να περιέχει δεδομένα σκηνής. Έχετε ενεργοποιήσει αυτό κατά την εξαγωγή;",
|
||||||
"cannotRestoreFromImage": "Η σκηνή δεν ήταν δυνατό να αποκατασταθεί από αυτό το αρχείο εικόνας",
|
"cannotRestoreFromImage": "Η σκηνή δεν ήταν δυνατό να αποκατασταθεί από αυτό το αρχείο εικόνας",
|
||||||
"invalidSceneUrl": "",
|
"invalidSceneUrl": "",
|
||||||
"resetLibrary": "Αυτό θα καθαρίσει τη βιβλιοθήκη σας. Είστε σίγουροι;"
|
"resetLibrary": "Αυτό θα καθαρίσει τη βιβλιοθήκη σας. Είστε σίγουροι;",
|
||||||
|
"invalidEncryptionKey": "Το κλειδί κρυπτογράφησης πρέπει να είναι 22 χαρακτήρες. Η ζωντανή συνεργασία είναι απενεργοποιημένη."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Επιλογή",
|
"selection": "Επιλογή",
|
||||||
@@ -164,7 +167,7 @@
|
|||||||
"ellipse": "Έλλειψη",
|
"ellipse": "Έλλειψη",
|
||||||
"arrow": "Βέλος",
|
"arrow": "Βέλος",
|
||||||
"line": "Γραμμή",
|
"line": "Γραμμή",
|
||||||
"freedraw": "",
|
"freedraw": "Σχεδίαση",
|
||||||
"text": "Κείμενο",
|
"text": "Κείμενο",
|
||||||
"library": "Βιβλιοθήκη",
|
"library": "Βιβλιοθήκη",
|
||||||
"lock": "Κράτησε επιλεγμένο το εργαλείο μετά το σχέδιο"
|
"lock": "Κράτησε επιλεγμένο το εργαλείο μετά το σχέδιο"
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Κάνε κλικ για να ξεκινήσεις πολλαπλά σημεία, σύρε για μια γραμμή",
|
"linearElement": "Κάνε κλικ για να ξεκινήσεις πολλαπλά σημεία, σύρε για μια γραμμή",
|
||||||
"freeDraw": "Κάντε κλικ και σύρτε, απελευθερώσατε όταν έχετε τελειώσει",
|
"freeDraw": "Κάντε κλικ και σύρτε, απελευθερώσατε όταν έχετε τελειώσει",
|
||||||
"text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών",
|
"text": "Tip: μπορείτε επίσης να προσθέστε κείμενο με διπλό-κλικ οπουδήποτε με το εργαλείο επιλογών",
|
||||||
|
"text_selected": "Κάντε διπλό κλικ ή πατήστε ENTER για να επεξεργαστείτε το κείμενο",
|
||||||
|
"text_editing": "Πατήστε Escape ή CtrlOrCmd+ENTER για να ολοκληρώσετε την επεξεργασία",
|
||||||
"linearElementMulti": "Κάνε κλικ στο τελευταίο σημείο ή πάτησε Escape ή Enter για να τελειώσεις",
|
"linearElementMulti": "Κάνε κλικ στο τελευταίο σημείο ή πάτησε Escape ή Enter για να τελειώσεις",
|
||||||
"lockAngle": "Μπορείτε να περιορίσετε τη γωνία κρατώντας πατημένο το SHIFT",
|
"lockAngle": "Μπορείτε να περιορίσετε τη γωνία κρατώντας πατημένο το SHIFT",
|
||||||
"resize": "Μπορείς να περιορίσεις τις αναλογίες κρατώντας το SHIFT ενώ αλλάζεις μέγεθος,\nκράτησε πατημένο το ALT για αλλαγή μεγέθους από το κέντρο",
|
"resize": "Μπορείς να περιορίσεις τις αναλογίες κρατώντας το SHIFT ενώ αλλάζεις μέγεθος,\nκράτησε πατημένο το ALT για αλλαγή μεγέθους από το κέντρο",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "",
|
"disk_button": "",
|
||||||
"link_title": "",
|
"link_title": "",
|
||||||
"link_details": "",
|
"link_details": "",
|
||||||
"link_button": ""
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Διαβάστε το Blog μας",
|
"blog": "Διαβάστε το Blog μας",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Κυρτό βέλος",
|
"curvedArrow": "Κυρτό βέλος",
|
||||||
"curvedLine": "Κυρτή γραμμή",
|
"curvedLine": "Κυρτή γραμμή",
|
||||||
"documentation": "Εγχειρίδιο",
|
"documentation": "Εγχειρίδιο",
|
||||||
|
"doubleClick": "",
|
||||||
"drag": "σύρε",
|
"drag": "σύρε",
|
||||||
"editor": "Επεξεργαστής",
|
"editor": "Επεξεργαστής",
|
||||||
|
"editSelectedShape": "",
|
||||||
"github": "Βρήκατε πρόβλημα; Υποβάλετε το",
|
"github": "Βρήκατε πρόβλημα; Υποβάλετε το",
|
||||||
"howto": "Ακολουθήστε τους οδηγούς μας",
|
"howto": "Ακολουθήστε τους οδηγούς μας",
|
||||||
"or": "ή",
|
"or": "ή",
|
||||||
"preventBinding": "Αποτροπή δέσμευσης βέλων",
|
"preventBinding": "Αποτροπή δέσμευσης βέλων",
|
||||||
"shapes": "Σχήματα",
|
"shapes": "Σχήματα",
|
||||||
"shortcuts": "Συντομεύσεις πληκτρολογίου",
|
"shortcuts": "Συντομεύσεις πληκτρολογίου",
|
||||||
"textFinish": "Ολοκλήρωση επεξεργασίας (κείμενο)",
|
"textFinish": "",
|
||||||
"textNewLine": "Προσθήκη νέας γραμμής (κείμενο)",
|
"textNewLine": "",
|
||||||
"title": "Βοήθεια",
|
"title": "Βοήθεια",
|
||||||
"view": "Προβολή",
|
"view": "Προβολή",
|
||||||
"zoomToFit": "Zoom ώστε να χωρέσουν όλα τα στοιχεία",
|
"zoomToFit": "Zoom ώστε να χωρέσουν όλα τα στοιχεία",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "Αποθηκεύτηκε στο {filename}",
|
"fileSavedToFilename": "Αποθηκεύτηκε στο {filename}",
|
||||||
"canvas": "καμβάς",
|
"canvas": "καμβάς",
|
||||||
"selection": "επιλογή"
|
"selection": "επιλογή"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "",
|
||||||
|
"fd7e14": "",
|
||||||
|
"000000": "",
|
||||||
|
"343a40": "",
|
||||||
|
"495057": "",
|
||||||
|
"c92a2a": "",
|
||||||
|
"a61e4d": "",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "",
|
||||||
|
"d9480f": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "Importing images isn't supported at the moment.\n\nDid you want to import a scene? This image does not seem to contain any scene data. Have you enabled this during export?",
|
"imageDoesNotContainScene": "Importing images isn't supported at the moment.\n\nDid you want to import a scene? This image does not seem to contain any scene data. Have you enabled this during export?",
|
||||||
"cannotRestoreFromImage": "Scene couldn't be restored from this image file",
|
"cannotRestoreFromImage": "Scene couldn't be restored from this image file",
|
||||||
"invalidSceneUrl": "Couldn't import scene from the supplied URL. It's either malformed, or doesn't contain valid Excalidraw JSON data.",
|
"invalidSceneUrl": "Couldn't import scene from the supplied URL. It's either malformed, or doesn't contain valid Excalidraw JSON data.",
|
||||||
"resetLibrary": "This will clear your library. Are you sure?"
|
"resetLibrary": "This will clear your library. Are you sure?",
|
||||||
|
"invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selection",
|
"selection": "Selection",
|
||||||
@@ -180,6 +181,8 @@
|
|||||||
"linearElement": "Click to start multiple points, drag for single line",
|
"linearElement": "Click to start multiple points, drag for single line",
|
||||||
"freeDraw": "Click and drag, release when you're finished",
|
"freeDraw": "Click and drag, release when you're finished",
|
||||||
"text": "Tip: you can also add text by double-clicking anywhere with the selection tool",
|
"text": "Tip: you can also add text by double-clicking anywhere with the selection tool",
|
||||||
|
"text_selected": "Double-click or press ENTER to edit text",
|
||||||
|
"text_editing": "Press Escape or CtrlOrCmd+ENTER to finish editing",
|
||||||
"linearElementMulti": "Click on last point or press Escape or Enter to finish",
|
"linearElementMulti": "Click on last point or press Escape or Enter to finish",
|
||||||
"lockAngle": "You can constrain angle by holding SHIFT",
|
"lockAngle": "You can constrain angle by holding SHIFT",
|
||||||
"resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center",
|
"resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center",
|
||||||
@@ -225,7 +228,10 @@
|
|||||||
"disk_button": "Save to file",
|
"disk_button": "Save to file",
|
||||||
"link_title": "Shareable link",
|
"link_title": "Shareable link",
|
||||||
"link_details": "Export as a read-only link.",
|
"link_details": "Export as a read-only link.",
|
||||||
"link_button": "Export to Link"
|
"link_button": "Export to Link",
|
||||||
|
"excalidrawplus_description": "Save the scene to your Excalidraw+ workspace.",
|
||||||
|
"excalidrawplus_button": "Export",
|
||||||
|
"excalidrawplus_exportError": "Couldn't export to Excalidraw+ at this moment..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Read our blog",
|
"blog": "Read our blog",
|
||||||
@@ -233,16 +239,18 @@
|
|||||||
"curvedArrow": "Curved arrow",
|
"curvedArrow": "Curved arrow",
|
||||||
"curvedLine": "Curved line",
|
"curvedLine": "Curved line",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
|
"doubleClick": "double-click",
|
||||||
"drag": "drag",
|
"drag": "drag",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
|
"editSelectedShape": "Edit selected shape (text/arrow/line)",
|
||||||
"github": "Found an issue? Submit",
|
"github": "Found an issue? Submit",
|
||||||
"howto": "Follow our guides",
|
"howto": "Follow our guides",
|
||||||
"or": "or",
|
"or": "or",
|
||||||
"preventBinding": "Prevent arrow binding",
|
"preventBinding": "Prevent arrow binding",
|
||||||
"shapes": "Shapes",
|
"shapes": "Shapes",
|
||||||
"shortcuts": "Keyboard shortcuts",
|
"shortcuts": "Keyboard shortcuts",
|
||||||
"textFinish": "Finish editing (text)",
|
"textFinish": "Finish editing (text editor)",
|
||||||
"textNewLine": "Add new line (text)",
|
"textNewLine": "Add new line (text editor)",
|
||||||
"title": "Help",
|
"title": "Help",
|
||||||
"view": "View",
|
"view": "View",
|
||||||
"zoomToFit": "Zoom to fit all elements",
|
"zoomToFit": "Zoom to fit all elements",
|
||||||
@@ -275,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "Saved to {filename}",
|
"fileSavedToFilename": "Saved to {filename}",
|
||||||
"canvas": "canvas",
|
"canvas": "canvas",
|
||||||
"selection": "selection"
|
"selection": "selection"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "White",
|
||||||
|
"f8f9fa": "Gray 0",
|
||||||
|
"f1f3f5": "Gray 1",
|
||||||
|
"fff5f5": "Red 0",
|
||||||
|
"fff0f6": "Pink 0",
|
||||||
|
"f8f0fc": "Grape 0",
|
||||||
|
"f3f0ff": "Violet 0",
|
||||||
|
"edf2ff": "Indigo 0",
|
||||||
|
"e7f5ff": "Blue 0",
|
||||||
|
"e3fafc": "Cyan 0",
|
||||||
|
"e6fcf5": "Teal 0",
|
||||||
|
"ebfbee": "Green 0",
|
||||||
|
"f4fce3": "Lime 0",
|
||||||
|
"fff9db": "Yellow 0",
|
||||||
|
"fff4e6": "Orange 0",
|
||||||
|
"transparent": "Transparent",
|
||||||
|
"ced4da": "Gray 4",
|
||||||
|
"868e96": "Gray 6",
|
||||||
|
"fa5252": "Red 6",
|
||||||
|
"e64980": "Pink 6",
|
||||||
|
"be4bdb": "Grape 6",
|
||||||
|
"7950f2": "Violet 6",
|
||||||
|
"4c6ef5": "Indigo 6",
|
||||||
|
"228be6": "Blue 6",
|
||||||
|
"15aabf": "Cyan 6",
|
||||||
|
"12b886": "Teal 6",
|
||||||
|
"40c057": "Green 6",
|
||||||
|
"82c91e": "Lime 6",
|
||||||
|
"fab005": "Yellow 6",
|
||||||
|
"fd7e14": "Orange 6",
|
||||||
|
"000000": "Black",
|
||||||
|
"343a40": "Gray 8",
|
||||||
|
"495057": "Gray 7",
|
||||||
|
"c92a2a": "Red 9",
|
||||||
|
"a61e4d": "Pink 9",
|
||||||
|
"862e9c": "Grape 9",
|
||||||
|
"5f3dc4": "Violet 9",
|
||||||
|
"364fc7": "Indigo 9",
|
||||||
|
"1864ab": "Blue 9",
|
||||||
|
"0b7285": "Cyan 9",
|
||||||
|
"087f5b": "Teal 9",
|
||||||
|
"2b8a3e": "Green 9",
|
||||||
|
"5c940d": "Lime 9",
|
||||||
|
"e67700": "Yellow 9",
|
||||||
|
"d9480f": "Orange 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
"background": "Fondo",
|
"background": "Fondo",
|
||||||
"fill": "Rellenar",
|
"fill": "Rellenar",
|
||||||
"strokeWidth": "Grosor del trazo",
|
"strokeWidth": "Grosor del trazo",
|
||||||
"strokeShape": "",
|
"strokeShape": "Estilo del trazo",
|
||||||
"strokeShape_gel": "",
|
"strokeShape_gel": "Bolígrafo de gel",
|
||||||
"strokeShape_fountain": "",
|
"strokeShape_fountain": "Pluma estilográfica",
|
||||||
"strokeShape_brush": "",
|
"strokeShape_brush": "Rotulador",
|
||||||
"strokeStyle": "Estilo del trazo",
|
"strokeStyle": "Estilo del trazo",
|
||||||
"strokeStyle_solid": "Sólido",
|
"strokeStyle_solid": "Sólido",
|
||||||
"strokeStyle_dashed": "Discontinua",
|
"strokeStyle_dashed": "Discontinua",
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
"fontSize": "Tamaño de la fuente",
|
"fontSize": "Tamaño de la fuente",
|
||||||
"fontFamily": "Tipo de fuente",
|
"fontFamily": "Tipo de fuente",
|
||||||
"onlySelected": "Sólo seleccionados",
|
"onlySelected": "Sólo seleccionados",
|
||||||
"withBackground": "",
|
"withBackground": "Fondo",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "Embeber escena",
|
||||||
"exportEmbedScene_details": "Los datos de escena se guardarán en el archivo PNG/SVG exportado, así la escena puede ser restaurada de la misma.\nEsto aumentará el tamaño del archivo exportado.",
|
"exportEmbedScene_details": "Los datos de escena se guardarán en el archivo PNG/SVG exportado, así la escena puede ser restaurada de la misma.\nEsto aumentará el tamaño del archivo exportado.",
|
||||||
"addWatermark": "Agregar \"Hecho con Excalidraw\"",
|
"addWatermark": "Agregar \"Hecho con Excalidraw\"",
|
||||||
"handDrawn": "Dibujado a mano",
|
"handDrawn": "Dibujado a mano",
|
||||||
@@ -101,19 +101,21 @@
|
|||||||
"viewMode": "Modo presentación",
|
"viewMode": "Modo presentación",
|
||||||
"toggleExportColorScheme": "Cambiar el esquema de colores de exportación",
|
"toggleExportColorScheme": "Cambiar el esquema de colores de exportación",
|
||||||
"share": "Compartir",
|
"share": "Compartir",
|
||||||
|
"showStroke": "Mostrar el selector de color del trazo",
|
||||||
|
"showBackground": "Mostrar el selector de color de fondo",
|
||||||
"toggleTheme": "Alternar tema"
|
"toggleTheme": "Alternar tema"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Limpiar lienzo y reiniciar el color de fondo",
|
"clearReset": "Limpiar lienzo y reiniciar el color de fondo",
|
||||||
"exportJSON": "",
|
"exportJSON": "Exportar como archivo",
|
||||||
"exportImage": "",
|
"exportImage": "Guardar como imagen",
|
||||||
"export": "Exportar",
|
"export": "Exportar",
|
||||||
"exportToPng": "Exportar a PNG",
|
"exportToPng": "Exportar a PNG",
|
||||||
"exportToSvg": "Exportar a SVG",
|
"exportToSvg": "Exportar a SVG",
|
||||||
"copyToClipboard": "Copiar al portapapeles",
|
"copyToClipboard": "Copiar al portapapeles",
|
||||||
"copyPngToClipboard": "Copiar PNG al portapapeles",
|
"copyPngToClipboard": "Copiar PNG al portapapeles",
|
||||||
"scale": "Escalar",
|
"scale": "Escalar",
|
||||||
"save": "",
|
"save": "Guardal al archivo actual",
|
||||||
"saveAs": "Guardar como",
|
"saveAs": "Guardar como",
|
||||||
"load": "Cargar",
|
"load": "Cargar",
|
||||||
"getShareableLink": "Obtener enlace para compartir",
|
"getShareableLink": "Obtener enlace para compartir",
|
||||||
@@ -122,7 +124,7 @@
|
|||||||
"scrollBackToContent": "Volver al contenido",
|
"scrollBackToContent": "Volver al contenido",
|
||||||
"zoomIn": "Acercarse",
|
"zoomIn": "Acercarse",
|
||||||
"zoomOut": "Alejarse",
|
"zoomOut": "Alejarse",
|
||||||
"resetZoom": "Restablecer acercamiento",
|
"resetZoom": "Restablecer zoom",
|
||||||
"menu": "Menú",
|
"menu": "Menú",
|
||||||
"done": "Hecho",
|
"done": "Hecho",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "La importación de imágenes no está homologada en este momento.\n\n¿Deseas importar una escena? Esta imagen no parece contener ningún dato de escena. ¿Lo has activado durante la exportación?",
|
"imageDoesNotContainScene": "La importación de imágenes no está homologada en este momento.\n\n¿Deseas importar una escena? Esta imagen no parece contener ningún dato de escena. ¿Lo has activado durante la exportación?",
|
||||||
"cannotRestoreFromImage": "No se pudo restaurar la escena desde este archivo de imagen",
|
"cannotRestoreFromImage": "No se pudo restaurar la escena desde este archivo de imagen",
|
||||||
"invalidSceneUrl": "No se ha podido importar la escena desde la URL proporcionada. Está mal formada, o no contiene datos de Excalidraw JSON válidos.",
|
"invalidSceneUrl": "No se ha podido importar la escena desde la URL proporcionada. Está mal formada, o no contiene datos de Excalidraw JSON válidos.",
|
||||||
"resetLibrary": "Esto eliminará tu librería. ¿Estás seguro?"
|
"resetLibrary": "Esto eliminará tu librería. ¿Estás seguro?",
|
||||||
|
"invalidEncryptionKey": "La clave de cifrado debe tener 22 caracteres. La colaboración en vivo está deshabilitada."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selección",
|
"selection": "Selección",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Haz clic para dibujar múltiples puntos, arrastrar para solo una línea",
|
"linearElement": "Haz clic para dibujar múltiples puntos, arrastrar para solo una línea",
|
||||||
"freeDraw": "Haz clic y arrastra, suelta al terminar",
|
"freeDraw": "Haz clic y arrastra, suelta al terminar",
|
||||||
"text": "Consejo: también puedes añadir texto haciendo doble clic en cualquier lugar con la herramienta de selección",
|
"text": "Consejo: también puedes añadir texto haciendo doble clic en cualquier lugar con la herramienta de selección",
|
||||||
|
"text_selected": "Doble clic o pulse ENTER para editar el texto",
|
||||||
|
"text_editing": "Pulse Escape o CtrlOrCmd+ENTER para terminar de editar",
|
||||||
"linearElementMulti": "Haz clic en el último punto o presiona Escape o Enter para finalizar",
|
"linearElementMulti": "Haz clic en el último punto o presiona Escape o Enter para finalizar",
|
||||||
"lockAngle": "Puedes restringir el ángulo manteniendo presionado el botón SHIFT",
|
"lockAngle": "Puedes restringir el ángulo manteniendo presionado el botón SHIFT",
|
||||||
"resize": "Para mantener las proporciones mantén SHIFT presionado mientras modificas el tamaño, \nmantén presionado ALT para modificar el tamaño desde el centro",
|
"resize": "Para mantener las proporciones mantén SHIFT presionado mientras modificas el tamaño, \nmantén presionado ALT para modificar el tamaño desde el centro",
|
||||||
@@ -218,12 +223,15 @@
|
|||||||
"title": "Error"
|
"title": "Error"
|
||||||
},
|
},
|
||||||
"exportDialog": {
|
"exportDialog": {
|
||||||
"disk_title": "",
|
"disk_title": "Guardar en el disco",
|
||||||
"disk_details": "",
|
"disk_details": "Exportar los datos de la escena a un archivo desde el cual se puede importar más tarde.",
|
||||||
"disk_button": "",
|
"disk_button": "Guardar en el archivo",
|
||||||
"link_title": "",
|
"link_title": "Enlace para compartir",
|
||||||
"link_details": "",
|
"link_details": "Exportar como enlace de sólo lectura.",
|
||||||
"link_button": ""
|
"link_button": "Exportar al link",
|
||||||
|
"excalidrawplus_description": "Guarda la escena en tu espacio de trabajo de Excalidraw+.",
|
||||||
|
"excalidrawplus_button": "Exportar",
|
||||||
|
"excalidrawplus_exportError": "No se pudo exportar a Excalidraw+ en este momento..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Lee nuestro blog",
|
"blog": "Lee nuestro blog",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Flecha curvada",
|
"curvedArrow": "Flecha curvada",
|
||||||
"curvedLine": "Línea curva",
|
"curvedLine": "Línea curva",
|
||||||
"documentation": "Documentación",
|
"documentation": "Documentación",
|
||||||
|
"doubleClick": "doble clic",
|
||||||
"drag": "arrastrar",
|
"drag": "arrastrar",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
|
"editSelectedShape": "Editar la forma seleccionada (texto/flecha/línea)",
|
||||||
"github": "¿Has encontrado un problema? Envíalo",
|
"github": "¿Has encontrado un problema? Envíalo",
|
||||||
"howto": "Siga nuestras guías",
|
"howto": "Siga nuestras guías",
|
||||||
"or": "o",
|
"or": "o",
|
||||||
"preventBinding": "Evitar yuxtaposición de flechas",
|
"preventBinding": "Evitar yuxtaposición de flechas",
|
||||||
"shapes": "Formas",
|
"shapes": "Formas",
|
||||||
"shortcuts": "Atajos del teclado",
|
"shortcuts": "Atajos del teclado",
|
||||||
"textFinish": "Finalizar edición (texto)",
|
"textFinish": "Finalizar edición (editor de texto)",
|
||||||
"textNewLine": "Añadir nueva línea (texto)",
|
"textNewLine": "Añadir nueva linea (editor de texto)",
|
||||||
"title": "Ayuda",
|
"title": "Ayuda",
|
||||||
"view": "Vista",
|
"view": "Vista",
|
||||||
"zoomToFit": "Ajustar la vista para mostrar todos los elementos",
|
"zoomToFit": "Ajustar la vista para mostrar todos los elementos",
|
||||||
@@ -248,7 +258,7 @@
|
|||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Tus dibujos están cifrados de punto a punto, por lo que los servidores de Excalidraw nunca los verán.",
|
"tooltip": "Tus dibujos están cifrados de punto a punto, por lo que los servidores de Excalidraw nunca los verán.",
|
||||||
"link": ""
|
"link": "Entrada en el blog sobre cifrado de extremo a extremo"
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "Ángulo",
|
"angle": "Ángulo",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "Guardado en {filename}",
|
"fileSavedToFilename": "Guardado en {filename}",
|
||||||
"canvas": "lienzo",
|
"canvas": "lienzo",
|
||||||
"selection": "selección"
|
"selection": "selección"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "Blanco",
|
||||||
|
"f8f9fa": "Gris 0",
|
||||||
|
"f1f3f5": "Gris 1",
|
||||||
|
"fff5f5": "Rojo 0",
|
||||||
|
"fff0f6": "Rosa 0",
|
||||||
|
"f8f0fc": "Uva 0",
|
||||||
|
"f3f0ff": "Violeta 0",
|
||||||
|
"edf2ff": "Indigo 0",
|
||||||
|
"e7f5ff": "Azul 0",
|
||||||
|
"e3fafc": "Cian 0",
|
||||||
|
"e6fcf5": "Turquesa 0",
|
||||||
|
"ebfbee": "Verde 0",
|
||||||
|
"f4fce3": "Lima 0",
|
||||||
|
"fff9db": "Amarillo 0",
|
||||||
|
"fff4e6": "Naranja 0",
|
||||||
|
"transparent": "Transparente",
|
||||||
|
"ced4da": "Gris 4",
|
||||||
|
"868e96": "Gris 6",
|
||||||
|
"fa5252": "Rojo 6",
|
||||||
|
"e64980": "Rosa 6",
|
||||||
|
"be4bdb": "Uva 6",
|
||||||
|
"7950f2": "Violeta 6",
|
||||||
|
"4c6ef5": "Indigo 6",
|
||||||
|
"228be6": "Azul 6",
|
||||||
|
"15aabf": "Cian 6",
|
||||||
|
"12b886": "Turquesa 6",
|
||||||
|
"40c057": "Verde 6",
|
||||||
|
"82c91e": "Lima 6",
|
||||||
|
"fab005": "Amarillo 6",
|
||||||
|
"fd7e14": "Naranja 6",
|
||||||
|
"000000": "Negro",
|
||||||
|
"343a40": "Gris 8",
|
||||||
|
"495057": "Gris 7",
|
||||||
|
"c92a2a": "Rojo 9",
|
||||||
|
"a61e4d": "Rosa 9",
|
||||||
|
"862e9c": "Uva 9",
|
||||||
|
"5f3dc4": "Violeta 9",
|
||||||
|
"364fc7": "Indigo 9",
|
||||||
|
"1864ab": "Azul 9",
|
||||||
|
"0b7285": "Cian 9",
|
||||||
|
"087f5b": "Turquesa 9",
|
||||||
|
"2b8a3e": "Verde 9",
|
||||||
|
"5c940d": "Lima 9",
|
||||||
|
"e67700": "Amarillo 9",
|
||||||
|
"d9480f": "Naranja 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"labels": {
|
"labels": {
|
||||||
"paste": "جای گذاری",
|
"paste": "جای گذاری",
|
||||||
"pasteCharts": "قراردادن نمودار",
|
"pasteCharts": "قراردادن نمودارها",
|
||||||
"selectAll": "انتخاب همه",
|
"selectAll": "انتخاب همه",
|
||||||
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید.",
|
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید.",
|
||||||
"moveCanvas": "بوم را حرکت بدهید",
|
"moveCanvas": "جابجایی بوم",
|
||||||
"cut": "جابجایی",
|
"cut": "بریدن",
|
||||||
"copy": "کپی",
|
"copy": "کپی",
|
||||||
"copyAsPng": "کپی در حافطه موقت به صورت PNG",
|
"copyAsPng": "کپی در حافطه موقت به صورت PNG",
|
||||||
"copyAsSvg": "کپی در حافطه موقت به صورت SVG",
|
"copyAsSvg": "کپی در حافطه موقت به صورت SVG",
|
||||||
@@ -16,33 +16,33 @@
|
|||||||
"delete": "حذف",
|
"delete": "حذف",
|
||||||
"copyStyles": "کپی سبک",
|
"copyStyles": "کپی سبک",
|
||||||
"pasteStyles": "جای گذاری سبک",
|
"pasteStyles": "جای گذاری سبک",
|
||||||
"stroke": "خط",
|
"stroke": "حاشیه",
|
||||||
"background": "پس زمینه",
|
"background": "پس زمینه",
|
||||||
"fill": "رنگ آمیزی",
|
"fill": "رنگ آمیزی",
|
||||||
"strokeWidth": "ضخامت خط",
|
"strokeWidth": "ضخامت حاشیه",
|
||||||
"strokeShape": "",
|
"strokeShape": "حاشیه شکل",
|
||||||
"strokeShape_gel": "",
|
"strokeShape_gel": "",
|
||||||
"strokeShape_fountain": "",
|
"strokeShape_fountain": "",
|
||||||
"strokeShape_brush": "",
|
"strokeShape_brush": "",
|
||||||
"strokeStyle": "استایل خط",
|
"strokeStyle": "استایل حاشیه",
|
||||||
"strokeStyle_solid": "یکدست",
|
"strokeStyle_solid": "یکدست",
|
||||||
"strokeStyle_dashed": "خط چین",
|
"strokeStyle_dashed": "خط چین",
|
||||||
"strokeStyle_dotted": "نقطه چین",
|
"strokeStyle_dotted": "نقطه چین",
|
||||||
"sloppiness": "دقت",
|
"sloppiness": "دقت",
|
||||||
"opacity": "تاری",
|
"opacity": "شفافیت",
|
||||||
"textAlign": "چیدمان متن",
|
"textAlign": "چیدمان متن",
|
||||||
"edges": "لبه ها",
|
"edges": "لبه ها",
|
||||||
"sharp": "تیز",
|
"sharp": "تیز",
|
||||||
"round": "دور",
|
"round": "دور",
|
||||||
"arrowheads": "سر پیکان",
|
"arrowheads": "سر پیکان",
|
||||||
"arrowhead_none": "هیچ کدام",
|
"arrowhead_none": "هیچ کدام",
|
||||||
"arrowhead_arrow": "فلش",
|
"arrowhead_arrow": "پیکان",
|
||||||
"arrowhead_bar": "میله ای",
|
"arrowhead_bar": "میله ای",
|
||||||
"arrowhead_dot": "نقطه",
|
"arrowhead_dot": "نقطه",
|
||||||
"fontSize": "اندازه قلم",
|
"fontSize": "اندازه قلم",
|
||||||
"fontFamily": "نوع قلم",
|
"fontFamily": "نوع قلم",
|
||||||
"onlySelected": "فقط انتخاب شده ها",
|
"onlySelected": "فقط انتخاب شده ها",
|
||||||
"withBackground": "",
|
"withBackground": "پس زمینه",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "",
|
||||||
"exportEmbedScene_details": "متحوای صحنه به فایل خروجی SVG/PNG اضافه خواهد شد برای بازیابی صحنه به آن اضافه خواهد شد.\nباعث افزایش حجم فایل خروجی میشود.",
|
"exportEmbedScene_details": "متحوای صحنه به فایل خروجی SVG/PNG اضافه خواهد شد برای بازیابی صحنه به آن اضافه خواهد شد.\nباعث افزایش حجم فایل خروجی میشود.",
|
||||||
"addWatermark": "\"ساخته شده با Excalidraw\" را اضافه کن",
|
"addWatermark": "\"ساخته شده با Excalidraw\" را اضافه کن",
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"architect": "معمار",
|
"architect": "معمار",
|
||||||
"artist": "هنرمند",
|
"artist": "هنرمند",
|
||||||
"cartoonist": "کارتونیست",
|
"cartoonist": "کارتونیست",
|
||||||
"fileTitle": "",
|
"fileTitle": "نام فایل",
|
||||||
"colorPicker": "انتخابگر رنگ",
|
"colorPicker": "انتخابگر رنگ",
|
||||||
"canvasBackground": "بوم",
|
"canvasBackground": "بوم",
|
||||||
"drawingCanvas": "بوم نقاشی",
|
"drawingCanvas": "بوم نقاشی",
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"group": "گروهبندی انتخابها",
|
"group": "گروهبندی انتخابها",
|
||||||
"ungroup": "حذف گروهبندی انتخابها",
|
"ungroup": "حذف گروهبندی انتخابها",
|
||||||
"collaborators": "همکاران",
|
"collaborators": "همکاران",
|
||||||
"showGrid": "",
|
"showGrid": "نمایش گرید",
|
||||||
"addToLibrary": "افزودن به کتابخانه",
|
"addToLibrary": "افزودن به کتابخانه",
|
||||||
"removeFromLibrary": "حذف از کتابخانه",
|
"removeFromLibrary": "حذف از کتابخانه",
|
||||||
"libraryLoadingMessage": "بارگذاری کتابخانه…",
|
"libraryLoadingMessage": "بارگذاری کتابخانه…",
|
||||||
@@ -96,24 +96,26 @@
|
|||||||
"centerHorizontally": "وسط قرار دادن به صورت افقی",
|
"centerHorizontally": "وسط قرار دادن به صورت افقی",
|
||||||
"distributeHorizontally": "توزیع کردن به صورت افقی",
|
"distributeHorizontally": "توزیع کردن به صورت افقی",
|
||||||
"distributeVertically": "توزیع کردن به صورت عمودی",
|
"distributeVertically": "توزیع کردن به صورت عمودی",
|
||||||
"flipHorizontal": "",
|
"flipHorizontal": "چرخش افقی",
|
||||||
"flipVertical": "",
|
"flipVertical": "چرخش عمودی",
|
||||||
"viewMode": "",
|
"viewMode": "حالت نمایش",
|
||||||
"toggleExportColorScheme": "",
|
"toggleExportColorScheme": "",
|
||||||
"share": "",
|
"share": "اشتراکگذاری",
|
||||||
"toggleTheme": ""
|
"showStroke": "نمایش انتخاب کننده رنگ حاشیه",
|
||||||
|
"showBackground": "نمایش انتخاب کننده رنگ پس زمینه",
|
||||||
|
"toggleTheme": "تغییر تم"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "پاکسازی بوم نقاشی",
|
"clearReset": "پاکسازی بوم نقاشی",
|
||||||
"exportJSON": "",
|
"exportJSON": "خروجی در فایل",
|
||||||
"exportImage": "",
|
"exportImage": "ذخیره به عنوان عکس",
|
||||||
"export": "تبدیل",
|
"export": "تبدیل",
|
||||||
"exportToPng": "تبدیل به PNG",
|
"exportToPng": "تبدیل به PNG",
|
||||||
"exportToSvg": "تبدیل به SVG",
|
"exportToSvg": "تبدیل به SVG",
|
||||||
"copyToClipboard": "کپی در حافظه موقت",
|
"copyToClipboard": "کپی در حافظه موقت",
|
||||||
"copyPngToClipboard": "کپی PNG در حافظه موقت",
|
"copyPngToClipboard": "کپی PNG در حافظه موقت",
|
||||||
"scale": "مقیاس",
|
"scale": "مقیاس",
|
||||||
"save": "",
|
"save": "ذخیره در همین فایل",
|
||||||
"saveAs": "ذخیره با نام",
|
"saveAs": "ذخیره با نام",
|
||||||
"load": "بارگذاری",
|
"load": "بارگذاری",
|
||||||
"getShareableLink": "دریافت لینک قابل اشتراک",
|
"getShareableLink": "دریافت لینک قابل اشتراک",
|
||||||
@@ -149,13 +151,14 @@
|
|||||||
"loadSceneOverridePrompt": "بارگزاری یک طرح خارجی محتوای فعلی رو از بین میبرد. آیا میخواهید ادامه دهید؟",
|
"loadSceneOverridePrompt": "بارگزاری یک طرح خارجی محتوای فعلی رو از بین میبرد. آیا میخواهید ادامه دهید؟",
|
||||||
"collabStopOverridePrompt": "",
|
"collabStopOverridePrompt": "",
|
||||||
"errorLoadingLibrary": "خطایی در بارگذاری کتابخانه ثالث وجود داشت.",
|
"errorLoadingLibrary": "خطایی در بارگذاری کتابخانه ثالث وجود داشت.",
|
||||||
"errorAddingToLibrary": "",
|
"errorAddingToLibrary": "مورد به کتابخانه اضافه نشد",
|
||||||
"errorRemovingFromLibrary": "",
|
"errorRemovingFromLibrary": "مورد از کتابخانه حذف نشد",
|
||||||
"confirmAddLibrary": "{{numShapes}} از اشکال به کتابخانه شما اضافه خواهد شد. مطمئن هستید؟",
|
"confirmAddLibrary": "{{numShapes}} از اشکال به کتابخانه شما اضافه خواهد شد. مطمئن هستید؟",
|
||||||
"imageDoesNotContainScene": "وارد کردن تصویر در این لحظه امکان پذیر نمی باشد.\nآیا مایل به وارد کردن یک صحنه هستید؟ این تصویر به نظر می رسد که فاقد هرگونه اطلاعاتی مربوط به صحنه باشد. آیا این گزینه را در زمان وارد کردن تصویر فعال کرده اید؟",
|
"imageDoesNotContainScene": "وارد کردن تصویر در این لحظه امکان پذیر نمی باشد.\nآیا مایل به وارد کردن یک صحنه هستید؟ این تصویر به نظر می رسد که فاقد هرگونه اطلاعاتی مربوط به صحنه باشد. آیا این گزینه را در زمان وارد کردن تصویر فعال کرده اید؟",
|
||||||
"cannotRestoreFromImage": "صحنه را نمی توان از این فایل تصویری بازیابی کرد",
|
"cannotRestoreFromImage": "صحنه را نمی توان از این فایل تصویری بازیابی کرد",
|
||||||
"invalidSceneUrl": "",
|
"invalidSceneUrl": "",
|
||||||
"resetLibrary": ""
|
"resetLibrary": "",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "گزینش",
|
"selection": "گزینش",
|
||||||
@@ -164,7 +167,7 @@
|
|||||||
"ellipse": "بیضی",
|
"ellipse": "بیضی",
|
||||||
"arrow": "پیکان",
|
"arrow": "پیکان",
|
||||||
"line": "خط",
|
"line": "خط",
|
||||||
"freedraw": "",
|
"freedraw": "کشیدن",
|
||||||
"text": "متن",
|
"text": "متن",
|
||||||
"library": "کتابخانه",
|
"library": "کتابخانه",
|
||||||
"lock": "ابزار انتخاب شده را بعد از کشیدن نگه دار"
|
"lock": "ابزار انتخاب شده را بعد از کشیدن نگه دار"
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "برای چند نقطه کلیک و برای یک خط بکشید",
|
"linearElement": "برای چند نقطه کلیک و برای یک خط بکشید",
|
||||||
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
|
"freeDraw": "کلیک کنید و بکشید و وقتی کار تمام شد رها کنید",
|
||||||
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
|
"text": "نکته: با برنامه انتخاب شده شما میتوانید با دوبار کلیک کردن هرکجا میخواید متن اظاف کنید",
|
||||||
|
"text_selected": "",
|
||||||
|
"text_editing": "",
|
||||||
"linearElementMulti": "روی آخرین نقطه کلیک کنید یا کلید ESC را بزنید یا کلید Enter را بزنید برای اتمام کار",
|
"linearElementMulti": "روی آخرین نقطه کلیک کنید یا کلید ESC را بزنید یا کلید Enter را بزنید برای اتمام کار",
|
||||||
"lockAngle": "با نگه داشتن SHIFT هنگام چرخش می توانید زاویه ها را محدود کنید",
|
"lockAngle": "با نگه داشتن SHIFT هنگام چرخش می توانید زاویه ها را محدود کنید",
|
||||||
"resize": "می توانید با نگه داشتن SHIFT در هنگام تغییر اندازه، نسبت ها را محدود کنید،ALT را برای تغییر اندازه از مرکز نگه دارید",
|
"resize": "می توانید با نگه داشتن SHIFT در هنگام تغییر اندازه، نسبت ها را محدود کنید،ALT را برای تغییر اندازه از مرکز نگه دارید",
|
||||||
@@ -218,29 +223,34 @@
|
|||||||
"title": "خطا"
|
"title": "خطا"
|
||||||
},
|
},
|
||||||
"exportDialog": {
|
"exportDialog": {
|
||||||
"disk_title": "",
|
"disk_title": "ذخیره در دیسک",
|
||||||
"disk_details": "",
|
"disk_details": "",
|
||||||
"disk_button": "",
|
"disk_button": "ذخیره در فایل",
|
||||||
"link_title": "",
|
"link_title": "لینک قابل اشتراکگذاری",
|
||||||
"link_details": "",
|
"link_details": "",
|
||||||
"link_button": ""
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "خروجی گرفتن",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "بلاگ ما را بخوانید",
|
"blog": "بلاگ ما را بخوانید",
|
||||||
"click": "",
|
"click": "کلیک",
|
||||||
"curvedArrow": "فلش خمیده",
|
"curvedArrow": "فلش خمیده",
|
||||||
"curvedLine": "منحنی",
|
"curvedLine": "منحنی",
|
||||||
"documentation": "مستندات",
|
"documentation": "مستندات",
|
||||||
"drag": "",
|
"doubleClick": "دابل کلیک",
|
||||||
|
"drag": "کشیدن",
|
||||||
"editor": "ویرایشگر",
|
"editor": "ویرایشگر",
|
||||||
|
"editSelectedShape": "ویرایش شکل انتخاب شده (متن/فلش/خط)",
|
||||||
"github": "اشکالی می بینید؟ گزارش دهید",
|
"github": "اشکالی می بینید؟ گزارش دهید",
|
||||||
"howto": "راهنمای ما را دنبال کنید",
|
"howto": "راهنمای ما را دنبال کنید",
|
||||||
"or": "یا",
|
"or": "یا",
|
||||||
"preventBinding": "مانع شدن از چسبیدن فلش ها",
|
"preventBinding": "مانع شدن از چسبیدن فلش ها",
|
||||||
"shapes": "شکلها",
|
"shapes": "شکلها",
|
||||||
"shortcuts": "میانبرهای صفحه کلید",
|
"shortcuts": "میانبرهای صفحه کلید",
|
||||||
"textFinish": "",
|
"textFinish": "پایان ویرایش (ویرایشگر متن)",
|
||||||
"textNewLine": "یک خط جدید اضافه کنید (متن)",
|
"textNewLine": "افزودن خط جدید (ویرایشگر متن)",
|
||||||
"title": "راهنما",
|
"title": "راهنما",
|
||||||
"view": "مشاهده",
|
"view": "مشاهده",
|
||||||
"zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
|
"zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
|
||||||
@@ -260,18 +270,65 @@
|
|||||||
"storage": "حافظه",
|
"storage": "حافظه",
|
||||||
"title": "آمار برای نردها",
|
"title": "آمار برای نردها",
|
||||||
"total": "مجموع",
|
"total": "مجموع",
|
||||||
"version": "",
|
"version": "نسخه",
|
||||||
"versionCopy": "",
|
"versionCopy": "برای کپی کردن کلیک کنید",
|
||||||
"versionNotAvailable": "",
|
"versionNotAvailable": "نسخه غیرقابل دسترس",
|
||||||
"width": "عرض"
|
"width": "عرض"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "کپی سبک.",
|
"copyStyles": "کپی سبک.",
|
||||||
"copyToClipboard": "",
|
"copyToClipboard": "در کلیپبورد کپی شد.",
|
||||||
"copyToClipboardAsPng": "",
|
"copyToClipboardAsPng": "",
|
||||||
"fileSaved": "",
|
"fileSaved": "فایل ذخیره شد.",
|
||||||
"fileSavedToFilename": "",
|
"fileSavedToFilename": "ذخیره در {filename}",
|
||||||
"canvas": "",
|
"canvas": "بوم",
|
||||||
"selection": ""
|
"selection": "انتخاب"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "",
|
||||||
|
"fd7e14": "",
|
||||||
|
"000000": "",
|
||||||
|
"343a40": "",
|
||||||
|
"495057": "",
|
||||||
|
"c92a2a": "",
|
||||||
|
"a61e4d": "",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "",
|
||||||
|
"d9480f": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@
|
|||||||
"fontSize": "Kirjasinkoko",
|
"fontSize": "Kirjasinkoko",
|
||||||
"fontFamily": "Kirjasintyyppi",
|
"fontFamily": "Kirjasintyyppi",
|
||||||
"onlySelected": "Vain valitut",
|
"onlySelected": "Vain valitut",
|
||||||
"withBackground": "",
|
"withBackground": "Taustalla",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "Upota työ",
|
||||||
"exportEmbedScene_details": "Teoksen tiedot tallennetaan PNG/SVG-tiedostoon, jolloin teoksen voi palauttaa siitä. Kasvattaa tallennetun tiedoston kokoa.",
|
"exportEmbedScene_details": "Teoksen tiedot tallennetaan PNG/SVG-tiedostoon, jolloin teoksen voi palauttaa siitä. Kasvattaa tallennetun tiedoston kokoa.",
|
||||||
"addWatermark": "Lisää \"Tehty Excalidrawilla\"",
|
"addWatermark": "Lisää \"Tehty Excalidrawilla\"",
|
||||||
"handDrawn": "Käsinkirjoitettu",
|
"handDrawn": "Käsinkirjoitettu",
|
||||||
@@ -101,19 +101,21 @@
|
|||||||
"viewMode": "Katselutila",
|
"viewMode": "Katselutila",
|
||||||
"toggleExportColorScheme": "Vaihda viennin väriteema",
|
"toggleExportColorScheme": "Vaihda viennin väriteema",
|
||||||
"share": "Jaa",
|
"share": "Jaa",
|
||||||
|
"showStroke": "Näytä viivan värin valitsin",
|
||||||
|
"showBackground": "Näytä taustavärin valitsin",
|
||||||
"toggleTheme": "Vaihda teema"
|
"toggleTheme": "Vaihda teema"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Tyhjennä piirtoalue",
|
"clearReset": "Tyhjennä piirtoalue",
|
||||||
"exportJSON": "",
|
"exportJSON": "Vie tiedostoon",
|
||||||
"exportImage": "",
|
"exportImage": "Tallenna kuvana",
|
||||||
"export": "Vie",
|
"export": "Vie",
|
||||||
"exportToPng": "Vie PNG-tiedostona",
|
"exportToPng": "Vie PNG-tiedostona",
|
||||||
"exportToSvg": "Vie SVG-tiedostona",
|
"exportToSvg": "Vie SVG-tiedostona",
|
||||||
"copyToClipboard": "Kopioi leikepöydälle",
|
"copyToClipboard": "Kopioi leikepöydälle",
|
||||||
"copyPngToClipboard": "Kopioi PNG-tiedosto leikepöydälle",
|
"copyPngToClipboard": "Kopioi PNG-tiedosto leikepöydälle",
|
||||||
"scale": "Koko",
|
"scale": "Koko",
|
||||||
"save": "",
|
"save": "Tallenna nykyiseen tiedostoon",
|
||||||
"saveAs": "Tallenna nimellä",
|
"saveAs": "Tallenna nimellä",
|
||||||
"load": "Avaa",
|
"load": "Avaa",
|
||||||
"getShareableLink": "Hae jaettava linkki",
|
"getShareableLink": "Hae jaettava linkki",
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "Kuvien lisääminen ei ole tällä hetkellä mahdollista.\n\nHaluatko tuoda piirroksen? Tämä kuva ei näytä sisältävän tarvittavia tietoja. Oletko ottanut piirrostietojen tallennuksen käyttöön viennin aikana?",
|
"imageDoesNotContainScene": "Kuvien lisääminen ei ole tällä hetkellä mahdollista.\n\nHaluatko tuoda piirroksen? Tämä kuva ei näytä sisältävän tarvittavia tietoja. Oletko ottanut piirrostietojen tallennuksen käyttöön viennin aikana?",
|
||||||
"cannotRestoreFromImage": "Teosta ei voitu palauttaa tästä kuvatiedostosta",
|
"cannotRestoreFromImage": "Teosta ei voitu palauttaa tästä kuvatiedostosta",
|
||||||
"invalidSceneUrl": "Teosta ei voitu tuoda annetusta URL-osoitteesta. Tallenne on vioittunut, tai osoitteessa ei ole Excalidraw JSON-dataa.",
|
"invalidSceneUrl": "Teosta ei voitu tuoda annetusta URL-osoitteesta. Tallenne on vioittunut, tai osoitteessa ei ole Excalidraw JSON-dataa.",
|
||||||
"resetLibrary": "Tämä tyhjentää kirjastosi. Jatketaanko?"
|
"resetLibrary": "Tämä tyhjentää kirjastosi. Jatketaanko?",
|
||||||
|
"invalidEncryptionKey": "Salausavaimen on oltava 22 merkkiä pitkä. Live-yhteistyö ei ole käytössä."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Valinta",
|
"selection": "Valinta",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Klikkaa piirtääksesi useampi piste, raahaa piirtääksesi yksittäinen viiva",
|
"linearElement": "Klikkaa piirtääksesi useampi piste, raahaa piirtääksesi yksittäinen viiva",
|
||||||
"freeDraw": "Paina ja raahaa, päästä irti kun olet valmis",
|
"freeDraw": "Paina ja raahaa, päästä irti kun olet valmis",
|
||||||
"text": "Vinkki: voit myös lisätä tekstiä kaksoisnapsauttamalla mihin tahansa valintatyökalulla",
|
"text": "Vinkki: voit myös lisätä tekstiä kaksoisnapsauttamalla mihin tahansa valintatyökalulla",
|
||||||
|
"text_selected": "Kaksoisnapsauta tai paina ENTER muokataksesi tekstiä",
|
||||||
|
"text_editing": "Paina Escape tai CtrlOrCmd+ENTER lopettaaksesi muokkaamisen",
|
||||||
"linearElementMulti": "Lopeta klikkaamalla viimeistä pistettä, painamalla Escape- tai Enter-näppäintä",
|
"linearElementMulti": "Lopeta klikkaamalla viimeistä pistettä, painamalla Escape- tai Enter-näppäintä",
|
||||||
"lockAngle": "Voit rajoittaa kulmaa pitämällä SHIFT-näppäintä alaspainettuna",
|
"lockAngle": "Voit rajoittaa kulmaa pitämällä SHIFT-näppäintä alaspainettuna",
|
||||||
"resize": "Voit rajoittaa mittasuhteet pitämällä SHIFT-näppäintä alaspainettuna kun muutat kokoa, pidä ALT-näppäintä alaspainettuna muuttaaksesi kokoa keskipisteen suhteen",
|
"resize": "Voit rajoittaa mittasuhteet pitämällä SHIFT-näppäintä alaspainettuna kun muutat kokoa, pidä ALT-näppäintä alaspainettuna muuttaaksesi kokoa keskipisteen suhteen",
|
||||||
@@ -218,12 +223,15 @@
|
|||||||
"title": "Virhe"
|
"title": "Virhe"
|
||||||
},
|
},
|
||||||
"exportDialog": {
|
"exportDialog": {
|
||||||
"disk_title": "",
|
"disk_title": "Tallenna levylle",
|
||||||
"disk_details": "",
|
"disk_details": "Vie työn tiedot tiedostoon, josta sen voi tuoda myöhemmin.",
|
||||||
"disk_button": "",
|
"disk_button": "Tallenna tiedostoon",
|
||||||
"link_title": "",
|
"link_title": "Jaettava linkki",
|
||||||
"link_details": "",
|
"link_details": "Vie vain luku -linkkinä.",
|
||||||
"link_button": ""
|
"link_button": "Vie linkkinä",
|
||||||
|
"excalidrawplus_description": "Tallenna teos Excalidraw+ tilaan.",
|
||||||
|
"excalidrawplus_button": "Vie",
|
||||||
|
"excalidrawplus_exportError": "Ei voitu viedä Excalidraw+-palveluun tällä hetkellä..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Lue blogiamme",
|
"blog": "Lue blogiamme",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Kaareva nuoli",
|
"curvedArrow": "Kaareva nuoli",
|
||||||
"curvedLine": "Kaareva viiva",
|
"curvedLine": "Kaareva viiva",
|
||||||
"documentation": "Käyttöohjeet",
|
"documentation": "Käyttöohjeet",
|
||||||
|
"doubleClick": "kaksoisnapsautus",
|
||||||
"drag": "vedä",
|
"drag": "vedä",
|
||||||
"editor": "Muokkausohjelma",
|
"editor": "Muokkausohjelma",
|
||||||
|
"editSelectedShape": "Muokkaa valittua muotoa (teksti/nuoli/viiva)",
|
||||||
"github": "Löysitkö ongelman? Kerro meille",
|
"github": "Löysitkö ongelman? Kerro meille",
|
||||||
"howto": "Seuraa oppaitamme",
|
"howto": "Seuraa oppaitamme",
|
||||||
"or": "tai",
|
"or": "tai",
|
||||||
"preventBinding": "Estä nuolten kiinnitys",
|
"preventBinding": "Estä nuolten kiinnitys",
|
||||||
"shapes": "Muodot",
|
"shapes": "Muodot",
|
||||||
"shortcuts": "Pikanäppäimet",
|
"shortcuts": "Pikanäppäimet",
|
||||||
"textFinish": "Lopeta muokkaus (teksti)",
|
"textFinish": "Lopeta muokkaus (tekstieditori)",
|
||||||
"textNewLine": "Lisää uusi rivi (teksti)",
|
"textNewLine": "Lisää uusi rivi (tekstieditori)",
|
||||||
"title": "Ohjeet",
|
"title": "Ohjeet",
|
||||||
"view": "Näkymä",
|
"view": "Näkymä",
|
||||||
"zoomToFit": "Näytä kaikki elementit",
|
"zoomToFit": "Näytä kaikki elementit",
|
||||||
@@ -248,7 +258,7 @@
|
|||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "Piirroksesi ovat päästä-päähän-salattuja, joten Excalidrawin palvelimet eivät koskaan näe niitä.",
|
"tooltip": "Piirroksesi ovat päästä-päähän-salattuja, joten Excalidrawin palvelimet eivät koskaan näe niitä.",
|
||||||
"link": ""
|
"link": "Blogiartikkeli päästä päähän -salauksesta Excalidraw:ssa"
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "Kulma",
|
"angle": "Kulma",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "Tallennettiin kohteeseen {filename}",
|
"fileSavedToFilename": "Tallennettiin kohteeseen {filename}",
|
||||||
"canvas": "piirtoalue",
|
"canvas": "piirtoalue",
|
||||||
"selection": "valinta"
|
"selection": "valinta"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "Valkoinen",
|
||||||
|
"f8f9fa": "Harmaa 0",
|
||||||
|
"f1f3f5": "Harmaa 1",
|
||||||
|
"fff5f5": "Punainen 0",
|
||||||
|
"fff0f6": "Pinkki 0",
|
||||||
|
"f8f0fc": "Rypäle 0",
|
||||||
|
"f3f0ff": "Violetti 0",
|
||||||
|
"edf2ff": "Indigo 0",
|
||||||
|
"e7f5ff": "Sininen 0",
|
||||||
|
"e3fafc": "Syaani 0",
|
||||||
|
"e6fcf5": "Sinivihreä 0",
|
||||||
|
"ebfbee": "Vihreä 0",
|
||||||
|
"f4fce3": "Limenvihreä 0",
|
||||||
|
"fff9db": "Keltainen 0",
|
||||||
|
"fff4e6": "Oranssi 0",
|
||||||
|
"transparent": "Läpinäkyvä",
|
||||||
|
"ced4da": "Harmaa 4",
|
||||||
|
"868e96": "Harmaa 6",
|
||||||
|
"fa5252": "Punainen 6",
|
||||||
|
"e64980": "Pinkki 6",
|
||||||
|
"be4bdb": "Rypäle 6",
|
||||||
|
"7950f2": "Violetti 6",
|
||||||
|
"4c6ef5": "Indigo 6",
|
||||||
|
"228be6": "Sininen 6",
|
||||||
|
"15aabf": "Syaani 6",
|
||||||
|
"12b886": "Sinivihreä 6",
|
||||||
|
"40c057": "Vihreä 6",
|
||||||
|
"82c91e": "Limenvihreä 6",
|
||||||
|
"fab005": "Keltainen 6",
|
||||||
|
"fd7e14": "Oranssi 6",
|
||||||
|
"000000": "Musta",
|
||||||
|
"343a40": "Harmaa 8",
|
||||||
|
"495057": "Harmaa 7",
|
||||||
|
"c92a2a": "Punainen 9",
|
||||||
|
"a61e4d": "Pinkki 9",
|
||||||
|
"862e9c": "Rypäle 9",
|
||||||
|
"5f3dc4": "Violetti 9",
|
||||||
|
"364fc7": "Indigo 9",
|
||||||
|
"1864ab": "Sininen 9",
|
||||||
|
"0b7285": "Syaani 9",
|
||||||
|
"087f5b": "Sinivihreä 9",
|
||||||
|
"2b8a3e": "Vihreä 9",
|
||||||
|
"5c940d": "Limenvihreä 9",
|
||||||
|
"e67700": "Keltainen 9",
|
||||||
|
"d9480f": "Oranssi 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
"copyStyles": "Copier les styles",
|
"copyStyles": "Copier les styles",
|
||||||
"pasteStyles": "Coller les styles",
|
"pasteStyles": "Coller les styles",
|
||||||
"stroke": "Contour",
|
"stroke": "Trait",
|
||||||
"background": "Arrière-plan",
|
"background": "Arrière-plan",
|
||||||
"fill": "Remplissage",
|
"fill": "Remplissage",
|
||||||
"strokeWidth": "Largeur du trait",
|
"strokeWidth": "Largeur du trait",
|
||||||
@@ -43,10 +43,10 @@
|
|||||||
"fontFamily": "Police",
|
"fontFamily": "Police",
|
||||||
"onlySelected": "Uniquement la sélection",
|
"onlySelected": "Uniquement la sélection",
|
||||||
"withBackground": "Arrière-plan",
|
"withBackground": "Arrière-plan",
|
||||||
"exportEmbedScene": "Scène intégrée",
|
"exportEmbedScene": "Intégrer la scène",
|
||||||
"exportEmbedScene_details": "Les données de scène seront enregistrées dans le fichier PNG/SVG exporté, afin que la scène puisse être restaurée à partir de celui-ci.\nCela augmentera la taille du fichier exporté.",
|
"exportEmbedScene_details": "Les données de scène seront enregistrées dans le fichier PNG/SVG exporté, afin que la scène puisse être restaurée à partir de celui-ci.\nCela augmentera la taille du fichier exporté.",
|
||||||
"addWatermark": "Ajouter \"Fait avec Excalidraw\"",
|
"addWatermark": "Ajouter \"Fait avec Excalidraw\"",
|
||||||
"handDrawn": "À main levée",
|
"handDrawn": "Manuscrit",
|
||||||
"normal": "Normale",
|
"normal": "Normale",
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
"small": "Petit",
|
"small": "Petit",
|
||||||
@@ -101,19 +101,21 @@
|
|||||||
"viewMode": "Mode présentation",
|
"viewMode": "Mode présentation",
|
||||||
"toggleExportColorScheme": "Activer/Désactiver l'export du thème de couleur",
|
"toggleExportColorScheme": "Activer/Désactiver l'export du thème de couleur",
|
||||||
"share": "Partager",
|
"share": "Partager",
|
||||||
|
"showStroke": "Afficher le sélecteur de couleur de trait",
|
||||||
|
"showBackground": "Afficher le sélecteur de couleur d'arrière-plan",
|
||||||
"toggleTheme": "Changer le thème"
|
"toggleTheme": "Changer le thème"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "Réinitialiser le canevas",
|
"clearReset": "Réinitialiser le canevas",
|
||||||
"exportJSON": "Exporter dans un fichier",
|
"exportJSON": "Exporter comme fichier",
|
||||||
"exportImage": "Enregistrer comme image",
|
"exportImage": "Enregistrer comme image",
|
||||||
"export": "Exporter",
|
"export": "Exporter",
|
||||||
"exportToPng": "Exporter en PNG",
|
"exportToPng": "Enregistrer en PNG",
|
||||||
"exportToSvg": "Exporter en SVG",
|
"exportToSvg": "Enregistrer en SVG",
|
||||||
"copyToClipboard": "Copier dans le presse-papier",
|
"copyToClipboard": "Copier dans le presse-papier",
|
||||||
"copyPngToClipboard": "Copier le PNG vers le presse-papier",
|
"copyPngToClipboard": "Copier le PNG dans le presse-papier",
|
||||||
"scale": "Échelle",
|
"scale": "Échelle",
|
||||||
"save": "Sauvegarder dans le fichier actuel",
|
"save": "Enregistrer dans le fichier actuel",
|
||||||
"saveAs": "Enregistrer sous",
|
"saveAs": "Enregistrer sous",
|
||||||
"load": "Ouvrir",
|
"load": "Ouvrir",
|
||||||
"getShareableLink": "Obtenir un lien de partage",
|
"getShareableLink": "Obtenir un lien de partage",
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "L'importation d'images n'est pas prise en charge pour le moment.\n\nVouliez-vous importer une scène ? Cette image ne semble pas contenir de données de scène. Avez-vous activé cette option lors de l'exportation ?",
|
"imageDoesNotContainScene": "L'importation d'images n'est pas prise en charge pour le moment.\n\nVouliez-vous importer une scène ? Cette image ne semble pas contenir de données de scène. Avez-vous activé cette option lors de l'exportation ?",
|
||||||
"cannotRestoreFromImage": "Impossible de restaurer la scène depuis ce fichier image",
|
"cannotRestoreFromImage": "Impossible de restaurer la scène depuis ce fichier image",
|
||||||
"invalidSceneUrl": "Impossible d'importer la scène depuis l'URL fournie. Elle est soit incorrecte, soit ne contient pas de données JSON Excalidraw valides.",
|
"invalidSceneUrl": "Impossible d'importer la scène depuis l'URL fournie. Elle est soit incorrecte, soit ne contient pas de données JSON Excalidraw valides.",
|
||||||
"resetLibrary": "Cela va effacer votre bibliothèque. Êtes-vous sûr·e ?"
|
"resetLibrary": "Cela va effacer votre bibliothèque. Êtes-vous sûr·e ?",
|
||||||
|
"invalidEncryptionKey": "La clé de chiffrement doit comporter 22 caractères. La collaboration en direct est désactivée."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Sélection",
|
"selection": "Sélection",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Cliquez pour démarrer plusieurs points, faites glisser pour une seule ligne",
|
"linearElement": "Cliquez pour démarrer plusieurs points, faites glisser pour une seule ligne",
|
||||||
"freeDraw": "Cliquez et faites glissez, relâchez quand vous avez terminé",
|
"freeDraw": "Cliquez et faites glissez, relâchez quand vous avez terminé",
|
||||||
"text": "Astuce : vous pouvez aussi ajouter du texte en double-cliquant n'importe où avec l'outil de sélection",
|
"text": "Astuce : vous pouvez aussi ajouter du texte en double-cliquant n'importe où avec l'outil de sélection",
|
||||||
|
"text_selected": "Double-cliquez ou appuyez sur ENTRÉE pour modifier le texte",
|
||||||
|
"text_editing": "Appuyez sur ÉCHAP ou Ctrl/Cmd+ENTRÉE pour terminer l'édition",
|
||||||
"linearElementMulti": "Cliquez sur le dernier point ou appuyez sur Échap ou Entrée pour terminer",
|
"linearElementMulti": "Cliquez sur le dernier point ou appuyez sur Échap ou Entrée pour terminer",
|
||||||
"lockAngle": "Vous pouvez restreindre l'angle en maintenant MAJ",
|
"lockAngle": "Vous pouvez restreindre l'angle en maintenant MAJ",
|
||||||
"resize": "Vous pouvez conserver les proportions en maintenant la touche MAJ pendant le redimensionnement,\nmaintenez la touche ALT pour redimensionner par rapport au centre",
|
"resize": "Vous pouvez conserver les proportions en maintenant la touche MAJ pendant le redimensionnement,\nmaintenez la touche ALT pour redimensionner par rapport au centre",
|
||||||
@@ -218,12 +223,15 @@
|
|||||||
"title": "Erreur"
|
"title": "Erreur"
|
||||||
},
|
},
|
||||||
"exportDialog": {
|
"exportDialog": {
|
||||||
"disk_title": "Sauvegarder sur le disque",
|
"disk_title": "Enregistrer sur le disque",
|
||||||
"disk_details": "Exportez les données de la scène dans un fichier que vous pourrez importer ultérieurement.",
|
"disk_details": "Exporter les données de la scène comme un fichier que vous pourrez importer ultérieurement.",
|
||||||
"disk_button": "Sauvegarder dans un fichier",
|
"disk_button": "Enregistrer comme fichier",
|
||||||
"link_title": "Lien à partager",
|
"link_title": "Lien partageable",
|
||||||
"link_details": "Exporter comme un lien en lecture seule.",
|
"link_details": "Exporter comme un lien en lecture seule.",
|
||||||
"link_button": "Exporter vers un lien"
|
"link_button": "Exporter comme lien",
|
||||||
|
"excalidrawplus_description": "Enregistrer la scène dans votre espace de travail Excalidraw+.",
|
||||||
|
"excalidrawplus_button": "Exporter",
|
||||||
|
"excalidrawplus_exportError": "Impossible d'exporter vers Excalidraw+ pour le moment..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Lire notre blog",
|
"blog": "Lire notre blog",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Flèche courbée",
|
"curvedArrow": "Flèche courbée",
|
||||||
"curvedLine": "Ligne courbée",
|
"curvedLine": "Ligne courbée",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
|
"doubleClick": "double-clic",
|
||||||
"drag": "glisser",
|
"drag": "glisser",
|
||||||
"editor": "Éditeur",
|
"editor": "Éditeur",
|
||||||
|
"editSelectedShape": "Modifier la forme sélectionnée (texte/flèche/ligne)",
|
||||||
"github": "Problème trouvé ? Soumettre",
|
"github": "Problème trouvé ? Soumettre",
|
||||||
"howto": "Suivez nos guides",
|
"howto": "Suivez nos guides",
|
||||||
"or": "ou",
|
"or": "ou",
|
||||||
"preventBinding": "Empêcher la liaison de flèche",
|
"preventBinding": "Empêcher la liaison de flèche",
|
||||||
"shapes": "Formes",
|
"shapes": "Formes",
|
||||||
"shortcuts": "Raccourcis clavier",
|
"shortcuts": "Raccourcis clavier",
|
||||||
"textFinish": "Terminer l'édition (texte)",
|
"textFinish": "Terminer l'édition (éditeur de texte)",
|
||||||
"textNewLine": "Ajouter une nouvelle ligne (texte)",
|
"textNewLine": "Ajouter une nouvelle ligne (éditeur de texte)",
|
||||||
"title": "Aide",
|
"title": "Aide",
|
||||||
"view": "Affichage",
|
"view": "Affichage",
|
||||||
"zoomToFit": "Zoomer pour voir tous les éléments",
|
"zoomToFit": "Zoomer pour voir tous les éléments",
|
||||||
@@ -267,11 +277,58 @@
|
|||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "Styles copiés.",
|
"copyStyles": "Styles copiés.",
|
||||||
"copyToClipboard": "Copié vers le presse-papiers.",
|
"copyToClipboard": "Copié dans le presse-papier.",
|
||||||
"copyToClipboardAsPng": "{{exportSelection}} copié dans le presse-papiers en PNG\n({{exportColorScheme}})",
|
"copyToClipboardAsPng": "{{exportSelection}} copié dans le presse-papier en PNG\n({{exportColorScheme}})",
|
||||||
"fileSaved": "Fichier enregistré.",
|
"fileSaved": "Fichier enregistré.",
|
||||||
"fileSavedToFilename": "Enregistré sous {filename}",
|
"fileSavedToFilename": "Enregistré sous {filename}",
|
||||||
"canvas": "canevas",
|
"canvas": "canevas",
|
||||||
"selection": "sélection"
|
"selection": "sélection"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "Blanc",
|
||||||
|
"f8f9fa": "Gris 0",
|
||||||
|
"f1f3f5": "Gris 1",
|
||||||
|
"fff5f5": "Rouge 0",
|
||||||
|
"fff0f6": "Rose 0",
|
||||||
|
"f8f0fc": "Mauve 0",
|
||||||
|
"f3f0ff": "Violet 0",
|
||||||
|
"edf2ff": "Indigo 0",
|
||||||
|
"e7f5ff": "Bleu 0",
|
||||||
|
"e3fafc": "Cyan 0",
|
||||||
|
"e6fcf5": "Turquoise 0",
|
||||||
|
"ebfbee": "Vert 0",
|
||||||
|
"f4fce3": "Citron vert 0",
|
||||||
|
"fff9db": "Jaune 0",
|
||||||
|
"fff4e6": "Orange 0",
|
||||||
|
"transparent": "Transparent",
|
||||||
|
"ced4da": "Gris 4",
|
||||||
|
"868e96": "Gris 6",
|
||||||
|
"fa5252": "Rouge 6",
|
||||||
|
"e64980": "Rose 6",
|
||||||
|
"be4bdb": "Mauve 6",
|
||||||
|
"7950f2": "Violet 6",
|
||||||
|
"4c6ef5": "Indigo 6",
|
||||||
|
"228be6": "Bleu 6",
|
||||||
|
"15aabf": "Cyan 6",
|
||||||
|
"12b886": "Turquoise 6",
|
||||||
|
"40c057": "Vert 6",
|
||||||
|
"82c91e": "Citron vert 6",
|
||||||
|
"fab005": "Jaune 6",
|
||||||
|
"fd7e14": "Orange 6",
|
||||||
|
"000000": "Noir",
|
||||||
|
"343a40": "Gris 8",
|
||||||
|
"495057": "Gris 7",
|
||||||
|
"c92a2a": "Rouge 9",
|
||||||
|
"a61e4d": "Rose 9",
|
||||||
|
"862e9c": "Mauve 9",
|
||||||
|
"5f3dc4": "Violet 9",
|
||||||
|
"364fc7": "Indigo 9",
|
||||||
|
"1864ab": "Bleu 9",
|
||||||
|
"0b7285": "Cyan 9",
|
||||||
|
"087f5b": "Turquoise 9",
|
||||||
|
"2b8a3e": "Vert 9",
|
||||||
|
"5c940d": "Citron vert 9",
|
||||||
|
"e67700": "Jaune 9",
|
||||||
|
"d9480f": "Orange 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
"background": "רקע",
|
"background": "רקע",
|
||||||
"fill": "מילוי",
|
"fill": "מילוי",
|
||||||
"strokeWidth": "עובי קו מתאר",
|
"strokeWidth": "עובי קו מתאר",
|
||||||
"strokeShape": "",
|
"strokeShape": "סגנון קו המתאר",
|
||||||
"strokeShape_gel": "",
|
"strokeShape_gel": "עט נובע",
|
||||||
"strokeShape_fountain": "",
|
"strokeShape_fountain": "עט נובע",
|
||||||
"strokeShape_brush": "",
|
"strokeShape_brush": "מברשת",
|
||||||
"strokeStyle": "סגנון קו המתאר",
|
"strokeStyle": "סגנון קו המתאר",
|
||||||
"strokeStyle_solid": "מלא",
|
"strokeStyle_solid": "מלא",
|
||||||
"strokeStyle_dashed": "מקווקו",
|
"strokeStyle_dashed": "מקווקו",
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
"fontSize": "גודל גופן",
|
"fontSize": "גודל גופן",
|
||||||
"fontFamily": "סוג הגופן",
|
"fontFamily": "סוג הגופן",
|
||||||
"onlySelected": "רק מה שנבחר",
|
"onlySelected": "רק מה שנבחר",
|
||||||
"withBackground": "",
|
"withBackground": "רקע",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "הטמעה של מידע הסצנה",
|
||||||
"exportEmbedScene_details": "מידע התצוגה יישמר לקובץ המיוצא מסוג PNG/SVG כך שיהיה ניתן לשחזרה ממנו.\nהפעולה תגדיל את גודל הקובץ המיוצא.",
|
"exportEmbedScene_details": "מידע התצוגה יישמר לקובץ המיוצא מסוג PNG/SVG כך שיהיה ניתן לשחזרה ממנו.\nהפעולה תגדיל את גודל הקובץ המיוצא.",
|
||||||
"addWatermark": "הוסף \"נוצר באמצעות Excalidraw\"",
|
"addWatermark": "הוסף \"נוצר באמצעות Excalidraw\"",
|
||||||
"handDrawn": "כתב יד",
|
"handDrawn": "כתב יד",
|
||||||
@@ -65,14 +65,14 @@
|
|||||||
"architect": "ארכיטקט",
|
"architect": "ארכיטקט",
|
||||||
"artist": "אמן",
|
"artist": "אמן",
|
||||||
"cartoonist": "קריקטוריסט",
|
"cartoonist": "קריקטוריסט",
|
||||||
"fileTitle": "",
|
"fileTitle": "שם קובץ",
|
||||||
"colorPicker": "בחירת צבע",
|
"colorPicker": "בחירת צבע",
|
||||||
"canvasBackground": "רקע הלוח",
|
"canvasBackground": "רקע הלוח",
|
||||||
"drawingCanvas": "לוח ציור",
|
"drawingCanvas": "לוח ציור",
|
||||||
"layers": "שכבות",
|
"layers": "שכבות",
|
||||||
"actions": "פעולות",
|
"actions": "פעולות",
|
||||||
"language": "שפה",
|
"language": "שפה",
|
||||||
"liveCollaboration": "",
|
"liveCollaboration": "התחל שיתוף חי",
|
||||||
"duplicateSelection": "שכפל",
|
"duplicateSelection": "שכפל",
|
||||||
"untitled": "ללא כותרת",
|
"untitled": "ללא כותרת",
|
||||||
"name": "שם",
|
"name": "שם",
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"group": "אחד לקבוצה",
|
"group": "אחד לקבוצה",
|
||||||
"ungroup": "פרק קבוצה",
|
"ungroup": "פרק קבוצה",
|
||||||
"collaborators": "שותפים",
|
"collaborators": "שותפים",
|
||||||
"showGrid": "",
|
"showGrid": "הצג רשת",
|
||||||
"addToLibrary": "הוסף לספריה",
|
"addToLibrary": "הוסף לספריה",
|
||||||
"removeFromLibrary": "הסר מספריה",
|
"removeFromLibrary": "הסר מספריה",
|
||||||
"libraryLoadingMessage": "טוען ספריה…",
|
"libraryLoadingMessage": "טוען ספריה…",
|
||||||
@@ -96,24 +96,26 @@
|
|||||||
"centerHorizontally": "מרכז אופקית",
|
"centerHorizontally": "מרכז אופקית",
|
||||||
"distributeHorizontally": "חלוקה אופקית",
|
"distributeHorizontally": "חלוקה אופקית",
|
||||||
"distributeVertically": "חלוקה אנכית",
|
"distributeVertically": "חלוקה אנכית",
|
||||||
"flipHorizontal": "",
|
"flipHorizontal": "סובב אופקית",
|
||||||
"flipVertical": "",
|
"flipVertical": "סובב אנכית",
|
||||||
"viewMode": "מצב תצוגה",
|
"viewMode": "מצב תצוגה",
|
||||||
"toggleExportColorScheme": "",
|
"toggleExportColorScheme": "שנה את ערכת צבעי הייצוא",
|
||||||
"share": "",
|
"share": "שתף",
|
||||||
"toggleTheme": ""
|
"showStroke": "הצג צבעי קו מתאר",
|
||||||
|
"showBackground": "הצג צבעי רקע",
|
||||||
|
"toggleTheme": "שינוי ערכת העיצוב"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"clearReset": "אפס את הלוח",
|
"clearReset": "אפס את הלוח",
|
||||||
"exportJSON": "",
|
"exportJSON": "ייצא לקובץ",
|
||||||
"exportImage": "",
|
"exportImage": "שמירה כתמונה",
|
||||||
"export": "ייצא",
|
"export": "ייצא",
|
||||||
"exportToPng": "יצא ל PNG",
|
"exportToPng": "יצא ל PNG",
|
||||||
"exportToSvg": "יצא ל SVG",
|
"exportToSvg": "יצא ל SVG",
|
||||||
"copyToClipboard": "העתק ללוח",
|
"copyToClipboard": "העתק ללוח",
|
||||||
"copyPngToClipboard": "העתק PNG ללוח",
|
"copyPngToClipboard": "העתק PNG ללוח",
|
||||||
"scale": "קנה מידה",
|
"scale": "קנה מידה",
|
||||||
"save": "",
|
"save": "שמירת קובץ נוכחי",
|
||||||
"saveAs": "שמירה בשם",
|
"saveAs": "שמירה בשם",
|
||||||
"load": "טען",
|
"load": "טען",
|
||||||
"getShareableLink": "קבל קישור לשיתוף",
|
"getShareableLink": "קבל קישור לשיתוף",
|
||||||
@@ -147,15 +149,16 @@
|
|||||||
"decryptFailed": "לא ניתן לפענח מידע.",
|
"decryptFailed": "לא ניתן לפענח מידע.",
|
||||||
"uploadedSecurly": "ההעלאה הוצפנה מקצה לקצה, ולכן שרת Excalidraw וצד שלישי לא יכולים לקרוא את התוכן.",
|
"uploadedSecurly": "ההעלאה הוצפנה מקצה לקצה, ולכן שרת Excalidraw וצד שלישי לא יכולים לקרוא את התוכן.",
|
||||||
"loadSceneOverridePrompt": "טעינה של ציור חיצוני תחליף את התוכן הקיים שלך. האם תרצה להמשיך?",
|
"loadSceneOverridePrompt": "טעינה של ציור חיצוני תחליף את התוכן הקיים שלך. האם תרצה להמשיך?",
|
||||||
"collabStopOverridePrompt": "",
|
"collabStopOverridePrompt": "עצירת השיתוף תוביל למחיקת התרשימים השמורים בדפדפן. האם את/ה בטוח/ה?\n(אם תרצה לשמור את התרשימים הקיימים, תוכל לסגור את הדפדפן מבלי לסיים את השיתוף.)",
|
||||||
"errorLoadingLibrary": "קרתה שגיאה בטעינת הספריה החיצונית.",
|
"errorLoadingLibrary": "קרתה שגיאה בטעינת הספריה החיצונית.",
|
||||||
"errorAddingToLibrary": "",
|
"errorAddingToLibrary": "לא ניתן להוסיף פריט לספרייה",
|
||||||
"errorRemovingFromLibrary": "",
|
"errorRemovingFromLibrary": "לא ניתן למחוק פריט מהספריה",
|
||||||
"confirmAddLibrary": "הפעולה תוסיף {{numShapes}} צורה(ות) לספריה שלך. האם אתה בטוח?",
|
"confirmAddLibrary": "הפעולה תוסיף {{numShapes}} צורה(ות) לספריה שלך. האם אתה בטוח?",
|
||||||
"imageDoesNotContainScene": "אין תמיכה בייבוא תמונות כעת.\n\nהאם אתה רוצה לייבא תצוגה? התמונה הזאת אינה מכילה מידע על תצוגה. האם הפעלת את האפשרות הזאת בזמן הוצאת המידע?",
|
"imageDoesNotContainScene": "אין תמיכה בייבוא תמונות כעת.\n\nהאם אתה רוצה לייבא תצוגה? התמונה הזאת אינה מכילה מידע על תצוגה. האם הפעלת את האפשרות הזאת בזמן הוצאת המידע?",
|
||||||
"cannotRestoreFromImage": "לא הצלחנו לשחזר את התצוגה מקובץ התמונה",
|
"cannotRestoreFromImage": "לא הצלחנו לשחזר את התצוגה מקובץ התמונה",
|
||||||
"invalidSceneUrl": "",
|
"invalidSceneUrl": "ייבוא המידע מן סצינה מכתובת האינטרנט נכשלה. המידע בנוי באופן משובש או שהוא אינו קובץ JSON תקין של Excalidraw.",
|
||||||
"resetLibrary": ""
|
"resetLibrary": "פעולה זו תנקה את כל הלוח. אתה בטוח?",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "בחירה",
|
"selection": "בחירה",
|
||||||
@@ -164,7 +167,7 @@
|
|||||||
"ellipse": "אליפסה",
|
"ellipse": "אליפסה",
|
||||||
"arrow": "חץ",
|
"arrow": "חץ",
|
||||||
"line": "קו",
|
"line": "קו",
|
||||||
"freedraw": "",
|
"freedraw": "צייר",
|
||||||
"text": "טקסט",
|
"text": "טקסט",
|
||||||
"library": "ספריה",
|
"library": "ספריה",
|
||||||
"lock": "השאר את הכלי הנבחר פעיל גם לאחר סיום הציור"
|
"lock": "השאר את הכלי הנבחר פעיל גם לאחר סיום הציור"
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "הקלק בשביל לבחור נקודות מרובות, גרור בשביל קו בודד",
|
"linearElement": "הקלק בשביל לבחור נקודות מרובות, גרור בשביל קו בודד",
|
||||||
"freeDraw": "לחץ וגרור, שחרר כשסיימת",
|
"freeDraw": "לחץ וגרור, שחרר כשסיימת",
|
||||||
"text": "טיפ: אפשר להוסיף טקסט על ידי לחיצה כפולה בכל מקום עם כלי הבחירה",
|
"text": "טיפ: אפשר להוסיף טקסט על ידי לחיצה כפולה בכל מקום עם כלי הבחירה",
|
||||||
|
"text_selected": "לחץ לחיצה כפולה או אנטר לעריכת הנקודות",
|
||||||
|
"text_editing": "כדי לסיים את העריכה לחצו על מקש Escape או על Ctrl ומקש Enter (Cmd במחשבי אפל)",
|
||||||
"linearElementMulti": "הקלק על הנקודה האחרונה או הקש Escape או Enter לסיום",
|
"linearElementMulti": "הקלק על הנקודה האחרונה או הקש Escape או Enter לסיום",
|
||||||
"lockAngle": "אתה יכול להגביל זווית ע״י לחיצה על SHIFT",
|
"lockAngle": "אתה יכול להגביל זווית ע״י לחיצה על SHIFT",
|
||||||
"resize": "ניתן להגביל פרופורציות על ידי לחיצה על SHIFT תוך כדי שינוי גודל,\nהחזק ALT בשביל לשנות גודל ביחס למרכז",
|
"resize": "ניתן להגביל פרופורציות על ידי לחיצה על SHIFT תוך כדי שינוי גודל,\nהחזק ALT בשביל לשנות גודל ביחס למרכז",
|
||||||
@@ -212,43 +217,48 @@
|
|||||||
"desc_inProgressIntro": "שיתוף חי כרגע בפעולה.",
|
"desc_inProgressIntro": "שיתוף חי כרגע בפעולה.",
|
||||||
"desc_shareLink": "שתף את הקישור עם כל מי שאתה מעוניין לעבוד אתו:",
|
"desc_shareLink": "שתף את הקישור עם כל מי שאתה מעוניין לעבוד אתו:",
|
||||||
"desc_exitSession": "עצירת השיתוף תנתק אותך מהחדר, אבל עדיין תוכל להמשיך לעבוד על הלוח, מקומית. שים לב שזה לא ישפיע על אנשים אחרים, והם עדיין יוכלו לשתף פעולה עם הגירסה שלהם.",
|
"desc_exitSession": "עצירת השיתוף תנתק אותך מהחדר, אבל עדיין תוכל להמשיך לעבוד על הלוח, מקומית. שים לב שזה לא ישפיע על אנשים אחרים, והם עדיין יוכלו לשתף פעולה עם הגירסה שלהם.",
|
||||||
"shareTitle": ""
|
"shareTitle": "הצטרף לסשן שיתוף בזמן אמת של Excalidraw"
|
||||||
},
|
},
|
||||||
"errorDialog": {
|
"errorDialog": {
|
||||||
"title": "שגיאה"
|
"title": "שגיאה"
|
||||||
},
|
},
|
||||||
"exportDialog": {
|
"exportDialog": {
|
||||||
"disk_title": "",
|
"disk_title": "שמור לכונן",
|
||||||
"disk_details": "",
|
"disk_details": "ייצוא מידע הסצינה לקובץ אותו ניתן יהיה לייבא בהמשך.",
|
||||||
"disk_button": "",
|
"disk_button": "שמירה לקובץ",
|
||||||
"link_title": "",
|
"link_title": "העתקת קישור לשיתוף",
|
||||||
"link_details": "",
|
"link_details": "ייצוא כקישור לקריאה בלבד.",
|
||||||
"link_button": ""
|
"link_button": "ייצוא כקישור",
|
||||||
|
"excalidrawplus_description": "שמור את המפה לסביבת העבודה שלך ב-Excalidraw+.",
|
||||||
|
"excalidrawplus_button": "ייצוא",
|
||||||
|
"excalidrawplus_exportError": "הייצוא ל-Excalidraw+ לא הצליח לעת עתה..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "קרא את הבלוג שלנו",
|
"blog": "קרא את הבלוג שלנו",
|
||||||
"click": "קליק",
|
"click": "קליק",
|
||||||
"curvedArrow": "",
|
"curvedArrow": "חץ מעוגל",
|
||||||
"curvedLine": "",
|
"curvedLine": "קו מעוגל",
|
||||||
"documentation": "תיעוד",
|
"documentation": "תיעוד",
|
||||||
|
"doubleClick": "לחיצה כפולה",
|
||||||
"drag": "לגרור",
|
"drag": "לגרור",
|
||||||
"editor": "עורך",
|
"editor": "עורך",
|
||||||
|
"editSelectedShape": "ערוך את הצורה הנבחרת (טקסט/חץ/קו)",
|
||||||
"github": "מצאת בעיה? דווח",
|
"github": "מצאת בעיה? דווח",
|
||||||
"howto": "עקוב אחר המדריכים שלנו",
|
"howto": "עקוב אחר המדריכים שלנו",
|
||||||
"or": "או",
|
"or": "או",
|
||||||
"preventBinding": "",
|
"preventBinding": "למנוע נעיצת חיצים",
|
||||||
"shapes": "צורות",
|
"shapes": "צורות",
|
||||||
"shortcuts": "קיצורי מקלדת",
|
"shortcuts": "קיצורי מקלדת",
|
||||||
"textFinish": "סיים עריכה (טקסט)",
|
"textFinish": "סיים עריכה (טקסט)",
|
||||||
"textNewLine": "הוסף שורה חדשה (טקסט)",
|
"textNewLine": "הוסף שורה חדשה (טקסט)",
|
||||||
"title": "עזרה",
|
"title": "עזרה",
|
||||||
"view": "תצוגה",
|
"view": "תצוגה",
|
||||||
"zoomToFit": "",
|
"zoomToFit": "גלילה להצגת כל האלמנטים במסך",
|
||||||
"zoomToSelection": "התמקד בבחירה"
|
"zoomToSelection": "התמקד בבחירה"
|
||||||
},
|
},
|
||||||
"encrypted": {
|
"encrypted": {
|
||||||
"tooltip": "הרישומים שלך מוצפנים מקצה לקצה כך שהשרתים של Excalidraw לא יראו אותם לעולם.",
|
"tooltip": "הרישומים שלך מוצפנים מקצה לקצה כך שהשרתים של Excalidraw לא יראו אותם לעולם.",
|
||||||
"link": ""
|
"link": "פוסט בבלוג על הצפנה מקצה לקצב ב-Excalidraw"
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "זווית",
|
"angle": "זווית",
|
||||||
@@ -260,18 +270,65 @@
|
|||||||
"storage": "אחסון",
|
"storage": "אחסון",
|
||||||
"title": "סטטיסטיקות לחנונים",
|
"title": "סטטיסטיקות לחנונים",
|
||||||
"total": "סה״כ",
|
"total": "סה״כ",
|
||||||
"version": "",
|
"version": "גרסה",
|
||||||
"versionCopy": "לחץ להעתקה",
|
"versionCopy": "לחץ להעתקה",
|
||||||
"versionNotAvailable": "",
|
"versionNotAvailable": "הגרסה אינה זמינה",
|
||||||
"width": "רוחב"
|
"width": "רוחב"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"copyStyles": "העתק סגנונות.",
|
"copyStyles": "העתק סגנונות.",
|
||||||
"copyToClipboard": "",
|
"copyToClipboard": "הועתק אל הלוח.",
|
||||||
"copyToClipboardAsPng": "",
|
"copyToClipboardAsPng": "{{exportSelection}} הועתקה ללוח כ-PNG\n({{exportColorScheme}})",
|
||||||
"fileSaved": "קובץ נשמר.",
|
"fileSaved": "קובץ נשמר.",
|
||||||
"fileSavedToFilename": "",
|
"fileSavedToFilename": "נשמר לקובץ {filename}",
|
||||||
"canvas": "",
|
"canvas": "משטח ציור",
|
||||||
"selection": ""
|
"selection": "בחירה"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "לבן",
|
||||||
|
"f8f9fa": "אפור 0",
|
||||||
|
"f1f3f5": "אפור 1",
|
||||||
|
"fff5f5": "אדום 0",
|
||||||
|
"fff0f6": "ורוד 0",
|
||||||
|
"f8f0fc": "ענבים 0",
|
||||||
|
"f3f0ff": "סגול 0",
|
||||||
|
"edf2ff": "כחול כהה 0",
|
||||||
|
"e7f5ff": "כחול 0",
|
||||||
|
"e3fafc": "טורקיז 0",
|
||||||
|
"e6fcf5": "ירקרק 0",
|
||||||
|
"ebfbee": "ירוק 0",
|
||||||
|
"f4fce3": "ליים 0",
|
||||||
|
"fff9db": "צהוב",
|
||||||
|
"fff4e6": "כתום 0",
|
||||||
|
"transparent": "שקוף",
|
||||||
|
"ced4da": "אפור 4",
|
||||||
|
"868e96": "אפור 6",
|
||||||
|
"fa5252": "אדום 6",
|
||||||
|
"e64980": "ורוד 6",
|
||||||
|
"be4bdb": "ענבים 6",
|
||||||
|
"7950f2": "סגול 6",
|
||||||
|
"4c6ef5": "כחול כהה 6",
|
||||||
|
"228be6": "כחול 6",
|
||||||
|
"15aabf": "טורקיז 6",
|
||||||
|
"12b886": "ירקרק 6",
|
||||||
|
"40c057": "ירוק 6",
|
||||||
|
"82c91e": "ליים 6",
|
||||||
|
"fab005": "צהוב 6",
|
||||||
|
"fd7e14": "כתום 6",
|
||||||
|
"000000": "שחור",
|
||||||
|
"343a40": "אפור 8",
|
||||||
|
"495057": "אפור 7",
|
||||||
|
"c92a2a": "אדום 9",
|
||||||
|
"a61e4d": "ורוד 9",
|
||||||
|
"862e9c": "ענבים 9",
|
||||||
|
"5f3dc4": "סגול 9",
|
||||||
|
"364fc7": "כחול כהה 9",
|
||||||
|
"1864ab": "כחול 9",
|
||||||
|
"0b7285": "טורקיז 9",
|
||||||
|
"087f5b": "ירקרק 9",
|
||||||
|
"2b8a3e": "ירוק 9",
|
||||||
|
"5c940d": "ליים 9",
|
||||||
|
"e67700": "ירוק 9",
|
||||||
|
"d9480f": "כתום 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "",
|
"viewMode": "",
|
||||||
"toggleExportColorScheme": "",
|
"toggleExportColorScheme": "",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"showStroke": "",
|
||||||
|
"showBackground": "",
|
||||||
"toggleTheme": ""
|
"toggleTheme": ""
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "दृश्य में छवि नहीं है",
|
"imageDoesNotContainScene": "दृश्य में छवि नहीं है",
|
||||||
"cannotRestoreFromImage": "छवि फ़ाइल बहाल दृश्य नहीं है",
|
"cannotRestoreFromImage": "छवि फ़ाइल बहाल दृश्य नहीं है",
|
||||||
"invalidSceneUrl": "",
|
"invalidSceneUrl": "",
|
||||||
"resetLibrary": ""
|
"resetLibrary": "",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "चयन",
|
"selection": "चयन",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "कई बिंदुओं को शुरू करने के लिए क्लिक करें, सिंगल लाइन के लिए खींचें",
|
"linearElement": "कई बिंदुओं को शुरू करने के लिए क्लिक करें, सिंगल लाइन के लिए खींचें",
|
||||||
"freeDraw": "क्लिक करें और खींचें। समाप्त करने के लिए, छोड़ो",
|
"freeDraw": "क्लिक करें और खींचें। समाप्त करने के लिए, छोड़ो",
|
||||||
"text": "आप चयन टूल से कहीं भी डबल-क्लिक करके टेक्स्ट जोड़ सकते हैं",
|
"text": "आप चयन टूल से कहीं भी डबल-क्लिक करके टेक्स्ट जोड़ सकते हैं",
|
||||||
|
"text_selected": "",
|
||||||
|
"text_editing": "",
|
||||||
"linearElementMulti": "अंतिम बिंदु पर क्लिक करें या समाप्त होने के लिए एस्केप या एंटर दबाएं",
|
"linearElementMulti": "अंतिम बिंदु पर क्लिक करें या समाप्त होने के लिए एस्केप या एंटर दबाएं",
|
||||||
"lockAngle": "आप घूर्णन करते समय SHIFT पकड़कर कोणों को मोड़ सकते हैं",
|
"lockAngle": "आप घूर्णन करते समय SHIFT पकड़कर कोणों को मोड़ सकते हैं",
|
||||||
"resize": "आकार बदलते समय आप SHIFT को पकड़ कर अनुपात में कमी कर सकते हैं,\nकेंद्र से आकार बदलने के लिए ALT दबाए रखें",
|
"resize": "आकार बदलते समय आप SHIFT को पकड़ कर अनुपात में कमी कर सकते हैं,\nकेंद्र से आकार बदलने के लिए ALT दबाए रखें",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "",
|
"disk_button": "",
|
||||||
"link_title": "",
|
"link_title": "",
|
||||||
"link_details": "",
|
"link_details": "",
|
||||||
"link_button": ""
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "हमारा ब्लॉग पढे",
|
"blog": "हमारा ब्लॉग पढे",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "वक्र तीर",
|
"curvedArrow": "वक्र तीर",
|
||||||
"curvedLine": "वक्र रेखा",
|
"curvedLine": "वक्र रेखा",
|
||||||
"documentation": "",
|
"documentation": "",
|
||||||
|
"doubleClick": "",
|
||||||
"drag": "खींचें",
|
"drag": "खींचें",
|
||||||
"editor": "संपादक",
|
"editor": "संपादक",
|
||||||
|
"editSelectedShape": "",
|
||||||
"github": "मुद्दा मिला? प्रस्तुत करें",
|
"github": "मुद्दा मिला? प्रस्तुत करें",
|
||||||
"howto": "हमारे गाइड का पालन करें",
|
"howto": "हमारे गाइड का पालन करें",
|
||||||
"or": "या",
|
"or": "या",
|
||||||
"preventBinding": "तीर बंधन रोकें",
|
"preventBinding": "तीर बंधन रोकें",
|
||||||
"shapes": "आकृतियाँ",
|
"shapes": "आकृतियाँ",
|
||||||
"shortcuts": "कीबोर्ड के शॉर्टकट्स",
|
"shortcuts": "कीबोर्ड के शॉर्टकट्स",
|
||||||
"textFinish": "संपादन समाप्त करें (पाठ)",
|
"textFinish": "",
|
||||||
"textNewLine": "नई पंक्ति जोड़ें (पाठ)",
|
"textNewLine": "",
|
||||||
"title": "मदद",
|
"title": "मदद",
|
||||||
"view": "दृश्य",
|
"view": "दृश्य",
|
||||||
"zoomToFit": "सभी तत्वों को फिट करने के लिए ज़ूम करें",
|
"zoomToFit": "सभी तत्वों को फिट करने के लिए ज़ूम करें",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "",
|
"fileSavedToFilename": "",
|
||||||
"canvas": "",
|
"canvas": "",
|
||||||
"selection": ""
|
"selection": ""
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "",
|
||||||
|
"fd7e14": "",
|
||||||
|
"000000": "",
|
||||||
|
"343a40": "",
|
||||||
|
"495057": "",
|
||||||
|
"c92a2a": "",
|
||||||
|
"a61e4d": "",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "",
|
||||||
|
"d9480f": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "",
|
"viewMode": "",
|
||||||
"toggleExportColorScheme": "",
|
"toggleExportColorScheme": "",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"showStroke": "",
|
||||||
|
"showBackground": "",
|
||||||
"toggleTheme": ""
|
"toggleTheme": ""
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "Képek importálása egyelőre nem támogatott.\n\nEgy jelenetet szeretnél betölteni? Úgy tűnik ez a kép fájl nem tartalmazza a szükséges adatokat. Exportáláskor ezt egy külön opcióval lehet beállítani.",
|
"imageDoesNotContainScene": "Képek importálása egyelőre nem támogatott.\n\nEgy jelenetet szeretnél betölteni? Úgy tűnik ez a kép fájl nem tartalmazza a szükséges adatokat. Exportáláskor ezt egy külön opcióval lehet beállítani.",
|
||||||
"cannotRestoreFromImage": "A jelenet visszaállítása nem sikerült ebből a kép fájlból",
|
"cannotRestoreFromImage": "A jelenet visszaállítása nem sikerült ebből a kép fájlból",
|
||||||
"invalidSceneUrl": "",
|
"invalidSceneUrl": "",
|
||||||
"resetLibrary": ""
|
"resetLibrary": "",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Kijelölés",
|
"selection": "Kijelölés",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Kattintással görbe, az eger húzásával pedig egyenes nyilat rajzolhatsz",
|
"linearElement": "Kattintással görbe, az eger húzásával pedig egyenes nyilat rajzolhatsz",
|
||||||
"freeDraw": "Kattints és húzd, majd engedd el, amikor végeztél",
|
"freeDraw": "Kattints és húzd, majd engedd el, amikor végeztél",
|
||||||
"text": "Tipp: A kijelölés eszközzel a dupla kattintás új szöveget hoz létre",
|
"text": "Tipp: A kijelölés eszközzel a dupla kattintás új szöveget hoz létre",
|
||||||
|
"text_selected": "",
|
||||||
|
"text_editing": "",
|
||||||
"linearElementMulti": "Kattints a következő ív pozíciójára, vagy fejezd be a nyilat az Escape vagy Enter megnyomásával",
|
"linearElementMulti": "Kattints a következő ív pozíciójára, vagy fejezd be a nyilat az Escape vagy Enter megnyomásával",
|
||||||
"lockAngle": "A SHIFT billentyű lenyomva tartásával korlátozhatja forgatás szögét",
|
"lockAngle": "A SHIFT billentyű lenyomva tartásával korlátozhatja forgatás szögét",
|
||||||
"resize": "A SHIFT billentyű lenyomva tartásával az átméretezés megtartja az arányokat,\naz ALT lenyomva tartásával pedig a középpont egy helyben marad",
|
"resize": "A SHIFT billentyű lenyomva tartásával az átméretezés megtartja az arányokat,\naz ALT lenyomva tartásával pedig a középpont egy helyben marad",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "",
|
"disk_button": "",
|
||||||
"link_title": "",
|
"link_title": "",
|
||||||
"link_details": "",
|
"link_details": "",
|
||||||
"link_button": ""
|
"link_button": "",
|
||||||
|
"excalidrawplus_description": "",
|
||||||
|
"excalidrawplus_button": "",
|
||||||
|
"excalidrawplus_exportError": ""
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "",
|
"blog": "",
|
||||||
@@ -231,8 +239,10 @@
|
|||||||
"curvedArrow": "",
|
"curvedArrow": "",
|
||||||
"curvedLine": "",
|
"curvedLine": "",
|
||||||
"documentation": "",
|
"documentation": "",
|
||||||
|
"doubleClick": "",
|
||||||
"drag": "",
|
"drag": "",
|
||||||
"editor": "",
|
"editor": "",
|
||||||
|
"editSelectedShape": "",
|
||||||
"github": "",
|
"github": "",
|
||||||
"howto": "",
|
"howto": "",
|
||||||
"or": "",
|
"or": "",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "",
|
"fileSavedToFilename": "",
|
||||||
"canvas": "",
|
"canvas": "",
|
||||||
"selection": ""
|
"selection": ""
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "",
|
||||||
|
"f8f9fa": "",
|
||||||
|
"f1f3f5": "",
|
||||||
|
"fff5f5": "",
|
||||||
|
"fff0f6": "",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "",
|
||||||
|
"fff4e6": "",
|
||||||
|
"transparent": "",
|
||||||
|
"ced4da": "",
|
||||||
|
"868e96": "",
|
||||||
|
"fa5252": "",
|
||||||
|
"e64980": "",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "",
|
||||||
|
"fd7e14": "",
|
||||||
|
"000000": "",
|
||||||
|
"343a40": "",
|
||||||
|
"495057": "",
|
||||||
|
"c92a2a": "",
|
||||||
|
"a61e4d": "",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "",
|
||||||
|
"d9480f": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "Mode tampilan",
|
"viewMode": "Mode tampilan",
|
||||||
"toggleExportColorScheme": "Ubah skema warna ekspor",
|
"toggleExportColorScheme": "Ubah skema warna ekspor",
|
||||||
"share": "Bagikan",
|
"share": "Bagikan",
|
||||||
|
"showStroke": "Tampilkan garis pengambil warna",
|
||||||
|
"showBackground": "Tampilkan latar pengambil warna",
|
||||||
"toggleTheme": "Ubah tema"
|
"toggleTheme": "Ubah tema"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "Mengimpor gambar tidak didukung saat ini.\n\nApakah Anda ingin impor pemandangan? Gambar ini tidak berisi data pemandangan. Sudah ka Anda aktifkan ini ketika ekspor?",
|
"imageDoesNotContainScene": "Mengimpor gambar tidak didukung saat ini.\n\nApakah Anda ingin impor pemandangan? Gambar ini tidak berisi data pemandangan. Sudah ka Anda aktifkan ini ketika ekspor?",
|
||||||
"cannotRestoreFromImage": "Pemandangan tidak dapat dipulihkan dari file gambar ini",
|
"cannotRestoreFromImage": "Pemandangan tidak dapat dipulihkan dari file gambar ini",
|
||||||
"invalidSceneUrl": "Tidak dapat impor pemandangan dari URL. Kemungkinan URL itu rusak atau tidak berisi data JSON Excalidraw yang valid.",
|
"invalidSceneUrl": "Tidak dapat impor pemandangan dari URL. Kemungkinan URL itu rusak atau tidak berisi data JSON Excalidraw yang valid.",
|
||||||
"resetLibrary": "Ini akan menghapus pustaka Anda. Anda yakin?"
|
"resetLibrary": "Ini akan menghapus pustaka Anda. Anda yakin?",
|
||||||
|
"invalidEncryptionKey": "Sandi enkripsi harus 22 karakter. Kolaborasi langsung dinonaktifkan."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Pilihan",
|
"selection": "Pilihan",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Klik untuk memulai banyak poin, seret untuk satu baris",
|
"linearElement": "Klik untuk memulai banyak poin, seret untuk satu baris",
|
||||||
"freeDraw": "Klik dan seret, lepaskan jika Anda selesai",
|
"freeDraw": "Klik dan seret, lepaskan jika Anda selesai",
|
||||||
"text": "Tip: Anda juga dapat menambahkan teks dengan klik ganda di mana saja dengan alat pemilihan",
|
"text": "Tip: Anda juga dapat menambahkan teks dengan klik ganda di mana saja dengan alat pemilihan",
|
||||||
|
"text_selected": "Klik ganda atau tekan ENTER untuk edit teks",
|
||||||
|
"text_editing": "Tekan Escape atau CtrlAtauCmd+ENTER untuk selesai mengedit",
|
||||||
"linearElementMulti": "Klik pada titik akhir atau tekan Escape atau Enter untuk menyelesaikan",
|
"linearElementMulti": "Klik pada titik akhir atau tekan Escape atau Enter untuk menyelesaikan",
|
||||||
"lockAngle": "Anda dapat menjaga sudut dengan menahan SHIFT",
|
"lockAngle": "Anda dapat menjaga sudut dengan menahan SHIFT",
|
||||||
"resize": "Anda dapat menjaga proposi dengan menekan SHIFT sambil mengubah ukuran,\ntekan AlT untuk mengubah ukuran dari tengah",
|
"resize": "Anda dapat menjaga proposi dengan menekan SHIFT sambil mengubah ukuran,\ntekan AlT untuk mengubah ukuran dari tengah",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "Simpan ke file",
|
"disk_button": "Simpan ke file",
|
||||||
"link_title": "Tautan",
|
"link_title": "Tautan",
|
||||||
"link_details": "Ekspor sebagai tautan yang hanya dibaca.",
|
"link_details": "Ekspor sebagai tautan yang hanya dibaca.",
|
||||||
"link_button": "Ekspor ke tautan"
|
"link_button": "Ekspor ke tautan",
|
||||||
|
"excalidrawplus_description": "Simpan pemandangan ke ruang kerja Excalidraw+ Anda.",
|
||||||
|
"excalidrawplus_button": "Ekspor",
|
||||||
|
"excalidrawplus_exportError": "Tidak dapat ekspor ke Excalidraw+ saat ini..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Baca blog kami",
|
"blog": "Baca blog kami",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Panah lengkung",
|
"curvedArrow": "Panah lengkung",
|
||||||
"curvedLine": "Garis lengkung",
|
"curvedLine": "Garis lengkung",
|
||||||
"documentation": "Dokumentasi",
|
"documentation": "Dokumentasi",
|
||||||
|
"doubleClick": "klik-ganda",
|
||||||
"drag": "seret",
|
"drag": "seret",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
|
"editSelectedShape": "Edit bentuk yang dipilih (teks/panah/garis)",
|
||||||
"github": "Menemukan masalah? Kirimkan",
|
"github": "Menemukan masalah? Kirimkan",
|
||||||
"howto": "Ikuti panduan kami",
|
"howto": "Ikuti panduan kami",
|
||||||
"or": "atau",
|
"or": "atau",
|
||||||
"preventBinding": "Cegah pengikatan panah",
|
"preventBinding": "Cegah pengikatan panah",
|
||||||
"shapes": "Bentuk",
|
"shapes": "Bentuk",
|
||||||
"shortcuts": "Pintasan keyboard",
|
"shortcuts": "Pintasan keyboard",
|
||||||
"textFinish": "Selesai mengedit (teks)",
|
"textFinish": "Selesai mengedit (editor teks)",
|
||||||
"textNewLine": "Tambahkan baris baru (teks)",
|
"textNewLine": "Tambahkan garis baru (editor teks)",
|
||||||
"title": "Bantuan",
|
"title": "Bantuan",
|
||||||
"view": "Tampilan",
|
"view": "Tampilan",
|
||||||
"zoomToFit": "Perbesar agar sesuai dengan semua elemen",
|
"zoomToFit": "Perbesar agar sesuai dengan semua elemen",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "Disimpan ke {filename}",
|
"fileSavedToFilename": "Disimpan ke {filename}",
|
||||||
"canvas": "kanvas",
|
"canvas": "kanvas",
|
||||||
"selection": "pilihan"
|
"selection": "pilihan"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "Putih",
|
||||||
|
"f8f9fa": "Abu-abu 0",
|
||||||
|
"f1f3f5": "Abu-abu 1",
|
||||||
|
"fff5f5": "Merah 0",
|
||||||
|
"fff0f6": "Merah muda 0",
|
||||||
|
"f8f0fc": "Ungu 0",
|
||||||
|
"f3f0ff": "Violet 0",
|
||||||
|
"edf2ff": "Indigo 0",
|
||||||
|
"e7f5ff": "Biru 0",
|
||||||
|
"e3fafc": "Cyan 0",
|
||||||
|
"e6fcf5": "Teal 0",
|
||||||
|
"ebfbee": "Hijau 0",
|
||||||
|
"f4fce3": "Lime 0",
|
||||||
|
"fff9db": "Kuning 0",
|
||||||
|
"fff4e6": "Jingga 0",
|
||||||
|
"transparent": "Transparan",
|
||||||
|
"ced4da": "Abu-abu 4",
|
||||||
|
"868e96": "Abu-abu 6",
|
||||||
|
"fa5252": "Merah 6",
|
||||||
|
"e64980": "Merah muda 6",
|
||||||
|
"be4bdb": "Ungu 6",
|
||||||
|
"7950f2": "Violet 6",
|
||||||
|
"4c6ef5": "Indigo 6",
|
||||||
|
"228be6": "Biru 6",
|
||||||
|
"15aabf": "Cyan 6",
|
||||||
|
"12b886": "Teal 6",
|
||||||
|
"40c057": "Hijau 6",
|
||||||
|
"82c91e": "Lime 6",
|
||||||
|
"fab005": "Kuning 6",
|
||||||
|
"fd7e14": "Jingga 6",
|
||||||
|
"000000": "Hitam",
|
||||||
|
"343a40": "Abu-abu 8",
|
||||||
|
"495057": "Abu-abu 7",
|
||||||
|
"c92a2a": "Merah 9",
|
||||||
|
"a61e4d": "Merah muda 9",
|
||||||
|
"862e9c": "Ungu 9",
|
||||||
|
"5f3dc4": "Violet 9",
|
||||||
|
"364fc7": "Indigo 9",
|
||||||
|
"1864ab": "Biru 9",
|
||||||
|
"0b7285": "Cyan 9",
|
||||||
|
"087f5b": "Teal 9",
|
||||||
|
"2b8a3e": "Hijau 9",
|
||||||
|
"5c940d": "Lime 9",
|
||||||
|
"e67700": "Kuning 9",
|
||||||
|
"d9480f": "Jingga 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "Modalità visualizzazione",
|
"viewMode": "Modalità visualizzazione",
|
||||||
"toggleExportColorScheme": "Cambia lo schema di colori in esportazione",
|
"toggleExportColorScheme": "Cambia lo schema di colori in esportazione",
|
||||||
"share": "Condividi",
|
"share": "Condividi",
|
||||||
|
"showStroke": "Mostra selettore colore del tratto",
|
||||||
|
"showBackground": "Mostra selettore colore di sfondo",
|
||||||
"toggleTheme": "Cambia tema"
|
"toggleTheme": "Cambia tema"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "L'importazione di immagini al momento non è supportata.\n\nVuoi importare una scena? Questa immagine non sembra contenere alcun dato di scena. Hai abilitato questa opzione durante l'esportazione?",
|
"imageDoesNotContainScene": "L'importazione di immagini al momento non è supportata.\n\nVuoi importare una scena? Questa immagine non sembra contenere alcun dato di scena. Hai abilitato questa opzione durante l'esportazione?",
|
||||||
"cannotRestoreFromImage": "Impossibile ripristinare la scena da questo file immagine",
|
"cannotRestoreFromImage": "Impossibile ripristinare la scena da questo file immagine",
|
||||||
"invalidSceneUrl": "Impossibile importare la scena dall'URL fornito. Potrebbe essere malformato o non contenere dati JSON Excalidraw validi.",
|
"invalidSceneUrl": "Impossibile importare la scena dall'URL fornito. Potrebbe essere malformato o non contenere dati JSON Excalidraw validi.",
|
||||||
"resetLibrary": "Questa azione cancellerà l'intera libreria. Sei sicuro?"
|
"resetLibrary": "Questa azione cancellerà l'intera libreria. Sei sicuro?",
|
||||||
|
"invalidEncryptionKey": "La chiave di cifratura deve essere composta da 22 caratteri. La collaborazione live è disabilitata."
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Selezione",
|
"selection": "Selezione",
|
||||||
@@ -164,7 +167,7 @@
|
|||||||
"ellipse": "Ellisse",
|
"ellipse": "Ellisse",
|
||||||
"arrow": "Freccia",
|
"arrow": "Freccia",
|
||||||
"line": "Linea",
|
"line": "Linea",
|
||||||
"freedraw": "",
|
"freedraw": "Disegno",
|
||||||
"text": "Testo",
|
"text": "Testo",
|
||||||
"library": "Libreria",
|
"library": "Libreria",
|
||||||
"lock": "Mantieni lo strumento selezionato attivo dopo aver disegnato"
|
"lock": "Mantieni lo strumento selezionato attivo dopo aver disegnato"
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Clicca per iniziare una linea in più punti, trascina per singola linea",
|
"linearElement": "Clicca per iniziare una linea in più punti, trascina per singola linea",
|
||||||
"freeDraw": "Clicca e trascina, rilascia quando avrai finito",
|
"freeDraw": "Clicca e trascina, rilascia quando avrai finito",
|
||||||
"text": "Suggerimento: puoi anche aggiungere del testo facendo doppio clic ovunque con lo strumento di selezione",
|
"text": "Suggerimento: puoi anche aggiungere del testo facendo doppio clic ovunque con lo strumento di selezione",
|
||||||
|
"text_selected": "Fai doppio click o premi INVIO per modificare il testo",
|
||||||
|
"text_editing": "Premi ESC o CtrlOCmd+INVIO per completare le modifiche",
|
||||||
"linearElementMulti": "Clicca sull'ultimo punto o premi Esc o Invio per finire",
|
"linearElementMulti": "Clicca sull'ultimo punto o premi Esc o Invio per finire",
|
||||||
"lockAngle": "Puoi limitare l'angolo tenendo premuto SHIFT",
|
"lockAngle": "Puoi limitare l'angolo tenendo premuto SHIFT",
|
||||||
"resize": "Per vincolare le proporzioni, tieni premuto MAIUSC durante il ridimensionamento;\nper ridimensionare dal centro, tieni premuto ALT",
|
"resize": "Per vincolare le proporzioni, tieni premuto MAIUSC durante il ridimensionamento;\nper ridimensionare dal centro, tieni premuto ALT",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "Salva su file",
|
"disk_button": "Salva su file",
|
||||||
"link_title": "Link condivisibile",
|
"link_title": "Link condivisibile",
|
||||||
"link_details": "Esporta come link di sola lettura.",
|
"link_details": "Esporta come link di sola lettura.",
|
||||||
"link_button": "Esporta come Link"
|
"link_button": "Esporta come Link",
|
||||||
|
"excalidrawplus_description": "Salva la scena nel tuo spazio di lavoro Excalidraw+.",
|
||||||
|
"excalidrawplus_button": "Esporta",
|
||||||
|
"excalidrawplus_exportError": "Non è stato possibile esportare su Excalidraw+ al questo momento..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Leggi il nostro blog",
|
"blog": "Leggi il nostro blog",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Freccia curva",
|
"curvedArrow": "Freccia curva",
|
||||||
"curvedLine": "Linea curva",
|
"curvedLine": "Linea curva",
|
||||||
"documentation": "Documentazione",
|
"documentation": "Documentazione",
|
||||||
|
"doubleClick": "doppio-click",
|
||||||
"drag": "trascina",
|
"drag": "trascina",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
|
"editSelectedShape": "Modifica la forma selezionata (testo/freccia/linea)",
|
||||||
"github": "Trovato un problema? Segnalalo",
|
"github": "Trovato un problema? Segnalalo",
|
||||||
"howto": "Segui le nostre guide",
|
"howto": "Segui le nostre guide",
|
||||||
"or": "oppure",
|
"or": "oppure",
|
||||||
"preventBinding": "Impedisci legame della freccia",
|
"preventBinding": "Impedisci legame della freccia",
|
||||||
"shapes": "Forme",
|
"shapes": "Forme",
|
||||||
"shortcuts": "Scorciatoie da tastiera",
|
"shortcuts": "Scorciatoie da tastiera",
|
||||||
"textFinish": "Termina la modifica (testo)",
|
"textFinish": "Completa la modifica (editor di testo)",
|
||||||
"textNewLine": "Aggiungi nuova riga (testo)",
|
"textNewLine": "Aggiungi nuova riga (editor di testo)",
|
||||||
"title": "Guida",
|
"title": "Guida",
|
||||||
"view": "Vista",
|
"view": "Vista",
|
||||||
"zoomToFit": "Adatta zoom per mostrare tutti gli elementi",
|
"zoomToFit": "Adatta zoom per mostrare tutti gli elementi",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "Salvato in {filename}",
|
"fileSavedToFilename": "Salvato in {filename}",
|
||||||
"canvas": "tela",
|
"canvas": "tela",
|
||||||
"selection": "selezione"
|
"selection": "selezione"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "Bianco",
|
||||||
|
"f8f9fa": "Grigio 0",
|
||||||
|
"f1f3f5": "Grigio 1",
|
||||||
|
"fff5f5": "Rosso 0",
|
||||||
|
"fff0f6": "Rosa 0",
|
||||||
|
"f8f0fc": "Uva 0",
|
||||||
|
"f3f0ff": "Viola 0",
|
||||||
|
"edf2ff": "Indaco 0",
|
||||||
|
"e7f5ff": "Blu 0",
|
||||||
|
"e3fafc": "Ciano 0",
|
||||||
|
"e6fcf5": "Verde acqua 0",
|
||||||
|
"ebfbee": "Verde 0",
|
||||||
|
"f4fce3": "Lime 0",
|
||||||
|
"fff9db": "Giallo 0",
|
||||||
|
"fff4e6": "Arancio 0",
|
||||||
|
"transparent": "Trasparente",
|
||||||
|
"ced4da": "Grigio 4",
|
||||||
|
"868e96": "Grigio 6",
|
||||||
|
"fa5252": "Rosso 6",
|
||||||
|
"e64980": "Rosa 6",
|
||||||
|
"be4bdb": "Uva 6",
|
||||||
|
"7950f2": "Viola 6",
|
||||||
|
"4c6ef5": "Indaco 6",
|
||||||
|
"228be6": "Blu 6",
|
||||||
|
"15aabf": "Ciano 6",
|
||||||
|
"12b886": "Verde acqua 6",
|
||||||
|
"40c057": "Verde 6",
|
||||||
|
"82c91e": "Lime 6",
|
||||||
|
"fab005": "Giallo 6",
|
||||||
|
"fd7e14": "Arancio 6",
|
||||||
|
"000000": "Nero",
|
||||||
|
"343a40": "Grigio 8",
|
||||||
|
"495057": "Grigio 7",
|
||||||
|
"c92a2a": "Rosso 9",
|
||||||
|
"a61e4d": "Rosa 9",
|
||||||
|
"862e9c": "Uva 9",
|
||||||
|
"5f3dc4": "Viola 9",
|
||||||
|
"364fc7": "Indaco 9",
|
||||||
|
"1864ab": "Blu 9",
|
||||||
|
"0b7285": "Ciano 9",
|
||||||
|
"087f5b": "Verde acqua 9",
|
||||||
|
"2b8a3e": "Verde 9",
|
||||||
|
"5c940d": "Lime 9",
|
||||||
|
"e67700": "Giallo 9",
|
||||||
|
"d9480f": "Arancio 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"strokeWidth": "線の幅",
|
"strokeWidth": "線の幅",
|
||||||
"strokeShape": "ストロークの形状",
|
"strokeShape": "ストロークの形状",
|
||||||
"strokeShape_gel": "ジェルペン",
|
"strokeShape_gel": "ジェルペン",
|
||||||
"strokeShape_fountain": "噴水ペン",
|
"strokeShape_fountain": "万年筆",
|
||||||
"strokeShape_brush": "ブラシペン",
|
"strokeShape_brush": "ブラシペン",
|
||||||
"strokeStyle": "線の種類",
|
"strokeStyle": "線の種類",
|
||||||
"strokeStyle_solid": "実線",
|
"strokeStyle_solid": "実線",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"fontFamily": "フォントの種類",
|
"fontFamily": "フォントの種類",
|
||||||
"onlySelected": "選択中のみ",
|
"onlySelected": "選択中のみ",
|
||||||
"withBackground": "背景",
|
"withBackground": "背景",
|
||||||
"exportEmbedScene": "",
|
"exportEmbedScene": "埋め込みシーン",
|
||||||
"exportEmbedScene_details": "シーンデータはエクスポートされたPNG/SVGファイルに保存され、シーンを復元することができます。\nエクスポートされたファイルのサイズは増加します。",
|
"exportEmbedScene_details": "シーンデータはエクスポートされたPNG/SVGファイルに保存され、シーンを復元することができます。\nエクスポートされたファイルのサイズは増加します。",
|
||||||
"addWatermark": "\"Made with Excalidraw\"と表示",
|
"addWatermark": "\"Made with Excalidraw\"と表示",
|
||||||
"handDrawn": "手描き風",
|
"handDrawn": "手描き風",
|
||||||
@@ -99,8 +99,10 @@
|
|||||||
"flipHorizontal": "水平方向に反転",
|
"flipHorizontal": "水平方向に反転",
|
||||||
"flipVertical": "垂直方向に反転",
|
"flipVertical": "垂直方向に反転",
|
||||||
"viewMode": "閲覧モード",
|
"viewMode": "閲覧モード",
|
||||||
"toggleExportColorScheme": "",
|
"toggleExportColorScheme": "エクスポートカラースキームの切り替え",
|
||||||
"share": "共有",
|
"share": "共有",
|
||||||
|
"showStroke": "ストロークカラーピッカーを表示",
|
||||||
|
"showBackground": "背景色ピッカーを表示",
|
||||||
"toggleTheme": "テーマの切り替え"
|
"toggleTheme": "テーマの切り替え"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "現在、画像のインポートはサポートされていません。\n\nシーンをインポートしようとしましたか?この画像にはシーンデータが含まれていないようです。エクスポート中に有効にしていましたか?",
|
"imageDoesNotContainScene": "現在、画像のインポートはサポートされていません。\n\nシーンをインポートしようとしましたか?この画像にはシーンデータが含まれていないようです。エクスポート中に有効にしていましたか?",
|
||||||
"cannotRestoreFromImage": "このイメージファイルからシーンを復元できませんでした",
|
"cannotRestoreFromImage": "このイメージファイルからシーンを復元できませんでした",
|
||||||
"invalidSceneUrl": "指定された URL からシーンをインポートできませんでした。不正な形式であるか、有効な Excalidraw JSON データが含まれていません。",
|
"invalidSceneUrl": "指定された URL からシーンをインポートできませんでした。不正な形式であるか、有効な Excalidraw JSON データが含まれていません。",
|
||||||
"resetLibrary": "ライブラリを消去します。本当によろしいですか?"
|
"resetLibrary": "ライブラリを消去します。本当によろしいですか?",
|
||||||
|
"invalidEncryptionKey": "暗号化キーは22文字でなければなりません。ライブコラボレーションは無効化されています。"
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "選択",
|
"selection": "選択",
|
||||||
@@ -175,9 +178,11 @@
|
|||||||
"shapes": "図形"
|
"shapes": "図形"
|
||||||
},
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"linearElement": "クリックして複数の点を開始し、1行にドラッグします。",
|
"linearElement": "クリックして複数の点を開始し、ドラッグで直線を引きます。",
|
||||||
"freeDraw": "クリックしてドラッグします。離すと終了します",
|
"freeDraw": "クリックしてドラッグします。離すと終了します",
|
||||||
"text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます",
|
"text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます",
|
||||||
|
"text_selected": "テキストを編集するには、ダブルクリックまたはEnterキーを押します",
|
||||||
|
"text_editing": "Esc キーまたは CtrlOrCmd+ENTER キーを押して編集を終了します",
|
||||||
"linearElementMulti": "最後のポイントをクリックするか、エスケープまたはEnterを押して終了します",
|
"linearElementMulti": "最後のポイントをクリックするか、エスケープまたはEnterを押して終了します",
|
||||||
"lockAngle": "SHIFTを押したままにすると、角度を制限することができます",
|
"lockAngle": "SHIFTを押したままにすると、角度を制限することができます",
|
||||||
"resize": "サイズを変更中にSHIFTを押しすと比率を制御できます。Altを押すと中央からサイズを変更できます。",
|
"resize": "サイズを変更中にSHIFTを押しすと比率を制御できます。Altを押すと中央からサイズを変更できます。",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "ファイルへ保存",
|
"disk_button": "ファイルへ保存",
|
||||||
"link_title": "共有可能なリンク",
|
"link_title": "共有可能なリンク",
|
||||||
"link_details": "読み取り専用リンクとしてエクスポート",
|
"link_details": "読み取り専用リンクとしてエクスポート",
|
||||||
"link_button": "リンクとしてエクスポート"
|
"link_button": "リンクとしてエクスポート",
|
||||||
|
"excalidrawplus_description": "Excalidraw+ ワークスペースにシーンを保存します。",
|
||||||
|
"excalidrawplus_button": "エクスポート",
|
||||||
|
"excalidrawplus_exportError": "Excalidraw+ にエクスポートできませんでした..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "公式ブログを読む",
|
"blog": "公式ブログを読む",
|
||||||
@@ -231,15 +239,17 @@
|
|||||||
"curvedArrow": "カーブした矢印",
|
"curvedArrow": "カーブした矢印",
|
||||||
"curvedLine": "曲線",
|
"curvedLine": "曲線",
|
||||||
"documentation": "ドキュメント",
|
"documentation": "ドキュメント",
|
||||||
|
"doubleClick": "ダブルクリック",
|
||||||
"drag": "ドラッグ",
|
"drag": "ドラッグ",
|
||||||
"editor": "エディタ",
|
"editor": "エディタ",
|
||||||
|
"editSelectedShape": "選択した図形の編集 (テキスト/矢印/線)",
|
||||||
"github": "不具合報告はこちら",
|
"github": "不具合報告はこちら",
|
||||||
"howto": "ヘルプ・マニュアル",
|
"howto": "ヘルプ・マニュアル",
|
||||||
"or": "または",
|
"or": "または",
|
||||||
"preventBinding": "矢印を結合しない",
|
"preventBinding": "矢印を結合しない",
|
||||||
"shapes": "図形",
|
"shapes": "図形",
|
||||||
"shortcuts": "キーボードショートカット",
|
"shortcuts": "キーボードショートカット",
|
||||||
"textFinish": "編集を終了する (テキスト)",
|
"textFinish": "編集を終了 (テキストエディタ)",
|
||||||
"textNewLine": "新しい行を追加 (テキスト)",
|
"textNewLine": "新しい行を追加 (テキスト)",
|
||||||
"title": "ヘルプ",
|
"title": "ヘルプ",
|
||||||
"view": "表示",
|
"view": "表示",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "{filename} に保存しました",
|
"fileSavedToFilename": "{filename} に保存しました",
|
||||||
"canvas": "キャンバス",
|
"canvas": "キャンバス",
|
||||||
"selection": "選択"
|
"selection": "選択"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "ホワイト",
|
||||||
|
"f8f9fa": "グレー 0",
|
||||||
|
"f1f3f5": "グレー 1",
|
||||||
|
"fff5f5": "レッド 0",
|
||||||
|
"fff0f6": "ピンク 0",
|
||||||
|
"f8f0fc": "グレープ 0",
|
||||||
|
"f3f0ff": "バイオレット 0",
|
||||||
|
"edf2ff": "インディゴ 0",
|
||||||
|
"e7f5ff": "ブルー 0",
|
||||||
|
"e3fafc": "シアン 0",
|
||||||
|
"e6fcf5": "ティール 0",
|
||||||
|
"ebfbee": "グリーン 0",
|
||||||
|
"f4fce3": "ライム 0",
|
||||||
|
"fff9db": "イエロー 0",
|
||||||
|
"fff4e6": "オレンジ 0",
|
||||||
|
"transparent": "透明",
|
||||||
|
"ced4da": "グレー 4",
|
||||||
|
"868e96": "グレー 6",
|
||||||
|
"fa5252": "レッド 6",
|
||||||
|
"e64980": "ピンク 6",
|
||||||
|
"be4bdb": "グレープ 6",
|
||||||
|
"7950f2": "バイオレット 6",
|
||||||
|
"4c6ef5": "インディゴ 6",
|
||||||
|
"228be6": "ブルー 6",
|
||||||
|
"15aabf": "シアン 6",
|
||||||
|
"12b886": "ティール 6",
|
||||||
|
"40c057": "グリーン 6",
|
||||||
|
"82c91e": "ライム 6",
|
||||||
|
"fab005": "イエロー 6",
|
||||||
|
"fd7e14": "オレンジ 6",
|
||||||
|
"000000": "ブラック",
|
||||||
|
"343a40": "グレー 8",
|
||||||
|
"495057": "グレー 7",
|
||||||
|
"c92a2a": "レッド 9",
|
||||||
|
"a61e4d": "ピンク 9",
|
||||||
|
"862e9c": "グレープ 9",
|
||||||
|
"5f3dc4": "バイオレット 9",
|
||||||
|
"364fc7": "インディゴ 9",
|
||||||
|
"1864ab": "ブルー 9",
|
||||||
|
"0b7285": "シアン 9",
|
||||||
|
"087f5b": "ティール 9",
|
||||||
|
"2b8a3e": "グリーン 9",
|
||||||
|
"5c940d": "ライム 9",
|
||||||
|
"e67700": "イエロー 9",
|
||||||
|
"d9480f": "オレンジ 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"background": "Agilal",
|
"background": "Agilal",
|
||||||
"fill": "Taččart",
|
"fill": "Taččart",
|
||||||
"strokeWidth": "Tehri n yizirig",
|
"strokeWidth": "Tehri n yizirig",
|
||||||
"strokeShape": "",
|
"strokeShape": "Talɣa n yizirig",
|
||||||
"strokeShape_gel": "",
|
"strokeShape_gel": "",
|
||||||
"strokeShape_fountain": "",
|
"strokeShape_fountain": "",
|
||||||
"strokeShape_brush": "Amfezzu",
|
"strokeShape_brush": "Amfezzu",
|
||||||
@@ -101,6 +101,8 @@
|
|||||||
"viewMode": "Askar n tmuɣli",
|
"viewMode": "Askar n tmuɣli",
|
||||||
"toggleExportColorScheme": "Sermed/sens asifeḍ usentel n yini",
|
"toggleExportColorScheme": "Sermed/sens asifeḍ usentel n yini",
|
||||||
"share": "Bḍu",
|
"share": "Bḍu",
|
||||||
|
"showStroke": "Beqqeḍ amelqaḍ n yini n yizirig",
|
||||||
|
"showBackground": "Beqqeḍ amelqaḍ n yini n ugilal",
|
||||||
"toggleTheme": "Snifel asentel"
|
"toggleTheme": "Snifel asentel"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -155,7 +157,8 @@
|
|||||||
"imageDoesNotContainScene": "Taktert n tugniwin ur tettwadhel ara akka tura.\nTebɣiḍ ad tketreḍ asayes? Tugna-agi tettban-d ur tegbir ara isefka n usnas. Tesremdeḍ ayagi deg usifeḍ?",
|
"imageDoesNotContainScene": "Taktert n tugniwin ur tettwadhel ara akka tura.\nTebɣiḍ ad tketreḍ asayes? Tugna-agi tettban-d ur tegbir ara isefka n usnas. Tesremdeḍ ayagi deg usifeḍ?",
|
||||||
"cannotRestoreFromImage": "Asayes ulamek ara d-yettwarr seg ufaylu-agi n tugna",
|
"cannotRestoreFromImage": "Asayes ulamek ara d-yettwarr seg ufaylu-agi n tugna",
|
||||||
"invalidSceneUrl": "Ulamek taktert n usayes seg URL i d-ittunefken. Ahat mačči d tameɣtut neɣ ur tegbir ara isefka JSON n Excalidraw.",
|
"invalidSceneUrl": "Ulamek taktert n usayes seg URL i d-ittunefken. Ahat mačči d tameɣtut neɣ ur tegbir ara isefka JSON n Excalidraw.",
|
||||||
"resetLibrary": "Ayagi ad isfeḍ tamkarḍit-inek•m. Tetḥeqqeḍ?"
|
"resetLibrary": "Ayagi ad isfeḍ tamkarḍit-inek•m. Tetḥeqqeḍ?",
|
||||||
|
"invalidEncryptionKey": ""
|
||||||
},
|
},
|
||||||
"toolBar": {
|
"toolBar": {
|
||||||
"selection": "Tafrayt",
|
"selection": "Tafrayt",
|
||||||
@@ -178,6 +181,8 @@
|
|||||||
"linearElement": "Ssit akken ad tebduḍ aṭas n tenqiḍin, zuɣer i yiwen n yizirig",
|
"linearElement": "Ssit akken ad tebduḍ aṭas n tenqiḍin, zuɣer i yiwen n yizirig",
|
||||||
"freeDraw": "Ssit yerna zuɣer, serreḥ ticki tfukeḍ",
|
"freeDraw": "Ssit yerna zuɣer, serreḥ ticki tfukeḍ",
|
||||||
"text": "Tixidest: tzemreḍ daɣen ad ternuḍ aḍris s usiti snat n tikkal anida tebɣiḍ s ufecku n tefrayt",
|
"text": "Tixidest: tzemreḍ daɣen ad ternuḍ aḍris s usiti snat n tikkal anida tebɣiḍ s ufecku n tefrayt",
|
||||||
|
"text_selected": "Ssit snat n tikkal neɣ ssed taqeffalt Kcem akken ad tẓergeḍ aḍris",
|
||||||
|
"text_editing": "Ssit Escape neɣ CtrlOrCmd+ENTER akken ad tfakkeḍ asiẓreg",
|
||||||
"linearElementMulti": "Ssit ɣef tenqiḍt taneggarut neɣ ssed taqeffalt Escape neɣ taqeffalt Kcem akken ad tfakkeḍ",
|
"linearElementMulti": "Ssit ɣef tenqiḍt taneggarut neɣ ssed taqeffalt Escape neɣ taqeffalt Kcem akken ad tfakkeḍ",
|
||||||
"lockAngle": "Tzemreḍ ad tḥettmeḍ tiɣmert s tuṭṭfa n tqeffalt SHIFT",
|
"lockAngle": "Tzemreḍ ad tḥettmeḍ tiɣmert s tuṭṭfa n tqeffalt SHIFT",
|
||||||
"resize": "Tzemreḍ ad tḥettemeḍ assaɣ s tuṭṭfa n tqeffalt SHIFT mi ara tettbeddileḍ tiddi,\nma teṭṭfeḍ ALT abeddel n tiddi ad yili si tlemmast",
|
"resize": "Tzemreḍ ad tḥettemeḍ assaɣ s tuṭṭfa n tqeffalt SHIFT mi ara tettbeddileḍ tiddi,\nma teṭṭfeḍ ALT abeddel n tiddi ad yili si tlemmast",
|
||||||
@@ -223,7 +228,10 @@
|
|||||||
"disk_button": "Sekles deg ufaylu",
|
"disk_button": "Sekles deg ufaylu",
|
||||||
"link_title": "Aseɣwen n beṭṭu",
|
"link_title": "Aseɣwen n beṭṭu",
|
||||||
"link_details": "Sifeḍ am useɣwen n tɣuri kan.",
|
"link_details": "Sifeḍ am useɣwen n tɣuri kan.",
|
||||||
"link_button": "Sifeḍ deg useɣwen"
|
"link_button": "Sifeḍ deg useɣwen",
|
||||||
|
"excalidrawplus_description": "Sekles asayes-inek•inem di tallunt n umahil Excalidraw+.",
|
||||||
|
"excalidrawplus_button": "Sifeḍ",
|
||||||
|
"excalidrawplus_exportError": "Ulamek asifeḍ ɣer Excalidraw+ akka tura..."
|
||||||
},
|
},
|
||||||
"helpDialog": {
|
"helpDialog": {
|
||||||
"blog": "Ɣeṛ ablug-nneɣ",
|
"blog": "Ɣeṛ ablug-nneɣ",
|
||||||
@@ -231,16 +239,18 @@
|
|||||||
"curvedArrow": "Taneccabt izelgen",
|
"curvedArrow": "Taneccabt izelgen",
|
||||||
"curvedLine": "Izirig izelgen",
|
"curvedLine": "Izirig izelgen",
|
||||||
"documentation": "Tasemlit",
|
"documentation": "Tasemlit",
|
||||||
|
"doubleClick": "ssit snat n tikkal",
|
||||||
"drag": "zuɣer",
|
"drag": "zuɣer",
|
||||||
"editor": "Amaẓrag",
|
"editor": "Amaẓrag",
|
||||||
|
"editSelectedShape": "Ẓreg talɣa yettwafernen (aḍris/taneccabt/izirig)",
|
||||||
"github": "Tufiḍ-d ugur? Azen-aɣ-d",
|
"github": "Tufiḍ-d ugur? Azen-aɣ-d",
|
||||||
"howto": "Ḍfer imniren-nneɣ",
|
"howto": "Ḍfer imniren-nneɣ",
|
||||||
"or": "neɣ",
|
"or": "neɣ",
|
||||||
"preventBinding": "Seḥbes tuqqna n tneccabin",
|
"preventBinding": "Seḥbes tuqqna n tneccabin",
|
||||||
"shapes": "Talɣiwin",
|
"shapes": "Talɣiwin",
|
||||||
"shortcuts": "Inegzumen n unasiw",
|
"shortcuts": "Inegzumen n unasiw",
|
||||||
"textFinish": "Fak asiẓreg (aḍris)",
|
"textFinish": "Fak asiẓreg (amaẓrag n uḍris)",
|
||||||
"textNewLine": "Rnu ajerriḍ amaynut (aḍris)",
|
"textNewLine": "Rnu ajerriḍ amaynut (amaẓrag n uḍris)",
|
||||||
"title": "Tallelt",
|
"title": "Tallelt",
|
||||||
"view": "Tamuɣli",
|
"view": "Tamuɣli",
|
||||||
"zoomToFit": "Simɣur akken ad twliḍ akk iferdisen",
|
"zoomToFit": "Simɣur akken ad twliḍ akk iferdisen",
|
||||||
@@ -273,5 +283,52 @@
|
|||||||
"fileSavedToFilename": "Yettwasekles di {filename}",
|
"fileSavedToFilename": "Yettwasekles di {filename}",
|
||||||
"canvas": "taɣzut n usuneɣ",
|
"canvas": "taɣzut n usuneɣ",
|
||||||
"selection": "tafrayt"
|
"selection": "tafrayt"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"ffffff": "Amellal",
|
||||||
|
"f8f9fa": "Aɣiɣdi 0",
|
||||||
|
"f1f3f5": "Aɣiɣdi 1",
|
||||||
|
"fff5f5": "Azeggaɣ",
|
||||||
|
"fff0f6": "Axuxi 0",
|
||||||
|
"f8f0fc": "",
|
||||||
|
"f3f0ff": "Amidadi 0",
|
||||||
|
"edf2ff": "",
|
||||||
|
"e7f5ff": "Anili 0",
|
||||||
|
"e3fafc": "",
|
||||||
|
"e6fcf5": "",
|
||||||
|
"ebfbee": "Azegzaw 0",
|
||||||
|
"f4fce3": "",
|
||||||
|
"fff9db": "Awraɣ 0",
|
||||||
|
"fff4e6": "Aččinawi 0",
|
||||||
|
"transparent": "Afrawan",
|
||||||
|
"ced4da": "Aɣiɣdi 4",
|
||||||
|
"868e96": "Aɣiɣdi 6",
|
||||||
|
"fa5252": "Azeggaɣ 6",
|
||||||
|
"e64980": "Axuxi 6",
|
||||||
|
"be4bdb": "",
|
||||||
|
"7950f2": "Amidadi 6",
|
||||||
|
"4c6ef5": "",
|
||||||
|
"228be6": "Anili 6",
|
||||||
|
"15aabf": "",
|
||||||
|
"12b886": "",
|
||||||
|
"40c057": "Azegzaw 0",
|
||||||
|
"82c91e": "",
|
||||||
|
"fab005": "Awraɣ 6",
|
||||||
|
"fd7e14": "Aččinawi 6",
|
||||||
|
"000000": "Aberkan",
|
||||||
|
"343a40": "Aɣiɣdi 8",
|
||||||
|
"495057": "Aɣiɣdi 7",
|
||||||
|
"c92a2a": "Azeggaɣ 9",
|
||||||
|
"a61e4d": "Axuxi 9",
|
||||||
|
"862e9c": "",
|
||||||
|
"5f3dc4": "Amidadi 9",
|
||||||
|
"364fc7": "",
|
||||||
|
"1864ab": "Anili 9",
|
||||||
|
"0b7285": "",
|
||||||
|
"087f5b": "",
|
||||||
|
"2b8a3e": "Azegzaw 9",
|
||||||
|
"5c940d": "",
|
||||||
|
"e67700": "Awraɣ 9",
|
||||||
|
"d9480f": "Aččinawi 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user