diff --git a/src/components/AutoQueryGrid.vue b/src/components/AutoQueryGrid.vue index 1c7879e..36f36e4 100644 --- a/src/components/AutoQueryGrid.vue +++ b/src/components/AutoQueryGrid.vue @@ -486,6 +486,7 @@ async function refresh() { await update() } +const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) async function search(args:any) { const op = apis.value.AnyQuery if (!op) { @@ -493,14 +494,20 @@ async function search(args:any) { return } let requestDto = createDto(op, args) + + let r = await client.api(requestDto) let complete = delaySet(x => { api.value.response = api.value.error = undefined apiLoading.value = x + if (!isIOS) { + api.value = r + } else { + // Fix for iOS which doesn't pick up reactive update on initial onload + nextTick(() => api.value = r) + } }) - let r = await client.api(requestDto) complete() - // Fix for iOS which doesn't pick up reactive update on initial onload - nextTick(() => api.value = r) + let results = mapGet(r.response as any,'results') || [] if (!r.succeeded || results.label == 0) return // Forms.fetchLookupValues(results, this.columns, () => this.refreshResults()) diff --git a/src/types.ts b/src/types.ts index ca74a68..e393dd6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,12 +105,24 @@ export interface UiConfig { navigate?: (url:string) => void assetsPathResolver?: (src:string) => string fallbackPathResolver?: (src:string) => string + apisResolver?:(type:string|null, metaTypes?:MetadataTypes|null) => AutoQueryApis|null + apiResolver?:(name:string) => MetadataOperationType|null + typeResolver?:(name:string,namespace?:string|null) => MetadataType|null autoQueryGridDefaults?: AutoQueryGridDefaults storage?:Storage tableIcon?:ImageInfo scopeWhitelist?: {[k:string]:Function} } +export interface AutoQueryApis { + Query?: MetadataOperationType; + QueryInto?: MetadataOperationType; + Create?: MetadataOperationType; + Update?: MetadataOperationType; + Patch?: MetadataOperationType; + Delete?: MetadataOperationType; +} + export interface UploadedFile { fileName?: string filePath?: string @@ -378,8 +390,8 @@ export interface AuthInfo { export interface AutoQueryConvention { name: string; value: string; - types: string; - valueType: string; + types?: string; + valueType?: string; } export interface AutoQueryInfo { maxLimit?: number; diff --git a/src/use/metadata.ts b/src/use/metadata.ts index 1648259..a384ded 100644 --- a/src/use/metadata.ts +++ b/src/use/metadata.ts @@ -1,4 +1,4 @@ -import { type AppMetadata, type MetadataType, type MetadataPropertyType, type MetadataOperationType, type InputInfo, type KeyValuePair, type MetadataTypes, type AutoQueryConvention, type Filter, type RefInfo, type InputProp, type AppInfo, type MetadataTypeName, MetadataApp } from "@/types" +import { type AppMetadata, type MetadataType, type MetadataPropertyType, type MetadataOperationType, type InputInfo, type KeyValuePair, type MetadataTypes, type AutoQueryConvention, type Filter, type RefInfo, type InputProp, type AppInfo, type MetadataTypeName, type AutoQueryApis, MetadataApp } from "@/types" import { toDate, toCamelCase, chop, map, mapGet, toDateTime, leftPart, JsonServiceClient } from '@servicestack/client' import { computed, inject } from 'vue' import { Sole } from './config' @@ -81,7 +81,7 @@ function typeName(metaType?:MetadataTypeName) { /** Capture AutoQuery APIs */ -export class Apis +export class Apis implements AutoQueryApis { Query?: MetadataOperationType; QueryInto?: MetadataOperationType; @@ -128,6 +128,17 @@ export class Apis static forType(type?:string|null, metaTypes?:MetadataTypes|null) { let apis = new Apis() + if (Sole.config.apisResolver && type) { + const aqApis = Sole.config.apisResolver(type,metaTypes) + if (aqApis) { + apis.Query = aqApis.Query + apis.QueryInto = aqApis.QueryInto + apis.Create = aqApis.Create + apis.Update = aqApis.Update + apis.Patch = aqApis.Patch + apis.Delete = aqApis.Delete + } + } if (type) { metaTypes ??= Sole.metadata.value?.api metaTypes?.operations.forEach(op => { @@ -406,6 +417,10 @@ async function loadMetadata(args:{ * @param [namespace] - Find MetadataType by name and namespace */ export function typeOf(name?:string|null, namespace?:string|null) { + if (Sole.config.typeResolver) { + let type = Sole.config.typeResolver(name!,namespace) + if (type) return type + } let api = Sole.metadata.value?.api if (!api || !name) return null let type = api.types.find(x => x.name.toLowerCase() === name.toLowerCase() && (!namespace || x.namespace == namespace)) @@ -419,6 +434,10 @@ export function typeOf(name?:string|null, namespace?:string|null) { /** Resolve Request DTO {MetadataOperationType} by name */ export function apiOf(name:string) { + if (Sole.config.apiResolver) { + const op = Sole.config.apiResolver(name) + if (op) return op + } let api = Sole.metadata.value?.api if (!api) return null let requestOp = api.operations.find(x => x.request.name.toLowerCase() === name.toLowerCase()) @@ -669,12 +688,30 @@ export function formatFilterValue(type:string, value:string) { : `'${value}'` } +function nv(name:string,value:string) { return { name, value } } + +const defaultViewerConventions:AutoQueryConvention[] = [ + nv("=","%"), + nv("!=","%!"), + nv(">=",">%"), + nv(">","%>"), + nv("<=","%<"), + nv("<","<%"), + nv("In","%In"), + nv("Between","%Between"), + { name: "Starts With", value: "%StartsWith", types: "string" }, + { name: "Contains", value: "%Contains", types: "string" }, + { name: "Ends With", value: "%EndsWith", types: "string" }, + { name: "Exists", value: "%IsNotNull", valueType: "none" }, + { name: "Not Exists", value: "%IsNull", valueType: "none" }, +] + export function useMetadata() { /** Reactive accessor to Ref */ const metadataApp = computed(() => Sole.metadata.value?.app || null) const metadataApi = computed(() => Sole.metadata.value?.api || null) - const filterDefinitions = computed(() => Sole.metadata.value?.plugins.autoQuery.viewerConventions || []) + const filterDefinitions = computed(() => Sole.metadata.value?.plugins?.autoQuery?.viewerConventions || defaultViewerConventions) tryLoad()