Merge commit '640413af' into fix/512-delete-review-polish
This commit is contained in:
@@ -317,6 +317,9 @@ export function createImageTaggingController({api, target, source = 'images', on
|
||||
// A save in flight: closing hands the capture release to its completion.
|
||||
let saving = false;
|
||||
let observedOnce = false;
|
||||
// #515: the user typed since the last observation started (or before the first one answered), so the answer
|
||||
// keeps that text.
|
||||
let typed = false;
|
||||
|
||||
function set(patch) {
|
||||
if (closed) return;
|
||||
@@ -345,7 +348,8 @@ export function createImageTaggingController({api, target, source = 'images', on
|
||||
async function open({keepDraft = false} = {}) {
|
||||
if (closed) return;
|
||||
const serial = ++openSerial;
|
||||
const draft = keepDraft ? state.manual : null;
|
||||
// A reopen after an observation starts from its answer; text typed before any observation is kept (#515).
|
||||
if (observedOnce) typed = false;
|
||||
const seenRevision = state.tagRevision;
|
||||
stopGenerating();
|
||||
releaseCapture();
|
||||
@@ -385,15 +389,19 @@ export function createImageTaggingController({api, target, source = 'images', on
|
||||
const savedModel = !chosen && !modelPicked && read.model ? {model: read.model} : {};
|
||||
observedOnce = true;
|
||||
const {width, height, media_type: mediaType, preview_url: previewUrl, generate} = value.capture;
|
||||
// A kept draft stays as typed unless another writer changed the set meanwhile: then the set leads it (I2).
|
||||
const changed = draft !== null && value.tag_revision !== seenRevision;
|
||||
// The draft is read now, so text typed while observing is kept (#515). A kept draft stays as typed unless
|
||||
// another writer changed the set meanwhile: then the set leads it (I2). Text typed before the user saw any
|
||||
// saved tags follows them, so a Save never drops tags it never showed.
|
||||
const draft = keepDraft || typed ? state.manual : null;
|
||||
const changed = keepDraft && value.tag_revision !== seenRevision;
|
||||
const unseen = draft !== null && !keepDraft;
|
||||
const reason = generate.supported ? '' : (generate.reason || '');
|
||||
set({
|
||||
phase: 'ready',
|
||||
image: {filename: value.filename, title: value.title, width, height, mediaType, previewUrl,
|
||||
dimensions: width && height ? `${width} × ${height}` : null},
|
||||
savedTags: value.tags.slice(), tagRevision: value.tag_revision,
|
||||
manual: draft === null ? value.tags.join(', ') : changed ? mergeCurrent(value.tags, draft) : draft,
|
||||
manual: draft === null ? value.tags.join(', ') : changed || unseen ? mergeCurrent(value.tags, draft) : draft,
|
||||
draftRevision: state.draftRevision + 1,
|
||||
...savedModel, options: {...defaults, ...chosen?.options}, optionsRevision: state.optionsRevision + 1,
|
||||
generateSupported: generate.supported, generateReason: generate.reason,
|
||||
@@ -402,6 +410,7 @@ export function createImageTaggingController({api, target, source = 'images', on
|
||||
}
|
||||
|
||||
function setManual(text) {
|
||||
typed = true;
|
||||
set({manual: typeof text === 'string' ? text : ''});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export const REVIEW_FAILED = 'The review failed. Nothing was changed.';
|
||||
export const SOURCE_CHANGED = 'The sources changed. Refresh the images and try again.';
|
||||
export const STORAGE_CHANGED = 'Prompt storage was restored. Reopen Delete.';
|
||||
export const USE_LIBRARY_ONLY = 'To remove these files from the library anyway, choose “Remove from the library only”.';
|
||||
export const NO_TRASH_HERE = 'Moving to the trash is not available on this system yet.';
|
||||
export const NO_TRASH_HERE = 'Moving to the trash is not available for these files; each one lists why.';
|
||||
const SENTENCES = Object.freeze({
|
||||
libraryBackup: 'Removed images stay hidden unless the file is modified or replaced. Their tags, category, collections '
|
||||
+ 'and favorite can only be recovered by restoring the safety backup, which also undoes every later change.',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import '../api-client.js';
|
||||
import { createButton, createScope } from '../shared/toolkit/core.js';
|
||||
import { routes, mountRouter, summarizeStats, routeTitle, loadNavCounts, navigateTo, COLLECTION_ROUTE,
|
||||
LIBRARY_WRITE_COUNTS, NAV_COUNT_ROUTES, FAVORITE_WRITE_COUNTS, FAVORITE_COUNT_DELAY_MS, REVIEW_WRITE_COUNTS } from './state.mjs';
|
||||
LIBRARY_WRITE_COUNTS, NAV_COUNT_ROUTES, FAVORITE_WRITE_COUNTS, FAVORITE_COUNT_DELAY_MS, REVIEW_WRITE_COUNTS,
|
||||
JOB_END_COUNTS } from './state.mjs';
|
||||
import { mountWorkspaceTheme } from './theme.mjs';
|
||||
import { forgetImagesVisit, mountImages, onFavoriteChange } from './images.mjs';
|
||||
import { forgetLibraryVisit, mountLibrary, mountNewPromptAction, onLibraryWrite } from './library.mjs';
|
||||
@@ -196,16 +197,20 @@ function mountWorkspace() {
|
||||
scope.cleanup(() => window.removeEventListener(type, revive, true));
|
||||
}
|
||||
scope.listen(document, 'visibilitychange', () => { if (document.visibilityState === 'visible') revive(); });
|
||||
// A metadata scan that ends may have added items to Needs review: re-read its count once per ended scan.
|
||||
const runningScans = new Set();
|
||||
// A job that ends re-reads the counts it can change, once per ended job: a metadata scan may have added items to
|
||||
// Needs review; deleting images (#480) hides or trashes them, with their favorites, review items and collection
|
||||
// memberships (#501).
|
||||
const runningJobs = new Map();
|
||||
scope.cleanup(activity.subscribe(() => {
|
||||
let ended = false;
|
||||
const ended = new Set();
|
||||
for (const job of activity.jobs()) {
|
||||
if (job.kind !== 'scan') continue;
|
||||
if (job.state === 'running') runningScans.add(job.id);
|
||||
else if (runningScans.delete(job.id)) ended = true;
|
||||
if (!JOB_END_COUNTS[job.kind]) continue;
|
||||
if (job.state === 'running') runningJobs.set(job.id, job.kind);
|
||||
else if (runningJobs.delete(job.id)) ended.add(job.kind);
|
||||
}
|
||||
if (ended) refreshCounts(REVIEW_WRITE_COUNTS);
|
||||
if (!ended.size) return;
|
||||
refreshCounts([...new Set([...ended].flatMap(kind => JOB_END_COUNTS[kind]))]);
|
||||
if (ended.has('image_delete') && !leaving) collections.load();
|
||||
}));
|
||||
refreshCounts(NAV_COUNT_ROUTES);
|
||||
// S9: a finished restore replaced the library. The cached visits are dropped, the counts and saved views read
|
||||
|
||||
@@ -129,6 +129,11 @@ export const LIBRARY_WRITE_COUNTS = Object.freeze(['library', 'tags']);
|
||||
export const FAVORITE_WRITE_COUNTS = Object.freeze(['favorites']);
|
||||
/** The count a review decision or a finished metadata scan can change. */
|
||||
export const REVIEW_WRITE_COUNTS = Object.freeze(['review']);
|
||||
/** Job kind -> the counts re-read once when such a job ends (a scan feeds Needs review; #501: a delete hides images). */
|
||||
export const JOB_END_COUNTS = Object.freeze({
|
||||
scan: REVIEW_WRITE_COUNTS,
|
||||
image_delete: Object.freeze(['images', 'favorites', 'review']),
|
||||
});
|
||||
// Trailing delay that coalesces quick star toggles into one Favorites count read (#249 M1).
|
||||
export const FAVORITE_COUNT_DELAY_MS = 300;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user