Files
DemonGatanjieuandClaude Opus 5.5 bd156625fc Tidy: split the two files over 600 lines, drop 221 unused UI texts
ui_tts_import.js gives its frame and toolbar to ui_tts_import_screens.js and its window
drop to ui_tts_file_drop.js; material_inspector.js gives the PNG chunk reader to
png_metadata.js. 221 texts no code uses any more leave locales.js (both languages).
No behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:05:40 +08:00

115 lines
5.1 KiB
JavaScript

import { t } from './interface_settings.js';
import { importKind } from './tts_setup_api.js';
import { DROP_FILE_LIMIT, isPackage, skipFolder } from './tts_import_groups.js';
/**
* Files dropped from the OS on the audio studio, the sidebar or the import
* workbench: which character they were dropped on, and the folder each file came
* from (dropped folders are walked), so the workbench can sort them.
*/
const readAll = reader => new Promise((ok, fail) => reader.readEntries(ok, fail));
const hasFiles = event => [...(event.dataTransfer?.types || [])].includes('Files');
/**
* Files from the OS dropped anywhere on `box` (the import window): `is-dragover` while
* they hover, `onFiles(items)` with what readDroppedFiles walked, unless `signal` ended.
*/
export function bindWindowDrop(box, { signal, onFiles }) {
box.addEventListener('dragover', (event) => {
if (!hasFiles(event)) return;
event.preventDefault();
box.classList.add('is-dragover');
});
box.addEventListener('dragleave', (event) => { if (!box.contains(event.relatedTarget)) box.classList.remove('is-dragover'); });
box.addEventListener('drop', (event) => {
if (!hasFiles(event)) return;
event.preventDefault();
event.stopPropagation();
box.classList.remove('is-dragover');
readDroppedFiles(event.dataTransfer).then((items) => { if (!signal.aborted) onFiles(items); });
});
}
/**
* The entries of a drop, walked: `{ file, dir }` for each file an import can use,
* `{ skipped: folder }` for a folder left out (tts_import_groups.js skipFolder: never
* one that was dropped by itself), `{ truncated: true }` once `DROP_FILE_LIMIT` files
* were taken. Other files are not even opened: a program folder holds thousands.
*/
async function walkEntries(entries, dir, inPackage, count) {
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
const lists = await Promise.all(sorted.map(async (entry) => {
const at = dir ? `${dir}/${entry.name}` : entry.name;
if (entry.isFile) {
if (!importKind(entry.name)) return [];
if (count.files >= DROP_FILE_LIMIT) { count.truncated = true; return []; }
count.files++;
return [{ file: await new Promise((ok, fail) => entry.file(ok, fail)), dir }];
}
if (inPackage !== null && skipFolder(entry.name, inPackage)) return [{ skipped: at }];
const reader = entry.createReader();
const children = [];
for (;;) {
const batch = await readAll(reader);
if (!batch.length) break;
children.push(...batch);
}
return walkEntries(children, at, isPackage(children.map(child => child.name)), count);
}));
return lists.flat(); // in name order, whatever finished first
}
/**
* Files of a drop, with the folder each came from (dropped folders are walked).
* The entries must be taken during the drop event, so call it synchronously there.
* Several things dropped together are judged as one folder's contents.
*/
export function readDroppedFiles(dataTransfer) {
const entries = [...(dataTransfer?.items || [])].map(item => item.webkitGetAsEntry?.()).filter(Boolean);
if (!entries.length) return Promise.resolve([...(dataTransfer?.files || [])].map(file => ({ file, dir: '' })));
const count = { files: 0, truncated: false };
const together = entries.length > 1 ? isPackage(entries.map(entry => entry.name)) : null;
return walkEntries(entries, '', together, count).then(list => (count.truncated ? [...list, { truncated: true }] : list));
}
/**
* Let OS files be dropped on `root` (the studio or the sidebar). `onDrop(files, character)`
* gets `{ file, dir }` items and the character of the card they were dropped on, or null.
*/
export function bindTtsFileDrop(root, onDrop) {
const hasFiles = e => [...(e.dataTransfer?.types || [])].includes('Files');
let marked = null;
root.dataset.dropHint = t('ttsDropNew');
const mark = (card) => {
if (marked === card) return;
marked?.classList.remove('is-file-target');
card?.classList.add('is-file-target');
marked = card;
root.dataset.dropHint = card ? t('ttsDropAddTo', { name: card.dataset.ttsCharacter }) : t('ttsDropNew');
};
const cardAt = e => e.target?.closest?.('[data-tts-character]') || null;
root.addEventListener('dragover', (e) => {
if (!hasFiles(e)) return;
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
root.classList.add('is-file-dragover');
mark(cardAt(e));
});
root.addEventListener('dragleave', (e) => {
if (root.contains(e.relatedTarget)) return;
root.classList.remove('is-file-dragover');
mark(null);
});
root.addEventListener('drop', (e) => {
if (!hasFiles(e)) return;
e.preventDefault();
e.stopPropagation(); // ComfyUI would try to open the files as a workflow
const card = cardAt(e);
root.classList.remove('is-file-dragover');
mark(null);
readDroppedFiles(e.dataTransfer).then(files => onDrop(files, card?.dataset.ttsCharacter || null));
});
}