10000 feat(update): use `tailwindcss` (#174) · coder-long/electron-vite-react@d78dcf8 · GitHub
[go: up one dir, main page]

Skip to content

Commit d78dcf8

Browse files
authored
feat(update): use tailwindcss (electron-vite#174)
* feat(update): use `tailwindcss` * chore: clean up code specifications
1 parent aea7cc5 commit d78dcf8

File tree

11 files changed

+323
-4
lines changed

11 files changed

+323
-4
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
},
1414
"scripts": {
1515
"dev": "vite",
16-
"build": "tsc && vite build && electron-builder",
16+
"watch-tailwind": "tailwindcss --watch",
17+
"build": "tsc && vite build && electron-builder && tailwindcss",
1718
"preview": "vite preview",
1819
"pree2e": "vite build --mode=test",
1920
"e2e": "playwright test"
@@ -26,10 +27,12 @@
2627
"@types/react": "^18.2.20",
2728
"@types/react-dom": "^18.2.7",
2829
"@vitejs/plugin-react": "^4.0.4",
30+
"autoprefixer": "^10.4.16",
2931
"electron": "^26.0.0",
3032
"electron-builder": "^24.6.3",
3133
"react": "^18.2.0",
3234
"react-dom": "^18.2.0",
35+
"tailwindcss": "^3.3.3",
3336
"typescript": "^5.1.6",
3437
"vite": "^4.4.9",
3538
"vite-plugin-electron": "^0.13.0-beta.3",

postcss.config.cjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
plugins: {
3+
// 'tailwindcss/nesting': {}, // https://tailwindcss.com/docs/using-with-preprocessors#nesting
4+
tailwindcss: {},
5+
autoprefixer: {},
6+
},
7+
}

src/App.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from 'react'
2-
import Update from '@/components/update'
2+
import UpdateElectron from '@/components/update-tailwind'
33
import logoVite from './assets/logo-vite.svg'
44
import logoElectron from './assets/logo-electron.svg'
55
import './App.css'
@@ -32,9 +32,9 @@ function App() {
3232
Place static files into the<code>/public</code> folder <img style={{ width: '5em' }} src='./node.svg' alt='Node logo' />
3333
</div>
3434

35-
<Update />
35+
<UpdateElectron />
3636
</div>
3737
)
3838
}
3939

40-
export default App
40+
export default App
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React, { ReactNode } from "react";
2+
import { createPortal } from "react-dom";
3+
4+
const ModalTemplate: React.FC<
5+
React.PropsWithChildren<{
6+
title?: ReactNode;
7+
footer?: ReactNode;
8+
cancelText?: string;
9+
okText?: string;
10+
onCancel?: () => void;
11+
onOk?: () => void;
12+
width?: number;
13+
}>
14+
> = (props) => {
15+
const {
16+
title,
17+
children,
18+
footer,
19+
cancelText = "Cancel",
20+
okText = "OK",
21+
onCancel,
22+
onOk,
23+
width = 530,
24+
} = props;
25+
26+
return (
27+
<div>
28+
<div className="w-screen h-screen fixed top-0 left-0 z-10 bg-modalMask" />
29+
<div className="fixed top-1/2 left-1/2 z-20 -translate-x-1/2 -translate-y-1/2">
30+
<div
31+
className="shadow-modalContent overflow-hidden -border-r-4"
32+
style={{ width }}
33+
>
34+
<div className="flex leading-[38px] bg-crimson">
35+
<div className="font-bold w-0 flex-grow">{title}</div>
36+
<span
37+
className="w-[30px] h-[30px] m-[4px] text-center cursor-pointer leading-[30px]"
38+
onClick={onCancel}
39+
>
40+
<svg
41+
className="w-[17px] h-[17px]"
42+
viewBox="0 0 1024 1024"
43+
version="1.1"
44+
xmlns="http://www.w3.org/2000/svg"
45+
>
46+
<path
47+
d="M557.312 513.248l265.28-263.904c12.544-12.48 12.608-32.704 0.128-45.248-12.512-12.576-32.704-12.608-45.248-0.128l-265.344 263.936-263.04-263.84C236.64 191.584 216.384 191.52 203.84 204 191.328 216.48 191.296 236.736 203.776 249.28l262.976 263.776L201.6 776.8c-12.544 12.48-12.608 32.704-0.128 45.248 6.24 6.272 14.464 9.44 22.688 9.44 8.16 0 16.32-3.104 22.56-9.312l265.216-263.808 265.44 266.24c6.24 6.272 14.432 9.408 22.656 9.408 8.192 0 16.352-3.136 22.592-9.344 12.512-12.48 12.544-32.704 0.064-45.248L557.312 513.248z"
48+
p-id="2764"
49+
fill="currentColor"
50+
></path>
51+
</svg>
52+
</span>
53+
</div>
54+
<div className="p-[10px] bg-white text-darkGrey1">{children}</div>
55+
{typeof footer !== "undefined" ? (
56+
<div className="p-[10px] bg-white flex justify-end">
57+
<button
58+
className="p-[7px] bg-crimson text-sm ml-[10px] first:ml-0"
59+
onClick={onCancel}
60+
>
61+
{cancelText}
62+
</button>
63+
<button
64+
className="p-[7px] bg-crimson text-sm ml-[10px] first:ml-0"
65+
onClick={onOk}
66+
>
67+
{okText}
68+
</button>
69+
</div>
70+
) : (
71+
footer
72+
)}
73+
</div>
74+
</div>
75+
</div>
76+
);
77+
};
78+
79+
const Modal = (
80+
props: Parameters<typeof ModalTemplate>[0] & { open: boolean },
81+
) => {
82+
const { open, ...omit } = props;
83+
84+
return createPortal(open ? ModalTemplate(omit) : null, document.body);
85+
};
86+
87+
export default Modal;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from "react";
2+
3+
const Progress: React.FC<
4+
React.PropsWithChildren<{
5+
percent?: number;
6+
}>
7+
> = (props) => {
8+
const { percent = 0 } = props;
9+
10+
return (
11+
<div className="flex items-center">
12+
<div className="border-[1px] rounded-[3px] border-solid border-black h-[6px] w-[300px]">
13+
<div
14+
className="h-[6px] rounded-[4px] bg-gradient-to-r from-purple1 via-transparent to-crimson"
15+
style={{ width: `${3 * percent}px` }}
16+
/>
17+
</div>
18+
<span className="m-[0,10px]">
19+
{(percent ?? 0).toString().substring(0, 4)}%
20+
</span>
21+
</div>
22+
);
23+
};
24+
25+
export default Progress;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# electron-updater-tailwindcss
2+
3+
[tailwindcss docs](https://tailwindcss.com/).
4+
5+
6+
## If you don't want to use tailwindcss, want to use the default css style:
7+
8+
[`<Update/>` Written entirely in CSS](../update/)
9+
10+
### remove dependencies:
11+
```diff
12+
- autoprefixer
13+
- tailwindcss
14+
```
15+
### remove files:
16+
```diff
17+
- postcss.config.cjs
18+
- tailwind.config.cjs
19+
```
20+
### remove import:
21+
```diff
22+
//src/main.tsx
23+
- import "@/components/update-tailwind/tailwind.css";
24+
```
25+
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { ipcRenderer } from "electron";
2+
import type { ProgressInfo } from "electron-updater";
3+
import { useCallback, useEffect, useState } from "react";
4+
import Modal from "@/components/update-tailwind/Modal";
5+
import Progress from "@/components/update-tailwind/Progress";
6+
7+
const UpdateElectron = () => {
8+
const [checking, setChecking] = useState(false);
9+
const [updateAvailable, setUpdateAvailable] = useState(false);
10+
const [versionInfo, setVersionInfo] = useState<VersionInfo>();
11+
const [updateError, setUpdateError] = useState<ErrorType>();
12+
const [progressInfo, setProgressInfo] = useState<Partial<ProgressInfo>>();
13+
const [modalOpen, setModalOpen] = useState<boolean>(false);
14+
const [modalBtn, setModalBtn] = useState<{
15+
cancelText?: string;
16+
okText?: string;
17+
onCancel?: () => void;
18+
onOk?: () => void;
19+
}>({
20+
onCancel: () => setModalOpen(false),
21+
onOk: () => ipcRenderer.invoke("start-download"),
22+
});
23+
24+
const checkUpdate = async () => {
25+
setChecking(true);
26+
/**
27+
* @type {import('electron-updater').UpdateCheckResult | null | { message: string, error: Error }}
28+
*/
29+
const result = await ipcRenderer.invoke("check-update");
30+
setProgressInfo({ percent: 0 });
31+
setChecking(false);
32+
setModalOpen(true);
33+
if (result?.error) {
34+
setUpdateAvailable(false);
35+
setUpdateError(result?.error);
36+
}
37+
};
38+
39+
const onUpdateCanAvailable = useCallback(
40+
(_event: Electron.IpcRendererEvent, arg1: VersionInfo) => {
41+
setVersionInfo(arg1);
42+
setUpdateError(undefined);
43+
// Can be update
44+
if (arg1.update) {
45+
setModalBtn((state) => ({
46+
...state,
47+
cancelText: "Cancel",
48+
okText: "Update",
49+
onOk: () => ipcRenderer.invoke("start-download"),
50+
}));
51+
setUpdateAvailable(true);
52+
} else {
53+
setUpdateAvailable(false);
54+
}
55+
},
56+
[],
57+
);
58+
59+
const onUpdateError = useCallback(
60+
(_event: Electron.IpcRendererEvent, arg1: ErrorType) => {
61+
setUpdateAvailable(false);
62+
setUpdateError(arg1);
63+
},
64+
[],
65+
);
66+
67+
const onDownloadProgress = useCallback(
68+
(_event: Electron.IpcRendererEvent, arg1: ProgressInfo) => {
69+
setProgressInfo(arg1);
70+
},
71+
[],
72+
);
73+
74+
const onUpdateDownloaded = useCallback(
75+
(_event: Electron.IpcRendererEvent, ...args: any[]) => {
76+
setProgressInfo({ percent: 100 });
77+
setModalBtn((state) => ({
78+
...state,
79+
cancelText: "Later",
80+
okText: "Install now",
81+
onOk: () => ipcRenderer.invoke("quit-and-install"),
82+
}));
83+
},
84+
[],
85+
);
86+
87+
useEffect(() => {
88+
// Get version information and whether to update
89+
ipcRenderer.on("update-can-available", onUpdateCanAvailable);
90+
ipcRenderer.on("update-error", onUpdateError);
91+
ipcRenderer.on("download-progress", onDownloadProgress);
92+
ipcRenderer.on("update-downloaded", onUpdateDownloaded);
93+
94+
return () => {
95+
ipcRenderer.off("update-can-available", onUpdateCanAvailable);
96+
ipcRenderer.off("update-error", onUpdateError);
97+
ipcRenderer.off("download-progress", onDownloadProgress);
98+
ipcRenderer.off("update-downloaded", onUpdateDownloaded);
99+
};
100+
}, []);
101+
102+
return (
103+
<>
104+
<Modal
105+
open={modalOpen}
106+
cancelText={modalBtn?.cancelText}
107+
okText={modalBtn?.okText}
108+
onCancel={modalBtn?.onCancel}
109+
onOk={modalBtn?.onOk}
110+
footer={updateAvailable ? /* hide footer */ null : undefined}
111+
>
112+
<div>
113+
{updateError ? (
114+
<div>
115+
<p>Error downloading the latest version.</p>
116+
<p>{updateError.message}</p>
117+
</div>
118+
) : updateAvailable ? (
119+
<div>
120+
<div>The last version is: v{versionInfo?.newVersion}</div>
121+
<div className="ml-[40px]">
122+
v{versionInfo?.version} -&gt; v{versionInfo?.newVersion}
123+
</div>
124+
<div className="ml-[40px]">
125+
<div className="mr-[4px]">Update progress:</div>
126+
<div className="w-0 flex-grow">
127+
<Progress percent={progressInfo?.percent}></Progress>
128+
</div>
129+
</div>
130+
</div>
131+
) : (
132+
<div className="p-[20px] text-center">
133+
{JSON.stringify(versionInfo ?? {}, null, 2)}
134+
</div>
135+
)}
136+
</div>
137+
</Modal>
138+
<button
139+
disabled={checking}
140+
onClick={checkUpdate}
141+
>
142+
{checking ? "Checking..." : "Check update"}
143+
</button>
144+
</>
145+
);
146+
};
147+
148+
export default UpdateElectron;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;

src/main.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'
33
import App from './App'
44
import './samples/node-api'
55
import './index.css'
6+
import '@/components/update-tailwind/tailwind.css';
67

78
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
89
<React.StrictMode>

tailwind.config.cjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module.exports = {
2+
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
3+
theme: {
4+
extend: {
5+
colors: {
6+
crimson: "#e01e5a",
7+
darkGrey1: "#333",
8+
purple1: "#8256d0",
9+
modalMask: "rgba(0, 0, 0, 0.5)",
10+
},
11+
boxShadow: {
12+
modalContent: "0 0 10px -4px #8256d0",
13+
},
14+
},
15+
},
16+
corePlugins: {
17+
preflight: false,
18+
},
19+
plugins: [],
20+
};

0 commit comments

Comments
 (0)
0