Add extended filtering implementation
Signed-off-by: trivernis <trivernis@protonmail.com>pull/4/head
parent
8007bf64a7
commit
501a3c9df0
@ -1,61 +1,60 @@
|
|||||||
{
|
{
|
||||||
"name": "mediarepo-ui",
|
"name": "mediarepo-ui",
|
||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "~12.2.0",
|
"@angular/animations": "~13.1.1",
|
||||||
"@angular/cdk": "12.2.9",
|
"@angular/cdk": "^13.1.1",
|
||||||
"@angular/common": "~12.2.0",
|
"@angular/common": "~13.1.1",
|
||||||
"@angular/compiler": "~12.2.0",
|
"@angular/compiler": "~13.1.1",
|
||||||
"@angular/core": "~12.2.0",
|
"@angular/core": "~13.1.1",
|
||||||
"@angular/flex-layout": "^12.0.0-beta.35",
|
"@angular/flex-layout": "^13.0.0-beta.36",
|
||||||
"@angular/forms": "~12.2.0",
|
"@angular/forms": "~13.1.1",
|
||||||
"@angular/material": "12.2.9",
|
"@angular/material": "^13.1.1",
|
||||||
"@angular/platform-browser": "~12.2.0",
|
"@angular/platform-browser": "~13.1.1",
|
||||||
"@angular/platform-browser-dynamic": "~12.2.0",
|
"@angular/platform-browser-dynamic": "~13.1.1",
|
||||||
"@angular/router": "~12.2.0",
|
"@angular/router": "~13.1.1",
|
||||||
"@ng-icons/core": "^13.1.1",
|
"@ng-icons/core": "^13.2.0",
|
||||||
"@ng-icons/feather-icons": "^13.1.1",
|
"@ng-icons/feather-icons": "^13.2.0",
|
||||||
"@ng-icons/material-icons": "^13.1.1",
|
"@ng-icons/material-icons": "13.1.0",
|
||||||
"@tauri-apps/api": "^1.0.0-beta.8",
|
"@tauri-apps/api": "^1.0.0-beta.8",
|
||||||
"ngx-lightbox": "^2.5.1",
|
"primeicons": "^5.0.0",
|
||||||
"primeicons": "^4.1.0",
|
"primeng": "^13.0.4",
|
||||||
"primeng": "^12.2.1",
|
"rxjs": "~7.5.2",
|
||||||
"rxjs": "~6.6.0",
|
"tslib": "^2.3.1",
|
||||||
"tslib": "^2.3.0",
|
"zone.js": "~0.11.4"
|
||||||
"zone.js": "~0.11.4"
|
},
|
||||||
},
|
"devDependencies": {
|
||||||
"devDependencies": {
|
"@angular-devkit/build-angular": "~13.1.2",
|
||||||
"@angular-devkit/build-angular": "~12.2.9",
|
"@angular-eslint/builder": "^13.0.1",
|
||||||
"@angular-eslint/builder": "12.5.0",
|
"@angular-eslint/eslint-plugin": "^13.0.1",
|
||||||
"@angular-eslint/eslint-plugin": "12.5.0",
|
"@angular-eslint/eslint-plugin-template": "^13.0.1",
|
||||||
"@angular-eslint/eslint-plugin-template": "12.5.0",
|
"@angular-eslint/schematics": "^13.0.1",
|
||||||
"@angular-eslint/schematics": "12.5.0",
|
"@angular-eslint/template-parser": "^13.0.1",
|
||||||
"@angular-eslint/template-parser": "12.5.0",
|
"@angular/cli": "~13.1.2",
|
||||||
"@angular/cli": "~12.2.9",
|
"@angular/compiler-cli": "~13.1.1",
|
||||||
"@angular/compiler-cli": "~12.2.0",
|
"@tauri-apps/cli": "^1.0.0-beta.10",
|
||||||
"@tauri-apps/cli": "^1.0.0-beta.10",
|
"@types/file-saver": "^2.0.4",
|
||||||
"@types/file-saver": "^2.0.3",
|
"@types/jasmine": "~3.10.3",
|
||||||
"@types/jasmine": "~3.8.0",
|
"@types/node": "^16.11.19",
|
||||||
"@types/node": "^12.11.1",
|
"@typescript-eslint/eslint-plugin": "5.9.1",
|
||||||
"@typescript-eslint/eslint-plugin": "4.28.2",
|
"@typescript-eslint/parser": "^5.9.1",
|
||||||
"@typescript-eslint/parser": "4.28.2",
|
"eslint": "^8.6.0",
|
||||||
"eslint": "^7.26.0",
|
"jasmine-core": "~4.0.0",
|
||||||
"jasmine-core": "~3.8.0",
|
"karma": "~6.3.10",
|
||||||
"karma": "~6.3.0",
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
"karma-chrome-launcher": "~3.1.0",
|
"karma-coverage": "~2.1.0",
|
||||||
"karma-coverage": "~2.0.3",
|
"karma-jasmine": "~4.0.1",
|
||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine-html-reporter": "~1.7.0",
|
||||||
"karma-jasmine-html-reporter": "~1.7.0",
|
"typescript": "~4.5.4"
|
||||||
"typescript": "~4.3.5"
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
type CacheEntry<T> = {
|
||||||
|
ttl: number,
|
||||||
|
value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheMap: {
|
||||||
|
[key: string]: CacheEntry<any>
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
export class ShortCache {
|
||||||
|
|
||||||
|
public static async cached<T>(
|
||||||
|
key: any,
|
||||||
|
producer: () => Promise<T>,
|
||||||
|
ttl: number = 1000,
|
||||||
|
prefix: string = ""
|
||||||
|
): Promise<T> {
|
||||||
|
const cacheKey = prefix + JSON.stringify(key);
|
||||||
|
const entry = this.getCacheEntry<T>(cacheKey, ttl);
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
console.debug("cache hit for key", cacheKey);
|
||||||
|
return entry;
|
||||||
|
} else {
|
||||||
|
console.debug("cache miss key", cacheKey);
|
||||||
|
const value = await producer();
|
||||||
|
this.addCacheEntry(cacheKey, value, ttl);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static startTicking() {
|
||||||
|
(async () => {
|
||||||
|
while (true) {
|
||||||
|
ShortCache.tick();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static addCacheEntry(key: string, value: any, ttl: number) {
|
||||||
|
cacheMap[key] = {
|
||||||
|
ttl,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
console.debug("added cache entry with key", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getCacheEntry<T>(key: string, ttl: number): T | undefined {
|
||||||
|
const entry = cacheMap[key];
|
||||||
|
if (entry) {
|
||||||
|
entry.ttl = ttl;
|
||||||
|
}
|
||||||
|
return entry?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static tick() {
|
||||||
|
for (let key in cacheMap) {
|
||||||
|
cacheMap[key].ttl -= 100;
|
||||||
|
|
||||||
|
if (cacheMap[key].ttl < 0) {
|
||||||
|
console.debug("purged cache entry with key", key);
|
||||||
|
delete cacheMap[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortCache.startTicking();
|
@ -0,0 +1,292 @@
|
|||||||
|
import {FileStatus, FilterQuery, PropertyQuery, ValueComparator} from "../api-types/files";
|
||||||
|
|
||||||
|
export type Comparator = "Less" | "Equal" | "Greater" | "Between";
|
||||||
|
export type PropertyType =
|
||||||
|
"Status"
|
||||||
|
| "FileSize"
|
||||||
|
| "ImportedTime"
|
||||||
|
| "ChangedTime"
|
||||||
|
| "CreatedTime"
|
||||||
|
| "TagCount"
|
||||||
|
| "Cd"
|
||||||
|
| "Id";
|
||||||
|
|
||||||
|
export class FilterQueryBuilder {
|
||||||
|
|
||||||
|
public static tag(tag: string, negate: boolean): FilterQuery {
|
||||||
|
return { Tag: { tag, negate } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static status(status: FileStatus): FilterQuery {
|
||||||
|
return filterQuery({ Status: status });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fileSize(size: number, comparator: Comparator, max_size?: number): FilterQuery {
|
||||||
|
return filterQuery(
|
||||||
|
{ FileSize: valuesToCompareEnum(size, comparator, max_size) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static importedTime(date: Date, comparator: Comparator, max_date: Date): FilterQuery {
|
||||||
|
return filterQuery({
|
||||||
|
ImportedTime: valuesToCompareEnum(date, comparator,
|
||||||
|
max_date
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static changedTime(date: Date, comparator: Comparator, max_date: Date): FilterQuery {
|
||||||
|
return filterQuery({
|
||||||
|
ChangedTime: valuesToCompareEnum(date, comparator, max_date)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createdTime(date: Date, comparator: Comparator, max_date: Date): FilterQuery {
|
||||||
|
return filterQuery({
|
||||||
|
CreatedTime: valuesToCompareEnum(date, comparator, max_date)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static tagCount(count: number, comparator: Comparator, max_count: number): FilterQuery {
|
||||||
|
return filterQuery({
|
||||||
|
TagCount: valuesToCompareEnum(count, comparator, max_count)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static contentDescriptor(descriptor: string): FilterQuery {
|
||||||
|
return filterQuery({ Cd: descriptor });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fileId(id: number): FilterQuery {
|
||||||
|
return filterQuery({ Id: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static buildFilterFromString(filterStr: string): FilterQuery {
|
||||||
|
filterStr = filterStr.trim();
|
||||||
|
|
||||||
|
if (filterStr.startsWith(".")) {
|
||||||
|
const cleanFilter = filterStr.replace(/^\./, "");
|
||||||
|
const parsedPropertyFilter = this.parsePropertyFilterQuery(cleanFilter);
|
||||||
|
if (parsedPropertyFilter) {
|
||||||
|
return parsedPropertyFilter;
|
||||||
|
}
|
||||||
|
} else if (filterStr.startsWith("-")) {
|
||||||
|
const tag = filterStr.replace(/^-/, "").trim();
|
||||||
|
return this.tag(tag, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tag(filterStr, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parsePropertyFilterQuery(expression: string): FilterQuery | undefined {
|
||||||
|
let propertyName = "";
|
||||||
|
let compareValue = "";
|
||||||
|
let rawComparator = "";
|
||||||
|
let comparatorStarted = false;
|
||||||
|
let valueStarted = false;
|
||||||
|
|
||||||
|
for (const char of expression) {
|
||||||
|
console.log(char);
|
||||||
|
if (!valueStarted) {
|
||||||
|
switch (char) {
|
||||||
|
case " ":
|
||||||
|
break;
|
||||||
|
case "=":
|
||||||
|
case "!":
|
||||||
|
case ">":
|
||||||
|
case "<":
|
||||||
|
rawComparator += char;
|
||||||
|
comparatorStarted = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
valueStarted = comparatorStarted;
|
||||||
|
if (valueStarted) {
|
||||||
|
compareValue += char;
|
||||||
|
} else {
|
||||||
|
propertyName += char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
compareValue += char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.parseQueryFromParts(propertyName, rawComparator, compareValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parseQueryFromParts(
|
||||||
|
propertyName: string,
|
||||||
|
rawComparator: string,
|
||||||
|
compareValue: string
|
||||||
|
): FilterQuery | undefined {
|
||||||
|
const property = this.parsePropertyName(propertyName);
|
||||||
|
const comparator = this.parseComparator(rawComparator);
|
||||||
|
console.log("Parts: ", propertyName, rawComparator, compareValue);
|
||||||
|
|
||||||
|
if (property && comparator) {
|
||||||
|
let value;
|
||||||
|
switch (property) {
|
||||||
|
case "Status":
|
||||||
|
value = parseStatus(compareValue);
|
||||||
|
if (comparator === "Equal" && value != undefined) {
|
||||||
|
return this.status(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "FileSize":
|
||||||
|
value = this.parsePropertyValue(compareValue, parseNumber);
|
||||||
|
if (value != undefined) {
|
||||||
|
return this.fileSize(value[0], comparator, value[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ImportedTime":
|
||||||
|
value = this.parsePropertyValue(compareValue, parseDate);
|
||||||
|
if (value != undefined) {
|
||||||
|
return this.importedTime(value[0], comparator, value[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "ChangedTime":
|
||||||
|
value = this.parsePropertyValue(compareValue, parseDate);
|
||||||
|
if (value != undefined) {
|
||||||
|
return this.changedTime(value[0], comparator, value[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "CreatedTime":
|
||||||
|
value = this.parsePropertyValue(compareValue, parseDate);
|
||||||
|
if (value != undefined) {
|
||||||
|
return this.createdTime(value[0], comparator, value[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "TagCount":
|
||||||
|
value = this.parsePropertyValue(compareValue, parseNumber);
|
||||||
|
if (value != undefined) {
|
||||||
|
return this.tagCount(value[0], comparator, value[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Cd":
|
||||||
|
if (compareValue) {
|
||||||
|
return this.contentDescriptor(compareValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Id":
|
||||||
|
value = parseNumber(compareValue);
|
||||||
|
|
||||||
|
if (value != undefined) {
|
||||||
|
return this.fileId(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parseComparator(comparatorStr: string): Comparator | undefined {
|
||||||
|
switch (comparatorStr) {
|
||||||
|
case "=":
|
||||||
|
case "==":
|
||||||
|
return "Equal";
|
||||||
|
case "<":
|
||||||
|
return "Less";
|
||||||
|
case ">":
|
||||||
|
return "Greater";
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parsePropertyName(nameStr: string): PropertyType | undefined {
|
||||||
|
switch (nameStr.toLowerCase().replace(/-_/g, "")) {
|
||||||
|
case "status":
|
||||||
|
return "Status";
|
||||||
|
case "filesize":
|
||||||
|
return "FileSize";
|
||||||
|
case "importedat":
|
||||||
|
case "importeddate":
|
||||||
|
case "importedtime":
|
||||||
|
return "ImportedTime";
|
||||||
|
case "changedat":
|
||||||
|
case "changeddate":
|
||||||
|
case "changedtime":
|
||||||
|
return "ChangedTime";
|
||||||
|
case "createdat":
|
||||||
|
case "createddate":
|
||||||
|
case "createdtime":
|
||||||
|
return "CreatedTime";
|
||||||
|
case "tagcount":
|
||||||
|
return "TagCount";
|
||||||
|
case "cd":
|
||||||
|
case "contentdescriptor":
|
||||||
|
return "Cd";
|
||||||
|
case "id":
|
||||||
|
case "fileid":
|
||||||
|
return "Id";
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parsePropertyValue<T>(
|
||||||
|
valueStr: string,
|
||||||
|
parseFn: (valueStr: string) => T | undefined
|
||||||
|
): T[] | undefined {
|
||||||
|
const [firstValue, secondValue] = valueStr.split(" ");
|
||||||
|
if (secondValue != undefined) {
|
||||||
|
const firstValueParsed = parseFn(firstValue);
|
||||||
|
const secondValueParsed = parseFn(secondValue);
|
||||||
|
|
||||||
|
if (firstValueParsed && secondValueParsed) {
|
||||||
|
return [firstValueParsed, secondValueParsed];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const value = parseFn(firstValue);
|
||||||
|
return value != undefined ? [value] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterQuery(propertyQuery: PropertyQuery): FilterQuery {
|
||||||
|
return { Property: propertyQuery };
|
||||||
|
}
|
||||||
|
|
||||||
|
function valuesToCompareEnum<T>(min_value: T, comparator: Comparator, max_value?: T): ValueComparator<T> {
|
||||||
|
switch (comparator) {
|
||||||
|
case "Less":
|
||||||
|
return { Less: min_value };
|
||||||
|
case "Equal":
|
||||||
|
return { Equal: min_value };
|
||||||
|
case "Greater":
|
||||||
|
return { Greater: min_value };
|
||||||
|
case "Between":
|
||||||
|
return { Between: [min_value, max_value!] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNumber(value: string): number | undefined {
|
||||||
|
const num = Number(value);
|
||||||
|
return isNaN(num) ? undefined : num;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDate(value: string): Date | undefined {
|
||||||
|
const date = Date.parse(value);
|
||||||
|
|
||||||
|
if (isNaN(date)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return new Date(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStatus(value: string): FileStatus | undefined {
|
||||||
|
switch (value.toLowerCase()) {
|
||||||
|
case "imported":
|
||||||
|
return "Imported";
|
||||||
|
case "archived":
|
||||||
|
return "Archived";
|
||||||
|
case "deleted":
|
||||||
|
return "Deleted";
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
import {FilterExpression, FilterQuery} from "../api-types/files";
|
||||||
|
import * as deepEqual from "fast-deep-equal";
|
||||||
|
|
||||||
|
export class SearchFilters {
|
||||||
|
constructor(private filters: FilterExpression[]) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public get length() {
|
||||||
|
return this.filters.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFilters(): FilterExpression[] {
|
||||||
|
return this.filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasFilter(expression: FilterExpression): boolean {
|
||||||
|
return !!this.filters.find(f => deepEqual(f, expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
public addFilter(filter: FilterQuery, index: number) {
|
||||||
|
this.filters = [...this.filters.slice(
|
||||||
|
0,
|
||||||
|
index
|
||||||
|
), { Query: filter }, ...this.filters.slice(index)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public appendFilter(filter: FilterQuery) {
|
||||||
|
this.filters.push({ Query: filter });
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFilter(filterToRemove: FilterExpression) {
|
||||||
|
this.filters = this.filters.filter(f => !deepEqual(f, filterToRemove));
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFilterAtIndex(index: number) {
|
||||||
|
this.filters.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public appendSubfilter(filter: FilterQuery, index: number) {
|
||||||
|
const expressionEntry = this.filters[index];
|
||||||
|
|
||||||
|
if (expressionEntry && "OrExpression" in expressionEntry) {
|
||||||
|
expressionEntry["OrExpression"]!.push(filter);
|
||||||
|
} else {
|
||||||
|
const otherQuery = expressionEntry["Query"]!;
|
||||||
|
let entry = expressionEntry as unknown as { OrExpression: FilterQuery[], Query: undefined };
|
||||||
|
entry["Query"] = undefined;
|
||||||
|
entry["OrExpression"] = [otherQuery, filter];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeSubfilter(queryToRemove: FilterQuery) {
|
||||||
|
let index = this.filters.findIndex(f => {
|
||||||
|
if ("Query" in f) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
f["OrExpression"] = f["OrExpression"]!.filter(q => !deepEqual(q, queryToRemove));
|
||||||
|
return (f["OrExpression"]!.length === 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.filters.splice(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeSubfilterAtIndex(index: number, subindex: number) {
|
||||||
|
const filterEntry = this.filters[index];
|
||||||
|
|
||||||
|
if (filterEntry && "OrExpression" in filterEntry) {
|
||||||
|
filterEntry["OrExpression"]!.splice(subindex, 1);
|
||||||
|
|
||||||
|
if (filterEntry["OrExpression"]!.length === 0) {
|
||||||
|
this.removeFilterAtIndex(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,25 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||||
|
|
||||||
import { BusyDialogComponent } from './busy-dialog.component';
|
import {BusyDialogComponent} from "./busy-dialog.component";
|
||||||
|
|
||||||
describe('BusyDialogComponent', () => {
|
describe("BusyDialogComponent", () => {
|
||||||
let component: BusyDialogComponent;
|
let component: BusyDialogComponent;
|
||||||
let fixture: ComponentFixture<BusyDialogComponent>;
|
let fixture: ComponentFixture<BusyDialogComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ BusyDialogComponent ]
|
declarations: [BusyDialogComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BusyDialogComponent);
|
fixture = TestBed.createComponent(BusyDialogComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it("should create", () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
<span *ngIf="is('OrExpression')" class="or-expression">
|
||||||
|
<ng-container *ngFor="let query of this.orExpression().OrExpression">
|
||||||
|
<app-property-query-item *ngIf="this.queryIs(query, 'Property')"
|
||||||
|
[propertyQuery]="this.propertyQuery(query).Property"></app-property-query-item>
|
||||||
|
<app-tag-query-item *ngIf="this.queryIs(query, 'Tag')"
|
||||||
|
[tagQuery]="this.tagQuery(query).Tag"></app-tag-query-item>
|
||||||
|
</ng-container>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="is('Query')" class="query">
|
||||||
|
<app-property-query-item *ngIf="this.queryIs(this.query().Query, 'Property')"
|
||||||
|
[propertyQuery]="this.propertyQuery(this.query().Query).Property"></app-property-query-item>
|
||||||
|
<app-tag-query-item *ngIf="this.queryIs(this.query().Query, 'Tag')"
|
||||||
|
[tagQuery]="this.tagQuery(this.query().Query).Tag"></app-tag-query-item>
|
||||||
|
</span>
|
@ -0,0 +1,25 @@
|
|||||||
|
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||||
|
|
||||||
|
import {FilterExpressionItemComponent} from "./filter-expression-item.component";
|
||||||
|
|
||||||
|
describe("FilterItemComponent", () => {
|
||||||
|
let component: FilterExpressionItemComponent;
|
||||||
|
let fixture: ComponentFixture<FilterExpressionItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [FilterExpressionItemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(FilterExpressionItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,47 @@
|
|||||||
|
import {Component, Input} from "@angular/core";
|
||||||
|
import {
|
||||||
|
FilterExpression,
|
||||||
|
FilterExpressionOrExpression,
|
||||||
|
FilterExpressionQuery,
|
||||||
|
FilterQuery,
|
||||||
|
FilterQueryProperty,
|
||||||
|
FilterQueryTag
|
||||||
|
} from "../../../../../../api/api-types/files";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-filter-expression-item",
|
||||||
|
templateUrl: "./filter-expression-item.component.html",
|
||||||
|
styleUrls: ["./filter-expression-item.component.scss"]
|
||||||
|
})
|
||||||
|
export class FilterExpressionItemComponent {
|
||||||
|
|
||||||
|
|
||||||
|
@Input() filter!: FilterExpression;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public is(key: "OrExpression" | "Query"): boolean {
|
||||||
|
return key in this.filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public orExpression(): FilterExpressionOrExpression {
|
||||||
|
return this.filter as FilterExpressionOrExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public query(): FilterExpressionQuery {
|
||||||
|
return this.filter as FilterExpressionQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public queryIs(query: FilterQuery, key: "Property" | "Tag"): boolean {
|
||||||
|
return key in query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public propertyQuery(query: FilterQuery): FilterQueryProperty {
|
||||||
|
return query as FilterQueryProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public tagQuery(query: FilterQuery): FilterQueryTag {
|
||||||
|
return query as FilterQueryTag;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
<span>{{this.stringExpression}}</span>
|
@ -0,0 +1,25 @@
|
|||||||
|
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||||
|
|
||||||
|
import {PropertyQueryItemComponent} from "./property-query-item.component";
|
||||||
|
|
||||||
|
describe("PropertyQueryItemComponent", () => {
|
||||||
|
let component: PropertyQueryItemComponent;
|
||||||
|
let fixture: ComponentFixture<PropertyQueryItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [PropertyQueryItemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PropertyQueryItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,101 @@
|
|||||||
|
import {Component, Input, OnChanges, OnInit, SimpleChanges} from "@angular/core";
|
||||||
|
import {PropertyQuery, ValueComparator} from "../../../../../../../api/api-types/files";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-property-query-item",
|
||||||
|
templateUrl: "./property-query-item.component.html",
|
||||||
|
styleUrls: ["./property-query-item.component.scss"]
|
||||||
|
})
|
||||||
|
export class PropertyQueryItemComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
|
@Input() propertyQuery!: PropertyQuery;
|
||||||
|
|
||||||
|
public stringExpression: string = "No Expression";
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildExpression(property: string, comparator: string, value: string): string {
|
||||||
|
return `.${property} ${comparator} ${value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.stringExpression = this.getStringExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes["propertyQuery"]) {
|
||||||
|
this.stringExpression = this.getStringExpression();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStringExpression(): string {
|
||||||
|
if ("Status" in this.propertyQuery) {
|
||||||
|
return PropertyQueryItemComponent.buildExpression("Status", "is", this.propertyQuery.Status);
|
||||||
|
} else if ("FileSize" in this.propertyQuery) {
|
||||||
|
return PropertyQueryItemComponent.buildExpression(
|
||||||
|
"FileSize",
|
||||||
|
this.getComparator(this.propertyQuery.FileSize),
|
||||||
|
this.getValue(this.propertyQuery.FileSize).toString()
|
||||||
|
);
|
||||||
|
} else if ("ImportedTime" in this.propertyQuery) {
|
||||||
|
return PropertyQueryItemComponent.buildExpression(
|
||||||
|
"ImportedTime",
|
||||||
|
this.getComparator(this.propertyQuery.ImportedTime),
|
||||||
|
this.getValue(this.propertyQuery.ImportedTime).toISOString()
|
||||||
|
);
|
||||||
|
} else if ("ChangedTime" in this.propertyQuery) {
|
||||||
|
return PropertyQueryItemComponent.buildExpression(
|
||||||
|
"ChangedTime",
|
||||||
|
this.getComparator(this.propertyQuery.ChangedTime),
|
||||||
|
this.getValue(this.propertyQuery.ChangedTime).toISOString()
|
||||||
|
);
|
||||||
|
} else if ("CreatedTime" in this.propertyQuery) {
|
||||||
|
return PropertyQueryItemComponent.buildExpression(
|
||||||
|
"CreatedTime",
|
||||||
|
this.getComparator(this.propertyQuery.CreatedTime),
|
||||||
|
this.getValue(this.propertyQuery.CreatedTime).toISOString()
|
||||||
|
);
|
||||||
|
} else if ("TagCount" in this.propertyQuery) {
|
||||||
|
return PropertyQueryItemComponent.buildExpression(
|
||||||
|
"TagCount",
|
||||||
|
this.getComparator(this.propertyQuery.TagCount),
|
||||||
|
this.getValue(this.propertyQuery.TagCount).toString()
|
||||||
|
);
|
||||||
|
} else if ("Cd" in this.propertyQuery) {
|
||||||
|
return PropertyQueryItemComponent.buildExpression("ContentDescriptor", "is", this.propertyQuery.Cd);
|
||||||
|
} else if ("Id" in this.propertyQuery) {
|
||||||
|
return PropertyQueryItemComponent.buildExpression("FileId", "is", this.propertyQuery.Id.toString());
|
||||||
|
} else {
|
||||||
|
return "Invalid Expression";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getComparator(value: ValueComparator<any>): "=" | "<" | ">" | "between" {
|
||||||
|
if ("Greater" in value) {
|
||||||
|
return ">";
|
||||||
|
} else if ("Equal" in value) {
|
||||||
|
return "=";
|
||||||
|
} else if ("Less" in value) {
|
||||||
|
return "<";
|
||||||
|
} else {
|
||||||
|
return "between";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValue<T>(value: ValueComparator<T>): T {
|
||||||
|
const singleValueKeys: ("Greater" | "Equal" | "Less")[] = ["Greater", "Equal", "Less"];
|
||||||
|
|
||||||
|
for (const key of singleValueKeys) {
|
||||||
|
if (key in value) {
|
||||||
|
//@ts-ignore
|
||||||
|
return value[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("Between" in value) {
|
||||||
|
return value.Between[0];
|
||||||
|
} else {
|
||||||
|
return "" as unknown as T; // unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
<span><span *ngIf="tagQuery.negate" class="tag-negation">-</span>{{tagQuery.tag}}</span>
|
@ -0,0 +1,25 @@
|
|||||||
|
import {ComponentFixture, TestBed} from "@angular/core/testing";
|
||||||
|
|
||||||
|
import {TagQueryItemComponent} from "./tag-query-item.component";
|
||||||
|
|
||||||
|
describe("TagQueryItemComponent", () => {
|
||||||
|
let component: TagQueryItemComponent;
|
||||||
|
let fixture: ComponentFixture<TagQueryItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [TagQueryItemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TagQueryItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,15 @@
|
|||||||
|
import {Component, Input} from "@angular/core";
|
||||||
|
import {TagQuery} from "../../../../../../../api/api-types/files";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-tag-query-item",
|
||||||
|
templateUrl: "./tag-query-item.component.html",
|
||||||
|
styleUrls: ["./tag-query-item.component.scss"]
|
||||||
|
})
|
||||||
|
export class TagQueryItemComponent {
|
||||||
|
|
||||||
|
@Input() tagQuery!: TagQuery;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import {TestBed} from "@angular/core/testing";
|
||||||
|
|
||||||
import { JobService } from './job.service';
|
import {JobService} from "./job.service";
|
||||||
|
|
||||||
describe('JobService', () => {
|
describe("JobService", () => {
|
||||||
let service: JobService;
|
let service: JobService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({});
|
TestBed.configureTestingModule({});
|
||||||
service = TestBed.inject(JobService);
|
service = TestBed.inject(JobService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be created', () => {
|
it("should be created", () => {
|
||||||
expect(service).toBeTruthy();
|
expect(service).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue