Insole-production time tracker exported from the Create/Anything AI platform. Baseline snapshot before any reverse-engineering or cleanup. - apps/mobile: Expo Router app (iOS/Android/web), the only workspace - publisher/: standalone OpenNext/AWS deploy tooling for the web side - Backend (/api/tasks, /api/logs + DB) lives remotely, not in this repo
95 lines
2.2 KiB
TypeScript
95 lines
2.2 KiB
TypeScript
interface DocumentPickerAsset {
|
|
name: string;
|
|
size: number | null;
|
|
uri: string;
|
|
mimeType: string | null;
|
|
}
|
|
|
|
interface DocumentPickerResult {
|
|
canceled: boolean;
|
|
assets: DocumentPickerAsset[];
|
|
output: null;
|
|
}
|
|
|
|
interface DocumentPickerOptions {
|
|
type?: string | string[];
|
|
copyToCacheDirectory?: boolean;
|
|
multiple?: boolean;
|
|
}
|
|
|
|
export async function getDocumentAsync(
|
|
options?: DocumentPickerOptions
|
|
): Promise<DocumentPickerResult> {
|
|
return new Promise((resolve) => {
|
|
const input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.multiple = options?.multiple ?? false;
|
|
|
|
if (options?.type) {
|
|
const types = Array.isArray(options.type)
|
|
? options.type
|
|
: [options.type];
|
|
const filtered = types.filter((t) => t !== '*/*');
|
|
if (filtered.length > 0) {
|
|
input.accept = filtered.join(',');
|
|
}
|
|
}
|
|
|
|
input.style.display = 'none';
|
|
document.body.appendChild(input);
|
|
|
|
let resolved = false;
|
|
const cleanup = () => {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
document.body.removeChild(input);
|
|
}
|
|
};
|
|
|
|
input.addEventListener('change', () => {
|
|
const files = input.files;
|
|
if (!files || files.length === 0) {
|
|
cleanup();
|
|
resolve({ canceled: true, assets: [], output: null });
|
|
return;
|
|
}
|
|
|
|
const promises = Array.from(files).map(
|
|
(file) =>
|
|
new Promise<DocumentPickerAsset>((resolveAsset) => {
|
|
const reader = new FileReader();
|
|
reader.onload = () => {
|
|
resolveAsset({
|
|
name: file.name,
|
|
size: file.size,
|
|
uri: reader.result as string,
|
|
mimeType: file.type || null,
|
|
});
|
|
};
|
|
reader.readAsDataURL(file);
|
|
})
|
|
);
|
|
|
|
void Promise.all(promises).then((assets) => {
|
|
cleanup();
|
|
resolve({ canceled: false, assets, output: null });
|
|
});
|
|
});
|
|
|
|
window.addEventListener(
|
|
'focus',
|
|
() => {
|
|
setTimeout(() => {
|
|
if (!resolved) {
|
|
cleanup();
|
|
resolve({ canceled: true, assets: [], output: null });
|
|
}
|
|
}, 300);
|
|
},
|
|
{ once: true }
|
|
);
|
|
|
|
input.click();
|
|
});
|
|
}
|