Merge 22a7603c21
into 2f1cca13d6
1
.gitignore
vendored
@ -10,6 +10,7 @@ lerna-debug.log*
|
|||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dev-dist
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
|
@ -5,7 +5,12 @@
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||||
|
<link rel="icon" href="/favicon-32x32.png" type="image/png" sizes="32x32">
|
||||||
|
<link rel="icon" href="/favicon-16x16.png" type="image/png" sizes="16x16">
|
||||||
|
<meta name="theme-color" content="#14475b" media="(prefers-color-scheme: light)">
|
||||||
|
<meta name="theme-color" content="#14475b" media="(prefers-color-scheme: dark)">
|
||||||
|
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Online database entity-realtionship diagram editor, data modeler, and SQL generator. Design, visualize, and export scripts without an account and completely free of charge."
|
content="Online database entity-realtionship diagram editor, data modeler, and SQL generator. Design, visualize, and export scripts without an account and completely free of charge."
|
||||||
@ -35,7 +40,6 @@
|
|||||||
/>
|
/>
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
|
||||||
<link rel="apple-touch-icon" href="/favicon.ico" />
|
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"
|
||||||
|
2966
package-lock.json
generated
@ -52,7 +52,9 @@
|
|||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.32",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.2.5",
|
||||||
"tailwindcss": "^3.3.6",
|
"tailwindcss": "^3.3.6",
|
||||||
"vite": "^5.0.11"
|
"vite": "^5.0.11",
|
||||||
|
"vite-plugin-pwa": "^0.20.0",
|
||||||
|
"workbox-window": "^7.1.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"follow-redirects": "^1.15.4"
|
"follow-redirects": "^1.15.4"
|
||||||
|
BIN
public/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/favicon-16x16.png
Normal file
After Width: | Height: | Size: 641 B |
BIN
public/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/pwa-192x192.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
public/pwa-512x512.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
public/pwa-maskable-192x192.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
public/pwa-maskable-512x512.png
Normal file
After Width: | Height: | Size: 28 KiB |
@ -9,6 +9,7 @@ import LandingPage from "./pages/LandingPage";
|
|||||||
import SettingsContextProvider from "./context/SettingsContext";
|
import SettingsContextProvider from "./context/SettingsContext";
|
||||||
import { useSettings } from "./hooks";
|
import { useSettings } from "./hooks";
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
|
import { PwaUpdatePrompt } from "./components/PwaUpdatePrompt";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
@ -53,6 +54,7 @@ export default function App() {
|
|||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
<PwaUpdatePrompt />
|
||||||
</SettingsContextProvider>
|
</SettingsContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useRegisterSW } from "virtual:pwa-register/react";
|
||||||
import {
|
import {
|
||||||
IconCaretdown,
|
IconCaretdown,
|
||||||
|
IconCloud,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconChevronUp,
|
IconChevronUp,
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
@ -18,6 +20,7 @@ import {
|
|||||||
InputNumber,
|
InputNumber,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Spin,
|
Spin,
|
||||||
|
Tag,
|
||||||
Toast,
|
Toast,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
} from "@douyinfe/semi-ui";
|
} from "@douyinfe/semi-ui";
|
||||||
@ -1351,9 +1354,13 @@ export default function ControlPanel({
|
|||||||
});
|
});
|
||||||
useHotkeys("ctrl+alt+w, meta+alt+w", fitWindow, { preventDefault: true });
|
useHotkeys("ctrl+alt+w, meta+alt+w", fitWindow, { preventDefault: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
offlineReady: [isOfflineReady],
|
||||||
|
} = useRegisterSW();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{layout.header && header()}
|
{layout.header && header(isOfflineReady)}
|
||||||
{layout.toolbar && toolbar()}
|
{layout.toolbar && toolbar()}
|
||||||
<Modal
|
<Modal
|
||||||
modal={modal}
|
modal={modal}
|
||||||
@ -1560,10 +1567,10 @@ export default function ControlPanel({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function header() {
|
function header(isOfflineReady) {
|
||||||
return (
|
return (
|
||||||
<nav className="flex justify-between pt-1 items-center whitespace-nowrap">
|
<nav className="flex justify-between pt-1 items-center whitespace-nowrap">
|
||||||
<div className="flex justify-start items-center">
|
<div className="flex justify-start items-center grow">
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<img
|
<img
|
||||||
width={54}
|
width={54}
|
||||||
@ -1572,7 +1579,7 @@ export default function ControlPanel({
|
|||||||
className="ms-8 min-w-[54px]"
|
className="ms-8 min-w-[54px]"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="ms-1 mt-1">
|
<div className="ms-1 sm:me-1 xl:me-6 mt-1 grow">
|
||||||
<div className="flex items-center ms-3 gap-2">
|
<div className="flex items-center ms-3 gap-2">
|
||||||
{databases[database].image && (
|
{databases[database].image && (
|
||||||
<img
|
<img
|
||||||
@ -1602,7 +1609,7 @@ export default function ControlPanel({
|
|||||||
</div>
|
</div>
|
||||||
{(showEditName || modal === MODAL.RENAME) && <IconEdit />}
|
{(showEditName || modal === MODAL.RENAME) && <IconEdit />}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-start items-center">
|
||||||
<div className="flex justify-start text-md select-none me-2">
|
<div className="flex justify-start text-md select-none me-2">
|
||||||
{Object.keys(menu).map((category) => (
|
{Object.keys(menu).map((category) => (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@ -1709,6 +1716,13 @@ export default function ControlPanel({
|
|||||||
>
|
>
|
||||||
{getState()}
|
{getState()}
|
||||||
</Button>
|
</Button>
|
||||||
|
{isOfflineReady && (
|
||||||
|
<span className="ms-auto">
|
||||||
|
<Tag prefixIcon={<IconCloud />} size="large">
|
||||||
|
{t("available_offline")}
|
||||||
|
</Tag>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
34
src/components/PwaUpdatePrompt.jsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { useRegisterSW } from "virtual:pwa-register/react";
|
||||||
|
import { Typography, Toast } from "@douyinfe/semi-ui";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
export function PwaUpdatePrompt() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
needRefresh: [isRefreshNeeded],
|
||||||
|
updateServiceWorker,
|
||||||
|
} = useRegisterSW();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isRefreshNeeded) return;
|
||||||
|
|
||||||
|
Toast.info({
|
||||||
|
duration: 0, // indefinite
|
||||||
|
content: (
|
||||||
|
<div>
|
||||||
|
<h5>{t("update_available")}</h5>
|
||||||
|
<p className="text-xs">{t("reload_page_to_update")}</p>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Text link onClick={updateServiceWorker}>
|
||||||
|
{t("reload_now")}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}, [isRefreshNeeded, updateServiceWorker, t]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}
|
@ -6,6 +6,10 @@ const english = {
|
|||||||
|
|
||||||
const en = {
|
const en = {
|
||||||
translation: {
|
translation: {
|
||||||
|
available_offline: "Available offline",
|
||||||
|
update_available: "An updated version of drawDB is available!",
|
||||||
|
reload_page_to_update: "Reload the page to update",
|
||||||
|
reload_now: "Reload now",
|
||||||
report_bug: "Report a bug",
|
report_bug: "Report a bug",
|
||||||
import: "Import",
|
import: "Import",
|
||||||
file: "File",
|
file: "File",
|
||||||
|
@ -1,7 +1,55 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [
|
||||||
})
|
react(),
|
||||||
|
VitePWA({
|
||||||
|
workbox: {
|
||||||
|
globPatterns: ["**/*"],
|
||||||
|
maximumFileSizeToCacheInBytes: 15_000_000,
|
||||||
|
},
|
||||||
|
includeAssets: ["**/*"],
|
||||||
|
registerType: "prompt",
|
||||||
|
manifest: {
|
||||||
|
name: "DrawDB",
|
||||||
|
short_name: "DrawDB",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/pwa-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "any",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/pwa-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "any",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/pwa-maskable-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "maskable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/pwa-maskable-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "maskable",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scope: "/",
|
||||||
|
start_url: "/editor",
|
||||||
|
display: "standalone",
|
||||||
|
background_color: "#14475b",
|
||||||
|
theme_color: "#14475b",
|
||||||
|
description:
|
||||||
|
"Free, simple, and intuitive database design tool and SQL generator.",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|