feat(update): use `tailwindcss` (#174)

* feat(update): use `tailwindcss`

* chore: clean up code specifications
This commit is contained in:
阿菜 Cai 2023-09-28 09:52:30 +08:00 committed by GitHub
parent aea7cc5b31
commit d78dcf8822
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 323 additions and 4 deletions

View File

@ -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",

7
postcss.config.cjs Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
plugins: {
// 'tailwindcss/nesting': {}, // https://tailwindcss.com/docs/using-with-preprocessors#nesting
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -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<code>/public</code> folder <img style={{ width: '5em' }} src='./node.svg' alt='Node logo' />
</div>
<Update />
<UpdateElectron />
</div>
)
}
export default App
export default App

View File

@ -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 (
<div>
<div className="w-screen h-screen fixed top-0 left-0 z-10 bg-modalMask" />
<div className="fixed top-1/2 left-1/2 z-20 -translate-x-1/2 -translate-y-1/2">
<div
className="shadow-modalContent overflow-hidden -border-r-4"
style={{ width }}
>
<div className="flex leading-[38px] bg-crimson">
<div className="font-bold w-0 flex-grow">{title}</div>
<span
className="w-[30px] h-[30px] m-[4px] text-center cursor-pointer leading-[30px]"
onClick={onCancel}
>
<svg
className="w-[17px] h-[17px]"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path
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"
p-id="2764"
fill="currentColor"
></path>
</svg>
</span>
</div>
<div className="p-[10px] bg-white text-darkGrey1">{children}</div>
{typeof footer !== "undefined" ? (
<div className="p-[10px] bg-white flex justify-end">
<button
className="p-[7px] bg-crimson text-sm ml-[10px] first:ml-0"
onClick={onCancel}
>
{cancelText}
</button>
<button
className="p-[7px] bg-crimson text-sm ml-[10px] first:ml-0"
onClick={onOk}
>
{okText}
</button>
</div>
) : (
footer
)}
</div>
</div>
</div>
);
};
const Modal = (
props: Parameters<typeof ModalTemplate>[0] & { open: boolean },
) => {
const { open, ...omit } = props;
return createPortal(open ? ModalTemplate(omit) : null, document.body);
};
export default Modal;

View File

@ -0,0 +1,25 @@
import React from "react";
const Progress: React.FC<
React.PropsWithChildren<{
percent?: number;
}>
> = (props) => {
const { percent = 0 } = props;
return (
<div className="flex items-center">
<div className="border-[1px] rounded-[3px] border-solid border-black h-[6px] w-[300px]">
<div
className="h-[6px] rounded-[4px] bg-gradient-to-r from-purple1 via-transparent to-crimson"
style={{ width: `${3 * percent}px` }}
/>
</div>
<span className="m-[0,10px]">
{(percent ?? 0).toString().substring(0, 4)}%
</span>
</div>
);
};
export default Progress;

View File

@ -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:
[`<Update/>` 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";
```

View File

@ -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<VersionInfo>();
const [updateError, setUpdateError] = useState<ErrorType>();
const [progressInfo, setProgressInfo] = useState<Partial<ProgressInfo>>();
const [modalOpen, setModalOpen] = useState<boolean>(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 (
<>
<Modal
open={modalOpen}
cancelText={modalBtn?.cancelText}
okText={modalBtn?.okText}
onCancel={modalBtn?.onCancel}
onOk={modalBtn?.onOk}
footer={updateAvailable ? /* hide footer */ null : undefined}
>
<div>
{updateError ? (
<div>
<p>Error downloading the latest version.</p>
<p>{updateError.message}</p>
</div>
) : updateAvailable ? (
<div>
<div>The last version is: v{versionInfo?.newVersion}</div>
<div className="ml-[40px]">
v{versionInfo?.version} -&gt; v{versionInfo?.newVersion}
</div>
<div className="ml-[40px]">
<div className="mr-[4px]">Update progress:</div>
<div className="w-0 flex-grow">
<Progress percent={progressInfo?.percent}></Progress>
</div>
</div>
</div>
) : (
<div className="p-[20px] text-center">
{JSON.stringify(versionInfo ?? {}, null, 2)}
</div>
)}
</div>
</Modal>
<button
disabled={checking}
onClick={checkUpdate}
>
{checking ? "Checking..." : "Check update"}
</button>
</>
);
};
export default UpdateElectron;

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -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(
<React.StrictMode>

20
tailwind.config.cjs Normal file
View File

@ -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: [],
};