feat: combo-box on relationship searching on one type rels.

This commit is contained in:
Darshan
2025-09-12 11:17:03 +05:30
parent 3aa4ce0c5d
commit 2d9ec3a0ea
4 changed files with 62 additions and 42 deletions
+2 -2
View File
@@ -24,9 +24,9 @@
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@0fefd92",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@952b37e",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fefd92",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@952b37e",
"@faker-js/faker": "^9.9.0",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
+10 -10
View File
@@ -18,14 +18,14 @@ importers:
specifier: 0.25.0
version: 0.25.0
'@appwrite.io/pink-icons-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@0fefd92
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@0fefd92(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@952b37e
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@952b37e(svelte@5.25.3)
'@appwrite.io/pink-legacy':
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fefd92
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fefd92(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@952b37e
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@952b37e(svelte@5.25.3)
'@faker-js/faker':
specifier: ^9.9.0
version: 9.9.0
@@ -269,8 +269,8 @@ packages:
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@0fefd92':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@0fefd92}
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@952b37e':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@952b37e}
version: 2.0.0-RC.1
peerDependencies:
svelte: ^4.0.0
@@ -284,8 +284,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fefd92':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fefd92}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@952b37e':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@952b37e}
version: 2.0.0-RC.2
peerDependencies:
svelte: ^4.0.0
@@ -3706,7 +3706,7 @@ snapshots:
dependencies:
svelte: 5.25.3
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@0fefd92(svelte@5.25.3)':
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@952b37e(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
@@ -3719,7 +3719,7 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fefd92(svelte@5.25.3)':
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@952b37e(svelte@5.25.3)':
dependencies:
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
'@floating-ui/dom': 1.6.13
@@ -230,7 +230,6 @@
</SpreadsheetContainer>
{#if !$spreadsheetLoading}
<!-- Claude: Can this be truly centered without hacky left: xyz values? -->
<div
class="spreadsheet-fade-bottom"
data-collapsed-tabs={!$expandTabs}
@@ -34,7 +34,6 @@
let showInput = false;
let singleRel: string;
let search: string = null;
let loadingRelationships = false;
let newItemValue: string = '';
@@ -45,6 +44,21 @@
let searchNoResultsOption = undefined;
/**
* list of loaded rows to tune with searching!
*
* this is used when the view is not searching anything,
* therefore we don't have to make an API call again!
*
* Example:
* 1. initial rows are loaded -> cache the rows
* 2. user makes a search -> api call -> not results found
* 3. user clears the search -> this variable sets the rowsList back!
*
* `#3` allows us to not make a call to getRows again, saves time and bandwidth!
*/
let cachedRowsCopyList: Models.RowList<Models.Row>;
onMount(async () => {
if (isRelationshipToMany(column)) {
if (Array.isArray(value)) {
@@ -60,24 +74,22 @@
}
});
async function getRows(search: string = null) {
async function getRows() {
loadingRelationships = true;
// already includes the `$id`, dw!
const displayNames = preferences.getDisplayNames(column.relatedTable);
const queries = search
? [Query.select(displayNames), Query.startsWith('$id', search), Query.orderDesc('')]
: [Query.select(displayNames)];
const rows = await sdk
.forProject(page.params.region, page.params.project)
.tablesDB.listRows({
databaseId,
tableId: column.relatedTable,
queries
// limit `5` as `25` would look too much on sheet!
queries: [Query.select(displayNames), Query.limit(5)]
});
cachedRowsCopyList = rows;
loadingRelationships = false;
return rows;
@@ -89,7 +101,7 @@
const displayNames = preferences.getDisplayNames(column.relatedTable);
const queries = [];
displayNames.forEach((name) => queries.push(Query.contains(name, search)));
displayNames.forEach((name) => queries.push(Query.contains(name, [search])));
return await sdk.forProject(page.params.region, page.params.project).tablesDB.listRows({
databaseId,
@@ -127,13 +139,13 @@
if (newItemValue) {
relatedList = [...relatedList, newItemValue];
value = relatedList;
newItemValue = '';
newItemValue = null;
showInput = false;
}
}
function cancelAddItem() {
newItemValue = '';
newItemValue = null;
showInput = false;
}
@@ -184,46 +196,42 @@
return baseOptions;
}
// original list
let originalListPreSearch: Models.RowList<Models.Row>;
const debouncedSearch = debounce(async () => {
const debouncedSearch = debounce(async (searchTerm: string) => {
const availableOptions = getAvailableOptions();
const includesItem = availableOptions
.filter((opt) => {
const label = String(opt.label ?? '').toLowerCase();
const value = String(opt.value ?? '').toLowerCase();
return (
label.includes(newItemValue.toLowerCase()) ||
value.includes(newItemValue.toLowerCase())
label.includes(searchTerm.toLowerCase()) ||
value.includes(searchTerm.toLowerCase())
);
})
.filter(Boolean);
if (!includesItem.length) {
originalListPreSearch = rowList;
try {
searchNoResultsOption = {
disabled: true,
message: 'Searching...'
};
const searchResults = await searchRows(newItemValue);
const searchResults = await searchRows(searchTerm);
if (searchResults) {
rowList = searchResults;
}
} finally {
searchNoResultsOption = undefined;
}
} else {
// reset
rowList = originalListPreSearch;
} else if (cachedRowsCopyList) {
rowList = cachedRowsCopyList;
}
}, 500);
// Reactive statements
$: getRows(search).then((res) => (rowList = res));
$: if (!newItemValue && !cachedRowsCopyList) {
getRows().then((response) => (rowList = response));
}
$: paginatedItems = editing
? relatedList
@@ -250,8 +258,11 @@
label = undefined;
}
$: if (newItemValue && newItemValue !== '') {
debouncedSearch();
$: if (newItemValue) {
debouncedSearch(newItemValue);
} else if (!newItemValue && cachedRowsCopyList) {
rowList = cachedRowsCopyList;
searchNoResultsOption = undefined;
}
</script>
@@ -365,6 +376,7 @@
bind:value={newItemValue}
options={getAvailableOptions()}
on:change={addNewItem}
on:select={(item) => console.log(item)}
noResultsOption={searchNoResultsOption}
leadingIcon={!limited ? IconRelationship : undefined} />
@@ -389,20 +401,29 @@
</Layout.Stack>
{:else}
<Layout.Stack direction="row" alignItems="center" gap="s">
<InputSelect
<Input.ComboBox
{id}
{options}
autofocus={limited}
bind:value={singleRel}
bind:value={newItemValue}
required={column.required}
label={limited ? undefined : label}
placeholder={`Select ${column.key}`}
noResultsOption={searchNoResultsOption}
on:change={() => {
if (singleRel === null) {
if (newItemValue === null) {
value = null;
singleRel = null;
} else {
value = rowList.rows.find((row) => row.$id === singleRel);
const selectedRow = rowList.rows.find((row) => row.$id === newItemValue);
if (selectedRow) {
value = selectedRow;
singleRel = newItemValue;
}
}
newItemValue = null;
}}
leadingIcon={!limited ? IconRelationship : undefined} />