mirror of
https://github.com/drawdb-io/drawdb.git
synced 2026-02-12 02:00:40 +08:00
Configure i18n and add simplified chinese (#99)
This commit is contained in:
@@ -20,7 +20,6 @@ import {
|
||||
Spin,
|
||||
Toast,
|
||||
Popconfirm,
|
||||
Tag,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { toPng, toJpeg, toSvg } from "html-to-image";
|
||||
import { saveAs } from "file-saver";
|
||||
@@ -62,6 +61,7 @@ import { IconAddArea, IconAddNote, IconAddTable } from "../../icons";
|
||||
import LayoutDropdown from "./LayoutDropdown";
|
||||
import Sidesheet from "./SideSheet/Sidesheet";
|
||||
import Modal from "./Modal/Modal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ControlPanel({
|
||||
diagramId,
|
||||
@@ -100,6 +100,7 @@ export default function ControlPanel({
|
||||
const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo();
|
||||
const { selectedElement, setSelectedElement } = useSelect();
|
||||
const { transform, setTransform } = useTransform();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const invertLayout = (component) =>
|
||||
@@ -460,16 +461,12 @@ export default function ControlPanel({
|
||||
setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 }));
|
||||
const viewStrictMode = () => {
|
||||
setSettings((prev) => ({ ...prev, strictMode: !prev.strictMode }));
|
||||
Toast.success(`Stict mode is ${settings.strictMode ? "on" : "off"}.`);
|
||||
};
|
||||
const viewFieldSummary = () => {
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
showFieldSummary: !prev.showFieldSummary,
|
||||
}));
|
||||
Toast.success(
|
||||
`Field summary is ${settings.showFieldSummary ? "off" : "on"}.`,
|
||||
);
|
||||
};
|
||||
const copyAsImage = () => {
|
||||
toPng(document.getElementById("canvas")).then(function (dataUrl) {
|
||||
@@ -477,10 +474,10 @@ export default function ControlPanel({
|
||||
navigator.clipboard
|
||||
.write([new ClipboardItem({ "image/png": blob })])
|
||||
.then(() => {
|
||||
Toast.success("Copied to clipboard.");
|
||||
Toast.success(t("copied_to_clipboard"));
|
||||
})
|
||||
.catch(() => {
|
||||
Toast.error("Could not copy to clipboard.");
|
||||
Toast.error(t("oops_smth_went_wrong"));
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -607,23 +604,17 @@ export default function ControlPanel({
|
||||
case ObjectType.TABLE:
|
||||
navigator.clipboard
|
||||
.writeText(JSON.stringify({ ...tables[selectedElement.id] }))
|
||||
.catch(() => {
|
||||
Toast.error("Could not copy");
|
||||
});
|
||||
.catch(() => Toast.error(t("oops_smth_went_wrong")));
|
||||
break;
|
||||
case ObjectType.NOTE:
|
||||
navigator.clipboard
|
||||
.writeText(JSON.stringify({ ...notes[selectedElement.id] }))
|
||||
.catch(() => {
|
||||
Toast.error("Could not copy");
|
||||
});
|
||||
.catch(() => Toast.error(t("oops_smth_went_wrong")));
|
||||
break;
|
||||
case ObjectType.AREA:
|
||||
navigator.clipboard
|
||||
.writeText(JSON.stringify({ ...areas[selectedElement.id] }))
|
||||
.catch(() => {
|
||||
Toast.error("Could not copy");
|
||||
});
|
||||
.catch(() => Toast.error(t("oops_smth_went_wrong")));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -671,29 +662,29 @@ export default function ControlPanel({
|
||||
const saveDiagramAs = () => setModal(MODAL.SAVEAS);
|
||||
|
||||
const menu = {
|
||||
File: {
|
||||
New: {
|
||||
file: {
|
||||
new: {
|
||||
function: () => setModal(MODAL.NEW),
|
||||
},
|
||||
"New window": {
|
||||
new_window: {
|
||||
function: () => {
|
||||
const newWindow = window.open("/editor", "_blank");
|
||||
newWindow.name = window.name;
|
||||
},
|
||||
},
|
||||
Open: {
|
||||
open: {
|
||||
function: open,
|
||||
shortcut: "Ctrl+O",
|
||||
},
|
||||
Save: {
|
||||
save: {
|
||||
function: save,
|
||||
shortcut: "Ctrl+S",
|
||||
},
|
||||
"Save as": {
|
||||
save_as: {
|
||||
function: saveDiagramAs,
|
||||
shortcut: "Ctrl+Shift+S",
|
||||
},
|
||||
"Save as template": {
|
||||
save_as_template: {
|
||||
function: () => {
|
||||
db.templates
|
||||
.add({
|
||||
@@ -706,21 +697,20 @@ export default function ControlPanel({
|
||||
custom: 1,
|
||||
})
|
||||
.then(() => {
|
||||
Toast.success("Template saved!");
|
||||
Toast.success(t("template_saved"));
|
||||
});
|
||||
},
|
||||
},
|
||||
Rename: {
|
||||
rename: {
|
||||
function: () => {
|
||||
setModal(MODAL.RENAME);
|
||||
setPrevTitle(title);
|
||||
},
|
||||
},
|
||||
"Delete diagram": {
|
||||
delete_diagram: {
|
||||
warning: {
|
||||
title: "Delete diagram",
|
||||
message:
|
||||
"Are you sure you want to delete this diagram? This operation is irreversible.",
|
||||
title: t("delete_diagram"),
|
||||
message: t("are_you_sure_delete_diagram"),
|
||||
},
|
||||
function: async () => {
|
||||
await db.diagrams
|
||||
@@ -736,17 +726,17 @@ export default function ControlPanel({
|
||||
setUndoStack([]);
|
||||
setRedoStack([]);
|
||||
})
|
||||
.catch(() => Toast.error("Oops! Something went wrong."));
|
||||
.catch(() => Toast.error(t("oops_smth_went_wrong")));
|
||||
},
|
||||
},
|
||||
"Import diagram": {
|
||||
import_diagram: {
|
||||
function: fileImport,
|
||||
shortcut: "Ctrl+I",
|
||||
},
|
||||
"Import from source": {
|
||||
import_from_source: {
|
||||
function: () => setModal(MODAL.IMPORT_SRC),
|
||||
},
|
||||
"Export as": {
|
||||
export_as: {
|
||||
children: [
|
||||
{
|
||||
PNG: () => {
|
||||
@@ -856,7 +846,7 @@ export default function ControlPanel({
|
||||
],
|
||||
function: () => {},
|
||||
},
|
||||
"Export source": {
|
||||
export_source: {
|
||||
children: [
|
||||
{
|
||||
MySQL: () => {
|
||||
@@ -936,23 +926,27 @@ export default function ControlPanel({
|
||||
],
|
||||
function: () => {},
|
||||
},
|
||||
Exit: {
|
||||
exit: {
|
||||
function: () => {
|
||||
save();
|
||||
if (saveState === State.SAVED) navigate("/");
|
||||
},
|
||||
},
|
||||
},
|
||||
Edit: {
|
||||
Undo: {
|
||||
edit: {
|
||||
undo: {
|
||||
function: undo,
|
||||
shortcut: "Ctrl+Z",
|
||||
},
|
||||
Redo: {
|
||||
redo: {
|
||||
function: redo,
|
||||
shortcut: "Ctrl+Y",
|
||||
},
|
||||
Clear: {
|
||||
clear: {
|
||||
warning: {
|
||||
title: t("clear"),
|
||||
message: t("are_you_sure_clear"),
|
||||
},
|
||||
function: () => {
|
||||
setTables([]);
|
||||
setRelationships([]);
|
||||
@@ -962,57 +956,73 @@ export default function ControlPanel({
|
||||
setRedoStack([]);
|
||||
},
|
||||
},
|
||||
Edit: {
|
||||
edit: {
|
||||
function: edit,
|
||||
shortcut: "Ctrl+E",
|
||||
},
|
||||
Cut: {
|
||||
cut: {
|
||||
function: cut,
|
||||
shortcut: "Ctrl+X",
|
||||
},
|
||||
Copy: {
|
||||
copy: {
|
||||
function: copy,
|
||||
shortcut: "Ctrl+C",
|
||||
},
|
||||
Paste: {
|
||||
paste: {
|
||||
function: paste,
|
||||
shortcut: "Ctrl+V",
|
||||
},
|
||||
Duplicate: {
|
||||
duplicate: {
|
||||
function: duplicate,
|
||||
shortcut: "Ctrl+D",
|
||||
},
|
||||
Delete: {
|
||||
delete: {
|
||||
function: del,
|
||||
shortcut: "Del",
|
||||
},
|
||||
"Copy as image": {
|
||||
copy_as_image: {
|
||||
function: copyAsImage,
|
||||
shortcut: "Ctrl+Alt+C",
|
||||
},
|
||||
},
|
||||
View: {
|
||||
Header: {
|
||||
state: layout.header ? "on" : "off",
|
||||
view: {
|
||||
header: {
|
||||
state: layout.header ? (
|
||||
<i className="bi bi-toggle-on" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-off" />
|
||||
),
|
||||
function: () =>
|
||||
setLayout((prev) => ({ ...prev, header: !prev.header })),
|
||||
},
|
||||
Sidebar: {
|
||||
state: layout.sidebar ? "on" : "off",
|
||||
sidebar: {
|
||||
state: layout.sidebar ? (
|
||||
<i className="bi bi-toggle-on" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-off" />
|
||||
),
|
||||
function: () =>
|
||||
setLayout((prev) => ({ ...prev, sidebar: !prev.sidebar })),
|
||||
},
|
||||
Issues: {
|
||||
state: layout.issues ? "on" : "off",
|
||||
issues: {
|
||||
state: layout.issues ? (
|
||||
<i className="bi bi-toggle-on" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-off" />
|
||||
),
|
||||
function: () =>
|
||||
setLayout((prev) => ({ ...prev, issues: !prev.issues })),
|
||||
},
|
||||
"Strict mode": {
|
||||
state: settings.strictMode ? "off" : "on",
|
||||
strict_mode: {
|
||||
state: settings.strictMode ? (
|
||||
<i className="bi bi-toggle-off" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-on" />
|
||||
),
|
||||
function: viewStrictMode,
|
||||
shortcut: "Ctrl+Shift+M",
|
||||
},
|
||||
"Presentation mode": {
|
||||
presentation_mode: {
|
||||
function: () => {
|
||||
setLayout((prev) => ({
|
||||
...prev,
|
||||
@@ -1023,32 +1033,44 @@ export default function ControlPanel({
|
||||
enterFullscreen();
|
||||
},
|
||||
},
|
||||
"Field details": {
|
||||
state: settings.showFieldSummary ? "on" : "off",
|
||||
field_details: {
|
||||
state: settings.showFieldSummary ? (
|
||||
<i className="bi bi-toggle-on" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-off" />
|
||||
),
|
||||
function: viewFieldSummary,
|
||||
shortcut: "Ctrl+Shift+F",
|
||||
},
|
||||
"Reset view": {
|
||||
reset_view: {
|
||||
function: resetView,
|
||||
shortcut: "Ctrl+R",
|
||||
},
|
||||
"Show grid": {
|
||||
state: settings.showGrid ? "on" : "off",
|
||||
show_grid: {
|
||||
state: settings.showGrid ? (
|
||||
<i className="bi bi-toggle-on" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-off" />
|
||||
),
|
||||
function: viewGrid,
|
||||
shortcut: "Ctrl+Shift+G",
|
||||
},
|
||||
"Show cardinality": {
|
||||
state: settings.showCardinality ? "on" : "off",
|
||||
show_cardinality: {
|
||||
state: settings.showCardinality ? (
|
||||
<i className="bi bi-toggle-on" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-off" />
|
||||
),
|
||||
function: () =>
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
showCardinality: !prev.showCardinality,
|
||||
})),
|
||||
},
|
||||
Theme: {
|
||||
theme: {
|
||||
children: [
|
||||
{
|
||||
Light: () => {
|
||||
light: () => {
|
||||
const body = document.body;
|
||||
if (body.hasAttribute("theme-mode")) {
|
||||
body.setAttribute("theme-mode", "light");
|
||||
@@ -1058,7 +1080,7 @@ export default function ControlPanel({
|
||||
},
|
||||
},
|
||||
{
|
||||
Dark: () => {
|
||||
dark: () => {
|
||||
const body = document.body;
|
||||
if (body.hasAttribute("theme-mode")) {
|
||||
body.setAttribute("theme-mode", "dark");
|
||||
@@ -1070,71 +1092,75 @@ export default function ControlPanel({
|
||||
],
|
||||
function: () => {},
|
||||
},
|
||||
"Zoom in": {
|
||||
zoom_in: {
|
||||
function: zoomIn,
|
||||
shortcut: "Ctrl+Up/Wheel",
|
||||
},
|
||||
"Zoom out": {
|
||||
zoom_out: {
|
||||
function: zoomOut,
|
||||
shortcut: "Ctrl+Down/Wheel",
|
||||
},
|
||||
Fullscreen: {
|
||||
fullscreen: {
|
||||
function: enterFullscreen,
|
||||
},
|
||||
},
|
||||
Settings: {
|
||||
"Show timeline": {
|
||||
settings: {
|
||||
show_timeline: {
|
||||
function: () => setSidesheet(SIDESHEET.TIMELINE),
|
||||
},
|
||||
Autosave: {
|
||||
state: settings.autosave ? "on" : "off",
|
||||
autosave: {
|
||||
state: settings.autosave ? (
|
||||
<i className="bi bi-toggle-on" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-off" />
|
||||
),
|
||||
function: () =>
|
||||
setSettings((prev) => {
|
||||
Toast.success(`Autosave is ${settings.autosave ? "off" : "on"}`);
|
||||
return { ...prev, autosave: !prev.autosave };
|
||||
}),
|
||||
setSettings((prev) => ({ ...prev, autosave: !prev.autosave })),
|
||||
},
|
||||
Panning: {
|
||||
state: settings.panning ? "on" : "off",
|
||||
panning: {
|
||||
state: settings.panning ? (
|
||||
<i className="bi bi-toggle-on" />
|
||||
) : (
|
||||
<i className="bi bi-toggle-off" />
|
||||
),
|
||||
function: () =>
|
||||
setSettings((prev) => {
|
||||
Toast.success(`Panning is ${settings.panning ? "off" : "on"}`);
|
||||
return { ...prev, panning: !prev.panning };
|
||||
}),
|
||||
setSettings((prev) => ({ ...prev, panning: !prev.panning })),
|
||||
},
|
||||
"Table width": {
|
||||
table_width: {
|
||||
function: () => setModal(MODAL.TABLE_WIDTH),
|
||||
},
|
||||
"Flush storage": {
|
||||
language: {
|
||||
function: () => setModal(MODAL.LANGUAGE),
|
||||
},
|
||||
flush_storage: {
|
||||
warning: {
|
||||
title: "Flush storage",
|
||||
message:
|
||||
"Are you sure you want to flush the storage? This will irreversibly delete all your diagrams and custom templates.",
|
||||
title: t("flush_storage"),
|
||||
message: t("are_you_sure_flush_storage"),
|
||||
},
|
||||
function: async () => {
|
||||
db.delete()
|
||||
.then(() => {
|
||||
Toast.success("Storage flushed");
|
||||
Toast.success(t("storage_flushed"));
|
||||
window.location.reload(false);
|
||||
})
|
||||
.catch(() => {
|
||||
Toast.error("Oops! Something went wrong.");
|
||||
Toast.error(t("oops_smth_went_wrong"));
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
Help: {
|
||||
Shortcuts: {
|
||||
help: {
|
||||
shortcuts: {
|
||||
function: () => window.open("/shortcuts", "_blank"),
|
||||
shortcut: "Ctrl+H",
|
||||
},
|
||||
"Ask us on discord": {
|
||||
ask_on_discord: {
|
||||
function: () => window.open("https://discord.gg/BrjZgNrmR6", "_blank"),
|
||||
},
|
||||
"Report a bug": {
|
||||
report_bug: {
|
||||
function: () => window.open("/bug-report", "_blank"),
|
||||
},
|
||||
"Give feedback": {
|
||||
feedback: {
|
||||
function: () => window.open("/survey", "_blank"),
|
||||
},
|
||||
},
|
||||
@@ -1207,7 +1233,7 @@ export default function ControlPanel({
|
||||
onClick={fitWindow}
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<div>Fit window / Reset</div>
|
||||
<div>{t("fit_window_reset")}</div>
|
||||
<div className="text-gray-400">Ctrl+Alt+W</div>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
@@ -1225,8 +1251,8 @@ export default function ControlPanel({
|
||||
<Dropdown.Item>
|
||||
<InputNumber
|
||||
field="zoom"
|
||||
label="Custom zoom"
|
||||
placeholder="Zoom"
|
||||
label={t("zoom")}
|
||||
placeholder={t("zoom")}
|
||||
suffix={<div className="p-1">%</div>}
|
||||
onChange={(v) =>
|
||||
setTransform((prev) => ({
|
||||
@@ -1249,7 +1275,7 @@ export default function ControlPanel({
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
<Tooltip content="Zoom in" position="bottom">
|
||||
<Tooltip content={t("zoom_in")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded text-lg"
|
||||
onClick={() =>
|
||||
@@ -1259,7 +1285,7 @@ export default function ControlPanel({
|
||||
<i className="fa-solid fa-magnifying-glass-plus" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Zoom out" position="bottom">
|
||||
<Tooltip content={t("zoom_out")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded text-lg"
|
||||
onClick={() =>
|
||||
@@ -1270,7 +1296,7 @@ export default function ControlPanel({
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Divider layout="vertical" margin="8px" />
|
||||
<Tooltip content="Undo" position="bottom">
|
||||
<Tooltip content={t("undo")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||
onClick={undo}
|
||||
@@ -1281,7 +1307,7 @@ export default function ControlPanel({
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Redo" position="bottom">
|
||||
<Tooltip content={t("redo")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||
onClick={redo}
|
||||
@@ -1293,7 +1319,7 @@ export default function ControlPanel({
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Divider layout="vertical" margin="8px" />
|
||||
<Tooltip content="Add table" position="bottom">
|
||||
<Tooltip content={t("add_table")} position="bottom">
|
||||
<button
|
||||
className="flex items-center py-1 px-2 hover-2 rounded"
|
||||
onClick={() => addTable()}
|
||||
@@ -1301,7 +1327,7 @@ export default function ControlPanel({
|
||||
<IconAddTable />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Add subject area" position="bottom">
|
||||
<Tooltip content={t("add_area")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||
onClick={() => addArea()}
|
||||
@@ -1309,7 +1335,7 @@ export default function ControlPanel({
|
||||
<IconAddArea />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Add note" position="bottom">
|
||||
<Tooltip content={t("add_note")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||
onClick={() => addNote()}
|
||||
@@ -1318,7 +1344,7 @@ export default function ControlPanel({
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Divider layout="vertical" margin="8px" />
|
||||
<Tooltip content="Save" position="bottom">
|
||||
<Tooltip content={t("save")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded flex items-center"
|
||||
onClick={save}
|
||||
@@ -1326,7 +1352,7 @@ export default function ControlPanel({
|
||||
<IconSaveStroked size="extra-large" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="To-do" position="bottom">
|
||||
<Tooltip content={t("to_do")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded text-xl -mt-0.5"
|
||||
onClick={() => setSidesheet(SIDESHEET.TODO)}
|
||||
@@ -1335,16 +1361,16 @@ export default function ControlPanel({
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Divider layout="vertical" margin="8px" />
|
||||
<Tooltip content="Change theme" position="bottom">
|
||||
<Tooltip content={t("theme")} position="bottom">
|
||||
<button
|
||||
className="py-1 px-2 hover-2 rounded text-xl -mt-0.5"
|
||||
onClick={() => {
|
||||
const body = document.body;
|
||||
if (body.hasAttribute("theme-mode")) {
|
||||
if (body.getAttribute("theme-mode") === "light") {
|
||||
menu["View"]["Theme"].children[1]["Dark"]();
|
||||
menu["view"]["theme"].children[1]["dark"]();
|
||||
} else {
|
||||
menu["View"]["Theme"].children[0]["Light"]();
|
||||
menu["view"]["theme"].children[0]["light"]();
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -1366,15 +1392,15 @@ export default function ControlPanel({
|
||||
function getState() {
|
||||
switch (saveState) {
|
||||
case State.NONE:
|
||||
return "No changes";
|
||||
return t("no_changes");
|
||||
case State.LOADING:
|
||||
return "Loading . . .";
|
||||
return t("loading");
|
||||
case State.SAVED:
|
||||
return `Last saved ${lastSaved}`;
|
||||
return `${t("last_saved")} ${lastSaved}`;
|
||||
case State.SAVING:
|
||||
return "Saving . . .";
|
||||
return t("saving");
|
||||
case State.ERROR:
|
||||
return "Failed to save";
|
||||
return t("failed_to_save");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -1429,7 +1455,7 @@ export default function ControlPanel({
|
||||
key={i}
|
||||
onClick={Object.values(e)[0]}
|
||||
>
|
||||
{Object.keys(e)[0]}
|
||||
{t(Object.keys(e)[0])}
|
||||
</Dropdown.Item>
|
||||
),
|
||||
)}
|
||||
@@ -1444,7 +1470,7 @@ export default function ControlPanel({
|
||||
}}
|
||||
onClick={menu[category][item].function}
|
||||
>
|
||||
{item}
|
||||
{t(item)}
|
||||
<IconChevronRight />
|
||||
</Dropdown.Item>
|
||||
</Dropdown>
|
||||
@@ -1458,8 +1484,10 @@ export default function ControlPanel({
|
||||
content={menu[category][item].warning.message}
|
||||
onConfirm={menu[category][item].function}
|
||||
position="right"
|
||||
okText={t("confirm")}
|
||||
cancelText={t("cancel")}
|
||||
>
|
||||
<Dropdown.Item>{item}</Dropdown.Item>
|
||||
<Dropdown.Item>{t(item)}</Dropdown.Item>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
@@ -1476,18 +1504,15 @@ export default function ControlPanel({
|
||||
}
|
||||
>
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<div>{item}</div>
|
||||
<div>{t(item)}</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{menu[category][item].shortcut && (
|
||||
<div className="text-gray-400">
|
||||
{menu[category][item].shortcut}
|
||||
</div>
|
||||
)}
|
||||
{menu[category][item].state && (
|
||||
<Tag color="blue">
|
||||
{menu[category][item].state}
|
||||
</Tag>
|
||||
)}
|
||||
{menu[category][item].state &&
|
||||
menu[category][item].state}
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
@@ -1496,7 +1521,9 @@ export default function ControlPanel({
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
>
|
||||
<div className="px-3 py-1 hover-2 rounded">{category}</div>
|
||||
<div className="px-3 py-1 hover-2 rounded">
|
||||
{t(category)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,12 @@ import {
|
||||
import { Dropdown } from "@douyinfe/semi-ui";
|
||||
import { useLayout } from "../../hooks";
|
||||
import { enterFullscreen, exitFullscreen } from "../../utils/fullscreen";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function LayoutDropdown() {
|
||||
const { layout, setLayout } = useLayout();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const invertLayout = (component) =>
|
||||
setLayout((prev) => ({ ...prev, [component]: !prev[component] }));
|
||||
|
||||
@@ -24,7 +27,7 @@ export default function LayoutDropdown() {
|
||||
}
|
||||
onClick={() => invertLayout("header")}
|
||||
>
|
||||
Header
|
||||
{t("header")}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
icon={
|
||||
@@ -32,7 +35,7 @@ export default function LayoutDropdown() {
|
||||
}
|
||||
onClick={() => invertLayout("sidebar")}
|
||||
>
|
||||
Sidebar
|
||||
{t("sidebar")}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
icon={
|
||||
@@ -40,7 +43,7 @@ export default function LayoutDropdown() {
|
||||
}
|
||||
onClick={() => invertLayout("issues")}
|
||||
>
|
||||
Issues
|
||||
{t("issues")}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item
|
||||
@@ -54,7 +57,7 @@ export default function LayoutDropdown() {
|
||||
invertLayout("fullscreen");
|
||||
}}
|
||||
>
|
||||
Fullscreen
|
||||
{t("fullscreen")}
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
import { Upload, Banner } from "@douyinfe/semi-ui";
|
||||
import { STATUS } from "../../../data/constants";
|
||||
import { useAreas, useNotes, useTables } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ImportDiagram({ setImportData, error, setError }) {
|
||||
const { areas } = useAreas();
|
||||
const { notes } = useNotes();
|
||||
const { tables, relationships } = useTables();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const diagramIsEmpty = () => {
|
||||
return (
|
||||
@@ -84,8 +86,8 @@ export default function ImportDiagram({ setImportData, error, setError }) {
|
||||
};
|
||||
}}
|
||||
draggable={true}
|
||||
dragMainText="Drag and drop the file here or click to upload."
|
||||
dragSubText="Support json and ddb"
|
||||
dragMainText={t("drag_and_drop_files")}
|
||||
dragSubText={t("support_json_and_ddb")}
|
||||
accept="application/json,.ddb"
|
||||
onRemove={() =>
|
||||
setError({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Upload, Checkbox, Banner } from "@douyinfe/semi-ui";
|
||||
import { STATUS } from "../../../data/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ImportSource({
|
||||
importData,
|
||||
@@ -7,6 +8,8 @@ export default function ImportSource({
|
||||
error,
|
||||
setError,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Upload
|
||||
@@ -30,8 +33,8 @@ export default function ImportSource({
|
||||
};
|
||||
}}
|
||||
draggable={true}
|
||||
dragMainText="Drag and drop the file here or click to upload."
|
||||
dragSubText="Upload an sql file to autogenerate your tables and columns."
|
||||
dragMainText={t("drag_and_drop_files")}
|
||||
dragSubText={t("upload_sql_to_generate_diagrams")}
|
||||
accept=".sql"
|
||||
onRemove={() => {
|
||||
setError({
|
||||
@@ -50,7 +53,7 @@ export default function ImportSource({
|
||||
/>
|
||||
<div>
|
||||
<div className="text-xs mb-3 mt-1 opacity-80">
|
||||
* For the time being loading only MySQL scripts is supported.
|
||||
{t("only_mysql_supported")}
|
||||
</div>
|
||||
<Checkbox
|
||||
aria-label="overwrite checkbox"
|
||||
@@ -63,7 +66,7 @@ export default function ImportSource({
|
||||
}))
|
||||
}
|
||||
>
|
||||
Overwrite existing diagram
|
||||
{t("overwrite_existing_diagram")}
|
||||
</Checkbox>
|
||||
<div className="mt-2">
|
||||
{error.type === STATUS.ERROR ? (
|
||||
|
||||
30
src/components/EditorHeader/Modal/Language.jsx
Normal file
30
src/components/EditorHeader/Modal/Language.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSettings } from "../../../hooks";
|
||||
import { languages } from "../../../i18n/i18n";
|
||||
|
||||
export default function Language() {
|
||||
const { settings } = useSettings();
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={l.code}
|
||||
onClick={() => i18n.changeLanguage(l.code)}
|
||||
className={`space-y-1 py-3 px-4 rounded-md border-2 ${
|
||||
settings.mode === "dark"
|
||||
? "bg-zinc-700 hover:bg-zinc-600"
|
||||
: "bg-zinc-100 hover:bg-zinc-200"
|
||||
} ${i18n.resolvedLanguage === l.code ? "border-zinc-400" : "border-transparent"}`}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="font-semibold">{l.native_name}</div>
|
||||
<div className="opacity-60">{l.code}</div>
|
||||
</div>
|
||||
<div className="text-start">{l.name}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -27,11 +27,13 @@ import New from "./New";
|
||||
import ImportDiagram from "./ImportDiagram";
|
||||
import ImportSource from "./ImportSource";
|
||||
import SetTableWidth from "./SetTableWidth";
|
||||
import Language from "./Language";
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { sql } from "@codemirror/lang-sql";
|
||||
import { vscodeDark } from "@uiw/codemirror-theme-vscode";
|
||||
import { json } from "@codemirror/lang-json";
|
||||
import { githubLight } from "@uiw/codemirror-theme-github";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const languageExtension = {
|
||||
sql: [sql()],
|
||||
@@ -49,6 +51,7 @@ export default function Modal({
|
||||
exportData,
|
||||
setExportData,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { setTables, setRelationships } = useTables();
|
||||
const { setNotes } = useNotes();
|
||||
const { setAreas } = useAreas();
|
||||
@@ -239,7 +242,7 @@ export default function Modal({
|
||||
case MODAL.SAVEAS:
|
||||
return (
|
||||
<Input
|
||||
placeholder="Diagram name"
|
||||
placeholder={t("name")}
|
||||
value={saveAsTitle}
|
||||
onChange={(v) => setSaveAsTitle(v)}
|
||||
/>
|
||||
@@ -261,10 +264,10 @@ export default function Modal({
|
||||
theme={settings.mode === "dark" ? vscodeDark : githubLight}
|
||||
/>
|
||||
)}
|
||||
<div className="text-sm font-semibold mt-2">Filename:</div>
|
||||
<div className="text-sm font-semibold mt-2">{t("filename")}:</div>
|
||||
<Input
|
||||
value={exportData.filename}
|
||||
placeholder="Filename"
|
||||
placeholder={t("filename")}
|
||||
suffix={<div className="p-2">{`.${exportData.extension}`}</div>}
|
||||
onChange={(value) =>
|
||||
setExportData((prev) => ({ ...prev, filename: value }))
|
||||
@@ -276,12 +279,14 @@ export default function Modal({
|
||||
} else {
|
||||
return (
|
||||
<div className="text-center my-3">
|
||||
<Spin tip="Loading..." size="large" />
|
||||
<Spin tip={t("loading")} size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case MODAL.TABLE_WIDTH:
|
||||
return <SetTableWidth />;
|
||||
case MODAL.LANGUAGE:
|
||||
return <Language />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
@@ -326,7 +331,7 @@ export default function Modal({
|
||||
(modal === MODAL.SAVEAS && saveAsTitle === "") ||
|
||||
(modal === MODAL.IMPORT_SRC && importSource.src === ""),
|
||||
}}
|
||||
cancelText="Cancel"
|
||||
cancelText={t("cancel")}
|
||||
width={modal === MODAL.NEW ? 740 : 600}
|
||||
>
|
||||
{getModalBody()}
|
||||
|
||||
@@ -2,9 +2,11 @@ import { db } from "../../../data/db";
|
||||
import { useSettings } from "../../../hooks";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import Thumbnail from "../../Thumbnail";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function New({ selectedTemplateId, setSelectedTemplateId }) {
|
||||
const { settings } = useSettings();
|
||||
const { t } = useTranslation();
|
||||
const templates = useLiveQuery(() => db.templates.toArray());
|
||||
|
||||
return (
|
||||
@@ -17,7 +19,7 @@ export default function New({ selectedTemplateId, setSelectedTemplateId }) {
|
||||
>
|
||||
<Thumbnail i={0} diagram={{}} zoom={0.24} theme={settings.mode} />
|
||||
</div>
|
||||
<div className="text-center mt-1">Blank</div>
|
||||
<div className="text-center mt-1">{t("blank")}</div>
|
||||
</div>
|
||||
{templates?.map((temp, i) => (
|
||||
<div key={i} onClick={() => setSelectedTemplateId(temp.id)}>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { db } from "../../../data/db";
|
||||
import { Banner } from "@douyinfe/semi-ui";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Open({ selectedDiagramId, setSelectedDiagramId }) {
|
||||
const diagrams = useLiveQuery(() => db.diagrams.toArray());
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getDiagramSize = (d) => {
|
||||
const size = JSON.stringify(d).length;
|
||||
@@ -32,9 +34,9 @@ export default function Open({ selectedDiagramId, setSelectedDiagramId }) {
|
||||
<table className="w-full text-left border-separate border-spacing-x-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Last Modified</th>
|
||||
<th>Size</th>
|
||||
<th>{t("name")}</th>
|
||||
<th>{t("last_modified")}</th>
|
||||
<th>{t("size")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Input } from "@douyinfe/semi-ui";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Rename({ title, setTitle }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Input
|
||||
placeholder="Diagram name"
|
||||
placeholder={t("name")}
|
||||
value={title}
|
||||
onChange={(v) => setTitle(v)}
|
||||
/>
|
||||
|
||||
@@ -6,8 +6,10 @@ import timeLineDark from "../../../assets/process_dark.png";
|
||||
import todo from "../../../assets/calendar.png";
|
||||
import Timeline from "./Timeline";
|
||||
import Todo from "./Todo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Sidesheet({ type, onClose }) {
|
||||
const { t } = useTranslation();
|
||||
const { settings } = useSettings();
|
||||
|
||||
function getTitle(type) {
|
||||
@@ -20,14 +22,14 @@ export default function Sidesheet({ type, onClose }) {
|
||||
className="w-7"
|
||||
alt="chat icon"
|
||||
/>
|
||||
<div className="ms-3 text-lg">Timeline</div>
|
||||
<div className="ms-3 text-lg">{t("timeline")}</div>
|
||||
</div>
|
||||
);
|
||||
case SIDESHEET.TODO:
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<img src={todo} className="w-7" alt="todo icon" />
|
||||
<div className="ms-3 text-lg">To-do list</div>
|
||||
<div className="ms-3 text-lg">{t("to_do")}</div>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUndoRedo } from "../../../hooks";
|
||||
import { List } from "@douyinfe/semi-ui";
|
||||
|
||||
export default function Timeline() {
|
||||
const { undoStack } = useUndoRedo();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (undoStack.length > 0) {
|
||||
return (
|
||||
@@ -22,11 +24,6 @@ export default function Timeline() {
|
||||
</List>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="m-5 sidesheet-theme">
|
||||
No activity was recorded. You have not added anything to your diagram
|
||||
yet.
|
||||
</div>
|
||||
);
|
||||
return <div className="m-5 sidesheet-theme">{t("no_activity")}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "@douyinfe/semi-icons";
|
||||
import { State } from "../../../data/constants";
|
||||
import { useTasks, useSaveState } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Priority = {
|
||||
NONE: 0,
|
||||
@@ -30,10 +31,10 @@ const Priority = {
|
||||
};
|
||||
|
||||
const SortOrder = {
|
||||
ORIGINAL: "My order",
|
||||
PRIORITY: "Priority",
|
||||
COMPLETED: "Completed",
|
||||
ALPHABETICALLY: "Alphabetically",
|
||||
ORIGINAL: "my_order",
|
||||
PRIORITY: "priority",
|
||||
COMPLETED: "completed",
|
||||
ALPHABETICALLY: "alphabetically",
|
||||
};
|
||||
|
||||
export default function Todo() {
|
||||
@@ -41,17 +42,18 @@ export default function Todo() {
|
||||
const [, setSortOrder] = useState(SortOrder.ORIGINAL);
|
||||
const { tasks, setTasks, updateTask } = useTasks();
|
||||
const { setSaveState } = useSaveState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const priorityLabel = (p) => {
|
||||
switch (p) {
|
||||
case Priority.NONE:
|
||||
return "None";
|
||||
return t("none");
|
||||
case Priority.LOW:
|
||||
return "Low";
|
||||
return t("low");
|
||||
case Priority.MEDIUM:
|
||||
return "Medium";
|
||||
return t("medium");
|
||||
case Priority.HIGH:
|
||||
return "High";
|
||||
return t("high");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -91,7 +93,7 @@ export default function Todo() {
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case SortOrder.ALPHABETICALLY:
|
||||
@@ -116,7 +118,7 @@ export default function Todo() {
|
||||
sort(order);
|
||||
}}
|
||||
>
|
||||
{order}
|
||||
{t(order)}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown.Menu>
|
||||
@@ -128,7 +130,7 @@ export default function Todo() {
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
>
|
||||
Sort by <IconCaretdown />
|
||||
{t("sort_by")} <IconCaretdown />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Button
|
||||
@@ -147,12 +149,12 @@ export default function Todo() {
|
||||
]);
|
||||
}}
|
||||
>
|
||||
Add task
|
||||
{t("add_task")}
|
||||
</Button>
|
||||
</div>
|
||||
{tasks.length > 0 ? (
|
||||
<List className="sidesheet-theme">
|
||||
{tasks.map((t, i) => (
|
||||
{tasks.map((task, i) => (
|
||||
<List.Item
|
||||
key={i}
|
||||
style={{ paddingLeft: "18px", paddingRight: "18px" }}
|
||||
@@ -163,7 +165,7 @@ export default function Todo() {
|
||||
<Row gutter={6} align="middle" type="flex" className="mb-2">
|
||||
<Col span={2}>
|
||||
<Checkbox
|
||||
checked={t.complete}
|
||||
checked={task.complete}
|
||||
onChange={(e) => {
|
||||
updateTask(i, { complete: e.target.checked });
|
||||
setSaveState(State.SAVING);
|
||||
@@ -172,25 +174,25 @@ export default function Todo() {
|
||||
</Col>
|
||||
<Col span={19}>
|
||||
<Input
|
||||
placeholder="Title"
|
||||
placeholder={t("title")}
|
||||
onChange={(v) => updateTask(i, { title: v })}
|
||||
value={t.title}
|
||||
value={task.title}
|
||||
onBlur={() => setSaveState(State.SAVING)}
|
||||
></Input>
|
||||
/>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Popover
|
||||
content={
|
||||
<div className="p-2 popover-theme">
|
||||
<div className="mb-2 font-semibold">
|
||||
Set priority:
|
||||
{t("priority")}:
|
||||
</div>
|
||||
<RadioGroup
|
||||
onChange={(e) => {
|
||||
updateTask(i, { priority: e.target.value });
|
||||
setSaveState(State.SAVING);
|
||||
}}
|
||||
value={t.priority}
|
||||
value={task.priority}
|
||||
direction="vertical"
|
||||
>
|
||||
<Radio value={Priority.NONE}>
|
||||
@@ -221,12 +223,12 @@ export default function Todo() {
|
||||
style={{ marginTop: "12px" }}
|
||||
onClick={() => {
|
||||
setTasks((prev) =>
|
||||
prev.filter((task, j) => i !== j)
|
||||
prev.filter((_, j) => i !== j),
|
||||
);
|
||||
setSaveState(State.SAVING);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -243,7 +245,7 @@ export default function Todo() {
|
||||
<Col span={2}></Col>
|
||||
<Col span={22}>
|
||||
<TextArea
|
||||
placeholder="Details"
|
||||
placeholder={t("details")}
|
||||
onChange={(v) => updateTask(i, { details: v })}
|
||||
value={t.details}
|
||||
onBlur={() => setSaveState(State.SAVING)}
|
||||
@@ -254,9 +256,9 @@ export default function Todo() {
|
||||
<Row>
|
||||
<Col span={2}></Col>
|
||||
<Col span={22}>
|
||||
Priority:{" "}
|
||||
<Tag color={priorityColor(t.priority)}>
|
||||
{priorityLabel(t.priority)}
|
||||
{t("priority")}:{" "}
|
||||
<Tag color={priorityColor(task.priority)}>
|
||||
{priorityLabel(task.priority)}
|
||||
</Tag>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -265,10 +267,7 @@ export default function Todo() {
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<div className="m-5 sidesheet-theme">
|
||||
You have no tasks yet. Add your to-dos and keep track of your
|
||||
progress.
|
||||
</div>
|
||||
<div className="m-5 sidesheet-theme">{t("no_tasks")}</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user