feat: Implement foundational UI styling, shared components, and web platform structure.
All checks were successful
Build and Release / build-and-release (push) Successful in 14m40s
All checks were successful
Build and Release / build-and-release (push) Successful in 14m40s
This commit is contained in:
@@ -35,9 +35,31 @@ export function UpdateProvider({ children }) {
|
||||
}
|
||||
|
||||
function ForcedUpdateModal({ latestVersion }) {
|
||||
const { links } = usePlatform();
|
||||
const handleDownload = () => {
|
||||
links.openExternal(RELEASE_URL);
|
||||
const { links, updates } = usePlatform();
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!downloading || !updates?.onDownloadProgress) return;
|
||||
updates.onDownloadProgress(({ percent }) => {
|
||||
setProgress(percent);
|
||||
});
|
||||
}, [downloading]);
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (updates?.installUpdate) {
|
||||
setDownloading(true);
|
||||
setError(null);
|
||||
try {
|
||||
await updates.installUpdate();
|
||||
} catch (e) {
|
||||
setError('Download failed. Please try again.');
|
||||
setDownloading(false);
|
||||
}
|
||||
} else {
|
||||
links.openExternal(RELEASE_URL);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -47,30 +69,65 @@ function ForcedUpdateModal({ latestVersion }) {
|
||||
<p>
|
||||
A new version (v{latestVersion}) is available. This update is required to continue using the app.
|
||||
</p>
|
||||
<button className="forced-update-btn" onClick={handleDownload}>
|
||||
Download Update
|
||||
</button>
|
||||
{downloading ? (
|
||||
<div className="update-progress">
|
||||
<div className="update-progress-bar-bg">
|
||||
<div
|
||||
className="update-progress-bar-fill"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="update-progress-text">Downloading... {progress}%</span>
|
||||
</div>
|
||||
) : (
|
||||
<button className="forced-update-btn" onClick={handleDownload}>
|
||||
Download Update
|
||||
</button>
|
||||
)}
|
||||
{error && <p className="update-error-text">{error}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TitleBarUpdateIcon() {
|
||||
const { links } = usePlatform();
|
||||
const { links, updates } = usePlatform();
|
||||
const update = useUpdateCheck();
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!downloading || !updates?.onDownloadProgress) return;
|
||||
updates.onDownloadProgress(({ percent }) => {
|
||||
setProgress(percent);
|
||||
});
|
||||
}, [downloading]);
|
||||
|
||||
if (!update) return null;
|
||||
|
||||
const handleClick = () => {
|
||||
links.openExternal(RELEASE_URL);
|
||||
const handleClick = async () => {
|
||||
if (updates?.installUpdate && !downloading) {
|
||||
setDownloading(true);
|
||||
try {
|
||||
await updates.installUpdate();
|
||||
} catch {
|
||||
setDownloading(false);
|
||||
}
|
||||
} else if (!downloading) {
|
||||
links.openExternal(RELEASE_URL);
|
||||
}
|
||||
};
|
||||
|
||||
const label = downloading
|
||||
? `Downloading update... ${progress}%`
|
||||
: `Update available: v${update.latestVersion}`;
|
||||
|
||||
return (
|
||||
<button
|
||||
className="titlebar-btn"
|
||||
onClick={handleClick}
|
||||
aria-label={`Update available: v${update.latestVersion}`}
|
||||
title={`Update available: v${update.latestVersion}`}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
style={{ borderRight: '1px solid var(--app-frame-border)' }}
|
||||
>
|
||||
<div style={{ marginRight: '12px' }}>
|
||||
@@ -79,4 +136,3 @@ export function TitleBarUpdateIcon() {
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3173,6 +3173,40 @@ body {
|
||||
background-color: var(--brand-experiment-hover);
|
||||
}
|
||||
|
||||
.update-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.update-progress-bar-bg {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.update-progress-bar-fill {
|
||||
height: 100%;
|
||||
background-color: var(--brand-experiment);
|
||||
border-radius: 4px;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
.update-progress-text {
|
||||
color: var(--header-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.update-error-text {
|
||||
color: var(--text-danger, #ed4245);
|
||||
font-size: 13px;
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
VOICE USER ITEM (sidebar)
|
||||
============================================ */
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
/**
|
||||
* @typedef {Object} PlatformUpdates
|
||||
* @property {() => Promise<object>} checkUpdate
|
||||
* @property {() => Promise<void>} [installUpdate] - Download and install APK (Android only)
|
||||
* @property {(callback: function) => void} [onDownloadProgress] - Listen for download progress events (Android only)
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user