mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-13 06:42:23 +00:00
Let a share fit a full Data Library and show entries it cannot serve
A share's entries joined the library after the stored ones, and the selection kept its first 64, so a link opened into a full library never showed what it shared and the next hash write dropped its lib=. Now the oldest entries the share leaves off make room for it, and their layers are removed with them. Browse counted an entry as added only while its layer was registered, but an entry this instance cannot serve (switched off by the operator, or its server unreachable) stayed in the selection and held one of the 64 places. A library full of them looked empty and refused every Add. Such entries now show as added, so they can be removed one by one or with Remove all.
This commit is contained in:
1 parent
4fe8796e89
commit
837eff7328
2 files changed
+118
-14
No files matched your search
@@ -184,6 +184,28 @@ export function createDataLibrary({
|
||||
persist();
|
||||
});
|
||||
|
||||
/**
|
||||
* The library after a share's entries join it. A share always fits: in a
|
||||
* full library the oldest entries it leaves off make room for it, so a
|
||||
* link opened into a full library still shows what it shares.
|
||||
*/
|
||||
function withShared(added, linked) {
|
||||
const kept = [...new Set(added)].filter((id) => switchable.has(id));
|
||||
const incoming = [...new Set(linked)].filter(
|
||||
(id) => switchable.has(id) && !kept.includes(id),
|
||||
);
|
||||
let room = MAX_SELECTED - kept.length - incoming.length;
|
||||
const result = [];
|
||||
for (const id of kept) {
|
||||
if (room < 0 && !linked.includes(id)) {
|
||||
room += 1;
|
||||
continue;
|
||||
}
|
||||
result.push(id);
|
||||
}
|
||||
return [...result, ...incoming];
|
||||
}
|
||||
|
||||
/** Entry ids for share tokens; unknown tokens are skipped. */
|
||||
const idsForTokens = (list) =>
|
||||
(list || []).map((token) => tokens.idByToken.get(token)).filter(Boolean);
|
||||
@@ -212,7 +234,17 @@ export function createDataLibrary({
|
||||
* is never remembered as the viewer's own choice.
|
||||
*/
|
||||
function applySelection(next, origin) {
|
||||
const before = selection.added;
|
||||
selection = normalizeLibrarySelection(next, switchable);
|
||||
// Entries a share made room for leave with their layers.
|
||||
for (const id of before) {
|
||||
if (selection.added.includes(id) || !isRegistered(id)) continue;
|
||||
dataManager
|
||||
.unregisterLibraryLayer(libraryLayerId(id))
|
||||
.catch((error) =>
|
||||
console.warn(`[Library] ${id} could not be removed:`, error),
|
||||
);
|
||||
}
|
||||
for (const id of selection.added) {
|
||||
const entry = byId.get(id);
|
||||
if (!entry || !addable(id)) continue;
|
||||
@@ -251,7 +283,7 @@ export function createDataLibrary({
|
||||
}
|
||||
const linked = idsForTokens(shareTokens);
|
||||
applySelection(
|
||||
{ added: [...selection.added, ...linked], on: linked },
|
||||
{ added: withShared(selection.added, linked), on: linked },
|
||||
'share-restore',
|
||||
);
|
||||
}
|
||||
@@ -297,7 +329,10 @@ export function createDataLibrary({
|
||||
// Only what the selection keeps registers: past its cap an entry would
|
||||
// be a row the library no longer knows.
|
||||
const next = normalizeLibrarySelection(
|
||||
{ added: [...stored.added, ...(linked || [])], on: linked ?? stored.on },
|
||||
{
|
||||
added: linked ? withShared(stored.added, linked) : stored.added,
|
||||
on: linked ?? stored.on,
|
||||
},
|
||||
switchable,
|
||||
);
|
||||
for (const id of next.added) {
|
||||
@@ -423,7 +458,9 @@ export function createDataLibrary({
|
||||
entry,
|
||||
available: state.available,
|
||||
reason: state.reason,
|
||||
added: selection.added.includes(entry.id) && isRegistered(entry.id),
|
||||
// An entry this instance cannot serve now stays added (and can be
|
||||
// removed) without a layer: it still holds a place in the library.
|
||||
added: selection.added.includes(entry.id),
|
||||
on: dataManager.isEnabled?.(libraryLayerId(entry.id)) === true,
|
||||
busy: pending.has(entry.id),
|
||||
canZoom: typeof zoomTo === 'function' && isRegistered(entry.id),
|
||||
|
||||
@@ -418,25 +418,92 @@ test('a full library refuses another entry before its layer exists', async (t) =
|
||||
assert.equal(manager.layers.size, 64);
|
||||
assert.equal(await library.add('entry-64'), false);
|
||||
assert.equal(manager.layers.size, 64, 'no layer outside the selection');
|
||||
// A share into a full library adds no row the library cannot keep.
|
||||
const shared = sealedManager(t);
|
||||
const opened = createDataLibrary({
|
||||
library.destroy();
|
||||
});
|
||||
|
||||
test('a share opened into a full library still shows what it shares', async (t) => {
|
||||
const many = validateLibraryCatalog(
|
||||
Array.from({ length: 67 }, (_, index) => tiles(`entry-${index}`)),
|
||||
).entries;
|
||||
const manyLedger = many.map(({ id }, index) => [
|
||||
id,
|
||||
index.toString(36).padStart(2, '0'),
|
||||
]);
|
||||
const availability = {
|
||||
async getAvailability() {
|
||||
return {
|
||||
server: true,
|
||||
availability: new Map(
|
||||
many.map(({ id }) => [id, { available: true, reason: null }]),
|
||||
),
|
||||
};
|
||||
},
|
||||
tileTemplate: (entry) => entry.source.url,
|
||||
};
|
||||
const full = JSON.stringify({
|
||||
added: many.slice(0, 64).map(({ id }) => id),
|
||||
on: ['entry-0'],
|
||||
});
|
||||
const manager = sealedManager(t);
|
||||
const shareLink = {
|
||||
...shareLinkDouble(),
|
||||
initialLibraryTokens: [manyLedger[64][1]],
|
||||
listener: null,
|
||||
setLibraryShareListener(listener) {
|
||||
this.listener = listener;
|
||||
},
|
||||
};
|
||||
const library = createDataLibrary({
|
||||
entries: many,
|
||||
ledger: manyLedger,
|
||||
dataManager: shared,
|
||||
dataManager: manager,
|
||||
services: {},
|
||||
storage: memoryStorage(full),
|
||||
shareLink: fakeShareLink([manyLedger[65][1]]),
|
||||
shareLink,
|
||||
source: availability,
|
||||
});
|
||||
await opened.start();
|
||||
assert.equal(shared.layers.has('lib-entry-65'), false);
|
||||
assert.equal(
|
||||
opened.list().filter((row) => row.added).length,
|
||||
shared.layers.size,
|
||||
await library.start();
|
||||
await manager.waitForLayerSettled?.('lib-entry-64');
|
||||
assert.equal(manager.isEnabled('lib-entry-64'), true);
|
||||
// The oldest entry the share leaves off made room.
|
||||
assert.equal(manager.layers.has('lib-entry-0'), false);
|
||||
assert.equal(manager.layers.size, 64);
|
||||
assert.equal(shareLink.encode(), manyLedger[64][1]);
|
||||
// A share navigated to in this tab makes room the same way, and the
|
||||
// entry it evicts leaves with its layer.
|
||||
shareLink.listener([manyLedger[65][1]]);
|
||||
await manager.waitForLayerSettled?.('lib-entry-65');
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
assert.equal(manager.isEnabled('lib-entry-65'), true);
|
||||
assert.equal(manager.layers.has('lib-entry-1'), false);
|
||||
assert.equal(manager.layers.size, 64);
|
||||
library.destroy();
|
||||
});
|
||||
|
||||
test('entries this instance cannot serve still show as added and can be removed', async (t) => {
|
||||
const manager = sealedManager(t);
|
||||
const storage = memoryStorage(
|
||||
JSON.stringify({ added: ['night-lights'], on: ['night-lights'] }),
|
||||
);
|
||||
const library = createDataLibrary({
|
||||
entries,
|
||||
ledger,
|
||||
dataManager: manager,
|
||||
services: {},
|
||||
storage,
|
||||
shareLink: fakeShareLink(null),
|
||||
source: source({
|
||||
'night-lights': { available: false, reason: 'disabled' },
|
||||
}),
|
||||
});
|
||||
await library.start();
|
||||
assert.equal(manager.layers.size, 0);
|
||||
const row = library.list().find(({ entry }) => entry.id === 'night-lights');
|
||||
assert.equal(row.added, true);
|
||||
assert.equal(library.summary().added, 1);
|
||||
assert.equal(await library.remove('night-lights'), true);
|
||||
assert.deepEqual(JSON.parse(storage.store.get(LIBRARY_STORAGE_KEY)).added, []);
|
||||
library.destroy();
|
||||
opened.destroy();
|
||||
});
|
||||
|
||||
test('a layer that fails to tear down stays in the library', async (t) => {
|
||||
|
||||
Reference in new issue
Block a user