Add MSC4522 username colors, release notes dialog, and profile layout fixes.
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 1s
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 1s
Replace legacy profile style metadata with MSC4133 profile fields, add in-app update notes, tighten account profile name spacing, and wire desktop media save helpers through the client.
This commit is contained in:
111
src/app/utils/registerDesktopMediaSaver.ts
Normal file
111
src/app/utils/registerDesktopMediaSaver.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { registerMediaSaver } from './saveMedia';
|
||||
import { isElectron, isTauri } from './tauri';
|
||||
|
||||
type ElectronSaveResult = {
|
||||
success?: boolean;
|
||||
canceled?: boolean;
|
||||
path?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type ElectronMediaApi = {
|
||||
saveFile?: (payload: {
|
||||
filename: string;
|
||||
mimeType?: string;
|
||||
data: Uint8Array;
|
||||
}) => Promise<ElectronSaveResult>;
|
||||
};
|
||||
|
||||
async function blobToUint8Array(blob: Blob): Promise<Uint8Array> {
|
||||
const buffer = await blob.arrayBuffer();
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
function guessFilters(filename: string, mimeType?: string): Array<{ name: string; extensions: string[] }> {
|
||||
const ext = filename.includes('.') ? filename.split('.').pop()!.toLowerCase() : '';
|
||||
const mime = (mimeType || '').toLowerCase();
|
||||
|
||||
if (mime.startsWith('image/') || ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'].includes(ext)) {
|
||||
return [
|
||||
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
];
|
||||
}
|
||||
if (mime.startsWith('video/') || ['mp4', 'webm', 'mkv', 'mov'].includes(ext)) {
|
||||
return [
|
||||
{ name: 'Videos', extensions: ['mp4', 'webm', 'mkv', 'mov'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
];
|
||||
}
|
||||
if (mime.startsWith('audio/') || ['mp3', 'ogg', 'wav', 'm4a', 'flac'].includes(ext)) {
|
||||
return [
|
||||
{ name: 'Audio', extensions: ['mp3', 'ogg', 'wav', 'm4a', 'flac'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
];
|
||||
}
|
||||
if (mime === 'application/pdf' || ext === 'pdf') {
|
||||
return [
|
||||
{ name: 'PDF', extensions: ['pdf'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
];
|
||||
}
|
||||
if (ext) {
|
||||
return [
|
||||
{ name: ext.toUpperCase(), extensions: [ext] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
];
|
||||
}
|
||||
return [{ name: 'All Files', extensions: ['*'] }];
|
||||
}
|
||||
|
||||
async function saveWithElectron(blob: Blob, filename: string): Promise<void> {
|
||||
const media = (window.electron as { media?: ElectronMediaApi } | undefined)?.media;
|
||||
if (!media?.saveFile) {
|
||||
throw new Error('Electron media.saveFile is unavailable');
|
||||
}
|
||||
|
||||
const data = await blobToUint8Array(blob);
|
||||
const result = await media.saveFile({
|
||||
filename,
|
||||
mimeType: blob.type || 'application/octet-stream',
|
||||
data,
|
||||
});
|
||||
|
||||
if (result?.canceled) return;
|
||||
if (result?.success === false) {
|
||||
throw new Error(result.error || 'Failed to save file');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveWithTauri(blob: Blob, filename: string): Promise<void> {
|
||||
const { save } = await import('@tauri-apps/plugin-dialog');
|
||||
const { writeFile } = await import('@tauri-apps/plugin-fs');
|
||||
|
||||
const path = await save({
|
||||
defaultPath: filename,
|
||||
filters: guessFilters(filename, blob.type),
|
||||
});
|
||||
if (!path) return;
|
||||
|
||||
const data = await blobToUint8Array(blob);
|
||||
await writeFile(path, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire Electron / Tauri native save dialogs into core saveMedia helpers.
|
||||
* Call once at desktop app startup. No-ops in plain browser.
|
||||
*/
|
||||
export function registerDesktopMediaSaver(): void {
|
||||
if (isElectron()) {
|
||||
registerMediaSaver(async (blob, filename) => {
|
||||
await saveWithElectron(blob, filename);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTauri()) {
|
||||
registerMediaSaver(async (blob, filename) => {
|
||||
await saveWithTauri(blob, filename);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user