moved filter autocomplete to worker

This commit is contained in:
Gani Georgiev
2024-02-24 13:46:16 +02:00
parent 4f46222de9
commit 20fba0f686
40 changed files with 2990 additions and 2918 deletions
+39
View File
@@ -0,0 +1,39 @@
import CommonHelper from "@/utils/CommonHelper";
const maxKeys = 11000;
onmessage = (e) => {
if (!e.data.collections) {
return;
}
const result = {};
result.baseKeys = CommonHelper.getCollectionAutocompleteKeys(e.data.collections, e.data.baseCollection?.name);
result.baseKeys = limitArray(result.baseKeys.sort(keysSort), maxKeys);
if (!e.data.disableRequestKeys) {
result.requestKeys = CommonHelper.getRequestAutocompleteKeys(e.data.collections, e.data.baseCollection?.name);
result.requestKeys = limitArray(result.requestKeys.sort(keysSort), maxKeys);
}
if (!e.data.disableCollectionJoinKeys) {
result.collectionJoinKeys = CommonHelper.getCollectionJoinAutocompleteKeys(e.data.collections);
result.collectionJoinKeys = limitArray(result.collectionJoinKeys.sort(keysSort), maxKeys);
}
postMessage(result);
};
// sort shorter keys first
function keysSort(a, b) {
return a.length - b.length;
}
function limitArray(arr, max) {
if (arr.length > max) {
return arr.slice(0, max);
}
return arr;
}
@@ -34,6 +34,7 @@
import { onMount, createEventDispatcher } from "svelte";
import { collections } from "@/stores/collections";
import CommonHelper from "@/utils/CommonHelper";
import AutocompleteWorker from "@/autocomplete.worker.js?worker";
// code mirror imports
// ---
import {
@@ -54,7 +55,7 @@
StreamLanguage,
syntaxTree,
} from "@codemirror/language";
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
import { defaultKeymap, indentWithTab, history, historyKeymap } from "@codemirror/commands";
import { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
import {
autocompletion,
@@ -75,7 +76,7 @@
export let singleLine = false;
export let extraAutocompleteKeys = []; // eg. ["test1", "test2"]
export let disableRequestKeys = false;
export let disableIndirectCollectionsKeys = false;
export let disableCollectionJoinKeys = false;
let editor;
let container;
@@ -84,10 +85,10 @@
let editableCompartment = new Compartment();
let readOnlyCompartment = new Compartment();
let placeholderCompartment = new Compartment();
let autocompleteWorker = new AutocompleteWorker();
let cachedCollections = [];
let cachedRequestKeys = [];
let cachedIndirectCollectionKeys = [];
let cachedCollectionJoinKeys = [];
let cachedBaseKeys = [];
let baseKeysChangeHash = "";
let oldBaseKeysChangeHash = "";
@@ -98,7 +99,7 @@
!disabled &&
(oldBaseKeysChangeHash != baseKeysChangeHash ||
disableRequestKeys !== -1 ||
disableIndirectCollectionsKeys !== -1)
disableCollectionJoinKeys !== -1)
) {
oldBaseKeysChangeHash = baseKeysChangeHash;
refreshCachedKeys();
@@ -146,18 +147,28 @@
editor?.focus();
}
// Refresh the cached autocomplete keys.
// ---
let refreshDebounceId = null;
// Refresh the cached autocomplete keys.
autocompleteWorker.onmessage = (e) => {
cachedBaseKeys = e.data.baseKeys || [];
cachedRequestKeys = e.data.requestKeys || [];
cachedCollectionJoinKeys = e.data.collectionJoinKeys || [];
};
function refreshCachedKeys() {
clearTimeout(refreshDebounceId);
refreshDebounceId = setTimeout(() => {
cachedCollections = concatWithBaseCollection($collections);
cachedBaseKeys = getBaseKeys();
cachedRequestKeys = !disableRequestKeys ? getRequestKeys() : [];
cachedIndirectCollectionKeys = !disableIndirectCollectionsKeys ? getIndirectCollectionKeys() : [];
}, 300);
autocompleteWorker.postMessage({
baseCollection: baseCollection,
collections: concatWithBaseCollection($collections),
disableRequestKeys: disableRequestKeys,
disableCollectionJoinKeys: disableCollectionJoinKeys,
});
}, 250);
}
// ---
// Return a collection keys hash string that can be used to compare with previous states.
function getCollectionKeysChangeHash(collection) {
@@ -211,129 +222,8 @@
}
}
// Returns a list with all collection field keys recursively.
function getCollectionFieldKeys(nameOrId, prefix = "", level = 0) {
let collection = cachedCollections.find((item) => item.name == nameOrId || item.id == nameOrId);
if (!collection || level >= 4) {
return [];
}
collection.schema = collection.schema || [];
let result = CommonHelper.getAllCollectionIdentifiers(collection, prefix);
for (const field of collection.schema) {
const key = prefix + field.name;
// add relation fields
if (field.type === "relation" && field.options?.collectionId) {
const subKeys = getCollectionFieldKeys(field.options.collectionId, key + ".", level + 1);
if (subKeys.length) {
result = result.concat(subKeys);
}
}
// add ":each" field modifier
if (field.type === "select" && field.options?.maxSelect != 1) {
result.push(key + ":each");
}
// add ":length" field modifier to arrayble fields
if (field.options?.maxSelect != 1 && ["select", "file", "relation"].includes(field.type)) {
result.push(key + ":length");
}
}
// add back relations
for (const ref of cachedCollections) {
ref.schema = ref.schema || [];
for (const field of ref.schema) {
if (field.type == "relation" && field.options?.collectionId == collection.id) {
const key = prefix + ref.name + "_via_" + field.name;
const subKeys = getCollectionFieldKeys(ref.id, key + ".", level + 2); // +2 to reduce the recursive results
if (subKeys.length) {
result = result.concat(subKeys);
}
}
}
}
return result;
}
// Returns baseCollection keys.
function getBaseKeys() {
return getCollectionFieldKeys(baseCollection?.name);
}
// Returns @request.* keys.
function getRequestKeys() {
const result = [];
result.push("@request.context");
result.push("@request.method");
result.push("@request.query.");
result.push("@request.data.");
result.push("@request.headers.");
result.push("@request.auth.id");
result.push("@request.auth.collectionId");
result.push("@request.auth.collectionName");
result.push("@request.auth.verified");
result.push("@request.auth.username");
result.push("@request.auth.email");
result.push("@request.auth.emailVisibility");
result.push("@request.auth.created");
result.push("@request.auth.updated");
// load auth collection fields
const authCollections = cachedCollections.filter((collection) => collection.type === "auth");
for (const collection of authCollections) {
const authKeys = getCollectionFieldKeys(collection.id, "@request.auth.");
for (const k of authKeys) {
CommonHelper.pushUnique(result, k);
}
}
// load base collection fields into @request.data.*
const issetExcludeList = ["created", "updated"];
if (baseCollection?.id) {
const keys = getCollectionFieldKeys(baseCollection.name, "@request.data.");
for (const key of keys) {
result.push(key);
// add ":isset" modifier to non-base keys
const parts = key.split(".");
if (
parts.length === 3 &&
// doesn't contain another modifier
parts[2].indexOf(":") === -1 &&
// is not from the exclude list
!issetExcludeList.includes(parts[2])
) {
result.push(key + ":isset");
}
}
}
return result;
}
// Returns @collection.* keys.
function getIndirectCollectionKeys() {
const result = [];
for (const collection of cachedCollections) {
const prefix = "@collection." + collection.name + ".";
const keys = getCollectionFieldKeys(collection.name, prefix);
for (const key of keys) {
result.push(key);
}
}
return result;
}
// Returns an array with all the supported keys.
function getAllKeys(includeRequestKeys = true, includeIndirectCollectionsKeys = true) {
function getAllKeys(includeRequestKeys = true, includeCollectionJoinKeys = true) {
let result = [].concat(extraAutocompleteKeys);
// add base keys
@@ -344,16 +234,11 @@
result = result.concat(cachedRequestKeys || []);
}
// add @collections.* keys
if (includeIndirectCollectionsKeys) {
result = result.concat(cachedIndirectCollectionKeys || []);
// add @collection.* keys
if (includeCollectionJoinKeys) {
result = result.concat(cachedCollectionJoinKeys || []);
}
// sort shorter keys first
result.sort(function (a, b) {
return a.length - b.length;
});
return result;
}
@@ -389,15 +274,20 @@
{ label: "@yearEnd" },
];
if (!disableIndirectCollectionsKeys) {
if (!disableCollectionJoinKeys) {
options.push({ label: "@collection.*", apply: "@collection." });
}
const keys = getAllKeys(!disableRequestKeys, !disableRequestKeys && word.text.startsWith("@c"));
let keys = getAllKeys(
!disableRequestKeys && word.text.startsWith("@r"),
!disableCollectionJoinKeys && word.text.startsWith("@c"),
);
for (const key of keys) {
options.push({
label: key.endsWith(".") ? key + "*" : key,
apply: key,
boost: key.indexOf("_via_") > 0 ? -1 : 0, // deprioritize _via_ keys
});
}
@@ -498,6 +388,7 @@
searchKeymap.find((item) => item.key === "Mod-d"),
...historyKeymap,
...completionKeymap,
indentWithTab,
]),
EditorView.lineWrapping,
autocompletion({
@@ -533,6 +424,7 @@
clearTimeout(refreshDebounceId);
removeLabelListeners();
editor?.destroy();
autocompleteWorker.terminate();
};
});
</script>
+1 -1
View File
@@ -66,7 +66,7 @@
id={uniqueId}
singleLine
disableRequestKeys
disableIndirectCollectionsKeys
disableCollectionJoinKeys
{extraAutocompleteKeys}
baseCollection={autocompleteCollection}
placeholder={value || placeholder}
+136
View File
@@ -1745,6 +1745,142 @@ export default class CommonHelper {
return result;
}
/**
* Generates recursively a list with all the autocomplete field keys
* for the collectionNameOrId collection.
*
* @param {Array} collections
* @param {String} collectionNameOrId
* @param {String} [prefix]
* @param {Number} [level]
* @return {Array}
*/
static getCollectionAutocompleteKeys(collections, collectionNameOrId, prefix = "", level = 0) {
let collection = collections.find((item) => item.name == collectionNameOrId || item.id == collectionNameOrId);
if (!collection || level >= 4) {
return [];
}
collection.schema = collection.schema || [];
let result = CommonHelper.getAllCollectionIdentifiers(collection, prefix);
for (const field of collection.schema) {
const key = prefix + field.name;
// add relation fields
if (field.type == "relation" && field.options?.collectionId) {
const subKeys = CommonHelper.getCollectionAutocompleteKeys(collections, field.options.collectionId, key + ".", level + 1);
if (subKeys.length) {
result = result.concat(subKeys);
}
}
// add ":each" field modifier
if (field.type == "select" && field.options?.maxSelect != 1) {
result.push(key + ":each");
}
// add ":length" field modifier to arrayble fields
if (field.options?.maxSelect != 1 && ["select", "file", "relation"].includes(field.type)) {
result.push(key + ":length");
}
}
// add back relations
for (const ref of collections) {
ref.schema = ref.schema || [];
for (const field of ref.schema) {
if (field.type == "relation" && field.options?.collectionId == collection.id) {
const key = prefix + ref.name + "_via_" + field.name;
const subKeys = CommonHelper.getCollectionAutocompleteKeys(collections, ref.id, key + ".", level + 2); // +2 to reduce the recursive results
if (subKeys.length) {
result = result.concat(subKeys);
}
}
}
}
return result;
}
/**
* Generates a list with all @collection.* autocomplete field keys.
*
* @param {Array} collections
* @return {Array}
*/
static getCollectionJoinAutocompleteKeys(collections) {
const result = [];
for (const collection of collections) {
const prefix = "@collection." + collection.name + ".";
const keys = CommonHelper.getCollectionAutocompleteKeys(collections, collection.name, prefix);
for (const key of keys) {
result.push(key);
}
}
return result;
}
/**
* Generates a list with all @request.* autocomplete field keys.
*
* @param {Array} collections
* @param {String} baseCollectionName (used for the `@request.data.*` fields)
* @return {Array}
*/
static getRequestAutocompleteKeys(collections, baseCollectionName) {
const result = [];
result.push("@request.context");
result.push("@request.method");
result.push("@request.query.");
result.push("@request.data.");
result.push("@request.headers.");
result.push("@request.auth.id");
result.push("@request.auth.collectionId");
result.push("@request.auth.collectionName");
result.push("@request.auth.verified");
result.push("@request.auth.username");
result.push("@request.auth.email");
result.push("@request.auth.emailVisibility");
result.push("@request.auth.created");
result.push("@request.auth.updated");
// load auth collection fields
const authCollections = collections.filter((collection) => collection.type === "auth");
for (const collection of authCollections) {
const authKeys = CommonHelper.getCollectionAutocompleteKeys(collections, collection.id, "@request.auth.");
for (const k of authKeys) {
CommonHelper.pushUnique(result, k);
}
}
// load base collection fields into @request.data.*
if (baseCollectionName) {
const issetExcludeList = ["created", "updated"];
const keys = CommonHelper.getCollectionAutocompleteKeys(collections, baseCollectionName, "@request.data.");
for (const key of keys) {
result.push(key);
// add ":isset" modifier to non-base keys
const parts = key.split(".");
if (
parts.length === 3 &&
// doesn't contain another modifier
parts[2].indexOf(":") === -1 &&
// is not from the exclude list
!issetExcludeList.includes(parts[2])
) {
result.push(key + ":isset");
}
}
}
return result;
}
/**
* Parses the specified SQL index and returns an object with its components.
*