feat(oekaki): add drawing flow for /i/ (#1144)

* feat(oekaki): add drawing flow for /i/

* fix(oekaki): address review feedback

* fix(oekaki): reset Tegaki edit sessions

* fix(oekaki): block drawing during export

* fix(oekaki): destroy Tegaki on preload errors

* fix(oekaki): preserve drawing on export failure

* fix(oekaki): unlock controls after export failure
This commit is contained in:
Tommaso Casaburi
2026-05-30 15:06:47 +07:00
committed by GitHub
parent 5a0b4909b8
commit 56894700c1
28 changed files with 2134 additions and 49 deletions
@@ -13,6 +13,7 @@ function createElectronApiMock() {
copyToClipboard: vi.fn(async () => ({ success: true })),
getPlatform: vi.fn(async () => ({ platform: 'darwin' as NodeJS.Platform, arch: 'x64', version: 'v20.0.0' })),
automateUploadMedia: vi.fn(async (options: { provider: ProviderId }) => ({ url: 'https://i.imgur.com/abc.png', provider: options.provider })),
automateUploadGeneratedMedia: vi.fn(async (options: { provider: ProviderId }) => ({ url: 'https://i.imgur.com/generated.png', provider: options.provider })),
getPathForFile: vi.fn((): string | null => '/tmp/image.png'),
};
}
@@ -67,6 +68,7 @@ describe('orchestrateElectronUpload', () => {
it('fails with provider attempt details if no file path can be resolved', async () => {
const electronApi = createElectronApiMock();
electronApi.getPathForFile = vi.fn((): string | null => null);
electronApi.automateUploadGeneratedMedia = undefined as unknown as typeof electronApi.automateUploadGeneratedMedia;
window.electronApi = electronApi;
const file = new File(['z'], 'z.png', { type: 'image/png' });
@@ -80,12 +82,30 @@ describe('orchestrateElectronUpload', () => {
};
expect(typedError.message).toBe('All providers failed');
expect(typedError.attempts?.[0]?.provider).toBe('imgur');
expect(typedError.attempts?.[0]?.error).toContain('File path required for Electron automation');
expect(typedError.attempts?.[0]?.error).toContain('File path unavailable and automateUploadGeneratedMedia is not available');
expect(typedError.attempts?.[0]?.elapsedMs).toBeGreaterThanOrEqual(0);
expect(typedError.attempts?.[0]?.stage).toBeDefined();
}
});
it('uses generated media automation when no file path can be resolved', async () => {
const electronApi = createElectronApiMock();
electronApi.getPathForFile = vi.fn((): string | null => null);
window.electronApi = electronApi;
const file = new File(['abc'], 'tegaki.png', { type: 'image/png' });
const url = await orchestrateElectronUpload(file, ['imgur']);
expect(url).toBe('https://i.imgur.com/generated.png');
expect(electronApi.automateUploadGeneratedMedia).toHaveBeenCalledWith({
provider: 'imgur',
fileName: 'tegaki.png',
mimeType: 'image/png',
bytes: [97, 98, 99],
});
expect(electronApi.automateUploadMedia).not.toHaveBeenCalled();
});
it('includes stage and matchedSelectors when provider throws block/file-input errors', async () => {
const electronApi = createElectronApiMock();
electronApi.automateUploadMedia = vi.fn().mockRejectedValue(new Error('No file input found for imgur. Tried: input[type="file"], #upload'));
+19 -2
View File
@@ -56,6 +56,10 @@ function resolveElectronFilePath(file: File): string | null {
return null;
}
async function fileToByteArray(file: File): Promise<number[]> {
return Array.from(new Uint8Array(await file.arrayBuffer()));
}
/**
* Uploads a file via a single provider. Catbox uses the web API;
* imgur/imgbb use Electron automation when available.
@@ -66,8 +70,21 @@ async function uploadViaProvider(provider: ProviderId, file: File): Promise<stri
const fn = typeof window !== 'undefined' && window.electronApi?.automateUploadMedia;
if (fn) {
const filePath = resolveElectronFilePath(file);
if (!filePath) throw new Error('File path required for Electron automation');
const { url } = await fn({ provider, filePath });
if (filePath) {
const { url } = await fn({ provider, filePath });
return url;
}
const generatedFn = window.electronApi?.automateUploadGeneratedMedia;
if (!generatedFn) {
throw new Error('File path unavailable and automateUploadGeneratedMedia is not available');
}
const { url } = await generatedFn({
provider,
fileName: file.name,
mimeType: file.type || 'application/octet-stream',
bytes: await fileToByteArray(file),
});
return url;
}
throw new Error(`Provider ${provider} requires Electron (automateUploadMedia not available)`);