feat: Add group support for pinned tabs

This commit introduces the ability to create groups of pinned tabs, allowing for more organized and hierarchical management of pinned tabs.

Key changes:

- Added `parentUuid` field to `zen_pins` table to represent parent group relationships.
- Implemented `getGroupChildren` function to fetch children of a given group.
- Updated `removePin` function to also remove all children of a group when deleting.
- Modified `getPins` function to sort pins by parent group and then by position.
- Added `isGroup` and `isEssential` fields to `zen_pins` table to represent the type of pinned item.
- Improved database schema with additional indices for performance optimization.
- Removed unused `recordChange` function.
This commit is contained in:
Kristijan Ribarić 2024-11-03 20:50:59 +01:00
parent a04b5c30b6
commit a82e2c2765

View file

@ -1,11 +1,7 @@
var ZenPinnedTabsStorage = {
async init() {
console.log('ZenPinnedTabsStorage: Initializing...');
try {
await this._ensureTable();
} catch (e) {
console.warn('ZenPinnedTabsStorage: Failed to initialize', e);
}
},
async _ensureTable() {
@ -16,20 +12,29 @@ var ZenPinnedTabsStorage = {
id INTEGER PRIMARY KEY,
uuid TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
url TEXT NOT NULL,
url TEXT,
container_id INTEGER,
workspace_uuid TEXT,
position INTEGER NOT NULL DEFAULT 0,
is_essential BOOLEAN NOT NULL DEFAULT 0,
is_group BOOLEAN NOT NULL DEFAULT 0,
parent_uuid TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
updated_at INTEGER NOT NULL,
FOREIGN KEY (parent_uuid) REFERENCES zen_pins(uuid) ON DELETE SET NULL
)
`);
// Create an index on the uuid column
// Create indices
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_zen_pins_uuid ON zen_pins(uuid)
`);
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_zen_pins_parent_uuid ON zen_pins(parent_uuid)
`);
// Create the changes tracking table if it doesn't exist
await db.execute(`
CREATE TABLE IF NOT EXISTS zen_pins_changes (
@ -70,33 +75,40 @@ var ZenPinnedTabsStorage = {
if ('position' in pin && Number.isFinite(pin.position)) {
newPosition = pin.position;
} else {
// Get the maximum position
const maxPositionResult = await db.execute(`SELECT MAX("position") as max_position FROM zen_pins`);
// Get the maximum position within the same parent group (or null for root level)
const maxPositionResult = await db.execute(`
SELECT MAX("position") as max_position
FROM zen_pins
WHERE COALESCE(parent_uuid, '') = COALESCE(:parent_uuid, '')
`, { parent_uuid: pin.parentUuid || null });
const maxPosition = maxPositionResult[0].getResultByName('max_position') || 0;
newPosition = maxPosition + 1000; // Add a large increment to avoid frequent reordering
newPosition = maxPosition + 1000;
}
// Insert or replace the pin
await db.executeCached(`
INSERT OR REPLACE INTO zen_pins (
uuid, title, url, container_id, workspace_uuid, position,
created_at, updated_at
is_essential, is_group, parent_uuid, created_at, updated_at
) VALUES (
:uuid, :title, :url, :container_id, :workspace_uuid, :position,
:is_essential, :is_group, :parent_uuid,
COALESCE((SELECT created_at FROM zen_pins WHERE uuid = :uuid), :now),
:now
)
`, {
uuid: pin.uuid,
title: pin.title,
url: pin.url,
url: pin.isGroup ? null : pin.url,
container_id: pin.containerTabId || null,
workspace_uuid: pin.workspaceUuid || null,
position: newPosition,
is_essential: pin.isEssential || false,
is_group: pin.isGroup || false,
parent_uuid: pin.parentUuid || null,
now
});
// Record the change
await db.execute(`
INSERT OR REPLACE INTO zen_pins_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
@ -106,7 +118,6 @@ var ZenPinnedTabsStorage = {
});
changedUUIDs.add(pin.uuid);
await this.updateLastChangeTimestamp(db);
});
});
@ -119,7 +130,8 @@ var ZenPinnedTabsStorage = {
async getPins() {
const db = await PlacesUtils.promiseDBConnection();
const rows = await db.executeCached(`
SELECT * FROM zen_pins ORDER BY position ASC
SELECT * FROM zen_pins
ORDER BY parent_uuid NULLS FIRST, position ASC
`);
return rows.map((row) => ({
uuid: row.getResultByName('uuid'),
@ -127,7 +139,31 @@ var ZenPinnedTabsStorage = {
url: row.getResultByName('url'),
containerTabId: row.getResultByName('container_id'),
workspaceUuid: row.getResultByName('workspace_uuid'),
position: row.getResultByName('position')
position: row.getResultByName('position'),
isEssential: Boolean(row.getResultByName('is_essential')),
isGroup: Boolean(row.getResultByName('is_group')),
parentUuid: row.getResultByName('parent_uuid')
}));
},
async getGroupChildren(groupUuid) {
const db = await PlacesUtils.promiseDBConnection();
const rows = await db.executeCached(`
SELECT * FROM zen_pins
WHERE parent_uuid = :groupUuid
ORDER BY position ASC
`, { groupUuid });
return rows.map((row) => ({
uuid: row.getResultByName('uuid'),
title: row.getResultByName('title'),
url: row.getResultByName('url'),
containerTabId: row.getResultByName('container_id'),
workspaceUuid: row.getResultByName('workspace_uuid'),
position: row.getResultByName('position'),
isEssential: Boolean(row.getResultByName('is_essential')),
isGroup: Boolean(row.getResultByName('is_group')),
parentUuid: row.getResultByName('parent_uuid')
}));
},
@ -135,23 +171,45 @@ var ZenPinnedTabsStorage = {
const changedUUIDs = [uuid];
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.removePin', async (db) => {
await db.executeTransaction(async () => {
// Get all child UUIDs first for change tracking
const children = await db.execute(
`SELECT uuid FROM zen_pins WHERE parent_uuid = :uuid`,
{ uuid }
);
// Add child UUIDs to changedUUIDs array
for (const child of children) {
changedUUIDs.push(child.getResultByName('uuid'));
}
// Delete all children in a single statement
await db.execute(
`DELETE FROM zen_pins WHERE parent_uuid = :uuid`,
{ uuid }
);
// Delete the pin/group itself
await db.execute(
`DELETE FROM zen_pins WHERE uuid = :uuid`,
{ uuid }
);
// Record the removal as a change
const now = Date.now();
// Record the changes
const now = Math.floor(Date.now() / 1000);
for (const changedUuid of changedUUIDs) {
await db.execute(`
INSERT OR REPLACE INTO zen_pins_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`, {
uuid,
timestamp: Math.floor(now / 1000)
uuid: changedUuid,
timestamp: now
});
}
await this.updateLastChangeTimestamp(db);
});
});
if (notifyObservers) {
this._notifyPinsChanged("zen-pin-removed", changedUUIDs);