improve tab (and other lexorank entities) sorting, ordering and reordering

This commit is contained in:
Fabian Freund
2026-05-02 04:50:53 +02:00
parent 222b920a47
commit 69f21bb7d8
43 changed files with 4965 additions and 418 deletions
@@ -44,9 +44,24 @@ class ToolbarButtonConfigDao extends DatabaseAccessor<UserDatabase>
.write(ToolbarButtonConfigsCompanion(orderKey: Value(orderKey)));
Future<void> assignVisibility(String buttonId, {required bool visible}) =>
(update(db.toolbarButtonConfigs)
..where((t) => t.buttonId.equals(buttonId)))
.write(ToolbarButtonConfigsCompanion(isVisible: Value(visible)));
transaction(() async {
// Land the toggled button at the trailing edge of its *new* section
// so its order_key always stays inside the visibility partition.
// Without this, a button keeps the key it was assigned in the other
// section and lands at an arbitrary position after the toggle.
final orderKey = await generateTrailingOrderKey(
isVisible: visible,
).getSingle();
await (update(
db.toolbarButtonConfigs,
)..where((t) => t.buttonId.equals(buttonId))).write(
ToolbarButtonConfigsCompanion(
isVisible: Value(visible),
orderKey: Value(orderKey),
),
);
});
Future<void> assignFallback(String buttonId, String? fallbackId) =>
(update(db.toolbarButtonConfigs)
@@ -74,7 +89,9 @@ class ToolbarButtonConfigDao extends DatabaseAccessor<UserDatabase>
await transaction(() async {
final inserted = <ToolbarButtonConfig>[];
for (final def in missing) {
final orderKey = await generateTrailingOrderKey().getSingle();
final orderKey = await generateTrailingOrderKey(
isVisible: def.defaultVisible,
).getSingle();
inserted.add(
ToolbarButtonConfig(
buttonId: def.buttonId,
@@ -96,18 +113,37 @@ class ToolbarButtonConfigDao extends DatabaseAccessor<UserDatabase>
});
}
SingleSelectable<String> generateLeadingOrderKey({int bucket = 0}) =>
db.definitionsDrift.toolbarLeadingOrderKey(bucket: bucket);
SingleSelectable<String> generateLeadingOrderKey({
int bucket = 0,
required bool isVisible,
}) => db.definitionsDrift.toolbarLeadingOrderKey(
bucket: bucket,
isVisible: isVisible,
);
SingleSelectable<String> generateTrailingOrderKey({int bucket = 0}) =>
db.definitionsDrift.toolbarTrailingOrderKey(bucket: bucket);
SingleSelectable<String> generateTrailingOrderKey({
int bucket = 0,
required bool isVisible,
}) => db.definitionsDrift.toolbarTrailingOrderKey(
bucket: bucket,
isVisible: isVisible,
);
SingleOrNullSelectable<String> generateOrderKeyAfterButtonId(
String buttonId,
) => db.definitionsDrift.toolbarOrderKeyAfterButton(buttonId: buttonId);
String buttonId, {
required bool isVisible,
}) => db.definitionsDrift.toolbarOrderKeyAfterButton(
buttonId: buttonId,
isVisible: isVisible,
);
SingleSelectable<String> generateOrderKeyBeforeButtonId(String buttonId) =>
db.definitionsDrift.toolbarOrderKeyBeforeButton(buttonId: buttonId);
SingleSelectable<String> generateOrderKeyBeforeButtonId(
String buttonId, {
required bool isVisible,
}) => db.definitionsDrift.toolbarOrderKeyBeforeButton(
buttonId: buttonId,
isVisible: isVisible,
);
Future<void> _insertWithDeferredFallbacks(
List<ToolbarButtonConfig> configs,
@@ -31,47 +31,56 @@ CREATE TABLE toolbar_button_configs (
CREATE INDEX idx_toolbar_order_key ON toolbar_button_configs(order_key);
toolbarLeadingOrderKey(:bucket AS INTEGER):
-- All four queries are scoped to a visibility partition (`is_visible`) so the
-- generated key falls strictly within the targeted section. The Customize
-- Toolbar UI renders Enabled and Disabled as two independent reorderable
-- lists; without the partition, leading/trailing keys would cross the section
-- boundary and surface as buttons "jumping" after a visibility toggle.
toolbarLeadingOrderKey(:bucket AS INTEGER, :is_visible AS BOOL):
SELECT lexo_rank_previous(
:bucket,
(
SELECT order_key
FROM toolbar_button_configs
WHERE is_visible = :is_visible
ORDER BY order_key
LIMIT 1
)
);
toolbarTrailingOrderKey(:bucket AS INTEGER):
toolbarTrailingOrderKey(:bucket AS INTEGER, :is_visible AS BOOL):
SELECT lexo_rank_next(
:bucket,
(
SELECT order_key
FROM toolbar_button_configs
WHERE is_visible = :is_visible
ORDER BY order_key DESC
LIMIT 1
)
);
toolbarOrderKeyAfterButton(:button_id AS TEXT):
toolbarOrderKeyAfterButton(:button_id AS TEXT, :is_visible AS BOOL):
WITH ordered_table AS (
SELECT
button_id,
order_key,
LEAD(order_key) OVER (ORDER BY order_key) AS next_order_key
FROM toolbar_button_configs
WHERE is_visible = :is_visible
)
SELECT lexo_rank_reorder_after(order_key, next_order_key)
FROM ordered_table
WHERE button_id = :button_id;
toolbarOrderKeyBeforeButton(:button_id AS TEXT):
toolbarOrderKeyBeforeButton(:button_id AS TEXT, :is_visible AS BOOL):
WITH ordered_table AS (
SELECT
button_id,
order_key,
LAG(order_key) OVER (ORDER BY order_key) AS prev_order_key
FROM toolbar_button_configs
WHERE is_visible = :is_visible
)
SELECT lexo_rank_reorder_before(order_key, prev_order_key)
FROM ordered_table
@@ -2024,36 +2024,46 @@ i0.Index get idxToolbarOrderKey => i0.Index(
class DefinitionsDrift extends i3.ModularAccessor {
DefinitionsDrift(i0.GeneratedDatabase db) : super(db);
i0.Selectable<String> toolbarLeadingOrderKey({required int bucket}) {
i0.Selectable<String> toolbarLeadingOrderKey({
required int bucket,
required bool isVisible,
}) {
return customSelect(
'SELECT lexo_rank_previous(?1, (SELECT order_key FROM toolbar_button_configs ORDER BY order_key LIMIT 1)) AS _c0',
variables: [i0.Variable<int>(bucket)],
'SELECT lexo_rank_previous(?1, (SELECT order_key FROM toolbar_button_configs WHERE is_visible = ?2 ORDER BY order_key LIMIT 1)) AS _c0',
variables: [i0.Variable<int>(bucket), i0.Variable<bool>(isVisible)],
readsFrom: {toolbarButtonConfigs},
).map((i0.QueryRow row) => row.read<String>('_c0'));
}
i0.Selectable<String> toolbarTrailingOrderKey({required int bucket}) {
i0.Selectable<String> toolbarTrailingOrderKey({
required int bucket,
required bool isVisible,
}) {
return customSelect(
'SELECT lexo_rank_next(?1, (SELECT order_key FROM toolbar_button_configs ORDER BY order_key DESC LIMIT 1)) AS _c0',
variables: [i0.Variable<int>(bucket)],
'SELECT lexo_rank_next(?1, (SELECT order_key FROM toolbar_button_configs WHERE is_visible = ?2 ORDER BY order_key DESC LIMIT 1)) AS _c0',
variables: [i0.Variable<int>(bucket), i0.Variable<bool>(isVisible)],
readsFrom: {toolbarButtonConfigs},
).map((i0.QueryRow row) => row.read<String>('_c0'));
}
i0.Selectable<String> toolbarOrderKeyAfterButton({required String buttonId}) {
i0.Selectable<String> toolbarOrderKeyAfterButton({
required bool isVisible,
required String buttonId,
}) {
return customSelect(
'WITH ordered_table AS (SELECT button_id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key FROM toolbar_button_configs) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE button_id = ?1',
variables: [i0.Variable<String>(buttonId)],
'WITH ordered_table AS (SELECT button_id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key FROM toolbar_button_configs WHERE is_visible = ?1) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE button_id = ?2',
variables: [i0.Variable<bool>(isVisible), i0.Variable<String>(buttonId)],
readsFrom: {toolbarButtonConfigs},
).map((i0.QueryRow row) => row.read<String>('_c0'));
}
i0.Selectable<String> toolbarOrderKeyBeforeButton({
required bool isVisible,
required String buttonId,
}) {
return customSelect(
'WITH ordered_table AS (SELECT button_id, order_key, LAG(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_order_key FROM toolbar_button_configs) SELECT lexo_rank_reorder_before(order_key, prev_order_key) AS _c0 FROM ordered_table WHERE button_id = ?1',
variables: [i0.Variable<String>(buttonId)],
'WITH ordered_table AS (SELECT button_id, order_key, LAG(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_order_key FROM toolbar_button_configs WHERE is_visible = ?1) SELECT lexo_rank_reorder_before(order_key, prev_order_key) AS _c0 FROM ordered_table WHERE button_id = ?2',
variables: [i0.Variable<bool>(isVisible), i0.Variable<String>(buttonId)],
readsFrom: {toolbarButtonConfigs},
).map((i0.QueryRow row) => row.read<String>('_c0'));
}