diff --git a/package.json b/package.json index ba514b8..2a17a0a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ }, "scripts": { "dev": "vite", - "build": "tsc && vite build && electron-builder", + "watch-tailwind": "tailwindcss --watch", + "build": "tsc && vite build && electron-builder && tailwindcss", "preview": "vite preview", "pree2e": "vite build --mode=test", "e2e": "playwright test" @@ -26,10 +27,12 @@ "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.0.4", + "autoprefixer": "^10.4.16", "electron": "^26.0.0", "electron-builder": "^24.6.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "tailwindcss": "^3.3.3", "typescript": "^5.1.6", "vite": "^4.4.9", "vite-plugin-electron": "^0.13.0-beta.3", diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..f204746 --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,7 @@ +module.exports = { + plugins: { + // 'tailwindcss/nesting': {}, // https://tailwindcss.com/docs/using-with-preprocessors#nesting + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index d5703ce..31c52f5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import Update from '@/components/update' +import UpdateElectron from '@/components/update-tailwind' import logoVite from './assets/logo-vite.svg' import logoElectron from './assets/logo-electron.svg' import './App.css' @@ -32,9 +32,9 @@ function App() { Place static files into the/public folder Node logo - + ) } -export default App +export default App \ No newline at end of file diff --git a/src/components/update-tailwind/Modal/index.tsx b/src/components/update-tailwind/Modal/index.tsx new file mode 100644 index 0000000..b04f4f6 --- /dev/null +++ b/src/components/update-tailwind/Modal/index.tsx @@ -0,0 +1,87 @@ +import React, { ReactNode } from "react"; +import { createPortal } from "react-dom"; + +const ModalTemplate: React.FC< + React.PropsWithChildren<{ + title?: ReactNode; + footer?: ReactNode; + cancelText?: string; + okText?: string; + onCancel?: () => void; + onOk?: () => void; + width?: number; + }> +> = (props) => { + const { + title, + children, + footer, + cancelText = "Cancel", + okText = "OK", + onCancel, + onOk, + width = 530, + } = props; + + return ( +
+
+
+
+
+
{title}
+ + + + + +
+
{children}
+ {typeof footer !== "undefined" ? ( +
+ + +
+ ) : ( + footer + )} +
+
+
+ ); +}; + +const Modal = ( + props: Parameters[0] & { open: boolean }, +) => { + const { open, ...omit } = props; + + return createPortal(open ? ModalTemplate(omit) : null, document.body); +}; + +export default Modal; diff --git a/src/components/update-tailwind/Progress/index.tsx b/src/components/update-tailwind/Progress/index.tsx new file mode 100644 index 0000000..5912399 --- /dev/null +++ b/src/components/update-tailwind/Progress/index.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +const Progress: React.FC< + React.PropsWithChildren<{ + percent?: number; + }> +> = (props) => { + const { percent = 0 } = props; + + return ( +
+
+
+
+ + {(percent ?? 0).toString().substring(0, 4)}% + +
+ ); +}; + +export default Progress; diff --git a/src/components/update-tailwind/README.md b/src/components/update-tailwind/README.md new file mode 100644 index 0000000..e2a0be3 --- /dev/null +++ b/src/components/update-tailwind/README.md @@ -0,0 +1,25 @@ +# electron-updater-tailwindcss + +[tailwindcss docs](https://tailwindcss.com/). + + +## If you don't want to use tailwindcss, want to use the default css style: + +[`` Written entirely in CSS](../update/) + +### remove dependencies: +```diff +- autoprefixer +- tailwindcss +``` +### remove files: +```diff +- postcss.config.cjs +- tailwind.config.cjs +``` +### remove import: +```diff +//src/main.tsx +- import "@/components/update-tailwind/tailwind.css"; +``` + diff --git a/src/components/update-tailwind/index.tsx b/src/components/update-tailwind/index.tsx new file mode 100644 index 0000000..11ceafb --- /dev/null +++ b/src/components/update-tailwind/index.tsx @@ -0,0 +1,148 @@ +import { ipcRenderer } from "electron"; +import type { ProgressInfo } from "electron-updater"; +import { useCallback, useEffect, useState } from "react"; +import Modal from "@/components/update-tailwind/Modal"; +import Progress from "@/components/update-tailwind/Progress"; + +const UpdateElectron = () => { + const [checking, setChecking] = useState(false); + const [updateAvailable, setUpdateAvailable] = useState(false); + const [versionInfo, setVersionInfo] = useState(); + const [updateError, setUpdateError] = useState(); + const [progressInfo, setProgressInfo] = useState>(); + const [modalOpen, setModalOpen] = useState(false); + const [modalBtn, setModalBtn] = useState<{ + cancelText?: string; + okText?: string; + onCancel?: () => void; + onOk?: () => void; + }>({ + onCancel: () => setModalOpen(false), + onOk: () => ipcRenderer.invoke("start-download"), + }); + + const checkUpdate = async () => { + setChecking(true); + /** + * @type {import('electron-updater').UpdateCheckResult | null | { message: string, error: Error }} + */ + const result = await ipcRenderer.invoke("check-update"); + setProgressInfo({ percent: 0 }); + setChecking(false); + setModalOpen(true); + if (result?.error) { + setUpdateAvailable(false); + setUpdateError(result?.error); + } + }; + + const onUpdateCanAvailable = useCallback( + (_event: Electron.IpcRendererEvent, arg1: VersionInfo) => { + setVersionInfo(arg1); + setUpdateError(undefined); + // Can be update + if (arg1.update) { + setModalBtn((state) => ({ + ...state, + cancelText: "Cancel", + okText: "Update", + onOk: () => ipcRenderer.invoke("start-download"), + })); + setUpdateAvailable(true); + } else { + setUpdateAvailable(false); + } + }, + [], + ); + + const onUpdateError = useCallback( + (_event: Electron.IpcRendererEvent, arg1: ErrorType) => { + setUpdateAvailable(false); + setUpdateError(arg1); + }, + [], + ); + + const onDownloadProgress = useCallback( + (_event: Electron.IpcRendererEvent, arg1: ProgressInfo) => { + setProgressInfo(arg1); + }, + [], + ); + + const onUpdateDownloaded = useCallback( + (_event: Electron.IpcRendererEvent, ...args: any[]) => { + setProgressInfo({ percent: 100 }); + setModalBtn((state) => ({ + ...state, + cancelText: "Later", + okText: "Install now", + onOk: () => ipcRenderer.invoke("quit-and-install"), + })); + }, + [], + ); + + useEffect(() => { + // Get version information and whether to update + ipcRenderer.on("update-can-available", onUpdateCanAvailable); + ipcRenderer.on("update-error", onUpdateError); + ipcRenderer.on("download-progress", onDownloadProgress); + ipcRenderer.on("update-downloaded", onUpdateDownloaded); + + return () => { + ipcRenderer.off("update-can-available", onUpdateCanAvailable); + ipcRenderer.off("update-error", onUpdateError); + ipcRenderer.off("download-progress", onDownloadProgress); + ipcRenderer.off("update-downloaded", onUpdateDownloaded); + }; + }, []); + + return ( + <> + +
+ {updateError ? ( +
+

Error downloading the latest version.

+

{updateError.message}

+
+ ) : updateAvailable ? ( +
+
The last version is: v{versionInfo?.newVersion}
+
+ v{versionInfo?.version} -> v{versionInfo?.newVersion} +
+
+
Update progress:
+
+ +
+
+
+ ) : ( +
+ {JSON.stringify(versionInfo ?? {}, null, 2)} +
+ )} +
+
+ + + ); +}; + +export default UpdateElectron; diff --git a/src/components/update-tailwind/tailwind.css b/src/components/update-tailwind/tailwind.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/src/components/update-tailwind/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index c4647c7..815321c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client' import App from './App' import './samples/node-api' import './index.css' +import '@/components/update-tailwind/tailwind.css'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/src/components/update/electron-updater.d.ts b/src/type/electron-updater.d.ts similarity index 100% rename from src/components/update/electron-updater.d.ts rename to src/type/electron-updater.d.ts diff --git a/tailwind.config.cjs b/tailwind.config.cjs new file mode 100644 index 0000000..746ff4c --- /dev/null +++ b/tailwind.config.cjs @@ -0,0 +1,20 @@ +module.exports = { + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + theme: { + extend: { + colors: { + crimson: "#e01e5a", + darkGrey1: "#333", + purple1: "#8256d0", + modalMask: "rgba(0, 0, 0, 0.5)", + }, + boxShadow: { + modalContent: "0 0 10px -4px #8256d0", + }, + }, + }, + corePlugins: { + preflight: false, + }, + plugins: [], +};