@olenbetong/appframe-core 2.16.0 → 2.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/af.common.js +24 -12
- package/dist/esm/af.common.legacy.js +15 -3
- package/dist/esm/af.common.legacy.min.js +3 -3
- package/dist/esm/af.common.legacy.min.js.map +1 -1
- package/dist/esm/af.common.min.js +3 -3
- package/dist/esm/af.common.min.js.map +1 -1
- package/dist/iife/af.common.js +24 -12
- package/dist/iife/af.common.legacy.js +15 -3
- package/dist/iife/af.common.legacy.min.js +3 -3
- package/dist/iife/af.common.legacy.min.js.map +1 -1
- package/dist/iife/af.common.min.js +3 -3
- package/dist/iife/af.common.min.js.map +1 -1
- package/es/index.js +1 -1
- package/es/report/recordSource.d.ts +1 -1
- package/es/report/recordSource.js +16 -2
- package/es/report/report.test.js +36 -0
- package/package.json +2 -2
- package/src/report/recordSource.ts +17 -2
- package/src/report/report.test.ts +39 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"af.common.min.js","names":["mitt","toBoolean","exports","mitt_default","n","i","e","_typeof","o","toPrimitive","t","r","toPropertyKey","_defineProperty","SessionHandler","authPromise","eventHandler","callback","resolve","dialog","link","type","eventName","handler","UserSession","userData","article","logActivity","response","activity","parameters","getSessionData","data","startActivity","start","startParameters","endParameters","time","logError","error","result","getSessionId","libVersion","pattern","name","url","urls","match","getPlatformVersions","versions","importMap","platform","chrome","script","sessionStart","sessionId","deviceId","browserSessionId","startSession","event","getLocalKey","key","localStorage_default","global","lsKey","value","defaultValue","baseKey","toLocalISODate","date","toLocalISODateTime","pad","num","digits","toLocalDateTime","downloadBlob","blob","parent","anchor","fileName","ex","reject","invariant","condition","provided","message","downloadRequest","bytesReceived","onProgress","request","reader","stream","pump","read","controller","err","signal","funcRegex","arrowBlockRegex","arrowExpressionRegex","parseArguments","args","a","parseFunc","funcString","func","arrowBlockMatch","arrowExpressionMatch","Namespace","requireNamespace","root","namespace","current","expose","funcName","keyName","pathParts","options","fetchLocalizedString","project","text","sourceFor","af","dictionary","getLocalizedString","string","waitOrOptions","source","promise","harvestAs","wait","createLocalizer","bound","localize","localized","stringParts","s","index","arg","PARAMETERLESS_OPERATORS","PERIOD_OPERATORS","PAIR_OPERATORS","LIST_OPERATORS","getOperatorArity","operator","OPERATOR_LABELS","DATE_OPERATOR_LABELS","getOperatorLabel","fieldType","EQUALITY","LIST_CHECKS","RANGE","COMPARISONS","STRING_CHECKS","OPERATORS_BY_TYPE","DEFAULT_OPERATOR_BY_TYPE","getOperatorsForField","operators","field","getDefaultOperator","getValueType","RELATIVE_DATE_VALUES","DYNAMIC_VALUE_LABELS","getDynamicValueLabel","getDynamicValuesForField","COLUMN_OPERATORS","supportsColumnValue","getComparableType","getColumnsForField","fields","other","OPERATOR_ALIASES","normalizeOperator","normalized","createEmptyFilter","createEmptyGroup","isGroup","item","isExpression","toFilterTree","filter","unwrapSingleAndGroup","only","isEmptyFilter","countExpressions","total","pruneFilter","isCompleteExpression","items","arity","end","isRangeEnd","v","DYNAMIC_VALUES_WITHOUT_FRAMEWORK_SUPPORT","needsConversion","valueType","needsFilterString","createExpression","op","createEmptyValue","changeOperator","previousArity","expression","holdsColumn","getNodeAtPath","node","path","updateNodeAtPath","updater","rest","child","updated","insertNodeAtPath","target","removeNodeAtPath","setGroupMode","addGroupAtPath","convertFilter","describeFilter","describe","describeExpression","parts","depth","part","separator","joined","caption","label","formatValue","formatted","formatScalar","FIELD_TYPES","toFilterFieldType","dataType","toBoolean$1","toFieldMetadata","record","toFieldMetadataList","records","b","isStandardLayout","coerceToStandardLayout","groups","expressions","collectExpressions","into","toSavedFilter","toAccessLevel","toBoolean","toDate","toSavedFilters","partitionSavedFilters","mine","shared","filters","findSavedFilter","filterId","canEditSavedFilter","canShareSavedFilter","canRenameSavedFilter","canDeleteSavedFilter","canSetCrossDomain","sizeMap","getUserImage","primKeyOrOptions","hash","sizePx","size","login","XML_ENTITIES","decodeXml","entity","encodeXml","readElement","xml","readElements","COLUMN_TYPES","readSelectColumns","block","columns","parseRecordSource","viewName","maxRecords","PARAMETER_TYPES","PROCEDURE_DEFAULT","isProcedureDefault","parseParameterItems","toParameterType","toParameterEditor","hasLookup","hasItems","toReportParameter","recordSource","valueMember","displayMember","column","toReportParameters","coerceParameterValue","parameter","parsed","getDefaultParameterValues","values","toXmlValue","serializeParameters","parseParameters","restoreParameterValues","stored","fetchParameterDefault","reportId","firstValue","resolveParameterDefaults","dynamic","literalDefaults","resolved","row","REPORT_EXTENSIONS","REPORT_MEDIA_TYPES","getReportExtension","format","getReportMediaType","UNSAFE_FILE_NAME_CHARACTERS","getReportFileName","safe","toRequestUrl","base","getReportRequest","query","entries","toParameterEntries","needsBody","headerText","hideFilterString","domain","timezone","body","renderReport","downloadReport","openReport","opened","printReport","document_","frame","cleanUp","isSet","getReportFormats","formats","toReport","addDialogTemplate","$template","waitForDialog","cancelValue","alertDialogPromiseMap","openAlertDialog","id","$dialog","$content","$menu","className","className_","title","$header","content","buttons","listItem","buttonElem","primary","dialogPromise","returnValue","ConfirmButtons","\"YesNo\"","\"OkCancel\"","confirm","optionButtons","prompt","alert","getKey","getSettings","settings","setting","saveSettings","userSettings"],"sources":["../../../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs","../../src/SessionHandler.ts","../../src/UserSession.ts","../../src/log.ts","../../src/errorHandler.ts","../../src/localStorage.ts","../../src/date.ts","../../src/downloadBlob.ts","../../src/invariant.ts","../../src/downloadRequest.ts","../../src/parseFunc.ts","../../src/Namespace.ts","../../src/requireNamespace.ts","../../src/expose.ts","../../src/getLocalizedString.ts","../../src/filter/operators.ts","../../src/filter/tree.ts","../../src/filter/convertFilter.ts","../../src/filter/describeFilter.ts","../../src/filter/fields.ts","../../src/filter/standardLayout.ts","../../src/filter/savedFilters.ts","../../src/getUserImage.ts","../../src/localize.ts","../../src/report/recordSource.ts","../../src/report/parameters.ts","../../src/report/parameterDefaults.ts","../../src/report/render.ts","../../src/report/report.ts","../../src/alert.ts","../../src/settings.ts","../../src/index.ts"],"sourcesContent":["export default function(n){return{all:n=n||new Map,on:function(t,e){var i=n.get(t);i?i.push(e):n.set(t,[e])},off:function(t,e){var i=n.get(t);i&&(e?i.splice(i.indexOf(e)>>>0,1):n.set(t,[]))},emit:function(t,e){var i=n.get(t);i&&i.slice().map(function(n){n(e)}),(i=n.get(\"*\"))&&i.slice().map(function(n){n(t,e)})}}}\n//# sourceMappingURL=mitt.mjs.map\n","import mitt, { type Emitter, type Handler } from \"mitt\";\n\ntype SessionHandlerEvent = \"onExpired\" | \"onAuthenticated\";\n\nexport default class SessionHandler {\n\tprivate eventHandler: Emitter<{ onAuthenticated: undefined; onExpired: undefined }> = mitt();\n\n\tauthenticated() {\n\t\tthis.eventHandler.emit(\"onAuthenticated\");\n\t}\n\n\tasync expired(callback?: () => void, type?: \"RequireTwoFactor\"): Promise<void> {\n\t\ttry {\n\t\t\tlet authPromise = new Promise<void>((resolve) => {\n\t\t\t\tlet eventHandler = () => {\n\t\t\t\t\tthis.eventHandler.off(\"onAuthenticated\", eventHandler);\n\t\t\t\t\tcallback?.();\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\n\t\t\t\tthis.eventHandler.on(\"onAuthenticated\", eventHandler);\n\t\t\t});\n\n\t\t\tlet dialog: HTMLDialogElement | null = document.querySelector(\"dialog.session-expired-dialog\");\n\t\t\tif (!dialog) {\n\t\t\t\tdialog = document.createElement(\"dialog\");\n\t\t\t\tdialog.className = \"session-expired-dialog\";\n\t\t\t\tdialog.style.display = \"flex\";\n\t\t\t\tdialog.style.padding = \"0\";\n\t\t\t\tdialog.style.left = \"50%\";\n\t\t\t\tdialog.style.transform = \"translateX(-50%)\";\n\n\t\t\t\tlet link = \"/login?ReturnUrl=%2fapi/sessionexpired/remove\";\n\t\t\t\tif (type !== \"RequireTwoFactor\") {\n\t\t\t\t\tawait fetch(\"/logout\", { method: \"POST\" });\n\t\t\t\t} else {\n\t\t\t\t\tlink = \"/login?RequireTwoFactor=1&ReturnUrl=%2fapi/sessionexpired/remove\";\n\t\t\t\t}\n\n\t\t\t\tdialog.innerHTML = `<iframe sandbox=\"allow-same-origin allow-scripts allow-modals allow-top-navigation-by-user-activation allow-forms\" src=\"${link}\" style=\"border:0;width:min(500px,100vw);height:min(700px, 100vh);\"></iframe>`;\n\t\t\t\tdocument.body.append(dialog);\n\t\t\t\tdialog.showModal();\n\t\t\t\tawait authPromise;\n\t\t\t\tdialog.remove();\n\t\t\t} else {\n\t\t\t\tawait authPromise;\n\t\t\t}\n\t\t} catch (_) {\n\t\t\twindow.location.href = `/login?ReturnUrl=${encodeURIComponent(window.location.pathname)}${encodeURIComponent(\n\t\t\t\twindow.location.search,\n\t\t\t)}`;\n\t\t}\n\t}\n\n\tattachEvent(eventName: SessionHandlerEvent, handler: Handler) {\n\t\treturn this.eventHandler.on(eventName, handler);\n\t}\n\n\tdetachEvent(eventName: SessionHandlerEvent, handler: Handler) {\n\t\treturn this.eventHandler.off(eventName, handler);\n\t}\n}\n","import SessionHandler from \"./SessionHandler.js\";\n\nexport class UserSession extends SessionHandler {\n\tamDesignator = \"a.m.\";\n\tdayNames: string[] = [\"sunday\", \"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\", \"saturday\"];\n\tdayNamesShort: string[] = [\"sun\", \"mon\", \"tue\", \"wed\", \"thu\", \"fri\", \"sat\"];\n\tdecimalSeparator = \".\";\n\tdomain = \"OB\";\n\temail: string | null | undefined;\n\tformats: undefined | Record<string, string>;\n\tgroupSeparator: string | null = null;\n\tintegratedSecurity = false;\n\tisDeveloper = false;\n\tlogin = \"\";\n\tloginType: string | undefined;\n\tmonthNames: string[] = [\n\t\t\"january\",\n\t\t\"february\",\n\t\t\"march\",\n\t\t\"april\",\n\t\t\"may\",\n\t\t\"june\",\n\t\t\"july\",\n\t\t\"august\",\n\t\t\"september\",\n\t\t\"october\",\n\t\t\"november\",\n\t\t\"december\",\n\t\t\"\",\n\t];\n\tmonthNamesShort: string[] = [\"jan\", \"feb\", \"mar\", \"apr\", \"may\", \"jun\", \"jul\", \"augt\", \"sep\", \"oct\", \"nov\", \"dec\", \"\"];\n\tname: string | undefined;\n\tpmDesignator = \"p.m.\";\n\tshortDateFormat = \"dd.MM.yyyy\";\n\tculture = \"en-GB\";\n\tuiculture = \"en-GB\";\n\n\tconstructor(userData: any) {\n\t\tsuper();\n\t\tthis.formats = {};\n\t\tObject.assign(this, userData);\n\t}\n}\n","const article = globalThis.af?.article?.id ?? globalThis.window?.location.pathname.substring(1).split(\"/\")[0];\n\n/**\n * Logs an activity to the website activity log\n *\n * @param activity name of the activity we are logging\n * @param parameters A dictionary of parameters used when doing the activity\n * @param timeElapsed Number of milliseconds elapsed while executing\n */\nexport async function logActivity(activity: string, parameters: Record<string, any> = {}, timeElapsed = 0) {\n\ttry {\n\t\tlet response = await fetch(\"/api/logging/log-activity\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify({\n\t\t\t\tarticle,\n\t\t\t\ttimeElapsed,\n\t\t\t\taction: activity,\n\t\t\t\tparameters: {\n\t\t\t\t\t...parameters,\n\t\t\t\t\t_session: getSessionData(),\n\t\t\t\t},\n\t\t\t}),\n\t\t\theaders: { \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tif (response.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\tlet data = await response.json();\n\t\t\t\tconsole.error(data.error);\n\t\t\t} else {\n\t\t\t\tconsole.error(`${response.status} ${response.statusText}`);\n\t\t\t}\n\t\t}\n\t} catch {}\n}\n\n/**\n * Records the current time, and returns a function that logs the activity with the\n * time elapsed when it is called.\n *\n * @param activity Name of the activity we are logging\n * @param parameters A dictionary of parameters used when doing the activity\n * @returns A function that should be called when the activity is completed to log it to the website log\n */\nexport function startActivity(activity: string, startParameters: Record<string, any> = {}) {\n\tlet start = performance.now();\n\treturn (endParameters: Record<string, any> = {}) =>\n\t\tlogActivity(activity, { ...startParameters, ...endParameters }, performance.now() - start);\n}\n\n/**\n * Records the time used to execute an operation and returns the value of the operation and the time\n * in milliseconds\n */\nexport async function time<T>(callback: () => T | Promise<T>): Promise<[T, number]> {\n\tlet start = performance.now();\n\tlet returnValue = await callback();\n\n\treturn [returnValue, performance.now() - start];\n}\n\nexport async function logError(error: any) {\n\ttry {\n\t\tif (!error) return;\n\n\t\tlet data =\n\t\t\ttypeof error === \"string\"\n\t\t\t\t? { error: error }\n\t\t\t\t: typeof error === \"object\"\n\t\t\t\t\t? \"message\" in error\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\terror: error.message,\n\t\t\t\t\t\t\t\tstack: error.stack ?? null,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: { error: error.toString() }\n\t\t\t\t\t: { error: \"Unspecified error\" };\n\n\t\tconsole.error(error);\n\n\t\tlet result = await fetch(\"/data/Logger/LogError\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(data),\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json; charset=utf-8\",\n\t\t\t},\n\t\t});\n\n\t\tif (result.ok) {\n\t\t\treturn await result.json();\n\t\t}\n\n\t\tconsole.error(Error(\"Failed to log error\"));\n\t} catch {}\n}\n\nfunction getSessionId() {\n\treturn crypto.randomUUID?.();\n}\n\n/** The version in the first URL served under `/lib/@olenbetong/<name>/<version>/`. */\nfunction libVersion(name: string, urls: Iterable<unknown>) {\n\tlet pattern = new RegExp(`/lib/@olenbetong/${name}/([^/]+)/`);\n\tfor (let url of urls) {\n\t\tlet match = typeof url === \"string\" ? pattern.exec(url) : null;\n\t\tif (match) return match[1];\n\t}\n\treturn undefined;\n}\n\n/**\n * The synergi-chrome and synergi-platform versions the page loads on the 2026 site templates,\n * read from the chrome's script tag and the platform import map rather than from the chrome\n * itself, so they are known even when the chrome failed to start. Empty on other pages.\n */\nfunction getPlatformVersions() {\n\tlet versions: Record<string, string> = {};\n\ttry {\n\t\tlet importMap = document.querySelector('script[type=\"importmap\"]');\n\t\tlet platform = importMap\n\t\t\t? libVersion(\"synergi-platform\", Object.values(JSON.parse(importMap.textContent ?? \"{}\").imports ?? {}))\n\t\t\t: undefined;\n\t\tif (platform) versions[\"synergi-platform\"] = platform;\n\t} catch {}\n\tlet chrome =\n\t\tlibVersion(\n\t\t\t\"synergi-chrome\",\n\t\t\tArray.from(document.scripts, (script) => script.src),\n\t\t) ?? (globalThis as any).ob?.chrome?.version;\n\tif (chrome) versions[\"synergi-chrome\"] = chrome;\n\treturn versions;\n}\n\nlet sessionStart = Date.now();\nlet sessionId = getSessionId();\nlet deviceId: string | null = \"\";\nlet browserSessionId: string | null = \"\";\n\nif (\"document\" in globalThis) {\n\tlet startSession = () =>\n\t\tlogActivity(\"AfSessionInit\", {\n\t\t\t\"appframe-core\": globalThis.af?.common?.version,\n\t\t\t\"appframe-data\": (globalThis.af as any)?.data?.version,\n\t\t\t\"app-version\": globalThis.af?.article?.version,\n\t\t\t...getPlatformVersions(),\n\t\t});\n\n\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\tdeviceId = localStorage.getItem(\"_afDeviceId\");\n\t\tbrowserSessionId = sessionStorage.getItem(\"_afSessionId\");\n\t\tif (!deviceId) {\n\t\t\tdeviceId = getSessionId();\n\t\t\tlocalStorage.setItem(\"_afDeviceId\", deviceId);\n\t\t}\n\n\t\tif (!browserSessionId) {\n\t\t\tbrowserSessionId = sessionId;\n\t\t\tsessionStorage.setItem(\"_afSessionId\", browserSessionId);\n\t\t}\n\n\t\tstartSession();\n\t});\n\n\tdocument.addEventListener(\"visibilitychange\", function logData() {\n\t\tif (document.visibilityState === \"hidden\") {\n\t\t\tnavigator.sendBeacon(\n\t\t\t\t\"/api/logging/log-activity\",\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tarticle,\n\t\t\t\t\taction: \"AfSessionEnd\",\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\t_session: getSessionData(),\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tstartSession();\n\t\t}\n\t});\n}\n\n/**\n * Session and device ID's and information about session start and duration.\n * ID's will be null/undefined if crypto.randomUUID() is not supported.\n */\nexport type SessionData = {\n\t/**\n\t * Random UUID generated on page load, and persisted in sessionStorage\n\t */\n\tbrowserSessionId: string | null;\n\t/**\n\t * Random UUID generated on page load, and persisted in localStorage\n\t */\n\tdeviceId: string | null;\n\t/**\n\t * Time of page load in MS since epoch\n\t */\n\tsessionStart: number;\n\t/**\n\t * Time since page load (in MS) when this method was called\n\t */\n\tsessionLength: number;\n\t/**\n\t * Random UUID generated on page load. Not persisted.\n\t */\n\tsessionId: string | null;\n};\n\nexport function getSessionData(): SessionData {\n\treturn {\n\t\tbrowserSessionId,\n\t\tsessionStart,\n\t\tsessionLength: Date.now() - sessionStart,\n\t\tsessionId,\n\t\tdeviceId,\n\t};\n}\n","import { logError } from \"./log.js\";\n\nif (process.env.NODE_ENV === \"production\") {\n\tglobalThis.window?.addEventListener(\"error\", (event) => {\n\t\tif (typeof event === \"object\" && \"error\" in event) {\n\t\t\tlogError(event.error);\n\t\t} else if (typeof event === \"object\" && \"message\" in event) {\n\t\t\tlogError(event);\n\t\t}\n\t});\n}\n","function getLocalKey(key: string): string {\n\treturn [af?.article?.id || \"\", \"common\", key].join(\".\");\n}\n\nclass LocalStorage {\n\tisPersisted = () => true;\n\n\tgetLocalStorageKey(key: string, global?: boolean) {\n\t\treturn global ? [\"common\", key].join(\".\") : getLocalKey(key);\n\t}\n\n\tget(key: string, defaultValue: unknown, global?: boolean) {\n\t\tconst lsKey = this.getLocalStorageKey(key, global);\n\t\tconst value = localStorage.getItem(lsKey);\n\n\t\treturn value !== null ? JSON.parse(value) : defaultValue;\n\t}\n\n\tset(key: string, value: unknown, global?: boolean) {\n\t\tlocalStorage.setItem(this.getLocalStorageKey(key, global), JSON.stringify(value));\n\t}\n\n\tremove(key: string, global?: boolean) {\n\t\tconst lsKey = this.getLocalStorageKey(key, global);\n\t\tif (localStorage.getItem(lsKey) !== null) {\n\t\t\tlocalStorage.removeItem(lsKey);\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tclear() {\n\t\tconst baseKey = getLocalKey(\"\");\n\n\t\tfor (let key in localStorage) {\n\t\t\tif (key.slice(0, baseKey.length) === baseKey) {\n\t\t\t\tlocalStorage.removeItem(key);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport default new LocalStorage();\n","export function toLocalISODate(date: Date): string;\nexport function toLocalISODate(date: Date | null): string | null;\nexport function toLocalISODate(date: Date | null): string | null {\n\treturn date ? toLocalISODateTime(date).substring(0, 10) : null;\n}\n\nfunction pad(num: number, digits = 2) {\n\treturn String(num).padStart(digits, \"0\");\n}\n\n/**\n * Returns an ISO string (yyyy-MM-ddTHH:mm:ss.SSS) representing the local time\n * of the date object.\n *\n * @param date\n */\nexport function toLocalISODateTime(date: Date): string;\nexport function toLocalISODateTime(date: Date | null): string | null;\nexport function toLocalISODateTime(date: Date | null): string | null {\n\treturn date\n\t\t? `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(\n\t\t\t\tdate.getMinutes(),\n\t\t\t)}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`\n\t\t: null;\n}\n\n/**\n * Converts a date object to a string compatible with input type datetime-local.\n * Will handle the UTC value of the date as if it was local, to be compatible with\n * data objects.\n *\n * If null or undefined is passed, returns an empty string.\n *\n * @param date\n */\nexport function toLocalDateTime(date?: Date | null): string {\n\treturn date ? date.toISOString().substring(0, 16) : \"\";\n}\n","export function downloadBlob(blob: Blob | MediaSource, fileName: string) {\n\treturn new Promise<void>((resolve, reject) => {\n\t\ttry {\n\t\t\tconst url = URL.createObjectURL(blob);\n\t\t\tconst parent = document.body;\n\t\t\tconst anchor = document.createElement(\"a\");\n\t\t\tanchor.setAttribute(\"download\", fileName);\n\t\t\tanchor.href = url;\n\t\t\tparent.append(anchor);\n\t\t\tanchor.click();\n\t\t\tparent.removeChild(anchor);\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tURL.revokeObjectURL(url);\n\t\t\t\tresolve();\n\t\t\t}, 250);\n\t\t} catch (ex) {\n\t\t\treject(ex);\n\t\t}\n\t});\n}\n","/**\n * Throws an error if the condition is falsy. The provided message is used as the error message.\n * A function can be provided to lazily generate the message.\n *\n * @param {any} condition\n * @param {string | (() => string)} [message]\n * @returns {void}\n */\nexport function invariant(condition: any, message?: string | (() => string)): asserts condition {\n\tif (condition) {\n\t\treturn;\n\t}\n\tconst provided: string | undefined = typeof message === \"function\" ? message() : message;\n\n\tthrow new Error(provided ?? \"Invariant failed\");\n}\n","import { downloadBlob } from \"./downloadBlob.js\";\nimport { invariant } from \"./invariant.js\";\n\nexport type DownloadItem = {\n\tstatus: \"cancelled\" | \"error\" | \"requested\" | \"downloading\" | \"downloaded\" | \"completed\";\n\tbytesReceived?: number;\n\terror?: string;\n};\n\nexport type DownloadParameters = {\n\tsignal?: AbortSignal;\n\tfileName: string;\n\tonProgress?: (progress: DownloadItem) => void;\n};\n\nexport async function downloadRequest(request: Request, { fileName, signal, onProgress }: DownloadParameters) {\n\tlet bytesReceived = 0;\n\n\tonProgress?.({ status: \"requested\" });\n\n\ttry {\n\t\tlet response = await fetch(request);\n\n\t\tif (!response.ok || !response.body) {\n\t\t\tif (response.headers.get(\"content-type\")?.includes(\"application/json\")) {\n\t\t\t\tlet data = await response.json();\n\t\t\t\tinvariant(!data.error, data.error);\n\t\t\t}\n\t\t\tthrow new Error(`${response.status} ${response.statusText}`);\n\t\t}\n\n\t\ttry {\n\t\t\tconst reader = response.body.getReader();\n\t\t\tconst stream = new ReadableStream({\n\t\t\t\tstart: (controller) => {\n\t\t\t\t\tfunction pump(): Promise<void> {\n\t\t\t\t\t\treturn reader.read().then((read) => {\n\t\t\t\t\t\t\tif (read.done) {\n\t\t\t\t\t\t\t\tcontroller.close();\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbytesReceived += read.value.length;\n\t\t\t\t\t\t\tonProgress?.({ bytesReceived, status: \"downloading\" });\n\t\t\t\t\t\t\tcontroller.enqueue(read.value);\n\n\t\t\t\t\t\t\treturn pump();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\treturn pump();\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tresponse = new Response(stream);\n\t\t} catch {}\n\n\t\tconst blob = await response.blob();\n\n\t\tonProgress?.({ bytesReceived, status: \"downloaded\" });\n\n\t\tawait downloadBlob(blob, fileName);\n\n\t\tonProgress?.({ bytesReceived, status: \"completed\" });\n\t} catch (err) {\n\t\tif (signal?.aborted) {\n\t\t\tonProgress?.({ status: \"cancelled\" });\n\t\t} else {\n\t\t\tonProgress?.({ error: (err as Error).message, status: \"error\" });\n\t\t}\n\t}\n}\n","import { invariant } from \"./invariant.js\";\n\nconst funcRegex =\n\t/^function(?:\\s+([a-zA-Z$_][a-zA-Z0-9$_]*))?\\s*\\(((?:(?:\\s*[a-zA-Z$_][a-zA-Z0-9$_]*)(?:\\s*,\\s*[a-zA-Z$_][a-zA-Z0-9$_]*)*)?)\\s*\\)\\s*\\{((?:.|\\n|\\r)*)\\}$/;\nconst arrowBlockRegex = /^(?:\\(([^)]*)\\)|([a-zA-Z$_][a-zA-Z0-9$_]*))\\s*=>\\s*\\{([\\s\\S]*)\\}$/;\nconst arrowExpressionRegex = /^(?:\\(([^)]*)\\)|([a-zA-Z$_][a-zA-Z0-9$_]*))\\s*=>\\s*([\\s\\S]+)$/;\n\nconst parseArguments = (args: string) =>\n\targs\n\t\t.split(\",\")\n\t\t.map((a) => a.trim())\n\t\t.filter(Boolean);\n\nexport function parseFunc(func: (...args: any[]) => any) {\n\tconst funcString = func.toString();\n\tconst arrowBlockMatch = funcString.match(arrowBlockRegex);\n\n\tif (arrowBlockMatch) {\n\t\tconst args = parseArguments(arrowBlockMatch[1] ?? arrowBlockMatch[2] ?? \"\");\n\n\t\treturn { name: func.name, args, body: arrowBlockMatch[3] };\n\t}\n\n\tconst arrowExpressionMatch = funcString.match(arrowExpressionRegex);\n\n\tif (arrowExpressionMatch) {\n\t\tconst args = parseArguments(arrowExpressionMatch[1] ?? arrowExpressionMatch[2] ?? \"\");\n\n\t\treturn { name: func.name, args, body: arrowExpressionMatch[3] };\n\t}\n\n\tconst match = funcString.match(funcRegex);\n\n\tinvariant(match, \"Unable to parse function!\");\n\n\treturn { name: func.name ?? match[1], args: parseArguments(match[2]), body: match[3] };\n}\n","function Namespace() {}\nNamespace.prototype = Object.create(null);\n\nexport default Namespace;\n","import Namespace from \"./Namespace.js\";\n\nexport function requireNamespace(root: any, namespace: string | Array<string>): any {\n\tif (typeof root === \"object\") {\n\t\tif (typeof namespace === \"string\") {\n\t\t\tlet path = namespace.split(\".\");\n\n\t\t\treturn requireNamespace(root, path);\n\t\t} else if (Array.isArray(namespace)) {\n\t\t\tlet current: any = root;\n\t\t\tfor (let i = 0; i < namespace.length; i++) {\n\t\t\t\tcurrent = current[namespace[i]] = current[namespace[i]] || new (Namespace as any)();\n\t\t\t}\n\n\t\t\treturn current;\n\t\t}\n\t}\n}\n","import { invariant } from \"./invariant.js\";\nimport { parseFunc } from \"./parseFunc.js\";\nimport { requireNamespace } from \"./requireNamespace.js\";\n\nexport type ExposeOptions = {\n\t/**\n\t * Whether we should overwrite any existing values without warning.\n\t */\n\toverwrite?: boolean;\n};\n\n/**\n * Expose a value to a potentially deeply nested global variable\n * @param name {string | object} Either a dot-separated path to the global variable, or an object to expose the value on\n * @param value {object | Function | Function[]} The value(s) to expose\n */\nexport function expose(\n\tname: object | string,\n\tvalue: object | ((...args: any[]) => any) | ((...args: any[]) => any)[] | string | number,\n\toptions: ExposeOptions = {},\n): void {\n\tif (Array.isArray(value)) {\n\t\tfor (let func of value) {\n\t\t\tconst { name: funcName } = parseFunc(func);\n\n\t\t\tinvariant(\n\t\t\t\tfuncName,\n\t\t\t\t\"Unable to detect name of function! Name it, or only supply one function and include name in first parameter.\",\n\t\t\t);\n\t\t\texpose(name, func); // Expose func on namespace found in pName\n\t\t}\n\t} else {\n\t\tlet keyName: string | undefined;\n\t\tlet namespace: any;\n\n\t\tif (typeof value === \"function\") {\n\t\t\tkeyName = parseFunc(value as (...args: any[]) => any).name;\n\t\t}\n\n\t\tif (typeof name === \"string\") {\n\t\t\tlet pathParts = name.split(\".\");\n\n\t\t\tif (!keyName) {\n\t\t\t\tkeyName = pathParts[pathParts.length - 1];\n\t\t\t\tpathParts = pathParts.slice(0, pathParts.length - 1);\n\t\t\t}\n\n\t\t\tnamespace = requireNamespace(window, pathParts);\n\t\t} else if (keyName && (typeof name === \"object\" || typeof name === \"function\")) {\n\t\t\tnamespace = name;\n\t\t} else {\n\t\t\tinvariant(false, \"Invalid typeof parameter 'name', or unable to parse pValue!\");\n\t\t}\n\n\t\tif (Object.hasOwn(namespace, keyName) && options?.overwrite !== true) {\n\t\t\t// If class is already defined, warn or show an alert\n\t\t\tconsole.warn(`${[name, keyName].join(\".\")} is already defined!`);\n\t\t} else {\n\t\t\tnamespace[keyName] = value;\n\t\t}\n\t}\n}\n","/**\n * Localized strings come from two places on a page:\n *\n * - `af.article.i18n`: the strings the article's project uses, rendered by the framework\n * into every page of that article. A string missing there is harvested by posting it to\n * `/api/user/localize/new/<article>`, which registers it as used by the article and\n * returns the translation.\n * - `af.i18n[<project>]`: the strings of another project, for code that runs on every\n * page without belonging to any one article - the site chrome bundle, the shared\n * component packages. Such a project is a name, not an article; the harvest endpoint\n * accepts any name, so `localize/new/<project>` registers the string under that project.\n *\n * Keeping a project's strings apart from the article's matters when a word means\n * different things: \"Articles\" is \"Artikler\" in the chrome and \"Varer\" in an accounting\n * app, which the framework's project-specific base strings express only if the two are\n * looked up in different dictionaries.\n *\n * A project's dictionary is in play only when something put it there: the template\n * rendered it (`@Render(\"ChromeStrings\", Project: \"...\")`), or the caller fetched it and\n * assigned `af.i18n[project]` itself (an empty object counts). On a page that did neither -\n * a legacy template - the project falls back to the article's dictionary and the article's\n * harvesting, so the page behaves as it always did instead of posting every string of a\n * grid on every load. The project fills up from the pages that render it.\n */\n\nexport type LocalizeOptions = {\n\t/**\n\t * The globalization project the string belongs to. Defaults to the article, whose\n\t * strings the framework renders into `af.article.i18n`. Falls back to the article when\n\t * the page carries no dictionary for the project (see the module comment).\n\t */\n\tproject?: string;\n\t/** Resolve with the translation once it has been fetched, instead of the base text at once. */\n\twait?: boolean;\n};\n\nasync function fetchLocalizedString(text: string, project: string) {\n\ttry {\n\t\tlet response = await fetch(`/api/user/localize/new/${encodeURIComponent(project)}`, {\n\t\t\tbody: JSON.stringify({ text }),\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json; */*\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"X-Requested-With\": \"XMLHttpRequest\",\n\t\t\t},\n\t\t\tmethod: \"POST\",\n\t\t});\n\n\t\tif (response.ok) {\n\t\t\treturn (await response.json()) as string;\n\t\t}\n\t} catch {}\n\n\treturn text;\n}\n\ntype Source = {\n\t/** Where the string is looked up and the fetched translation is kept for the page. */\n\tdictionary: Record<string, string>;\n\t/** The name a missing string is harvested under: the project, or the article's id. */\n\tharvestAs: string;\n};\n\n/**\n * The dictionary a string is resolved in: the project's when the page carries one,\n * otherwise the article's; nothing when the page has no `af` at all.\n */\nfunction sourceFor(project: string | undefined): Source | undefined {\n\tlet af = globalThis.af;\n\tif (!af) return undefined;\n\n\tif (project) {\n\t\tlet dictionary = af.i18n?.[project];\n\t\tif (dictionary) return { dictionary, harvestAs: project };\n\t}\n\n\tlet article = af.article;\n\tif (!article?.i18n) return undefined;\n\treturn { dictionary: article.i18n, harvestAs: article.id };\n}\n\nexport function getLocalizedStringAsync(string: undefined | null): undefined | null;\nexport function getLocalizedStringAsync(string: string, options?: Omit<LocalizeOptions, \"wait\">): Promise<string>;\nexport function getLocalizedStringAsync(string: string | undefined | null, options?: Omit<LocalizeOptions, \"wait\">) {\n\tif (!string) return string;\n\n\treturn getLocalizedString(string, { ...options, wait: true });\n}\n\nexport function getLocalizedString(string: undefined | null): undefined | null;\nexport function getLocalizedString(string: string): string;\nexport function getLocalizedString(string: string, wait: false): string;\nexport function getLocalizedString(string: string, wait: true): Promise<string>;\nexport function getLocalizedString(string: undefined | null, wait: true): undefined | null;\nexport function getLocalizedString(string: string, options: LocalizeOptions & { wait: true }): Promise<string>;\nexport function getLocalizedString(string: string, options: LocalizeOptions): string;\nexport function getLocalizedString(string: undefined | null, options: LocalizeOptions): undefined | null;\nexport function getLocalizedString(\n\tstring: string | undefined | null,\n\twaitOrOptions?: boolean | LocalizeOptions,\n): string | undefined | null | Promise<string> {\n\tif (typeof string === \"string\" && string.trim().length === 0) {\n\t\treturn string;\n\t}\n\n\tif (string === undefined || string === \"\" || string === null) {\n\t\treturn;\n\t}\n\n\tlet { project, wait } = typeof waitOrOptions === \"object\" ? waitOrOptions : { wait: waitOrOptions };\n\tlet source = sourceFor(project);\n\tif (source === undefined) {\n\t\treturn string;\n\t}\n\n\tlet { dictionary, harvestAs } = source;\n\tif (!Object.hasOwn(dictionary, string)) {\n\t\tdictionary[string] = string;\n\n\t\tlet promise = fetchLocalizedString(string, harvestAs).then((text) => {\n\t\t\tdictionary[string] = text;\n\t\t\treturn text;\n\t\t});\n\n\t\tif (wait) {\n\t\t\treturn promise;\n\t\t}\n\t}\n\n\treturn dictionary[string];\n}\n\n/**\n * `getLocalizedString` and `localize` bound to a globalization project, for code that is\n * not part of the article it runs on: the site chrome, the shared component packages.\n *\n * On a page whose template renders the project (`af.i18n[project]` exists) the strings are\n * looked up and harvested there; on any other page they resolve through the article's\n * dictionary as a plain `getLocalizedString` would.\n *\n * @example\n * ```ts\n * const { getLocalizedString: t } = createLocalizer(\"synergi-chrome\");\n * t(\"Applications\");\n * ```\n */\nexport function createLocalizer(project: string) {\n\tfunction bound(string: undefined | null): undefined | null;\n\tfunction bound(string: string): string;\n\tfunction bound(string: string, wait: true): Promise<string>;\n\tfunction bound(string: string | undefined | null, wait?: boolean): string | undefined | null | Promise<string> {\n\t\treturn getLocalizedString(string as string, { project, wait } as LocalizeOptions & { wait: true });\n\t}\n\n\tfunction localize(stringParts: TemplateStringsArray, ...args: unknown[]): string {\n\t\tlet stringToLocalize = stringParts.map((s, i) => (i === stringParts.length - 1 ? s : `${s}$${i}`)).join(\"\");\n\t\tlet localized = bound(stringToLocalize);\n\n\t\targs.forEach((arg, index) => {\n\t\t\tlocalized = localized.replace(`$${index}`, String(arg));\n\t\t});\n\n\t\treturn localized;\n\t}\n\n\treturn { project, getLocalizedString: bound, localize };\n}\n","import { getLocalizedString } from \"../getLocalizedString.js\";\nimport type { DynamicValue, FieldMetadata, FilterFieldType, FilterOperator, FilterValueType } from \"./types.js\";\n\n/**\n * Operators that take no value at all.\n *\n * `eOperators.ParameterLessChecks` plus the blank checks.\n */\nconst PARAMETERLESS_OPERATORS: FilterOperator[] = [\"isnull\", \"isnotnull\", \"istrue\", \"isfalse\", \"isblank\", \"isnotblank\"];\n\n/**\n * Whole-period checks for dates, such as \"Is last week\", in the order of the\n * Windows filter editor. They take no value. The framework has no operator for\n * them, so a filter using one is sent as a filter string (`needsFilterString`).\n */\nexport const PERIOD_OPERATORS: FilterOperator[] = [\n\t\"isbeyondthisyear\",\n\t\"islaterthisyear\",\n\t\"islaterthismonth\",\n\t\"isnextweek\",\n\t\"islaterthisweek\",\n\t\"istomorrow\",\n\t\"istoday\",\n\t\"isyesterday\",\n\t\"isearlierthisweek\",\n\t\"islastweek\",\n\t\"isearlierthismonth\",\n\t\"isearlierthisyear\",\n\t\"ispriorthisyear\",\n];\n\n/** Operators whose value is a two element `[from, to]` array. */\nconst PAIR_OPERATORS: FilterOperator[] = [\"between\", \"notbetween\", \"datebetween\", \"datenotbetween\"];\n\n/** Operators whose value is an array of values. */\nconst LIST_OPERATORS: FilterOperator[] = [\"inlist\", \"notinlist\"];\n\n/** How many values an operator needs. */\nexport type OperatorArity = \"none\" | \"single\" | \"pair\" | \"list\";\n\nexport function getOperatorArity(operator: FilterOperator): OperatorArity {\n\tif (PARAMETERLESS_OPERATORS.includes(operator) || PERIOD_OPERATORS.includes(operator)) return \"none\";\n\tif (PAIR_OPERATORS.includes(operator)) return \"pair\";\n\tif (LIST_OPERATORS.includes(operator)) return \"list\";\n\treturn \"single\";\n}\n\n/**\n * Operator labels, keyed by operator. Comparison operators have a different\n * label for dates than for numbers, matching the Windows filter editor.\n *\n * @see `appframe/Appframe3.Win/Controls/afFilterEditor/Utils/FilterLineControlUtils.vb`\n */\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n\tequals: \"Equal to\",\n\tnotequals: \"Not equal to\",\n\tgreaterthan: \"Greater than\",\n\tgreaterthanorequal: \"Greater than or equal to\",\n\tlessthan: \"Less than\",\n\tlessthanorequal: \"Less than or equal to\",\n\tbeginswith: \"Begins with\",\n\tnotbeginswith: \"Does not begin with\",\n\tendswith: \"Ends with\",\n\tnotendswith: \"Does not end with\",\n\tcontains: \"Contains\",\n\tnotcontains: \"Does not contain\",\n\tlike: \"Like\",\n\tnotlike: \"Not like\",\n\tisnull: \"Is null\",\n\tisnotnull: \"Is not null\",\n\tisblank: \"Is empty\",\n\tisnotblank: \"Is not empty\",\n\tistrue: \"Is true\",\n\tisfalse: \"Is false\",\n\tinlist: \"In list\",\n\tnotinlist: \"Not in list\",\n\tbetween: \"Between\",\n\tnotbetween: \"Not between\",\n\tdateequals: \"Equal to\",\n\tnotdatequals: \"Not equal to\",\n\tdategreaterthan: \"After\",\n\tdategreaterthanorequal: \"On or after\",\n\tdatelessthan: \"Before\",\n\tdatelessthanorequal: \"Up to\",\n\tdatebetween: \"Between\",\n\tdatenotbetween: \"Not between\",\n\tisbeyondthisyear: \"Is beyond this year\",\n\tislaterthisyear: \"Is later this year\",\n\tislaterthismonth: \"Is later this month\",\n\tisnextweek: \"Is next week\",\n\tislaterthisweek: \"Is later this week\",\n\tistomorrow: \"Is tomorrow\",\n\tistoday: \"Is today\",\n\tisyesterday: \"Is yesterday\",\n\tisearlierthisweek: \"Is earlier this week\",\n\tislastweek: \"Is last week\",\n\tisearlierthismonth: \"Is earlier this month\",\n\tisearlierthisyear: \"Is earlier this year\",\n\tispriorthisyear: \"Is prior to this year\",\n};\n\n/** Comparison operator labels used for date and time fields. */\nconst DATE_OPERATOR_LABELS: Partial<Record<FilterOperator, string>> = {\n\tgreaterthan: \"After\",\n\tgreaterthanorequal: \"On or after\",\n\tlessthan: \"Before\",\n\tlessthanorequal: \"Up to\",\n};\n\n/**\n * Returns the localized label for an operator. Date fields get the\n * \"After\"/\"Before\" wording instead of \"Greater than\"/\"Less than\".\n */\nexport function getOperatorLabel(operator: FilterOperator, fieldType?: FilterFieldType): string {\n\tlet isDate = fieldType === \"date\" || fieldType === \"datetime\";\n\tlet label = (isDate ? DATE_OPERATOR_LABELS[operator] : undefined) ?? OPERATOR_LABELS[operator];\n\n\treturn getLocalizedString(label ?? operator);\n}\n\nconst EQUALITY: FilterOperator[] = [\"equals\", \"notequals\"];\nconst LIST_CHECKS: FilterOperator[] = [\"inlist\", \"notinlist\"];\nconst RANGE: FilterOperator[] = [\"between\", \"notbetween\"];\nconst COMPARISONS: FilterOperator[] = [\"greaterthan\", \"greaterthanorequal\", \"lessthan\", \"lessthanorequal\"];\nconst STRING_CHECKS: FilterOperator[] = [\n\t\"contains\",\n\t\"notcontains\",\n\t\"beginswith\",\n\t\"notbeginswith\",\n\t\"endswith\",\n\t\"notendswith\",\n\t\"isblank\",\n\t\"isnotblank\",\n];\n\n/**\n * Operators offered per field type, in the order the Windows filter editor\n * presents them.\n */\nconst OPERATORS_BY_TYPE: Record<FilterFieldType, FilterOperator[]> = {\n\tdate: [...EQUALITY, ...LIST_CHECKS, ...RANGE, ...COMPARISONS, ...PERIOD_OPERATORS],\n\tdatetime: [...EQUALITY, ...LIST_CHECKS, ...RANGE, ...COMPARISONS, ...PERIOD_OPERATORS],\n\tnumber: [...EQUALITY, ...LIST_CHECKS, ...RANGE, ...COMPARISONS],\n\tboolean: [\"istrue\", \"isfalse\"],\n\tstring: [...EQUALITY, ...STRING_CHECKS, ...LIST_CHECKS, ...RANGE, ...COMPARISONS],\n\tuniqueidentifier: [...EQUALITY, ...STRING_CHECKS, ...LIST_CHECKS, ...RANGE, ...COMPARISONS],\n};\n\n/**\n * Default operator per field type.\n *\n * Text defaults to `contains` because a substring search is what people\n * reach for when they type into a filter box without picking an operator —\n * `beginswith` silently misses matches in the middle of the value.\n */\nconst DEFAULT_OPERATOR_BY_TYPE: Record<FilterFieldType, FilterOperator> = {\n\tdate: \"equals\",\n\tdatetime: \"equals\",\n\tnumber: \"equals\",\n\tboolean: \"istrue\",\n\tstring: \"contains\",\n\tuniqueidentifier: \"beginswith\",\n};\n\n/**\n * Returns the operators available for a field. Nullable fields additionally get\n * the null checks. Boolean fields never get null checks, since `istrue` and\n * `isfalse` already cover the parameterless cases.\n */\nexport function getOperatorsForField(field: Pick<FieldMetadata, \"type\" | \"nullable\">): FilterOperator[] {\n\tlet operators = [...(OPERATORS_BY_TYPE[field.type] ?? OPERATORS_BY_TYPE.string)];\n\n\tif (field.nullable && field.type !== \"boolean\") {\n\t\toperators.push(\"isnull\", \"isnotnull\");\n\t}\n\n\treturn operators;\n}\n\nexport function getDefaultOperator(field: Pick<FieldMetadata, \"type\">): FilterOperator {\n\treturn DEFAULT_OPERATOR_BY_TYPE[field.type] ?? \"equals\";\n}\n\n/**\n * The `valueType` to send for a field/operator combination.\n *\n * Parameterless operators send `\"null\"`, list and range operators send\n * `\"array\"`, everything else sends the field's own value type.\n */\nexport function getValueType(field: Pick<FieldMetadata, \"type\">, operator: FilterOperator): FilterValueType {\n\tif (getOperatorArity(operator) === \"none\") return \"null\";\n\n\tswitch (field.type) {\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"datetime\";\n\t\tcase \"uniqueidentifier\":\n\t\t\treturn \"uniqueidentifier\";\n\t\tdefault:\n\t\t\treturn \"string\";\n\t}\n}\n\n/** Relative date values offered in the date value editor, in menu order. */\nexport const RELATIVE_DATE_VALUES: DynamicValue[] = [\n\t\"Yesterday\",\n\t\"Today\",\n\t\"Now\",\n\t\"Tomorrow\",\n\t\"DayAfterTomorrow\",\n\t\"TwoWeeksAway\",\n\t\"ThisWeek\",\n\t\"ThisMonth\",\n\t\"ThisYear\",\n\t\"LastWeek\",\n\t\"LastMonth\",\n\t\"LastYear\",\n\t\"NextWeek\",\n\t\"NextMonth\",\n\t\"NextYear\",\n];\n\nconst DYNAMIC_VALUE_LABELS: Record<DynamicValue, string> = {\n\tCurrentUser: \"Current user\",\n\tYesterday: \"Yesterday\",\n\tToday: \"Today\",\n\tNow: \"Now\",\n\tTomorrow: \"Tomorrow\",\n\tDayAfterTomorrow: \"Day after tomorrow\",\n\tTwoWeeksAway: \"Two weeks away\",\n\tThisWeek: \"This week\",\n\tThisMonth: \"This month\",\n\tThisYear: \"This year\",\n\tLastWeek: \"Last week\",\n\tLastMonth: \"Last month\",\n\tLastYear: \"Last year\",\n\tNextWeek: \"Next week\",\n\tNextMonth: \"Next month\",\n\tNextYear: \"Next year\",\n\tUserYesterday: \"Yesterday\",\n\tUserToday: \"Today\",\n\tUserTomorrow: \"Tomorrow\",\n\tUserThisWeek: \"This week\",\n\tUserThisMonth: \"This month\",\n\tUserThisYear: \"This year\",\n\tUserLastWeek: \"Last week\",\n\tUserLastMonth: \"Last month\",\n\tUserLastYear: \"Last year\",\n\tUserNextWeek: \"Next week\",\n\tUserNextMonth: \"Next month\",\n\tUserNextYear: \"Next year\",\n};\n\nexport function getDynamicValueLabel(value: DynamicValue): string {\n\treturn getLocalizedString(DYNAMIC_VALUE_LABELS[value] ?? value);\n}\n\n/**\n * Returns the dynamic (\"special\") values that can be used with a field. Date\n * fields get the relative dates; string fields can be compared to the current\n * user name.\n */\nexport function getDynamicValuesForField(field: Pick<FieldMetadata, \"type\">): DynamicValue[] {\n\tif (field.type === \"date\" || field.type === \"datetime\") return [...RELATIVE_DATE_VALUES];\n\tif (field.type === \"string\") return [\"CurrentUser\"];\n\n\treturn [];\n}\n\n/** Operators that can compare a field with another column. */\nconst COLUMN_OPERATORS: FilterOperator[] = [...EQUALITY, ...RANGE, ...COMPARISONS];\n\n/** Whether an operator can take another column as its value. */\nexport function supportsColumnValue(operator: FilterOperator): boolean {\n\treturn COLUMN_OPERATORS.includes(operator);\n}\n\n/** Dates and datetimes compare with each other; other types only with their own. */\nfunction getComparableType(type: FilterFieldType): FilterFieldType {\n\treturn type === \"datetime\" ? \"date\" : type;\n}\n\n/**\n * The columns a field can be compared with: the other fields of the same type,\n * as in the Windows filter editor. Booleans have only true and false checks,\n * so they get none.\n */\nexport function getColumnsForField(\n\tfield: Pick<FieldMetadata, \"name\" | \"type\">,\n\tfields: FieldMetadata[],\n): FieldMetadata[] {\n\tif (field.type === \"boolean\") return [];\n\n\tlet type = getComparableType(field.type);\n\n\treturn fields.filter(\n\t\t(other) => other.name !== field.name && !other.excludeFromFilter && getComparableType(other.type) === type,\n\t);\n}\n\n/**\n * Aliases for names `eOperators.ToString()` can produce that are not operators\n * in their own right. `Not` is the bare negation flag (i.e. \"not equal to\"),\n * `Tweens` shares its numeric value with `NotBetween`, and combined flags are\n * rendered as a comma separated list.\n *\n * @see `FilterElementConverter.FilterExpressionToDictionary`\n */\nconst OPERATOR_ALIASES: Record<string, FilterOperator> = {\n\tnot: \"notequals\",\n\ttweens: \"notbetween\",\n\tnotdate: \"notdatequals\",\n\t// The enum member is `NotDateEquals`, but the wire format drops an \"e\".\n\tnotdateequals: \"notdatequals\",\n};\n\n/**\n * Normalizes an operator string coming from the server or from another filter\n * producer (`AfGrid` emits PascalCase names) to the canonical lower-case form.\n */\nexport function normalizeOperator(operator: string): FilterOperator {\n\tlet normalized = operator.toLowerCase().replace(/[\\s,]/g, \"\");\n\n\treturn OPERATOR_ALIASES[normalized] ?? (normalized as FilterOperator);\n}\n","import {\n\tgetDefaultOperator,\n\tgetOperatorArity,\n\tgetValueType,\n\tnormalizeOperator,\n\tPERIOD_OPERATORS,\n\tsupportsColumnValue,\n} from \"./operators.js\";\nimport type {\n\tFieldMetadata,\n\tFilterExpression,\n\tFilterGroup,\n\tFilterOperator,\n\tFilterRangeEnd,\n\tFilterTree,\n} from \"./types.js\";\n\n/**\n * Path to a node in a filter tree, as a list of item indexes from the root\n * group. The root itself is the empty path.\n *\n * @example\n * ```ts\n * // The second condition of the first group\n * let path: FilterPath = [0, 1];\n * ```\n */\nexport type FilterPath = number[];\n\nexport type FilterNode = FilterGroup | FilterExpression;\n\n/**\n * Creates the default empty filter: a single `and` group with no conditions.\n */\nexport function createEmptyFilter(): FilterTree {\n\treturn createEmptyGroup(\"and\");\n}\n\nexport function createEmptyGroup(mode: FilterGroup[\"mode\"] = \"and\"): FilterGroup {\n\treturn { type: \"group\", mode, items: [] };\n}\n\nexport function isGroup(item: FilterNode): item is FilterGroup {\n\treturn item.type === \"group\";\n}\n\nexport function isExpression(item: FilterNode): item is FilterExpression {\n\treturn item.type === \"expression\";\n}\n\n/**\n * Normalizes any filter value into a tree the editor can render, without\n * changing its structure. An expression is wrapped in a group; a missing filter\n * becomes the default layout.\n */\nexport function toFilterTree(filter: FilterNode | null | undefined): FilterTree {\n\tif (!filter) return createEmptyFilter();\n\tif (isExpression(filter)) return { type: \"group\", mode: \"and\", items: [filter] };\n\n\treturn filter;\n}\n\n/**\n * Returns the `and` group alone when it is the only item of an `or` group.\n *\n * The server parses filter strings into the standard layout, which wraps even\n * a plain list of conditions in an `or` group of one `and` group. That extra\n * level means nothing, so the builder shows the `and` group on its own, as the\n * Windows filter editor does.\n */\nexport function unwrapSingleAndGroup(filter: FilterTree): FilterTree {\n\tif (filter.mode !== \"or\" || filter.items.length !== 1) return filter;\n\n\tlet [only] = filter.items;\n\n\treturn isGroup(only) && only.mode === \"and\" ? only : filter;\n}\n\n/**\n * True when the filter contains no expressions. An empty filter must not be\n * saved or applied.\n */\nexport function isEmptyFilter(filter: FilterNode | null | undefined): boolean {\n\treturn countExpressions(filter) === 0;\n}\n\nexport function countExpressions(filter: FilterNode | null | undefined): number {\n\tif (!filter) return 0;\n\tif (isExpression(filter)) return 1;\n\n\treturn filter.items.reduce((total, item) => total + countExpressions(item), 0);\n}\n\n/**\n * Removes incomplete expressions and empty groups from the whole tree. Returns\n * null when nothing is left. Use before applying a filter to a data object so\n * incomplete rows are not sent.\n */\nexport function pruneFilter(filter: FilterNode | null | undefined): FilterGroup | null {\n\tif (!filter) return null;\n\n\tif (isExpression(filter)) {\n\t\treturn isCompleteExpression(filter) ? { type: \"group\", mode: \"and\", items: [filter] } : null;\n\t}\n\n\tlet items = filter.items\n\t\t.map((item) => (isGroup(item) ? pruneFilter(item) : isCompleteExpression(item) ? item : null))\n\t\t.filter((item): item is FilterNode => item !== null);\n\n\tif (items.length === 0) return null;\n\n\treturn { ...filter, items };\n}\n\n/**\n * True when an expression has everything it needs to be sent to the server.\n */\nexport function isCompleteExpression(item: FilterNode): boolean {\n\tif (isGroup(item)) return item.items.some(isCompleteExpression);\n\tif (!item.column || !item.operator) return false;\n\n\tlet arity = getOperatorArity(normalizeOperator(item.operator));\n\n\tif (arity === \"none\") return true;\n\tif (item.value === null || item.value === undefined || item.value === \"\") return false;\n\n\tif (arity === \"pair\") {\n\t\treturn (\n\t\t\tArray.isArray(item.value) &&\n\t\t\titem.value.length === 2 &&\n\t\t\titem.value.every((v: unknown) => {\n\t\t\t\tlet end = isRangeEnd(v) ? v.value : v;\n\t\t\t\treturn end !== \"\" && end != null;\n\t\t\t})\n\t\t);\n\t}\n\n\tif (arity === \"list\") {\n\t\treturn Array.isArray(item.value) && item.value.length > 0;\n\t}\n\n\treturn true;\n}\n\n/** Whether a range value end is a `FilterRangeEnd` rather than a plain value. */\nexport function isRangeEnd(value: unknown): value is FilterRangeEnd {\n\treturn typeof value === \"object\" && value !== null && !(value instanceof Date) && \"valueType\" in value;\n}\n\n/** Dynamic values the framework has no `eDynamicValue` member for. */\nconst DYNAMIC_VALUES_WITHOUT_FRAMEWORK_SUPPORT = new Set<string>([\"Now\", \"DayAfterTomorrow\", \"TwoWeeksAway\"]);\n\nfunction needsConversion(valueType: string, value: unknown): boolean {\n\tif (valueType === \"column\") return true;\n\n\treturn valueType === \"special\" && typeof value === \"string\" && DYNAMIC_VALUES_WITHOUT_FRAMEWORK_SUPPORT.has(value);\n}\n\n/**\n * True when the filter holds something the framework's data API cannot read\n * from a filter object: another column, a range end that is not a plain value,\n * one of the dates only the Windows editor offers, or a whole-period check. Such a filter must be\n * converted with `convertFilter` and sent as a filter string.\n */\nexport function needsFilterString(filter: FilterNode | null | undefined): boolean {\n\tif (!filter) return false;\n\tif (isGroup(filter)) return filter.items.some(needsFilterString);\n\tif (PERIOD_OPERATORS.includes(normalizeOperator(filter.operator))) return true;\n\tif (needsConversion(filter.valueType, filter.value)) return true;\n\n\treturn Array.isArray(filter.value) && filter.value.some((item: unknown) => isRangeEnd(item));\n}\n\n/**\n * Creates an expression for a field, using the field's default operator and an\n * empty value of the right shape.\n */\nexport function createExpression(field: FieldMetadata, operator?: FilterOperator): FilterExpression {\n\tlet op = operator ?? getDefaultOperator(field);\n\n\treturn {\n\t\ttype: \"expression\",\n\t\tcolumn: field.name,\n\t\toperator: op,\n\t\tvalue: createEmptyValue(op),\n\t\tvalueType: getValueType(field, op),\n\t};\n}\n\nfunction createEmptyValue(operator: FilterOperator): FilterExpression[\"value\"] {\n\tswitch (getOperatorArity(operator)) {\n\t\tcase \"none\":\n\t\t\treturn null;\n\t\tcase \"pair\":\n\t\t\treturn [\"\", \"\"];\n\t\tcase \"list\":\n\t\t\treturn [];\n\t\tdefault:\n\t\t\treturn \"\";\n\t}\n}\n\n/**\n * Returns a copy of the expression with a new operator, adjusting the value to\n * the new operator's arity and the value type accordingly.\n */\nexport function changeOperator(\n\texpression: FilterExpression,\n\toperator: FilterOperator,\n\tfield: Pick<FieldMetadata, \"type\">,\n): FilterExpression {\n\tlet previousArity = getOperatorArity(normalizeOperator(expression.operator));\n\tlet arity = getOperatorArity(operator);\n\tlet value = expression.value;\n\n\t// A column only makes sense for comparisons, not for text checks such as\n\t// \"contains\".\n\tlet holdsColumn =\n\t\texpression.valueType === \"column\" ||\n\t\t(Array.isArray(value) && value.some((item: unknown) => isRangeEnd(item) && item.valueType === \"column\"));\n\n\tif (arity !== previousArity || (holdsColumn && !supportsColumnValue(operator))) {\n\t\tvalue = createEmptyValue(operator);\n\t}\n\n\t// A dynamic value or a column stays valid across operators of the same arity.\n\tlet keepsValueType =\n\t\t(expression.valueType === \"special\" || expression.valueType === \"column\") && value === expression.value;\n\tlet valueType = keepsValueType ? expression.valueType : getValueType(field, operator);\n\n\treturn { ...expression, operator, value, valueType };\n}\n\n/** Returns the node at `path`, or null when the path does not exist. */\nexport function getNodeAtPath(filter: FilterNode, path: FilterPath): FilterNode | null {\n\tlet node: FilterNode | undefined = filter;\n\n\tfor (let index of path) {\n\t\tif (!node || !isGroup(node)) return null;\n\t\tnode = node.items[index];\n\t}\n\n\treturn node ?? null;\n}\n\n/**\n * Returns a copy of the tree with `updater` applied to the node at `path`.\n * Returning null removes the node. An unknown path leaves the tree untouched.\n */\nexport function updateNodeAtPath<T extends FilterNode>(\n\tfilter: T,\n\tpath: FilterPath,\n\tupdater: (node: FilterNode) => FilterNode | null,\n): T {\n\tif (path.length === 0) {\n\t\treturn (updater(filter) as T | null) ?? filter;\n\t}\n\n\tif (!isGroup(filter)) return filter;\n\n\tlet [index, ...rest] = path;\n\tlet child = filter.items[index as number];\n\tif (!child) return filter;\n\n\tlet items = [...filter.items];\n\n\tif (rest.length === 0) {\n\t\tlet updated = updater(child);\n\n\t\tif (updated === null) {\n\t\t\titems.splice(index as number, 1);\n\t\t} else {\n\t\t\titems[index as number] = updated;\n\t\t}\n\t} else {\n\t\titems[index as number] = updateNodeAtPath(child, rest, updater);\n\t}\n\n\treturn { ...filter, items };\n}\n\n/** Returns a copy of the tree with `node` appended to the group at `path`. */\nexport function insertNodeAtPath<T extends FilterNode>(filter: T, path: FilterPath, node: FilterNode): T {\n\treturn updateNodeAtPath(filter, path, (target) =>\n\t\tisGroup(target) ? { ...target, items: [...target.items, node] } : target,\n\t);\n}\n\n/**\n * Returns a copy of the tree with the node at `path` removed. The root is never\n * removed; it is reset to the default layout instead.\n */\nexport function removeNodeAtPath<T extends FilterNode>(filter: T, path: FilterPath): T {\n\tif (path.length === 0) return createEmptyFilter() as T;\n\n\treturn updateNodeAtPath(filter, path, () => null);\n}\n\n/** Returns a copy of the tree with the mode of the group at `path` changed. */\nexport function setGroupMode<T extends FilterNode>(filter: T, path: FilterPath, mode: FilterGroup[\"mode\"]): T {\n\treturn updateNodeAtPath(filter, path, (node) => (isGroup(node) ? { ...node, mode } : node));\n}\n\n/** Returns a copy of the tree with an empty group appended to the group at `path`. */\nexport function addGroupAtPath<T extends FilterNode>(filter: T, path: FilterPath = []): T {\n\tlet parent = getNodeAtPath(filter, path);\n\t// A nested group is only useful when it combines differently to its parent.\n\tlet mode: FilterGroup[\"mode\"] = parent && isGroup(parent) && parent.mode === \"and\" ? \"or\" : \"and\";\n\n\treturn insertNodeAtPath(filter, path, createEmptyGroup(mode));\n}\n","import { toFilterTree } from \"./tree.js\";\nimport type { FilterGroup } from \"./types.js\";\n\nconst CONVERT_URL = \"/api/filter/convert\";\n\nexport type FilterConversionResult = {\n\tfilterObject: FilterGroup | null;\n\tfilterString: string;\n\twhereClause: string;\n\t/** Set when the filter string could not be parsed into a structured filter. */\n\twarning?: string;\n};\n\nexport type ConvertRequest = {\n\tfilterObject?: FilterGroup | null;\n\tfilterString?: string;\n\t/** Optional view name, used server-side to validate field names. */\n\tviewName?: string;\n};\n\n/**\n * Converts between a structured filter and a DevExpress filter string.\n *\n * The conversion is done server-side by `api/filter/convert`, because the\n * criteria string grammar is implemented by the licensed DevExpress parser that\n * the Windows client and the report engine use. Doing it in the browser would\n * risk producing strings the server cannot parse back.\n */\nexport async function convertFilter(request: ConvertRequest, signal?: AbortSignal): Promise<FilterConversionResult> {\n\tlet response = await fetch(CONVERT_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify(request),\n\t\tsignal,\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to convert filter: ${response.status} ${response.statusText}`);\n\t}\n\n\tlet result = (await response.json()) as FilterConversionResult;\n\n\treturn {\n\t\t...result,\n\t\tfilterObject: result.filterObject ? toFilterTree(result.filterObject) : null,\n\t};\n}\n","import type { FilterExpression, FilterGroup } from \"./types.js\";\nimport { getDynamicValueLabel, getOperatorArity, getOperatorLabel, normalizeOperator } from \"./operators.js\";\nimport { isExpression, isRangeEnd } from \"./tree.js\";\nimport type { DynamicValue, FieldMetadata } from \"./types.js\";\n\nexport type DescribeFilterOptions = {\n\t/** Field metadata, used to resolve captions and date operator wording. */\n\tfields?: FieldMetadata[];\n};\n\n/**\n * Builds a human-readable description of a filter, without contacting the\n * server. Use this for read-only summaries; the authoritative filter string is\n * produced by `api/filter/convert`, since only the server can generate the\n * DevExpress criteria syntax.\n */\nexport function describeFilter(\n\tfilter: FilterGroup | FilterExpression | null | undefined,\n\toptions: DescribeFilterOptions = {},\n): string {\n\tif (!filter) return \"\";\n\n\tlet fieldsByName = new Map((options.fields ?? []).map((field) => [field.name.toLowerCase(), field]));\n\n\treturn describe(filter, fieldsByName, 0);\n}\n\nfunction describe(item: FilterGroup | FilterExpression, fields: Map<string, FieldMetadata>, depth: number): string {\n\tif (isExpression(item)) return describeExpression(item, fields);\n\n\tlet parts = item.items.map((child) => describe(child, fields, depth + 1)).filter((part) => part !== \"\");\n\n\tif (parts.length === 0) return \"\";\n\tif (parts.length === 1) return parts[0] as string;\n\n\tlet separator = item.mode === \"or\" ? \" OR \" : \" AND \";\n\tlet joined = parts.join(separator);\n\n\treturn depth === 0 ? joined : `(${joined})`;\n}\n\nfunction describeExpression(expression: FilterExpression, fields: Map<string, FieldMetadata>): string {\n\tif (!expression.column || !expression.operator) return \"\";\n\n\tlet field = fields.get(expression.column.toLowerCase());\n\tlet caption = field?.caption ?? expression.column;\n\tlet operator = normalizeOperator(expression.operator);\n\tlet label = getOperatorLabel(operator, field?.type);\n\tlet arity = getOperatorArity(operator);\n\n\tif (arity === \"none\") return `[${caption}] ${label}`;\n\n\treturn `[${caption}] ${label} ${formatValue(expression, arity, fields)}`;\n}\n\nfunction formatValue(\n\texpression: FilterExpression,\n\tarity: ReturnType<typeof getOperatorArity>,\n\tfields: Map<string, FieldMetadata>,\n): string {\n\tlet { value, valueType } = expression;\n\n\tif (Array.isArray(value)) {\n\t\tlet formatted = value.map((item: unknown) =>\n\t\t\tisRangeEnd(item) ? formatScalar(item.value, item.valueType, fields) : formatScalar(item, valueType, fields),\n\t\t);\n\n\t\treturn arity === \"pair\" ? formatted.join(\" - \") : `(${formatted.join(\", \")})`;\n\t}\n\n\treturn formatScalar(value, valueType, fields);\n}\n\nfunction formatScalar(value: unknown, valueType: string, fields: Map<string, FieldMetadata>): string {\n\tif (valueType === \"special\" && typeof value === \"string\") return getDynamicValueLabel(value as DynamicValue);\n\tif (valueType === \"column\" && typeof value === \"string\") {\n\t\treturn `[${fields.get(value.toLowerCase())?.caption ?? value}]`;\n\t}\n\tif (value === null || value === undefined) return \"\";\n\tif (value instanceof Date) return value.toISOString().slice(0, 10);\n\tif (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n\tif (valueType === \"number\" || valueType === \"boolean\") return String(value);\n\n\treturn `'${String(value)}'`;\n}\n","/**\n * Field metadata for the filter builder.\n *\n * `sviw_WebSiteCMS_FilterBuilder_ViewFields` normalizes the SQL data types into\n * the handful of types the builder groups fields by, so the mapping here is\n * mostly a rename. The loading lives in `useFilterFields` in\n * `@olenbetong/appframe-react`.\n */\n\nimport type { FieldMetadata, FilterFieldType } from \"./types.js\";\n\n/** A raw `sviw_WebSiteCMS_FilterBuilder_ViewFields` record. */\nexport type ViewFieldRecord = {\n\tFieldName?: string | null;\n\t/** Already normalized by the view; anything unexpected is treated as text. */\n\tDataType?: string | null;\n\tDBObjectID?: string | null;\n\tNullable?: boolean | number | null;\n\tExcludeFromFilter?: boolean | number | null;\n\tValueListRecordSource?: string | null;\n};\n\nconst FIELD_TYPES: FilterFieldType[] = [\"string\", \"number\", \"boolean\", \"date\", \"datetime\", \"uniqueidentifier\"];\n\n/** The builder type for a normalized `DataType`. Unknown types are filtered as text. */\nexport function toFilterFieldType(dataType: string | null | undefined): FilterFieldType {\n\tlet normalized = dataType?.trim().toLowerCase();\n\n\treturn FIELD_TYPES.find((type) => type === normalized) ?? \"string\";\n}\n\nfunction toBoolean(value: boolean | number | null | undefined) {\n\treturn value === true || value === 1;\n}\n\n/** Maps a view field record onto the metadata the builder renders. */\nexport function toFieldMetadata(record: ViewFieldRecord): FieldMetadata {\n\treturn {\n\t\tname: record.FieldName ?? \"\",\n\t\ttype: toFilterFieldType(record.DataType),\n\t\tnullable: toBoolean(record.Nullable),\n\t\texcludeFromFilter: toBoolean(record.ExcludeFromFilter),\n\t\tvalueListRecordSource: record.ValueListRecordSource || undefined,\n\t};\n}\n\n/**\n * Maps and sorts view field records. Fields the view marks as excluded are\n * dropped rather than hidden, since nothing else can use them.\n */\nexport function toFieldMetadataList(records: ViewFieldRecord[]): FieldMetadata[] {\n\treturn records\n\t\t.map(toFieldMetadata)\n\t\t.filter((field) => field.name !== \"\" && !field.excludeFromFilter)\n\t\t.sort((a, b) => a.name.localeCompare(b.name));\n}\n","import { isExpression, isGroup } from \"./tree.js\";\nimport type { FilterExpression, FilterGroup, FilterTree } from \"./types.js\";\n\n/**\n * The \"standard layout\" is the simplified shape the Windows filter editor can\n * render in its flow view: an outer `or` group whose items are `and` groups of\n * expressions. Anything else falls back to the advanced (DevExpress) editor\n * there, and to the nested tree editor on the web.\n *\n * @see `afFilterEditor.CanRenderFilter`\n */\nexport function isStandardLayout(filter: FilterGroup | FilterExpression | null | undefined): boolean {\n\tif (!filter || isExpression(filter) || filter.mode !== \"or\") return false;\n\n\treturn filter.items.every((item) => isGroup(item) && item.mode === \"and\" && item.items.every(isExpression));\n}\n\n/**\n * Coerces an arbitrary filter tree into the standard layout.\n *\n * An `or` group's items each become their own `and` group. Anything else is\n * flattened into a single `and` group. This loses nesting, so only use it when\n * the consumer cannot handle a nested tree.\n */\nexport function coerceToStandardLayout(filter: FilterGroup | FilterExpression | null | undefined): FilterTree {\n\tif (!filter) return { type: \"group\", mode: \"or\", items: [{ type: \"group\", mode: \"and\", items: [] }] };\n\n\tif (isExpression(filter)) {\n\t\treturn { type: \"group\", mode: \"or\", items: [{ type: \"group\", mode: \"and\", items: [filter] }] };\n\t}\n\n\tif (filter.mode === \"or\") {\n\t\tlet groups = filter.items.map((item) => {\n\t\t\tlet expressions: FilterExpression[] = [];\n\t\t\tcollectExpressions(item, expressions);\n\n\t\t\treturn { type: \"group\", mode: \"and\", items: expressions } satisfies FilterGroup;\n\t\t});\n\n\t\treturn {\n\t\t\ttype: \"group\",\n\t\t\tmode: \"or\",\n\t\t\titems: groups.length > 0 ? groups : [{ type: \"group\", mode: \"and\", items: [] }],\n\t\t};\n\t}\n\n\tlet expressions: FilterExpression[] = [];\n\tcollectExpressions(filter, expressions);\n\n\treturn { type: \"group\", mode: \"or\", items: [{ type: \"group\", mode: \"and\", items: expressions }] };\n}\n\n/**\n * Collects every expression in a (possibly nested) filter, in document order.\n */\nfunction collectExpressions(item: FilterGroup | FilterExpression, into: FilterExpression[]) {\n\tif (isExpression(item)) {\n\t\tinto.push(item);\n\t\treturn;\n\t}\n\n\tfor (let child of item.items) {\n\t\tcollectExpressions(child, into);\n\t}\n}\n","/**\n * Saved filters — the filters the Windows filter builder stores in\n * `stbl_Database_Filters` and shares between users through\n * `stbl_Database_FiltersPermissions`.\n *\n * This module is the pure part: the shape of a saved filter, how the raw\n * database records map onto it, and who is allowed to do what with one. The\n * loading and the mutations live in `useSavedFilters` in\n * `@olenbetong/appframe-react`.\n */\n\nimport type { FilterAccessLevel } from \"./types.js\";\n\n/**\n * Access a user has to a saved filter.\n *\n * `sviw_Database_MyFilters` only ever reports `Reader`, `Editor` or `Manager`,\n * and a filter the current user created is always reported as `Manager`.\n */\nexport type { FilterAccessLevel };\n\n/**\n * `sviw_Database_MyFilters` unions in a synthetic \"No Filter\" row so the Windows\n * tree can render a \"no filter\" entry. It is not a real filter and must be\n * dropped before the list is shown.\n */\nexport const NO_FILTER_ID = -1;\n\n/** A saved filter, as used by the filter builder UI. */\nexport type SavedFilter = {\n\tfilterId: number;\n\tprimKey: string;\n\tname: string;\n\t/** DevExpress criteria string, e.g. `[Name] Like '%a%'`. */\n\tcriteria: string;\n\tdbObjectId: string;\n\taccessLevel: FilterAccessLevel;\n\t/** True when the current user created the filter. */\n\tcreatedByUser: boolean;\n\tcreatedBy: string;\n\tdomain: string | null;\n\t/** True when the filter is visible in every domain the user has access to. */\n\tcrossDomain: boolean;\n\t/** True when the filter is shared with individual users rather than groups. */\n\tisPersonallyShared: boolean;\n\t/** True when the criteria are hidden from readers of a shared filter. */\n\thideCriteria: boolean;\n\theaderText: string | null;\n\tcomment: string | null;\n\t/** Report parameter values, stored as XML. Only used by report filters. */\n\tparameters: string | null;\n\treportTitle: string | null;\n\tcreated: Date | null;\n\tupdated: Date | null;\n};\n\n/** A raw `sviw_Database_MyFilters` / `sviw_Database_FiltersFromReportID` record. */\nexport type SavedFilterRecord = {\n\tPrimKey?: string | null;\n\tFilterID?: number | null;\n\tDBObjectID?: string | null;\n\tFilterName?: string | null;\n\tFilterCriteria?: string | null;\n\tAccessLevel?: string | null;\n\tCreatedByUser?: string | boolean | null;\n\tCreatedBy?: string | null;\n\tDomain?: string | null;\n\tCrossDomain?: boolean | number | null;\n\tIsPersonallyShared?: boolean | number | null;\n\tHideFilterCriteria?: boolean | number | null;\n\tOptionalHeaderText?: string | null;\n\tOptionalComment?: string | null;\n\tParameters?: string | null;\n\tReportTitle?: string | null;\n\tCreated?: Date | string | null;\n\tUpdated?: Date | string | null;\n};\n\n/**\n * Maps a database record onto a `SavedFilter`.\n *\n * `CreatedByUser` needs care: the view computes it as the *string* `'true'` or\n * `'false'` (and `'False'` on the synthetic row), not as a bit.\n */\nexport function toSavedFilter(record: SavedFilterRecord): SavedFilter {\n\treturn {\n\t\tfilterId: record.FilterID ?? NO_FILTER_ID,\n\t\tprimKey: record.PrimKey ?? \"\",\n\t\tname: record.FilterName ?? \"\",\n\t\tcriteria: record.FilterCriteria ?? \"\",\n\t\tdbObjectId: record.DBObjectID ?? \"\",\n\t\taccessLevel: toAccessLevel(record.AccessLevel),\n\t\tcreatedByUser: toBoolean(record.CreatedByUser),\n\t\tcreatedBy: record.CreatedBy ?? \"\",\n\t\tdomain: record.Domain ?? null,\n\t\tcrossDomain: toBoolean(record.CrossDomain),\n\t\tisPersonallyShared: toBoolean(record.IsPersonallyShared),\n\t\thideCriteria: toBoolean(record.HideFilterCriteria),\n\t\theaderText: record.OptionalHeaderText || null,\n\t\tcomment: record.OptionalComment || null,\n\t\tparameters: record.Parameters || null,\n\t\treportTitle: record.ReportTitle || null,\n\t\tcreated: toDate(record.Created),\n\t\tupdated: toDate(record.Updated),\n\t};\n}\n\nfunction toAccessLevel(value: string | null | undefined): FilterAccessLevel {\n\tif (value === \"Manager\" || value === \"Editor\") return value;\n\n\treturn \"Reader\";\n}\n\nfunction toBoolean(value: string | boolean | number | null | undefined): boolean {\n\tif (typeof value === \"string\") return value.toLowerCase() === \"true\";\n\n\treturn Boolean(value);\n}\n\nfunction toDate(value: Date | string | null | undefined): Date | null {\n\tif (!value) return null;\n\tif (value instanceof Date) return value;\n\n\tlet date = new Date(value);\n\n\treturn Number.isNaN(date.getTime()) ? null : date;\n}\n\n/** Drops the synthetic \"No Filter\" row and sorts by name. */\nexport function toSavedFilters(records: SavedFilterRecord[]): SavedFilter[] {\n\treturn records\n\t\t.filter((record) => (record.FilterID ?? NO_FILTER_ID) !== NO_FILTER_ID)\n\t\t.map(toSavedFilter)\n\t\t.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Splits filters into the two branches the Windows tree shows: the ones the user\n * created, and the ones that have been shared with them.\n */\nexport function partitionSavedFilters(filters: SavedFilter[]): { mine: SavedFilter[]; shared: SavedFilter[] } {\n\tlet mine: SavedFilter[] = [];\n\tlet shared: SavedFilter[] = [];\n\n\tfor (let filter of filters) {\n\t\tif (filter.createdByUser) {\n\t\t\tmine.push(filter);\n\t\t} else {\n\t\t\tshared.push(filter);\n\t\t}\n\t}\n\n\treturn { mine, shared };\n}\n\nexport function findSavedFilter(filters: SavedFilter[], filterId: number | null | undefined) {\n\tif (filterId == null) return undefined;\n\n\treturn filters.find((filter) => filter.filterId === filterId);\n}\n\n/** True when the user may overwrite the filter's criteria. */\nexport function canEditSavedFilter(filter: SavedFilter): boolean {\n\treturn filter.accessLevel === \"Editor\" || filter.accessLevel === \"Manager\";\n}\n\n/** True when the user may change who the filter is shared with. */\nexport function canShareSavedFilter(filter: SavedFilter): boolean {\n\treturn filter.accessLevel === \"Manager\";\n}\n\n/**\n * True when the user may rename or delete the filter.\n *\n * `sstp_WebSiteCMS_FilterBuilder_RenameFilter` and `…_DeleteFilter` both require\n * `CreatedBy = SUSER_SNAME()` — the manager-access branch is commented out in\n * both procedures, deliberately, because the UI only ever offers these for a\n * user's own filters.\n */\nexport function canRenameSavedFilter(filter: SavedFilter): boolean {\n\treturn filter.createdByUser;\n}\n\nexport const canDeleteSavedFilter = canRenameSavedFilter;\n\n/**\n * True when the user may toggle the cross-domain flag.\n *\n * `sstp_Database_Filters_SetCrossDomain` also filters on\n * `CreatedBy = SUSER_SNAME()`.\n */\nexport const canSetCrossDomain = canRenameSavedFilter;\n","export type ImageSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nexport interface getUserImageOptions {\n\t/**\n\t * Prim key of the record in atbl_Personnel_Employees\n\t */\n\tprimKey: string | null;\n\t/**\n\t * Person ID of the employee\n\t */\n\tlogin: string | null;\n\t/**\n\t * The value of the ImageHash column in atbl_Personnel_Employees\n\t */\n\thash: string | number | null;\n\t/**\n\t * The image will be resized according to this value. Default is 'xs':\n\t * - xs: 64px\n\t * - sm: 128px\n\t * - md: 256px\n\t * - lg: 512px\n\t * - xl: image is not resized\n\t */\n\tsize?: ImageSize;\n}\n\nconst sizeMap: Record<ImageSize, number | undefined> = {\n\txs: 64,\n\tsm: 128,\n\tmd: 256,\n\tlg: 512,\n\txl: undefined,\n};\n\n/**\n * Returns the absolute path of a users profile picture. Uses the EmployeesLookup\n * data resource to enable caching across applications.\n *\n * If one of primKey or hash are not provided, or are null, an empty string will be returned.\n */\nexport function getUserImage(\n\tprimKey: string | null,\n\tlogin: string | null,\n\thash: string | number | null,\n\tsize?: ImageSize,\n): string;\nexport function getUserImage(options: getUserImageOptions): string;\nexport function getUserImage(\n\tprimKeyOrOptions: string | null | getUserImageOptions,\n\tlogin?: string | null,\n\thash?: string | number | null,\n\tsize: ImageSize = \"xs\",\n) {\n\tif (primKeyOrOptions && typeof primKeyOrOptions === \"object\") {\n\t\treturn getUserImage(primKeyOrOptions.primKey, primKeyOrOptions.login, primKeyOrOptions.hash, primKeyOrOptions.size);\n\t}\n\n\tif (!primKeyOrOptions || !hash) {\n\t\treturn \"\";\n\t}\n\n\tlet sizePx = sizeMap[size];\n\treturn `/api/data/blob/EmployeesLookup/${primKeyOrOptions}/Image/${encodeURIComponent(login ?? \"\")}${\n\t\tlogin ? \".\" : \"\"\n\t}${encodeURIComponent(hash)}.jpg${sizePx ? `?width=${sizePx}` : \"\"}`;\n}\n","import { getLocalizedString } from \"./getLocalizedString.js\";\nimport { invariant } from \"./invariant.js\";\n\n/**\n * Localization function for tagged templates. Replaces the arguments\n * with $n, where n is the index, before asking Appframe to translate\n * the string.\n *\n * For example, if you call localize`Page ${2} or ${5}`, the string\n * \"Page $0 of $1\" will be sent to Appframe, and for norwegian,\n * \"Side 2 av 5\" will be returned.\n *\n * @param stringParts\n * @param args\n * @returns Translated string\n */\nexport function localize(stringParts: TemplateStringsArray, ...args: any[]): string {\n\tinvariant(\n\t\tArray.isArray(stringParts) && stringParts.length === args.length + 1,\n\t\t\"Method 'localize' must be used as a tag function\",\n\t);\n\n\tlet stringToLocalize = stringParts.map((s, i) => (i === stringParts.length - 1 ? s : `${s}$${i}`)).join(\"\");\n\tlet localized = getLocalizedString(stringToLocalize);\n\n\targs.forEach((arg, index) => {\n\t\tlocalized = localized.replace(`$${index}`, arg);\n\t});\n\n\treturn localized;\n}\n","/**\n * Reading the `afRecordSource` XML the Windows designer stores on reports and\n * on report parameters.\n *\n * A record source is a serialized .NET object, so the document is machine\n * generated and its shape is fixed. Only a handful of elements matter here, and\n * parsing them with a reader rather than `DOMParser` keeps this module usable\n * outside a browser.\n */\n\nimport type { ReportRecordSource, ReportRecordSourceColumn } from \"./types.js\";\n\nconst XML_ENTITIES: Record<string, string> = {\n\tamp: \"&\",\n\tlt: \"<\",\n\tgt: \">\",\n\tquot: '\"',\n\tapos: \"'\",\n};\n\n/** Resolves the XML entities the .NET serializer emits. */\nexport function decodeXml(value: string) {\n\treturn value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (match, entity: string) => {\n\t\tif (entity.startsWith(\"#x\") || entity.startsWith(\"#X\")) {\n\t\t\treturn String.fromCodePoint(Number.parseInt(entity.slice(2), 16));\n\t\t}\n\n\t\tif (entity.startsWith(\"#\")) {\n\t\t\treturn String.fromCodePoint(Number.parseInt(entity.slice(1), 10));\n\t\t}\n\n\t\treturn XML_ENTITIES[entity.toLowerCase()] ?? match;\n\t});\n}\n\n/** Escapes a value for use as XML text or as an attribute value. */\nexport function encodeXml(value: string) {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\n/**\n * The text content of the first `<name>` element, or `null` when the element is\n * missing or self-closing (`<FilterString />`, which the serializer writes for\n * an empty value).\n */\nexport function readElement(xml: string, name: string): string | null {\n\tlet match = new RegExp(`<${name}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)</${name}>`).exec(xml);\n\n\tif (!match) return null;\n\n\tlet value = decodeXml(match[1]).trim();\n\n\treturn value === \"\" ? null : value;\n}\n\n/** The text content of every `<name>` element, in document order. */\nexport function readElements(xml: string, name: string): string[] {\n\tlet matches = xml.matchAll(new RegExp(`<${name}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)</${name}>`, \"g\"));\n\n\treturn [...matches].map((match) => decodeXml(match[1]).trim());\n}\n\n/**\n * The CLR types the designer writes on a select column. Anything else — and a\n * column with no `DataType` at all, which is what it writes for strings — is\n * treated as text.\n */\nconst COLUMN_TYPES: Record<string, ReportRecordSourceColumn[\"type\"]> = {\n\tByte: \"number\",\n\tDecimal: \"number\",\n\tDouble: \"number\",\n\tInt16: \"number\",\n\tInt32: \"number\",\n\tInt64: \"number\",\n\tSingle: \"number\",\n\tBoolean: \"boolean\",\n\tDateTime: \"date\",\n};\n\n/** The columns of a `<SelectColumns>` block, in document order. */\nfunction readSelectColumns(xml: string): ReportRecordSourceColumn[] {\n\tlet block = /<SelectColumns>([\\s\\S]*?)<\\/SelectColumns>/.exec(xml)?.[1] ?? \"\";\n\tlet columns: ReportRecordSourceColumn[] = [];\n\n\tfor (let match of block.matchAll(/<afSelectColumn>([\\s\\S]*?)<\\/afSelectColumn>/g)) {\n\t\tlet name = readElement(match[1], \"Name\");\n\t\tif (!name) continue;\n\n\t\tcolumns.push({ name, type: COLUMN_TYPES[readElement(match[1], \"DataType\") ?? \"\"] ?? \"string\" });\n\t}\n\n\treturn columns;\n}\n\n/**\n * Reads the parts of an `afRecordSource` a web client can act on: the view, the\n * selected columns and the design-time filter.\n *\n * Returns `null` for anything that is not a record source, so a missing or\n * empty `RecordSource` column simply means \"no lookup\".\n */\nexport function parseRecordSource(xml: string | null | undefined): ReportRecordSource | null {\n\tif (!xml || !xml.includes(\"<afRecordSource\")) return null;\n\n\tlet viewName = readElement(xml, \"ViewName\");\n\tif (!viewName) return null;\n\n\tlet maxRecords = readElement(xml, \"MaxRecords\");\n\n\treturn {\n\t\tviewName,\n\t\tcolumns: readSelectColumns(xml),\n\t\twhereClause: readElement(xml, \"WhereClause\"),\n\t\tfilterString: readElement(xml, \"FilterString\"),\n\t\t// The designer writes 0 for \"no limit\"; the web data API spells that -1.\n\t\tmaxRecords: maxRecords === null || maxRecords === \"0\" ? null : Number(maxRecords),\n\t};\n}\n","/**\n * Report parameters: mapping the metadata records onto something renderable,\n * and the XML the parameter values are stored as on a saved filter.\n */\n\nimport { encodeXml, parseRecordSource, readElement } from \"./recordSource.js\";\nimport type {\n\tReportParameter,\n\tReportParameterEditor,\n\tReportParameterRecord,\n\tReportParameterType,\n\tReportParameterValue,\n\tReportParameterValues,\n} from \"./types.js\";\n\nconst PARAMETER_TYPES: ReportParameterType[] = [\"String\", \"Integer\", \"Double\", \"Date\", \"Boolean\"];\n\n/**\n * A default value naming a stored procedure, e.g. `astp_Reports_CurrentPeriod`.\n *\n * The Windows launcher executes the procedure and uses the first column of the\n * first row. The web client cannot execute an arbitrary procedure, so these are\n * resolved through the report endpoint instead.\n */\nconst PROCEDURE_DEFAULT = /^[a-z]stp_/i;\n\n/** True when a `DefaultValue` names a stored procedure rather than a literal. */\nexport function isProcedureDefault(defaultValue: string | null | undefined): boolean {\n\treturn typeof defaultValue === \"string\" && PROCEDURE_DEFAULT.test(defaultValue.trim());\n}\n\n/** The choices of a parameter, one per line of its `Items` column. */\nexport function parseParameterItems(items: string | null | undefined): string[] {\n\tif (!items) return [];\n\n\treturn items\n\t\t.split(/\\r\\n|\\r|\\n/)\n\t\t.map((item) => item.trim())\n\t\t.filter((item) => item !== \"\");\n}\n\nfunction toParameterType(dataType: string | null | undefined): ReportParameterType {\n\tlet match = PARAMETER_TYPES.find((type) => type.toLowerCase() === dataType?.trim().toLowerCase());\n\n\t// The designer only offers the five known types, but an unknown one is still\n\t// editable as text.\n\treturn match ?? \"String\";\n}\n\n/**\n * The editor a parameter should use. A lookup or a fixed item list wins over the\n * data type, which is what the Windows launcher does as well.\n */\nfunction toParameterEditor(type: ReportParameterType, hasLookup: boolean, hasItems: boolean): ReportParameterEditor {\n\tif (hasLookup) return \"lookup\";\n\tif (hasItems) return \"select\";\n\n\tswitch (type) {\n\t\tcase \"Integer\":\n\t\tcase \"Double\":\n\t\t\treturn \"number\";\n\t\tcase \"Date\":\n\t\t\treturn \"date\";\n\t\tcase \"Boolean\":\n\t\t\treturn \"boolean\";\n\t\tdefault:\n\t\t\treturn \"text\";\n\t}\n}\n\n/** Maps a `sviw_WinClient_ReportsParameters` record onto a `ReportParameter`. */\nexport function toReportParameter(record: ReportParameterRecord): ReportParameter {\n\tlet name = record.Name ?? \"\";\n\tlet type = toParameterType(record.DataType);\n\tlet items = parseParameterItems(record.Items);\n\tlet recordSource = parseRecordSource(record.RecordSource);\n\tlet valueMember = record.ValueMember ?? null;\n\n\tif (recordSource && !valueMember) {\n\t\tvalueMember = recordSource.columns[0]?.name ?? null;\n\t}\n\n\t/*\n\t * The designer has no display member: the Windows lookup grid shows every\n\t * selected column. On the web a combobox shows one, so the first column that\n\t * is not the value is used.\n\t */\n\tlet displayMember = recordSource?.columns.find((column) => column.name !== valueMember)?.name ?? null;\n\tlet hasLookup = Boolean(recordSource && valueMember);\n\n\treturn {\n\t\tname,\n\t\tlabel: record.Label?.trim() || name,\n\t\tdescription: record.Description?.trim() || null,\n\t\ttype,\n\t\teditor: toParameterEditor(type, hasLookup, items.length > 0),\n\t\tdefaultValue: record.DefaultValue ?? null,\n\t\titems,\n\t\trecordSource: hasLookup ? recordSource : null,\n\t\tvalueMember: hasLookup ? valueMember : null,\n\t\tdisplayMember: hasLookup ? displayMember : null,\n\t\tsortOrder: record.SortOrder ?? 0,\n\t\tlookupWidth: record.LookupWidth ?? null,\n\t\tlookupHeight: record.LookupHeight ?? null,\n\t};\n}\n\n/** Maps and sorts a report's parameter records. */\nexport function toReportParameters(records: ReportParameterRecord[]): ReportParameter[] {\n\treturn records\n\t\t.filter((record) => record.Name)\n\t\t.map(toReportParameter)\n\t\t.sort((a, b) => a.sortOrder - b.sortOrder || a.label.localeCompare(b.label));\n}\n\n/** Converts a raw value to the type a parameter expects. */\nexport function coerceParameterValue(\n\tparameter: ReportParameter,\n\tvalue: ReportParameterValue | undefined,\n): ReportParameterValue {\n\tif (value === null || value === undefined || value === \"\") {\n\t\treturn parameter.type === \"Boolean\" ? false : null;\n\t}\n\n\tswitch (parameter.type) {\n\t\tcase \"Boolean\":\n\t\t\treturn typeof value === \"boolean\" ? value : /^(true|1|ja|yes)$/i.test(String(value));\n\t\tcase \"Integer\": {\n\t\t\tlet parsed = Number.parseInt(String(value), 10);\n\n\t\t\treturn Number.isNaN(parsed) ? null : parsed;\n\t\t}\n\t\tcase \"Double\": {\n\t\t\t// Norwegian keyboards produce a comma, which `Number` does not accept.\n\t\t\tlet parsed = Number(String(value).replace(\",\", \".\"));\n\n\t\t\treturn Number.isNaN(parsed) ? null : parsed;\n\t\t}\n\t\tdefault:\n\t\t\treturn String(value);\n\t}\n}\n\n/**\n * The initial values for a set of parameters.\n *\n * Defaults naming a stored procedure are left unset: they have to be resolved\n * server-side, and the caller fills them in once they are.\n */\nexport function getDefaultParameterValues(parameters: ReportParameter[]): ReportParameterValues {\n\tlet values: ReportParameterValues = {};\n\n\tfor (let parameter of parameters) {\n\t\tvalues[parameter.name] = isProcedureDefault(parameter.defaultValue)\n\t\t\t? coerceParameterValue(parameter, null)\n\t\t\t: coerceParameterValue(parameter, parameter.defaultValue);\n\t}\n\n\treturn values;\n}\n\nfunction toXmlValue(parameter: ReportParameter, value: ReportParameterValue): string {\n\tif (value === null || value === undefined) return \"\";\n\t// The Windows client writes booleans the way .NET does.\n\tif (typeof value === \"boolean\") return value ? \"True\" : \"False\";\n\n\treturn String(value);\n}\n\n/**\n * Serializes parameter values the way the Windows filter builder does, so that\n * a filter saved on the web is readable in the Windows client and the other way\n * around.\n *\n * ```xml\n * <Parameters Version=\"1\">\n * <Parameter Name=\"Period\" Type=\"Integer\">202601</Parameter>\n * </Parameters>\n * ```\n */\nexport function serializeParameters(parameters: ReportParameter[], values: ReportParameterValues): string {\n\tlet entries = parameters.map((parameter) => {\n\t\tlet value = toXmlValue(parameter, values[parameter.name] ?? null);\n\n\t\treturn ` <Parameter Name=\"${encodeXml(parameter.name)}\" Type=\"${parameter.type}\">${encodeXml(value)}</Parameter>`;\n\t});\n\n\treturn `<Parameters Version=\"1\">\\n${entries.join(\"\\n\")}\\n</Parameters>`;\n}\n\n/**\n * Reads the values back out of the XML stored on a saved filter. Values are\n * returned as strings and coerced against the report's own parameters by\n * `restoreParameterValues`, since the XML may name parameters the report no\n * longer has.\n */\nexport function parseParameters(xml: string | null | undefined): Record<string, string> {\n\tif (!xml) return {};\n\n\tlet values: Record<string, string> = {};\n\n\tfor (let match of xml.matchAll(/<Parameter\\s+([^>]*?)\\/?>(?:([\\s\\S]*?)<\\/Parameter>)?/g)) {\n\t\tlet name = /Name\\s*=\\s*\"([^\"]*)\"/.exec(match[1])?.[1];\n\t\tif (!name) continue;\n\n\t\tvalues[name] = readElement(match[0], \"Parameter\") ?? \"\";\n\t}\n\n\treturn values;\n}\n\n/**\n * Applies the values stored on a saved filter to a report's parameters, keeping\n * the defaults for anything the filter does not mention.\n */\nexport function restoreParameterValues(\n\tparameters: ReportParameter[],\n\txml: string | null | undefined,\n): ReportParameterValues {\n\tlet stored = parseParameters(xml);\n\tlet values = getDefaultParameterValues(parameters);\n\n\tfor (let parameter of parameters) {\n\t\tif (parameter.name in stored) {\n\t\t\tvalues[parameter.name] = coerceParameterValue(parameter, stored[parameter.name]);\n\t\t}\n\t}\n\n\treturn values;\n}\n","/**\n * Resolving report parameter defaults that name a stored procedure.\n *\n * A `DefaultValue` matching `^[a-z]stp_` names a procedure that produces the\n * default rather than being the default itself. Executing it is a server\n * concern, and the framework already exposes it through the `WebsiteCMS`\n * `api/reporting/filters` route.\n */\n\nimport { isProcedureDefault } from \"./parameters.js\";\nimport type { ReportParameter, ReportParameterValue, ReportParameterValues } from \"./types.js\";\n\n/** A result set as the reporting route serializes it. */\ntype ProcedureResult = Record<string, unknown[]> | unknown[];\n\n/**\n * Runs the stored procedure a parameter's default names and returns the value\n * it produces — the first column of the first row, the way the Windows launcher\n * reads it.\n *\n * Returns `null` when the parameter has no procedure default or the procedure\n * produced no rows.\n */\nexport async function fetchParameterDefault(\n\treportId: string,\n\tparameter: ReportParameter,\n\tsignal?: AbortSignal,\n): Promise<ReportParameterValue> {\n\tif (!parameter.defaultValue || !isProcedureDefault(parameter.defaultValue)) return null;\n\n\tlet response = await fetch(`/api/reporting/filters/${encodeURIComponent(reportId)}/GetParameterDefaultValue`, {\n\t\tbody: JSON.stringify({ parameterName: parameter.name }),\n\t\tcredentials: \"same-origin\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t// The route rejects anything that is not an ajax request, as CSRF\n\t\t\t// protection.\n\t\t\t\"X-Requested-With\": \"XMLHttpRequest\",\n\t\t},\n\t\tmethod: \"POST\",\n\t\tsignal,\n\t});\n\n\tif (!response.ok) throw new Error(`${response.status} ${response.statusText}`);\n\n\tlet result = (await response.json()) as ProcedureResult;\n\n\treturn firstValue(result);\n}\n\n/**\n * Resolves every procedure default of a report in parallel and merges the\n * results over the literal defaults.\n *\n * A parameter whose procedure fails is left at its literal default rather than\n * failing the whole form — the user can still type a value.\n */\nexport async function resolveParameterDefaults(\n\treportId: string,\n\tparameters: ReportParameter[],\n\tliteralDefaults: ReportParameterValues,\n\tsignal?: AbortSignal,\n): Promise<ReportParameterValues> {\n\tlet dynamic = parameters.filter((parameter) => isProcedureDefault(parameter.defaultValue ?? \"\"));\n\n\tif (dynamic.length === 0) return literalDefaults;\n\n\tlet resolved = await Promise.all(\n\t\tdynamic.map(async (parameter) => {\n\t\t\ttry {\n\t\t\t\treturn [parameter.name, await fetchParameterDefault(reportId, parameter, signal)] as const;\n\t\t\t} catch {\n\t\t\t\treturn [parameter.name, null] as const;\n\t\t\t}\n\t\t}),\n\t);\n\n\tlet values: ReportParameterValues = { ...literalDefaults };\n\n\tfor (let [name, value] of resolved) {\n\t\tif (value !== null) values[name] = value;\n\t}\n\n\treturn values;\n}\n\n/** The first column of the first row of whichever result set came back. */\nfunction firstValue(result: ProcedureResult): ReportParameterValue {\n\tlet rows = Array.isArray(result) ? result : Object.values(result).find((value) => Array.isArray(value));\n\tlet row = rows?.[0];\n\n\tif (row === undefined || row === null) return null;\n\tif (Array.isArray(row)) return (row[0] as ReportParameterValue) ?? null;\n\tif (typeof row === \"object\") {\n\t\tlet values = Object.values(row as Record<string, unknown>);\n\n\t\treturn (values[0] as ReportParameterValue) ?? null;\n\t}\n\n\treturn row as ReportParameterValue;\n}\n","/**\n * Rendering a report through the framework's report route.\n *\n * The route is a CMS code module owned by the Appframe framework\n * (`WebsiteCMS.Reports`), registered on\n * `api/report/{ReportID}/{FileName}.{Extension}`. Everything here builds\n * requests for it — there is no report endpoint of our own.\n */\n\nimport { downloadRequest, type DownloadItem } from \"../downloadRequest.js\";\nimport type { ReportFormat, ReportParameterValues } from \"./types.js\";\n\n/**\n * The file extension each format is requested with. The route only accepts\n * `(pdf|csv|xlsx?|html|xbm|mht|rtf|txt)`, which is why `TEXT` is `txt` and\n * `IMAGE` is `xbm` rather than the format name.\n */\nconst REPORT_EXTENSIONS: Record<ReportFormat, string> = {\n\tCSV: \"csv\",\n\tHTML: \"html\",\n\tIMAGE: \"xbm\",\n\tMHT: \"mht\",\n\tPDF: \"pdf\",\n\tRTF: \"rtf\",\n\tTEXT: \"txt\",\n\tXLS: \"xls\",\n\tXLSX: \"xlsx\",\n};\n\n/** The media type each format is served as, for previewing a rendered report. */\nconst REPORT_MEDIA_TYPES: Record<ReportFormat, string> = {\n\tCSV: \"text/csv\",\n\tHTML: \"text/html\",\n\tIMAGE: \"image/x-xbitmap\",\n\tMHT: \"message/rfc822\",\n\tPDF: \"application/pdf\",\n\tRTF: \"text/enriched\",\n\tTEXT: \"text/plain\",\n\tXLS: \"application/vnd.ms-excel\",\n\tXLSX: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n};\n\nexport type ReportRenderOptions = {\n\treportId: string;\n\tformat: ReportFormat;\n\t/** File name without an extension. Defaults to the report ID. */\n\tfileName?: string;\n\t/** The filter string the report is run with. */\n\tfilter?: string | null;\n\t/** Parameter values by name. Only non-empty values are sent. */\n\tparameters?: ReportParameterValues | null;\n\t/** Text for the report's optional header band. */\n\theaderText?: string | null;\n\t/** Hides the filter string printed on the report. */\n\thideFilterString?: boolean;\n\t/** Runs the report in another domain than the current one. */\n\tdomain?: string | null;\n\t/** Timezone offset in hours or minutes. Defaults to the browser's. */\n\ttimezone?: number | null;\n\tsignal?: AbortSignal;\n};\n\n/** The extension a format is requested with. */\nexport function getReportExtension(format: ReportFormat): string {\n\treturn REPORT_EXTENSIONS[format];\n}\n\n/** The media type a format is served as. */\nexport function getReportMediaType(format: ReportFormat): string {\n\treturn REPORT_MEDIA_TYPES[format];\n}\n\n/**\n * Characters that must not reach the file name, because it is a path segment of\n * the route: separators, the extension delimiter, the query and fragment\n * delimiters, and control characters.\n */\n// oxlint-disable-next-line no-control-regex -- control characters are the point\nconst UNSAFE_FILE_NAME_CHARACTERS = /[\\\\/:*?\"<>|.#&+%\\u0000-\\u001f]/g;\n\n/**\n * The name a rendered report is downloaded as, including its extension.\n *\n * The file name is part of the route rather than a query parameter, so anything\n * that would change the path is replaced.\n */\nexport function getReportFileName(options: Pick<ReportRenderOptions, \"reportId\" | \"format\" | \"fileName\">): string {\n\tlet name = (options.fileName ?? options.reportId).trim();\n\tlet safe = name.replace(UNSAFE_FILE_NAME_CHARACTERS, \"-\").replace(/-{2,}/g, \"-\").replace(/^-|-$/g, \"\").trim();\n\n\treturn `${safe === \"\" ? options.reportId : safe}.${getReportExtension(options.format)}`;\n}\n\n/**\n * The route is always on the same origin, but a `Request` needs an absolute\n * URL, so the path is resolved against the document when there is one.\n */\nfunction toRequestUrl(path: string): string {\n\tlet base = globalThis.location?.href;\n\n\treturn base ? new URL(path, base).toString() : path;\n}\n\n/**\n * The request that renders a report.\n *\n * A report without parameters is a plain `GET`, so it can be opened directly in\n * a new tab. As soon as there are parameters or a header the options go in a\n * JSON body, which the route only reads from a `POST`.\n *\n * `filter` is always sent alongside `parameters`: the route reads the filter out\n * of the body *guarded by the presence of `parameters`*, and reads it with an\n * indexer that throws when the key is missing.\n */\nexport function getReportRequest(options: ReportRenderOptions): Request {\n\tlet { domain, filter, headerText, hideFilterString, parameters, reportId, signal, timezone } = options;\n\tlet fileName = getReportFileName(options);\n\tlet url = `/api/report/${encodeURIComponent(reportId)}/${encodeURIComponent(fileName)}`;\n\tlet query = new URLSearchParams();\n\tlet entries = toParameterEntries(parameters);\n\tlet needsBody = entries.length > 0 || Boolean(headerText) || hideFilterString === true;\n\n\tif (domain) query.set(\"Domain\", domain);\n\tquery.set(\"timezone\", String(timezone ?? -new Date().getTimezoneOffset() / 60));\n\n\tif (!needsBody) {\n\t\tif (filter) query.set(\"Filter\", filter);\n\n\t\treturn new Request(toRequestUrl(`${url}?${query}`), { credentials: \"same-origin\", signal });\n\t}\n\n\tlet body: Record<string, unknown> = {\n\t\tfilter: filter ?? \"\",\n\t\tparameters: entries,\n\t};\n\n\tif (headerText) body.optionalheader = headerText;\n\t// The route assigns `showfiltercriteria` straight to the report's\n\t// `HideFilterString`, so despite its name it *hides* the filter string.\n\tif (hideFilterString === true) body.showfiltercriteria = \"true\";\n\n\treturn new Request(toRequestUrl(`${url}?${query}`), {\n\t\tbody: JSON.stringify(body),\n\t\tcredentials: \"same-origin\",\n\t\theaders: { \"Content-Type\": \"application/json\", \"X-Requested-With\": \"XMLHttpRequest\" },\n\t\tmethod: \"POST\",\n\t\tsignal,\n\t});\n}\n\n/**\n * Renders a report and resolves with the rendered file.\n *\n * The route answers with a JSON body carrying an `error` when rendering fails,\n * rather than with a failing status, so a JSON response is turned into an error\n * here.\n */\nexport async function renderReport(options: ReportRenderOptions): Promise<Blob> {\n\tlet response = await fetch(getReportRequest(options));\n\tlet contentType = response.headers.get(\"content-type\") ?? \"\";\n\n\tif (contentType.includes(\"application/json\")) {\n\t\tlet data = (await response.json()) as { error?: string };\n\n\t\tthrow new Error(data.error ?? \"Failed to render report\");\n\t}\n\n\tif (!response.ok) throw new Error(`${response.status} ${response.statusText}`);\n\n\treturn response.blob();\n}\n\n/** Renders a report and saves it, reporting progress as it downloads. */\nexport function downloadReport(\n\toptions: ReportRenderOptions & { onProgress?: (progress: DownloadItem) => void },\n): Promise<void> {\n\treturn downloadRequest(getReportRequest(options), {\n\t\tfileName: getReportFileName(options),\n\t\tonProgress: options.onProgress,\n\t\tsignal: options.signal,\n\t});\n}\n\n/**\n * Renders a report and opens it in a new tab.\n *\n * The blob is rendered first rather than pointing a tab at the route, so a\n * failure surfaces as an error here instead of as a tab showing JSON, and so\n * reports with parameters — which have to be posted — can be previewed too.\n */\nexport async function openReport(options: ReportRenderOptions): Promise<void> {\n\tlet blob = await renderReport(options);\n\t// The route serves some formats as types a browser downloads rather than\n\t// displays; the format's own media type is what decides how it opens.\n\tlet url = URL.createObjectURL(blob.type ? blob : new Blob([blob], { type: getReportMediaType(options.format) }));\n\t// Not opened with the `noopener` feature: `open` then returns null even when\n\t// the tab opens, which read as a blocked pop-up and revoked the URL before\n\t// the tab could load it. The opener is cut off by hand instead.\n\tlet opened = globalThis.open(url, \"_blank\");\n\n\tif (!opened) {\n\t\tURL.revokeObjectURL(url);\n\t\tthrow new Error(\"The report could not be opened. Allow pop-ups for this site and try again.\");\n\t}\n\n\topened.opener = null;\n\n\t// Revoking immediately would cancel the load in some browsers, and never\n\t// revoking keeps the whole rendered report in memory.\n\tglobalThis.setTimeout(() => URL.revokeObjectURL(url), 60_000);\n}\n\n/**\n * Renders a report and opens the browser's print dialog for it.\n *\n * The report is loaded into an offscreen iframe rather than a new tab, so the\n * print dialog opens over the launcher and the user is not left with a stray\n * tab afterwards. Browsers that will not print an embedded document fall back\n * to opening it, which leaves the user one keystroke from printing.\n */\nexport async function printReport(options: ReportRenderOptions): Promise<void> {\n\tlet document_ = globalThis.document;\n\tif (!document_?.body) return openReport(options);\n\n\tlet blob = await renderReport(options);\n\tlet url = URL.createObjectURL(blob.type ? blob : new Blob([blob], { type: getReportMediaType(options.format) }));\n\tlet frame = document_.createElement(\"iframe\");\n\t// Hidden with `visibility` rather than `display: none`: a frame that is not\n\t// laid out has nothing to print in some browsers.\n\tframe.setAttribute(\"aria-hidden\", \"true\");\n\tframe.style.cssText =\n\t\t\"position:fixed;inset-block-start:0;inset-inline-start:0;inline-size:1px;block-size:1px;border:0;visibility:hidden\";\n\n\tlet cleanUp = () => {\n\t\tframe.remove();\n\t\tURL.revokeObjectURL(url);\n\t};\n\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tframe.addEventListener(\"load\", () => resolve(), { once: true });\n\t\t\tframe.addEventListener(\"error\", () => reject(new Error(\"The report could not be loaded for printing\")), {\n\t\t\t\tonce: true,\n\t\t\t});\n\t\t\tframe.src = url;\n\t\t\tdocument_.body.append(frame);\n\t\t});\n\n\t\tframe.contentWindow?.focus();\n\t\tframe.contentWindow?.print();\n\t} catch {\n\t\tcleanUp();\n\n\t\treturn openReport(options);\n\t}\n\n\t// The print dialog is modal but not awaitable, and revoking the URL or\n\t// removing the frame while it is open cancels the job.\n\tglobalThis.setTimeout(cleanUp, 60_000);\n}\n\n/**\n * Parameters in the shape the route expects — `{ name, value }` objects.\n *\n * Empty values are dropped: the route skips them anyway, and a report falls back\n * to its own default when a parameter is not sent.\n */\nfunction toParameterEntries(parameters: ReportParameterValues | null | undefined) {\n\tif (!parameters) return [];\n\n\treturn Object.entries(parameters)\n\t\t.filter(([, value]) => value !== null && value !== undefined && value !== \"\")\n\t\t.map(([name, value]) => ({ name, value: typeof value === \"boolean\" ? String(value) : value }));\n}\n","/**\n * Mapping a `sviw_WinClient_MyReports` record onto the report model, including\n * which output formats the report offers.\n */\n\nimport { parseRecordSource } from \"./recordSource.js\";\nimport type { Report, ReportFormat, ReportRecord } from \"./types.js\";\n\nfunction isSet(value: boolean | number | null | undefined) {\n\treturn value === true || value === 1;\n}\n\n/**\n * The formats a report may be rendered to, in the order the launcher offers\n * them.\n *\n * `FormatCSV` covers the whole spreadsheet family: the Windows launcher enables\n * \"save as XLS\" and \"save as XLSX\" from the same flag, because they all come out\n * of the same DevExpress export.\n */\nexport function getReportFormats(record: ReportRecord): ReportFormat[] {\n\tlet formats: ReportFormat[] = [];\n\n\tif (isSet(record.FormatPDF)) formats.push(\"PDF\");\n\tif (isSet(record.FormatCSV)) formats.push(\"XLSX\", \"XLS\", \"CSV\");\n\tif (isSet(record.FormatHTML)) formats.push(\"HTML\");\n\tif (isSet(record.FormatRTF)) formats.push(\"RTF\");\n\tif (isSet(record.FormatMHT)) formats.push(\"MHT\");\n\tif (isSet(record.FormatTEXT)) formats.push(\"TEXT\");\n\tif (isSet(record.FormatIMAGE)) formats.push(\"IMAGE\");\n\n\treturn formats;\n}\n\n/** Maps a report record onto the model the launcher renders. */\nexport function toReport(record: ReportRecord): Report {\n\tlet recordSource = parseRecordSource(record.RecordSource);\n\n\treturn {\n\t\treportId: record.ReportID ?? \"\",\n\t\tshortId: record.ShortID ?? null,\n\t\ttitle: record.Title?.trim() || null,\n\t\tdescription: record.Description?.trim() || null,\n\t\tviewName: recordSource?.viewName ?? null,\n\t\tinitialFilter: recordSource?.filterString ?? null,\n\t\thasTitle: isSet(record.HasTitle),\n\t\thasHeader: isSet(record.HasHeader),\n\t\tformats: getReportFormats(record),\n\t\ttimeout: record.Timeout ?? null,\n\t};\n}\n","import { getLocalizedString } from \"./getLocalizedString.js\";\n\nconst template = `\n\t<dialog class=\"alert-dialog\">\n\t\t\t<form method=\"dialog\">\n\t\t\t\t\t<article></article>\n\t\t\t\t\t<footer>\n\t\t\t\t\t\t\t<menu></menu>\n\t\t\t\t\t</footer>\n\t\t\t</form>\n\t</dialog>\n`;\n\n/*\n * The module can be loaded from `<head>`, where there is no body to append to\n * yet. Waiting for the DOM in that case keeps the whole bundle from throwing on\n * load — everything else it exports would be lost with it.\n */\nfunction addDialogTemplate() {\n\tif (document.querySelector(\"#AlertDialogTemplate\")) return;\n\n\tlet $template = document.createElement(\"template\");\n\t$template.id = \"AlertDialogTemplate\";\n\t$template.innerHTML = template;\n\tdocument.body.append($template);\n}\n\nif (globalThis.document) {\n\tif (document.body) {\n\t\taddDialogTemplate();\n\t} else {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", addDialogTemplate, { once: true });\n\t}\n}\n\nexport function waitForDialog(dialog: HTMLDialogElement, cancelValue = \"No\"): Promise<string> {\n\treturn new Promise((resolve) => {\n\t\tlet controller = new AbortController();\n\n\t\tdialog.addEventListener(\n\t\t\t\"close\",\n\t\t\t() => {\n\t\t\t\tresolve(dialog.returnValue);\n\t\t\t\tcontroller.abort();\n\t\t\t},\n\t\t\t{ signal: controller.signal },\n\t\t);\n\n\t\tdialog.addEventListener(\n\t\t\t\"cancel\",\n\t\t\t() => {\n\t\t\t\tresolve(cancelValue);\n\t\t\t\tcontroller.abort();\n\t\t\t},\n\t\t\t{ signal: controller.signal },\n\t\t);\n\t});\n}\nexport type AlertDialogButton = {\n\tlabel: string;\n\tvalue: string;\n\tprimary?: boolean;\n};\n\nexport type AlertDialogOptions = {\n\tbuttons: AlertDialogButton[];\n\tcontent: string | HTMLElement;\n\tclassName?: string;\n\tid?: string;\n\ttitle?: string;\n};\n\nconst alertDialogPromiseMap = new Map<string, Promise<string>>();\n\nexport async function openAlertDialog({ buttons, content, className, id, title }: AlertDialogOptions) {\n\tif (id && alertDialogPromiseMap.has(id)) {\n\t\treturn await alertDialogPromiseMap.get(id);\n\t}\n\n\tlet $template = document.querySelector<HTMLTemplateElement>(\"#AlertDialogTemplate\");\n\tif (!$template) throw Error(\"No alert dialog template found\");\n\n\tlet $dialog = $template.content.firstElementChild?.cloneNode(true) as HTMLDialogElement | null;\n\tlet $content = $dialog?.querySelector(\"article\");\n\tlet $menu = $dialog?.querySelector(\"footer menu\");\n\n\tif (!$dialog || !$content || !$menu) throw Error(\"Invalid alert dialog template\");\n\n\tif (className) {\n\t\tfor (const className_ of className.split(/\\s+/)) {\n\t\t\t$dialog?.classList.add(className_);\n\t\t}\n\t}\n\n\tif (title) {\n\t\tlet $header = document.createElement(\"header\");\n\t\t$header.append(title);\n\t\t$content.parentNode?.insertBefore($header, $content);\n\t}\n\n\t$content.appendChild(typeof content === \"string\" ? document.createTextNode(content) : content);\n\tfor (let { label, value, primary } of buttons) {\n\t\tlet listItem = document.createElement(\"li\");\n\t\tlet buttonElem = document.createElement(\"button\");\n\t\tbuttonElem.value = value;\n\t\tif (primary) {\n\t\t\tbuttonElem.classList.add(\"primary\");\n\t\t}\n\t\tbuttonElem.textContent = label;\n\t\tlistItem.appendChild(buttonElem);\n\t\t$menu.appendChild(buttonElem);\n\t}\n\n\tdocument.body.appendChild($dialog);\n\t$dialog.showModal();\n\n\tlet dialogPromise = waitForDialog($dialog).then((returnValue) => {\n\t\tif (id && alertDialogPromiseMap.has(id)) {\n\t\t\talertDialogPromiseMap.delete(id);\n\t\t}\n\t\tif ($dialog) document.body.removeChild($dialog);\n\t\treturn returnValue;\n\t});\n\n\tif (id) {\n\t\talertDialogPromiseMap.set(id, dialogPromise);\n\t}\n\n\treturn await dialogPromise;\n}\n\nexport enum ConfirmButtons {\n\tYesNo = 0,\n\tOkCancel = 1,\n}\n\nexport type ConfirmOptions = {\n\tbuttons?: ConfirmButtons;\n\ttitle?: string;\n};\n\nexport async function confirm(prompt: string, options: ConfirmOptions = {}): Promise<boolean> {\n\tlet { buttons: optionButtons = ConfirmButtons.YesNo, title } = options;\n\tlet buttons: AlertDialogButton[] =\n\t\toptionButtons === ConfirmButtons.OkCancel\n\t\t\t? [\n\t\t\t\t\t{ label: getLocalizedString(\"OK\"), value: \"yes\", primary: true },\n\t\t\t\t\t{ label: getLocalizedString(\"Cancel\"), value: \"no\" },\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t{ label: getLocalizedString(\"Yes\"), value: \"yes\", primary: true },\n\t\t\t\t\t{ label: getLocalizedString(\"No\"), value: \"no\" },\n\t\t\t\t];\n\n\tlet result = await openAlertDialog({ content: prompt, buttons, className: \"alert-dialog-confirm\", title });\n\treturn result === \"yes\";\n}\n\nexport type AlertOptions = {\n\ttitle?: string;\n};\n\nexport async function alert(prompt: string, options: AlertOptions = {}): Promise<void> {\n\tawait openAlertDialog({\n\t\tcontent: prompt,\n\t\tclassName: \"alert-dialog-alert\",\n\t\tbuttons: [{ label: getLocalizedString(\"OK\"), value: \"ok\", primary: true }],\n\t\t...options,\n\t});\n}\n","function getKey(article?: string) {\n\tconst articleKey =\n\t\tarticle ?? globalThis.af?.article?.id ?? globalThis.window?.location.pathname.substring(1).split(\"/\")[0];\n\treturn `${articleKey}.${globalThis.af?.userSession.login ?? \"anonymous\"}`;\n}\n\nexport function getSettings<T>(setting: string, article?: string): T | null {\n\tif (!globalThis.af?.userSession) return null;\n\n\tconst key = getKey(article);\n\tconst settings = JSON.parse(localStorage.getItem(key) || \"{}\");\n\n\tif (setting) {\n\t\treturn settings[setting] || null;\n\t}\n\n\treturn settings;\n}\n\nexport function saveSettings<T>(setting: string, value: T | null, article?: string): void {\n\tif (!globalThis.af?.userSession) return;\n\n\tconst key = getKey(article);\n\tlet userSettings = JSON.parse(localStorage.getItem(key) || \"{}\");\n\n\tif (typeof value !== \"undefined\") {\n\t\tuserSettings[setting] = value;\n\t} else {\n\t\tuserSettings = setting;\n\t}\n\n\tlocalStorage.setItem(key, JSON.stringify(userSettings));\n}\n","import type { alert, confirm, openAlertDialog } from \"./alert.js\";\nimport { UserSession } from \"./UserSession.js\";\nimport \"./errorHandler.js\";\nimport type { expose } from \"./expose.js\";\nimport type { getLocalizedString } from \"./getLocalizedString.js\";\nimport type { localize } from \"./localize.js\";\nimport afLocalStorage from \"./localStorage.js\";\nimport type { getSessionData, logActivity, logError, startActivity, time } from \"./log.js\";\n\nexport { default as mitt } from \"mitt\";\nexport { toLocalDateTime, toLocalISODate, toLocalISODateTime } from \"./date.js\";\nexport { downloadBlob } from \"./downloadBlob.js\";\nexport type { DownloadItem, DownloadParameters } from \"./downloadRequest.js\";\nexport { downloadRequest } from \"./downloadRequest.js\";\nexport { expose } from \"./expose.js\";\nexport * from \"./filter/index.js\";\nexport { createLocalizer, getLocalizedString, type LocalizeOptions } from \"./getLocalizedString.js\";\nexport type { getUserImageOptions } from \"./getUserImage.js\";\nexport { getUserImage } from \"./getUserImage.js\";\nexport { invariant } from \"./invariant.js\";\nexport { localize } from \"./localize.js\";\nexport { afLocalStorage as localStorage };\nexport { getSessionData, logActivity, logError, startActivity, time } from \"./log.js\";\nexport * from \"./report/index.js\";\nexport { default as SessionHandler } from \"./SessionHandler.js\";\nexport { UserSession };\nexport type { AlertDialogButton, AlertDialogOptions, AlertOptions, ConfirmOptions } from \"./alert.js\";\nexport { alert, ConfirmButtons, confirm, openAlertDialog, waitForDialog } from \"./alert.js\";\nexport { getSettings, saveSettings } from \"./settings.js\";\n\n// oxlint-disable-next-line no-use-before-define -- this is injected build time\nexport const version = __VERSION__;\n\ndeclare global {\n\tvar __VERSION__: string;\n\tvar __DEV__: number;\n\tvar af: AfCore;\n\n\tinterface Window {\n\t\taf: AfCore;\n\t}\n}\n\nexport interface AfArticle {\n\thostName: string;\n\ti18n: Record<string, string>;\n\tid: string;\n\tversion?: string;\n}\n\nexport interface AfCore {\n\tarticle: AfArticle;\n\t/**\n\t * Strings of globalization projects other than the article's, by project name, for\n\t * code that runs on every page without belonging to an article (the site chrome).\n\t * Rendered server side or filled by the caller; see `getLocalizedString`'s `project`.\n\t */\n\ti18n?: Record<string, Record<string, string>>;\n\tcommon: {\n\t\tgetLocalizedString: typeof getLocalizedString;\n\t\texpose: typeof expose;\n\t\tgetSessionData: typeof getSessionData;\n\t\tlocalize: typeof localize;\n\t\tlocalStorage: typeof afLocalStorage;\n\t\tlogActivity: typeof logActivity;\n\t\tlogError: typeof logError;\n\t\tstartActivity: typeof startActivity;\n\t\talert: typeof alert;\n\t\tconfirm: typeof confirm;\n\t\topenAlertDialog: typeof openAlertDialog;\n\t\ttime: typeof time;\n\t\tversion?: string;\n\t};\n\tcontrols?: {\n\t\talert?: (message: string, callback?: () => void) => void;\n\t\tconfirm?: (options: {\n\t\t\ttitle: string;\n\t\t\tmessage: string;\n\t\t\tbuttons: string[];\n\t\t\tcallback: (button: number) => void;\n\t\t}) => void;\n\t};\n\tuserSession: UserSession;\n}\n"],"ignoreList":[0],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"af.common.min.js","names":["mitt","toBoolean","exports","mitt_default","n","i","e","_typeof","o","toPrimitive","t","toPropertyKey","_defineProperty","r","SessionHandler","authPromise","eventHandler","callback","resolve","dialog","link","type","eventName","handler","UserSession","userData","article","logActivity","response","activity","parameters","getSessionData","data","startActivity","start","startParameters","endParameters","time","logError","error","result","getSessionId","libVersion","pattern","name","url","urls","match","getPlatformVersions","versions","importMap","platform","chrome","script","sessionStart","sessionId","deviceId","browserSessionId","startSession","event","getLocalKey","key","localStorage_default","global","lsKey","value","defaultValue","baseKey","toLocalISODate","date","toLocalISODateTime","pad","num","digits","toLocalDateTime","downloadBlob","blob","parent","anchor","fileName","ex","reject","invariant","condition","provided","message","downloadRequest","bytesReceived","onProgress","request","reader","stream","pump","read","controller","err","signal","funcRegex","arrowBlockRegex","arrowExpressionRegex","parseArguments","args","a","parseFunc","funcString","func","arrowBlockMatch","arrowExpressionMatch","Namespace","requireNamespace","root","namespace","current","expose","funcName","keyName","pathParts","options","fetchLocalizedString","project","text","sourceFor","af","dictionary","getLocalizedString","string","waitOrOptions","source","promise","harvestAs","wait","createLocalizer","bound","localize","localized","stringParts","s","index","arg","PARAMETERLESS_OPERATORS","PERIOD_OPERATORS","PAIR_OPERATORS","LIST_OPERATORS","getOperatorArity","operator","OPERATOR_LABELS","DATE_OPERATOR_LABELS","getOperatorLabel","fieldType","EQUALITY","LIST_CHECKS","RANGE","COMPARISONS","STRING_CHECKS","OPERATORS_BY_TYPE","DEFAULT_OPERATOR_BY_TYPE","getOperatorsForField","operators","field","getDefaultOperator","getValueType","RELATIVE_DATE_VALUES","DYNAMIC_VALUE_LABELS","getDynamicValueLabel","getDynamicValuesForField","COLUMN_OPERATORS","supportsColumnValue","getComparableType","getColumnsForField","fields","other","OPERATOR_ALIASES","normalizeOperator","normalized","createEmptyFilter","createEmptyGroup","isGroup","item","isExpression","toFilterTree","filter","unwrapSingleAndGroup","only","isEmptyFilter","countExpressions","total","pruneFilter","isCompleteExpression","items","arity","end","isRangeEnd","v","DYNAMIC_VALUES_WITHOUT_FRAMEWORK_SUPPORT","needsConversion","valueType","needsFilterString","createExpression","op","createEmptyValue","changeOperator","previousArity","expression","holdsColumn","getNodeAtPath","node","path","updateNodeAtPath","updater","rest","child","updated","insertNodeAtPath","target","removeNodeAtPath","setGroupMode","addGroupAtPath","convertFilter","describeFilter","describe","describeExpression","parts","depth","part","separator","joined","caption","label","formatValue","formatted","formatScalar","FIELD_TYPES","toFilterFieldType","dataType","toBoolean$1","toFieldMetadata","record","toFieldMetadataList","records","b","isStandardLayout","coerceToStandardLayout","groups","expressions","collectExpressions","into","toSavedFilter","toAccessLevel","toBoolean","toDate","toSavedFilters","partitionSavedFilters","mine","shared","filters","findSavedFilter","filterId","canEditSavedFilter","canShareSavedFilter","canRenameSavedFilter","canDeleteSavedFilter","canSetCrossDomain","sizeMap","getUserImage","primKeyOrOptions","hash","sizePx","size","login","XML_ENTITIES","decodeXml","entity","encodeXml","readElement","xml","readElements","COLUMN_TYPES","readSelectColumns","block","columns","pickRecordSource","document","sources","parseRecordSource","viewName","maxRecords","PARAMETER_TYPES","PROCEDURE_DEFAULT","isProcedureDefault","parseParameterItems","toParameterType","toParameterEditor","hasLookup","hasItems","toReportParameter","recordSource","valueMember","displayMember","column","toReportParameters","coerceParameterValue","parameter","parsed","getDefaultParameterValues","values","toXmlValue","serializeParameters","parseParameters","restoreParameterValues","stored","fetchParameterDefault","reportId","firstValue","resolveParameterDefaults","dynamic","literalDefaults","resolved","row","REPORT_EXTENSIONS","REPORT_MEDIA_TYPES","getReportExtension","format","getReportMediaType","UNSAFE_FILE_NAME_CHARACTERS","getReportFileName","safe","toRequestUrl","base","getReportRequest","query","entries","toParameterEntries","needsBody","headerText","hideFilterString","domain","timezone","body","renderReport","downloadReport","openReport","opened","printReport","document_","frame","cleanUp","isSet","getReportFormats","formats","toReport","addDialogTemplate","$template","waitForDialog","cancelValue","alertDialogPromiseMap","openAlertDialog","id","$dialog","$content","$menu","className","className_","title","$header","content","buttons","listItem","buttonElem","primary","dialogPromise","returnValue","ConfirmButtons","\"YesNo\"","\"OkCancel\"","confirm","optionButtons","prompt","alert","getKey","getSettings","settings","setting","saveSettings","userSettings"],"sources":["../../../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs","../../src/SessionHandler.ts","../../src/UserSession.ts","../../src/log.ts","../../src/errorHandler.ts","../../src/localStorage.ts","../../src/date.ts","../../src/downloadBlob.ts","../../src/invariant.ts","../../src/downloadRequest.ts","../../src/parseFunc.ts","../../src/Namespace.ts","../../src/requireNamespace.ts","../../src/expose.ts","../../src/getLocalizedString.ts","../../src/filter/operators.ts","../../src/filter/tree.ts","../../src/filter/convertFilter.ts","../../src/filter/describeFilter.ts","../../src/filter/fields.ts","../../src/filter/standardLayout.ts","../../src/filter/savedFilters.ts","../../src/getUserImage.ts","../../src/localize.ts","../../src/report/recordSource.ts","../../src/report/parameters.ts","../../src/report/parameterDefaults.ts","../../src/report/render.ts","../../src/report/report.ts","../../src/alert.ts","../../src/settings.ts","../../src/index.ts"],"sourcesContent":["export default function(n){return{all:n=n||new Map,on:function(t,e){var i=n.get(t);i?i.push(e):n.set(t,[e])},off:function(t,e){var i=n.get(t);i&&(e?i.splice(i.indexOf(e)>>>0,1):n.set(t,[]))},emit:function(t,e){var i=n.get(t);i&&i.slice().map(function(n){n(e)}),(i=n.get(\"*\"))&&i.slice().map(function(n){n(t,e)})}}}\n//# sourceMappingURL=mitt.mjs.map\n","import mitt, { type Emitter, type Handler } from \"mitt\";\n\ntype SessionHandlerEvent = \"onExpired\" | \"onAuthenticated\";\n\nexport default class SessionHandler {\n\tprivate eventHandler: Emitter<{ onAuthenticated: undefined; onExpired: undefined }> = mitt();\n\n\tauthenticated() {\n\t\tthis.eventHandler.emit(\"onAuthenticated\");\n\t}\n\n\tasync expired(callback?: () => void, type?: \"RequireTwoFactor\"): Promise<void> {\n\t\ttry {\n\t\t\tlet authPromise = new Promise<void>((resolve) => {\n\t\t\t\tlet eventHandler = () => {\n\t\t\t\t\tthis.eventHandler.off(\"onAuthenticated\", eventHandler);\n\t\t\t\t\tcallback?.();\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\n\t\t\t\tthis.eventHandler.on(\"onAuthenticated\", eventHandler);\n\t\t\t});\n\n\t\t\tlet dialog: HTMLDialogElement | null = document.querySelector(\"dialog.session-expired-dialog\");\n\t\t\tif (!dialog) {\n\t\t\t\tdialog = document.createElement(\"dialog\");\n\t\t\t\tdialog.className = \"session-expired-dialog\";\n\t\t\t\tdialog.style.display = \"flex\";\n\t\t\t\tdialog.style.padding = \"0\";\n\t\t\t\tdialog.style.left = \"50%\";\n\t\t\t\tdialog.style.transform = \"translateX(-50%)\";\n\n\t\t\t\tlet link = \"/login?ReturnUrl=%2fapi/sessionexpired/remove\";\n\t\t\t\tif (type !== \"RequireTwoFactor\") {\n\t\t\t\t\tawait fetch(\"/logout\", { method: \"POST\" });\n\t\t\t\t} else {\n\t\t\t\t\tlink = \"/login?RequireTwoFactor=1&ReturnUrl=%2fapi/sessionexpired/remove\";\n\t\t\t\t}\n\n\t\t\t\tdialog.innerHTML = `<iframe sandbox=\"allow-same-origin allow-scripts allow-modals allow-top-navigation-by-user-activation allow-forms\" src=\"${link}\" style=\"border:0;width:min(500px,100vw);height:min(700px, 100vh);\"></iframe>`;\n\t\t\t\tdocument.body.append(dialog);\n\t\t\t\tdialog.showModal();\n\t\t\t\tawait authPromise;\n\t\t\t\tdialog.remove();\n\t\t\t} else {\n\t\t\t\tawait authPromise;\n\t\t\t}\n\t\t} catch (_) {\n\t\t\twindow.location.href = `/login?ReturnUrl=${encodeURIComponent(window.location.pathname)}${encodeURIComponent(\n\t\t\t\twindow.location.search,\n\t\t\t)}`;\n\t\t}\n\t}\n\n\tattachEvent(eventName: SessionHandlerEvent, handler: Handler) {\n\t\treturn this.eventHandler.on(eventName, handler);\n\t}\n\n\tdetachEvent(eventName: SessionHandlerEvent, handler: Handler) {\n\t\treturn this.eventHandler.off(eventName, handler);\n\t}\n}\n","import SessionHandler from \"./SessionHandler.js\";\n\nexport class UserSession extends SessionHandler {\n\tamDesignator = \"a.m.\";\n\tdayNames: string[] = [\"sunday\", \"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\", \"saturday\"];\n\tdayNamesShort: string[] = [\"sun\", \"mon\", \"tue\", \"wed\", \"thu\", \"fri\", \"sat\"];\n\tdecimalSeparator = \".\";\n\tdomain = \"OB\";\n\temail: string | null | undefined;\n\tformats: undefined | Record<string, string>;\n\tgroupSeparator: string | null = null;\n\tintegratedSecurity = false;\n\tisDeveloper = false;\n\tlogin = \"\";\n\tloginType: string | undefined;\n\tmonthNames: string[] = [\n\t\t\"january\",\n\t\t\"february\",\n\t\t\"march\",\n\t\t\"april\",\n\t\t\"may\",\n\t\t\"june\",\n\t\t\"july\",\n\t\t\"august\",\n\t\t\"september\",\n\t\t\"october\",\n\t\t\"november\",\n\t\t\"december\",\n\t\t\"\",\n\t];\n\tmonthNamesShort: string[] = [\"jan\", \"feb\", \"mar\", \"apr\", \"may\", \"jun\", \"jul\", \"augt\", \"sep\", \"oct\", \"nov\", \"dec\", \"\"];\n\tname: string | undefined;\n\tpmDesignator = \"p.m.\";\n\tshortDateFormat = \"dd.MM.yyyy\";\n\tculture = \"en-GB\";\n\tuiculture = \"en-GB\";\n\n\tconstructor(userData: any) {\n\t\tsuper();\n\t\tthis.formats = {};\n\t\tObject.assign(this, userData);\n\t}\n}\n","const article = globalThis.af?.article?.id ?? globalThis.window?.location.pathname.substring(1).split(\"/\")[0];\n\n/**\n * Logs an activity to the website activity log\n *\n * @param activity name of the activity we are logging\n * @param parameters A dictionary of parameters used when doing the activity\n * @param timeElapsed Number of milliseconds elapsed while executing\n */\nexport async function logActivity(activity: string, parameters: Record<string, any> = {}, timeElapsed = 0) {\n\ttry {\n\t\tlet response = await fetch(\"/api/logging/log-activity\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify({\n\t\t\t\tarticle,\n\t\t\t\ttimeElapsed,\n\t\t\t\taction: activity,\n\t\t\t\tparameters: {\n\t\t\t\t\t...parameters,\n\t\t\t\t\t_session: getSessionData(),\n\t\t\t\t},\n\t\t\t}),\n\t\t\theaders: { \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tif (response.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\tlet data = await response.json();\n\t\t\t\tconsole.error(data.error);\n\t\t\t} else {\n\t\t\t\tconsole.error(`${response.status} ${response.statusText}`);\n\t\t\t}\n\t\t}\n\t} catch {}\n}\n\n/**\n * Records the current time, and returns a function that logs the activity with the\n * time elapsed when it is called.\n *\n * @param activity Name of the activity we are logging\n * @param parameters A dictionary of parameters used when doing the activity\n * @returns A function that should be called when the activity is completed to log it to the website log\n */\nexport function startActivity(activity: string, startParameters: Record<string, any> = {}) {\n\tlet start = performance.now();\n\treturn (endParameters: Record<string, any> = {}) =>\n\t\tlogActivity(activity, { ...startParameters, ...endParameters }, performance.now() - start);\n}\n\n/**\n * Records the time used to execute an operation and returns the value of the operation and the time\n * in milliseconds\n */\nexport async function time<T>(callback: () => T | Promise<T>): Promise<[T, number]> {\n\tlet start = performance.now();\n\tlet returnValue = await callback();\n\n\treturn [returnValue, performance.now() - start];\n}\n\nexport async function logError(error: any) {\n\ttry {\n\t\tif (!error) return;\n\n\t\tlet data =\n\t\t\ttypeof error === \"string\"\n\t\t\t\t? { error: error }\n\t\t\t\t: typeof error === \"object\"\n\t\t\t\t\t? \"message\" in error\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\terror: error.message,\n\t\t\t\t\t\t\t\tstack: error.stack ?? null,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: { error: error.toString() }\n\t\t\t\t\t: { error: \"Unspecified error\" };\n\n\t\tconsole.error(error);\n\n\t\tlet result = await fetch(\"/data/Logger/LogError\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(data),\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json; charset=utf-8\",\n\t\t\t},\n\t\t});\n\n\t\tif (result.ok) {\n\t\t\treturn await result.json();\n\t\t}\n\n\t\tconsole.error(Error(\"Failed to log error\"));\n\t} catch {}\n}\n\nfunction getSessionId() {\n\treturn crypto.randomUUID?.();\n}\n\n/** The version in the first URL served under `/lib/@olenbetong/<name>/<version>/`. */\nfunction libVersion(name: string, urls: Iterable<unknown>) {\n\tlet pattern = new RegExp(`/lib/@olenbetong/${name}/([^/]+)/`);\n\tfor (let url of urls) {\n\t\tlet match = typeof url === \"string\" ? pattern.exec(url) : null;\n\t\tif (match) return match[1];\n\t}\n\treturn undefined;\n}\n\n/**\n * The synergi-chrome and synergi-platform versions the page loads on the 2026 site templates,\n * read from the chrome's script tag and the platform import map rather than from the chrome\n * itself, so they are known even when the chrome failed to start. Empty on other pages.\n */\nfunction getPlatformVersions() {\n\tlet versions: Record<string, string> = {};\n\ttry {\n\t\tlet importMap = document.querySelector('script[type=\"importmap\"]');\n\t\tlet platform = importMap\n\t\t\t? libVersion(\"synergi-platform\", Object.values(JSON.parse(importMap.textContent ?? \"{}\").imports ?? {}))\n\t\t\t: undefined;\n\t\tif (platform) versions[\"synergi-platform\"] = platform;\n\t} catch {}\n\tlet chrome =\n\t\tlibVersion(\n\t\t\t\"synergi-chrome\",\n\t\t\tArray.from(document.scripts, (script) => script.src),\n\t\t) ?? (globalThis as any).ob?.chrome?.version;\n\tif (chrome) versions[\"synergi-chrome\"] = chrome;\n\treturn versions;\n}\n\nlet sessionStart = Date.now();\nlet sessionId = getSessionId();\nlet deviceId: string | null = \"\";\nlet browserSessionId: string | null = \"\";\n\nif (\"document\" in globalThis) {\n\tlet startSession = () =>\n\t\tlogActivity(\"AfSessionInit\", {\n\t\t\t\"appframe-core\": globalThis.af?.common?.version,\n\t\t\t\"appframe-data\": (globalThis.af as any)?.data?.version,\n\t\t\t\"app-version\": globalThis.af?.article?.version,\n\t\t\t...getPlatformVersions(),\n\t\t});\n\n\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\tdeviceId = localStorage.getItem(\"_afDeviceId\");\n\t\tbrowserSessionId = sessionStorage.getItem(\"_afSessionId\");\n\t\tif (!deviceId) {\n\t\t\tdeviceId = getSessionId();\n\t\t\tlocalStorage.setItem(\"_afDeviceId\", deviceId);\n\t\t}\n\n\t\tif (!browserSessionId) {\n\t\t\tbrowserSessionId = sessionId;\n\t\t\tsessionStorage.setItem(\"_afSessionId\", browserSessionId);\n\t\t}\n\n\t\tstartSession();\n\t});\n\n\tdocument.addEventListener(\"visibilitychange\", function logData() {\n\t\tif (document.visibilityState === \"hidden\") {\n\t\t\tnavigator.sendBeacon(\n\t\t\t\t\"/api/logging/log-activity\",\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tarticle,\n\t\t\t\t\taction: \"AfSessionEnd\",\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\t_session: getSessionData(),\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tstartSession();\n\t\t}\n\t});\n}\n\n/**\n * Session and device ID's and information about session start and duration.\n * ID's will be null/undefined if crypto.randomUUID() is not supported.\n */\nexport type SessionData = {\n\t/**\n\t * Random UUID generated on page load, and persisted in sessionStorage\n\t */\n\tbrowserSessionId: string | null;\n\t/**\n\t * Random UUID generated on page load, and persisted in localStorage\n\t */\n\tdeviceId: string | null;\n\t/**\n\t * Time of page load in MS since epoch\n\t */\n\tsessionStart: number;\n\t/**\n\t * Time since page load (in MS) when this method was called\n\t */\n\tsessionLength: number;\n\t/**\n\t * Random UUID generated on page load. Not persisted.\n\t */\n\tsessionId: string | null;\n};\n\nexport function getSessionData(): SessionData {\n\treturn {\n\t\tbrowserSessionId,\n\t\tsessionStart,\n\t\tsessionLength: Date.now() - sessionStart,\n\t\tsessionId,\n\t\tdeviceId,\n\t};\n}\n","import { logError } from \"./log.js\";\n\nif (process.env.NODE_ENV === \"production\") {\n\tglobalThis.window?.addEventListener(\"error\", (event) => {\n\t\tif (typeof event === \"object\" && \"error\" in event) {\n\t\t\tlogError(event.error);\n\t\t} else if (typeof event === \"object\" && \"message\" in event) {\n\t\t\tlogError(event);\n\t\t}\n\t});\n}\n","function getLocalKey(key: string): string {\n\treturn [af?.article?.id || \"\", \"common\", key].join(\".\");\n}\n\nclass LocalStorage {\n\tisPersisted = () => true;\n\n\tgetLocalStorageKey(key: string, global?: boolean) {\n\t\treturn global ? [\"common\", key].join(\".\") : getLocalKey(key);\n\t}\n\n\tget(key: string, defaultValue: unknown, global?: boolean) {\n\t\tconst lsKey = this.getLocalStorageKey(key, global);\n\t\tconst value = localStorage.getItem(lsKey);\n\n\t\treturn value !== null ? JSON.parse(value) : defaultValue;\n\t}\n\n\tset(key: string, value: unknown, global?: boolean) {\n\t\tlocalStorage.setItem(this.getLocalStorageKey(key, global), JSON.stringify(value));\n\t}\n\n\tremove(key: string, global?: boolean) {\n\t\tconst lsKey = this.getLocalStorageKey(key, global);\n\t\tif (localStorage.getItem(lsKey) !== null) {\n\t\t\tlocalStorage.removeItem(lsKey);\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tclear() {\n\t\tconst baseKey = getLocalKey(\"\");\n\n\t\tfor (let key in localStorage) {\n\t\t\tif (key.slice(0, baseKey.length) === baseKey) {\n\t\t\t\tlocalStorage.removeItem(key);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport default new LocalStorage();\n","export function toLocalISODate(date: Date): string;\nexport function toLocalISODate(date: Date | null): string | null;\nexport function toLocalISODate(date: Date | null): string | null {\n\treturn date ? toLocalISODateTime(date).substring(0, 10) : null;\n}\n\nfunction pad(num: number, digits = 2) {\n\treturn String(num).padStart(digits, \"0\");\n}\n\n/**\n * Returns an ISO string (yyyy-MM-ddTHH:mm:ss.SSS) representing the local time\n * of the date object.\n *\n * @param date\n */\nexport function toLocalISODateTime(date: Date): string;\nexport function toLocalISODateTime(date: Date | null): string | null;\nexport function toLocalISODateTime(date: Date | null): string | null {\n\treturn date\n\t\t? `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(\n\t\t\t\tdate.getMinutes(),\n\t\t\t)}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`\n\t\t: null;\n}\n\n/**\n * Converts a date object to a string compatible with input type datetime-local.\n * Will handle the UTC value of the date as if it was local, to be compatible with\n * data objects.\n *\n * If null or undefined is passed, returns an empty string.\n *\n * @param date\n */\nexport function toLocalDateTime(date?: Date | null): string {\n\treturn date ? date.toISOString().substring(0, 16) : \"\";\n}\n","export function downloadBlob(blob: Blob | MediaSource, fileName: string) {\n\treturn new Promise<void>((resolve, reject) => {\n\t\ttry {\n\t\t\tconst url = URL.createObjectURL(blob);\n\t\t\tconst parent = document.body;\n\t\t\tconst anchor = document.createElement(\"a\");\n\t\t\tanchor.setAttribute(\"download\", fileName);\n\t\t\tanchor.href = url;\n\t\t\tparent.append(anchor);\n\t\t\tanchor.click();\n\t\t\tparent.removeChild(anchor);\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tURL.revokeObjectURL(url);\n\t\t\t\tresolve();\n\t\t\t}, 250);\n\t\t} catch (ex) {\n\t\t\treject(ex);\n\t\t}\n\t});\n}\n","/**\n * Throws an error if the condition is falsy. The provided message is used as the error message.\n * A function can be provided to lazily generate the message.\n *\n * @param {any} condition\n * @param {string | (() => string)} [message]\n * @returns {void}\n */\nexport function invariant(condition: any, message?: string | (() => string)): asserts condition {\n\tif (condition) {\n\t\treturn;\n\t}\n\tconst provided: string | undefined = typeof message === \"function\" ? message() : message;\n\n\tthrow new Error(provided ?? \"Invariant failed\");\n}\n","import { downloadBlob } from \"./downloadBlob.js\";\nimport { invariant } from \"./invariant.js\";\n\nexport type DownloadItem = {\n\tstatus: \"cancelled\" | \"error\" | \"requested\" | \"downloading\" | \"downloaded\" | \"completed\";\n\tbytesReceived?: number;\n\terror?: string;\n};\n\nexport type DownloadParameters = {\n\tsignal?: AbortSignal;\n\tfileName: string;\n\tonProgress?: (progress: DownloadItem) => void;\n};\n\nexport async function downloadRequest(request: Request, { fileName, signal, onProgress }: DownloadParameters) {\n\tlet bytesReceived = 0;\n\n\tonProgress?.({ status: \"requested\" });\n\n\ttry {\n\t\tlet response = await fetch(request);\n\n\t\tif (!response.ok || !response.body) {\n\t\t\tif (response.headers.get(\"content-type\")?.includes(\"application/json\")) {\n\t\t\t\tlet data = await response.json();\n\t\t\t\tinvariant(!data.error, data.error);\n\t\t\t}\n\t\t\tthrow new Error(`${response.status} ${response.statusText}`);\n\t\t}\n\n\t\ttry {\n\t\t\tconst reader = response.body.getReader();\n\t\t\tconst stream = new ReadableStream({\n\t\t\t\tstart: (controller) => {\n\t\t\t\t\tfunction pump(): Promise<void> {\n\t\t\t\t\t\treturn reader.read().then((read) => {\n\t\t\t\t\t\t\tif (read.done) {\n\t\t\t\t\t\t\t\tcontroller.close();\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbytesReceived += read.value.length;\n\t\t\t\t\t\t\tonProgress?.({ bytesReceived, status: \"downloading\" });\n\t\t\t\t\t\t\tcontroller.enqueue(read.value);\n\n\t\t\t\t\t\t\treturn pump();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\treturn pump();\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tresponse = new Response(stream);\n\t\t} catch {}\n\n\t\tconst blob = await response.blob();\n\n\t\tonProgress?.({ bytesReceived, status: \"downloaded\" });\n\n\t\tawait downloadBlob(blob, fileName);\n\n\t\tonProgress?.({ bytesReceived, status: \"completed\" });\n\t} catch (err) {\n\t\tif (signal?.aborted) {\n\t\t\tonProgress?.({ status: \"cancelled\" });\n\t\t} else {\n\t\t\tonProgress?.({ error: (err as Error).message, status: \"error\" });\n\t\t}\n\t}\n}\n","import { invariant } from \"./invariant.js\";\n\nconst funcRegex =\n\t/^function(?:\\s+([a-zA-Z$_][a-zA-Z0-9$_]*))?\\s*\\(((?:(?:\\s*[a-zA-Z$_][a-zA-Z0-9$_]*)(?:\\s*,\\s*[a-zA-Z$_][a-zA-Z0-9$_]*)*)?)\\s*\\)\\s*\\{((?:.|\\n|\\r)*)\\}$/;\nconst arrowBlockRegex = /^(?:\\(([^)]*)\\)|([a-zA-Z$_][a-zA-Z0-9$_]*))\\s*=>\\s*\\{([\\s\\S]*)\\}$/;\nconst arrowExpressionRegex = /^(?:\\(([^)]*)\\)|([a-zA-Z$_][a-zA-Z0-9$_]*))\\s*=>\\s*([\\s\\S]+)$/;\n\nconst parseArguments = (args: string) =>\n\targs\n\t\t.split(\",\")\n\t\t.map((a) => a.trim())\n\t\t.filter(Boolean);\n\nexport function parseFunc(func: (...args: any[]) => any) {\n\tconst funcString = func.toString();\n\tconst arrowBlockMatch = funcString.match(arrowBlockRegex);\n\n\tif (arrowBlockMatch) {\n\t\tconst args = parseArguments(arrowBlockMatch[1] ?? arrowBlockMatch[2] ?? \"\");\n\n\t\treturn { name: func.name, args, body: arrowBlockMatch[3] };\n\t}\n\n\tconst arrowExpressionMatch = funcString.match(arrowExpressionRegex);\n\n\tif (arrowExpressionMatch) {\n\t\tconst args = parseArguments(arrowExpressionMatch[1] ?? arrowExpressionMatch[2] ?? \"\");\n\n\t\treturn { name: func.name, args, body: arrowExpressionMatch[3] };\n\t}\n\n\tconst match = funcString.match(funcRegex);\n\n\tinvariant(match, \"Unable to parse function!\");\n\n\treturn { name: func.name ?? match[1], args: parseArguments(match[2]), body: match[3] };\n}\n","function Namespace() {}\nNamespace.prototype = Object.create(null);\n\nexport default Namespace;\n","import Namespace from \"./Namespace.js\";\n\nexport function requireNamespace(root: any, namespace: string | Array<string>): any {\n\tif (typeof root === \"object\") {\n\t\tif (typeof namespace === \"string\") {\n\t\t\tlet path = namespace.split(\".\");\n\n\t\t\treturn requireNamespace(root, path);\n\t\t} else if (Array.isArray(namespace)) {\n\t\t\tlet current: any = root;\n\t\t\tfor (let i = 0; i < namespace.length; i++) {\n\t\t\t\tcurrent = current[namespace[i]] = current[namespace[i]] || new (Namespace as any)();\n\t\t\t}\n\n\t\t\treturn current;\n\t\t}\n\t}\n}\n","import { invariant } from \"./invariant.js\";\nimport { parseFunc } from \"./parseFunc.js\";\nimport { requireNamespace } from \"./requireNamespace.js\";\n\nexport type ExposeOptions = {\n\t/**\n\t * Whether we should overwrite any existing values without warning.\n\t */\n\toverwrite?: boolean;\n};\n\n/**\n * Expose a value to a potentially deeply nested global variable\n * @param name {string | object} Either a dot-separated path to the global variable, or an object to expose the value on\n * @param value {object | Function | Function[]} The value(s) to expose\n */\nexport function expose(\n\tname: object | string,\n\tvalue: object | ((...args: any[]) => any) | ((...args: any[]) => any)[] | string | number,\n\toptions: ExposeOptions = {},\n): void {\n\tif (Array.isArray(value)) {\n\t\tfor (let func of value) {\n\t\t\tconst { name: funcName } = parseFunc(func);\n\n\t\t\tinvariant(\n\t\t\t\tfuncName,\n\t\t\t\t\"Unable to detect name of function! Name it, or only supply one function and include name in first parameter.\",\n\t\t\t);\n\t\t\texpose(name, func); // Expose func on namespace found in pName\n\t\t}\n\t} else {\n\t\tlet keyName: string | undefined;\n\t\tlet namespace: any;\n\n\t\tif (typeof value === \"function\") {\n\t\t\tkeyName = parseFunc(value as (...args: any[]) => any).name;\n\t\t}\n\n\t\tif (typeof name === \"string\") {\n\t\t\tlet pathParts = name.split(\".\");\n\n\t\t\tif (!keyName) {\n\t\t\t\tkeyName = pathParts[pathParts.length - 1];\n\t\t\t\tpathParts = pathParts.slice(0, pathParts.length - 1);\n\t\t\t}\n\n\t\t\tnamespace = requireNamespace(window, pathParts);\n\t\t} else if (keyName && (typeof name === \"object\" || typeof name === \"function\")) {\n\t\t\tnamespace = name;\n\t\t} else {\n\t\t\tinvariant(false, \"Invalid typeof parameter 'name', or unable to parse pValue!\");\n\t\t}\n\n\t\tif (Object.hasOwn(namespace, keyName) && options?.overwrite !== true) {\n\t\t\t// If class is already defined, warn or show an alert\n\t\t\tconsole.warn(`${[name, keyName].join(\".\")} is already defined!`);\n\t\t} else {\n\t\t\tnamespace[keyName] = value;\n\t\t}\n\t}\n}\n","/**\n * Localized strings come from two places on a page:\n *\n * - `af.article.i18n`: the strings the article's project uses, rendered by the framework\n * into every page of that article. A string missing there is harvested by posting it to\n * `/api/user/localize/new/<article>`, which registers it as used by the article and\n * returns the translation.\n * - `af.i18n[<project>]`: the strings of another project, for code that runs on every\n * page without belonging to any one article - the site chrome bundle, the shared\n * component packages. Such a project is a name, not an article; the harvest endpoint\n * accepts any name, so `localize/new/<project>` registers the string under that project.\n *\n * Keeping a project's strings apart from the article's matters when a word means\n * different things: \"Articles\" is \"Artikler\" in the chrome and \"Varer\" in an accounting\n * app, which the framework's project-specific base strings express only if the two are\n * looked up in different dictionaries.\n *\n * A project's dictionary is in play only when something put it there: the template\n * rendered it (`@Render(\"ChromeStrings\", Project: \"...\")`), or the caller fetched it and\n * assigned `af.i18n[project]` itself (an empty object counts). On a page that did neither -\n * a legacy template - the project falls back to the article's dictionary and the article's\n * harvesting, so the page behaves as it always did instead of posting every string of a\n * grid on every load. The project fills up from the pages that render it.\n */\n\nexport type LocalizeOptions = {\n\t/**\n\t * The globalization project the string belongs to. Defaults to the article, whose\n\t * strings the framework renders into `af.article.i18n`. Falls back to the article when\n\t * the page carries no dictionary for the project (see the module comment).\n\t */\n\tproject?: string;\n\t/** Resolve with the translation once it has been fetched, instead of the base text at once. */\n\twait?: boolean;\n};\n\nasync function fetchLocalizedString(text: string, project: string) {\n\ttry {\n\t\tlet response = await fetch(`/api/user/localize/new/${encodeURIComponent(project)}`, {\n\t\t\tbody: JSON.stringify({ text }),\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json; */*\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"X-Requested-With\": \"XMLHttpRequest\",\n\t\t\t},\n\t\t\tmethod: \"POST\",\n\t\t});\n\n\t\tif (response.ok) {\n\t\t\treturn (await response.json()) as string;\n\t\t}\n\t} catch {}\n\n\treturn text;\n}\n\ntype Source = {\n\t/** Where the string is looked up and the fetched translation is kept for the page. */\n\tdictionary: Record<string, string>;\n\t/** The name a missing string is harvested under: the project, or the article's id. */\n\tharvestAs: string;\n};\n\n/**\n * The dictionary a string is resolved in: the project's when the page carries one,\n * otherwise the article's; nothing when the page has no `af` at all.\n */\nfunction sourceFor(project: string | undefined): Source | undefined {\n\tlet af = globalThis.af;\n\tif (!af) return undefined;\n\n\tif (project) {\n\t\tlet dictionary = af.i18n?.[project];\n\t\tif (dictionary) return { dictionary, harvestAs: project };\n\t}\n\n\tlet article = af.article;\n\tif (!article?.i18n) return undefined;\n\treturn { dictionary: article.i18n, harvestAs: article.id };\n}\n\nexport function getLocalizedStringAsync(string: undefined | null): undefined | null;\nexport function getLocalizedStringAsync(string: string, options?: Omit<LocalizeOptions, \"wait\">): Promise<string>;\nexport function getLocalizedStringAsync(string: string | undefined | null, options?: Omit<LocalizeOptions, \"wait\">) {\n\tif (!string) return string;\n\n\treturn getLocalizedString(string, { ...options, wait: true });\n}\n\nexport function getLocalizedString(string: undefined | null): undefined | null;\nexport function getLocalizedString(string: string): string;\nexport function getLocalizedString(string: string, wait: false): string;\nexport function getLocalizedString(string: string, wait: true): Promise<string>;\nexport function getLocalizedString(string: undefined | null, wait: true): undefined | null;\nexport function getLocalizedString(string: string, options: LocalizeOptions & { wait: true }): Promise<string>;\nexport function getLocalizedString(string: string, options: LocalizeOptions): string;\nexport function getLocalizedString(string: undefined | null, options: LocalizeOptions): undefined | null;\nexport function getLocalizedString(\n\tstring: string | undefined | null,\n\twaitOrOptions?: boolean | LocalizeOptions,\n): string | undefined | null | Promise<string> {\n\tif (typeof string === \"string\" && string.trim().length === 0) {\n\t\treturn string;\n\t}\n\n\tif (string === undefined || string === \"\" || string === null) {\n\t\treturn;\n\t}\n\n\tlet { project, wait } = typeof waitOrOptions === \"object\" ? waitOrOptions : { wait: waitOrOptions };\n\tlet source = sourceFor(project);\n\tif (source === undefined) {\n\t\treturn string;\n\t}\n\n\tlet { dictionary, harvestAs } = source;\n\tif (!Object.hasOwn(dictionary, string)) {\n\t\tdictionary[string] = string;\n\n\t\tlet promise = fetchLocalizedString(string, harvestAs).then((text) => {\n\t\t\tdictionary[string] = text;\n\t\t\treturn text;\n\t\t});\n\n\t\tif (wait) {\n\t\t\treturn promise;\n\t\t}\n\t}\n\n\treturn dictionary[string];\n}\n\n/**\n * `getLocalizedString` and `localize` bound to a globalization project, for code that is\n * not part of the article it runs on: the site chrome, the shared component packages.\n *\n * On a page whose template renders the project (`af.i18n[project]` exists) the strings are\n * looked up and harvested there; on any other page they resolve through the article's\n * dictionary as a plain `getLocalizedString` would.\n *\n * @example\n * ```ts\n * const { getLocalizedString: t } = createLocalizer(\"synergi-chrome\");\n * t(\"Applications\");\n * ```\n */\nexport function createLocalizer(project: string) {\n\tfunction bound(string: undefined | null): undefined | null;\n\tfunction bound(string: string): string;\n\tfunction bound(string: string, wait: true): Promise<string>;\n\tfunction bound(string: string | undefined | null, wait?: boolean): string | undefined | null | Promise<string> {\n\t\treturn getLocalizedString(string as string, { project, wait } as LocalizeOptions & { wait: true });\n\t}\n\n\tfunction localize(stringParts: TemplateStringsArray, ...args: unknown[]): string {\n\t\tlet stringToLocalize = stringParts.map((s, i) => (i === stringParts.length - 1 ? s : `${s}$${i}`)).join(\"\");\n\t\tlet localized = bound(stringToLocalize);\n\n\t\targs.forEach((arg, index) => {\n\t\t\tlocalized = localized.replace(`$${index}`, String(arg));\n\t\t});\n\n\t\treturn localized;\n\t}\n\n\treturn { project, getLocalizedString: bound, localize };\n}\n","import { getLocalizedString } from \"../getLocalizedString.js\";\nimport type { DynamicValue, FieldMetadata, FilterFieldType, FilterOperator, FilterValueType } from \"./types.js\";\n\n/**\n * Operators that take no value at all.\n *\n * `eOperators.ParameterLessChecks` plus the blank checks.\n */\nconst PARAMETERLESS_OPERATORS: FilterOperator[] = [\"isnull\", \"isnotnull\", \"istrue\", \"isfalse\", \"isblank\", \"isnotblank\"];\n\n/**\n * Whole-period checks for dates, such as \"Is last week\", in the order of the\n * Windows filter editor. They take no value. The framework has no operator for\n * them, so a filter using one is sent as a filter string (`needsFilterString`).\n */\nexport const PERIOD_OPERATORS: FilterOperator[] = [\n\t\"isbeyondthisyear\",\n\t\"islaterthisyear\",\n\t\"islaterthismonth\",\n\t\"isnextweek\",\n\t\"islaterthisweek\",\n\t\"istomorrow\",\n\t\"istoday\",\n\t\"isyesterday\",\n\t\"isearlierthisweek\",\n\t\"islastweek\",\n\t\"isearlierthismonth\",\n\t\"isearlierthisyear\",\n\t\"ispriorthisyear\",\n];\n\n/** Operators whose value is a two element `[from, to]` array. */\nconst PAIR_OPERATORS: FilterOperator[] = [\"between\", \"notbetween\", \"datebetween\", \"datenotbetween\"];\n\n/** Operators whose value is an array of values. */\nconst LIST_OPERATORS: FilterOperator[] = [\"inlist\", \"notinlist\"];\n\n/** How many values an operator needs. */\nexport type OperatorArity = \"none\" | \"single\" | \"pair\" | \"list\";\n\nexport function getOperatorArity(operator: FilterOperator): OperatorArity {\n\tif (PARAMETERLESS_OPERATORS.includes(operator) || PERIOD_OPERATORS.includes(operator)) return \"none\";\n\tif (PAIR_OPERATORS.includes(operator)) return \"pair\";\n\tif (LIST_OPERATORS.includes(operator)) return \"list\";\n\treturn \"single\";\n}\n\n/**\n * Operator labels, keyed by operator. Comparison operators have a different\n * label for dates than for numbers, matching the Windows filter editor.\n *\n * @see `appframe/Appframe3.Win/Controls/afFilterEditor/Utils/FilterLineControlUtils.vb`\n */\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n\tequals: \"Equal to\",\n\tnotequals: \"Not equal to\",\n\tgreaterthan: \"Greater than\",\n\tgreaterthanorequal: \"Greater than or equal to\",\n\tlessthan: \"Less than\",\n\tlessthanorequal: \"Less than or equal to\",\n\tbeginswith: \"Begins with\",\n\tnotbeginswith: \"Does not begin with\",\n\tendswith: \"Ends with\",\n\tnotendswith: \"Does not end with\",\n\tcontains: \"Contains\",\n\tnotcontains: \"Does not contain\",\n\tlike: \"Like\",\n\tnotlike: \"Not like\",\n\tisnull: \"Is null\",\n\tisnotnull: \"Is not null\",\n\tisblank: \"Is empty\",\n\tisnotblank: \"Is not empty\",\n\tistrue: \"Is true\",\n\tisfalse: \"Is false\",\n\tinlist: \"In list\",\n\tnotinlist: \"Not in list\",\n\tbetween: \"Between\",\n\tnotbetween: \"Not between\",\n\tdateequals: \"Equal to\",\n\tnotdatequals: \"Not equal to\",\n\tdategreaterthan: \"After\",\n\tdategreaterthanorequal: \"On or after\",\n\tdatelessthan: \"Before\",\n\tdatelessthanorequal: \"Up to\",\n\tdatebetween: \"Between\",\n\tdatenotbetween: \"Not between\",\n\tisbeyondthisyear: \"Is beyond this year\",\n\tislaterthisyear: \"Is later this year\",\n\tislaterthismonth: \"Is later this month\",\n\tisnextweek: \"Is next week\",\n\tislaterthisweek: \"Is later this week\",\n\tistomorrow: \"Is tomorrow\",\n\tistoday: \"Is today\",\n\tisyesterday: \"Is yesterday\",\n\tisearlierthisweek: \"Is earlier this week\",\n\tislastweek: \"Is last week\",\n\tisearlierthismonth: \"Is earlier this month\",\n\tisearlierthisyear: \"Is earlier this year\",\n\tispriorthisyear: \"Is prior to this year\",\n};\n\n/** Comparison operator labels used for date and time fields. */\nconst DATE_OPERATOR_LABELS: Partial<Record<FilterOperator, string>> = {\n\tgreaterthan: \"After\",\n\tgreaterthanorequal: \"On or after\",\n\tlessthan: \"Before\",\n\tlessthanorequal: \"Up to\",\n};\n\n/**\n * Returns the localized label for an operator. Date fields get the\n * \"After\"/\"Before\" wording instead of \"Greater than\"/\"Less than\".\n */\nexport function getOperatorLabel(operator: FilterOperator, fieldType?: FilterFieldType): string {\n\tlet isDate = fieldType === \"date\" || fieldType === \"datetime\";\n\tlet label = (isDate ? DATE_OPERATOR_LABELS[operator] : undefined) ?? OPERATOR_LABELS[operator];\n\n\treturn getLocalizedString(label ?? operator);\n}\n\nconst EQUALITY: FilterOperator[] = [\"equals\", \"notequals\"];\nconst LIST_CHECKS: FilterOperator[] = [\"inlist\", \"notinlist\"];\nconst RANGE: FilterOperator[] = [\"between\", \"notbetween\"];\nconst COMPARISONS: FilterOperator[] = [\"greaterthan\", \"greaterthanorequal\", \"lessthan\", \"lessthanorequal\"];\nconst STRING_CHECKS: FilterOperator[] = [\n\t\"contains\",\n\t\"notcontains\",\n\t\"beginswith\",\n\t\"notbeginswith\",\n\t\"endswith\",\n\t\"notendswith\",\n\t\"isblank\",\n\t\"isnotblank\",\n];\n\n/**\n * Operators offered per field type, in the order the Windows filter editor\n * presents them.\n */\nconst OPERATORS_BY_TYPE: Record<FilterFieldType, FilterOperator[]> = {\n\tdate: [...EQUALITY, ...LIST_CHECKS, ...RANGE, ...COMPARISONS, ...PERIOD_OPERATORS],\n\tdatetime: [...EQUALITY, ...LIST_CHECKS, ...RANGE, ...COMPARISONS, ...PERIOD_OPERATORS],\n\tnumber: [...EQUALITY, ...LIST_CHECKS, ...RANGE, ...COMPARISONS],\n\tboolean: [\"istrue\", \"isfalse\"],\n\tstring: [...EQUALITY, ...STRING_CHECKS, ...LIST_CHECKS, ...RANGE, ...COMPARISONS],\n\tuniqueidentifier: [...EQUALITY, ...STRING_CHECKS, ...LIST_CHECKS, ...RANGE, ...COMPARISONS],\n};\n\n/**\n * Default operator per field type.\n *\n * Text defaults to `contains` because a substring search is what people\n * reach for when they type into a filter box without picking an operator —\n * `beginswith` silently misses matches in the middle of the value.\n */\nconst DEFAULT_OPERATOR_BY_TYPE: Record<FilterFieldType, FilterOperator> = {\n\tdate: \"equals\",\n\tdatetime: \"equals\",\n\tnumber: \"equals\",\n\tboolean: \"istrue\",\n\tstring: \"contains\",\n\tuniqueidentifier: \"beginswith\",\n};\n\n/**\n * Returns the operators available for a field. Nullable fields additionally get\n * the null checks. Boolean fields never get null checks, since `istrue` and\n * `isfalse` already cover the parameterless cases.\n */\nexport function getOperatorsForField(field: Pick<FieldMetadata, \"type\" | \"nullable\">): FilterOperator[] {\n\tlet operators = [...(OPERATORS_BY_TYPE[field.type] ?? OPERATORS_BY_TYPE.string)];\n\n\tif (field.nullable && field.type !== \"boolean\") {\n\t\toperators.push(\"isnull\", \"isnotnull\");\n\t}\n\n\treturn operators;\n}\n\nexport function getDefaultOperator(field: Pick<FieldMetadata, \"type\">): FilterOperator {\n\treturn DEFAULT_OPERATOR_BY_TYPE[field.type] ?? \"equals\";\n}\n\n/**\n * The `valueType` to send for a field/operator combination.\n *\n * Parameterless operators send `\"null\"`, list and range operators send\n * `\"array\"`, everything else sends the field's own value type.\n */\nexport function getValueType(field: Pick<FieldMetadata, \"type\">, operator: FilterOperator): FilterValueType {\n\tif (getOperatorArity(operator) === \"none\") return \"null\";\n\n\tswitch (field.type) {\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"datetime\";\n\t\tcase \"uniqueidentifier\":\n\t\t\treturn \"uniqueidentifier\";\n\t\tdefault:\n\t\t\treturn \"string\";\n\t}\n}\n\n/** Relative date values offered in the date value editor, in menu order. */\nexport const RELATIVE_DATE_VALUES: DynamicValue[] = [\n\t\"Yesterday\",\n\t\"Today\",\n\t\"Now\",\n\t\"Tomorrow\",\n\t\"DayAfterTomorrow\",\n\t\"TwoWeeksAway\",\n\t\"ThisWeek\",\n\t\"ThisMonth\",\n\t\"ThisYear\",\n\t\"LastWeek\",\n\t\"LastMonth\",\n\t\"LastYear\",\n\t\"NextWeek\",\n\t\"NextMonth\",\n\t\"NextYear\",\n];\n\nconst DYNAMIC_VALUE_LABELS: Record<DynamicValue, string> = {\n\tCurrentUser: \"Current user\",\n\tYesterday: \"Yesterday\",\n\tToday: \"Today\",\n\tNow: \"Now\",\n\tTomorrow: \"Tomorrow\",\n\tDayAfterTomorrow: \"Day after tomorrow\",\n\tTwoWeeksAway: \"Two weeks away\",\n\tThisWeek: \"This week\",\n\tThisMonth: \"This month\",\n\tThisYear: \"This year\",\n\tLastWeek: \"Last week\",\n\tLastMonth: \"Last month\",\n\tLastYear: \"Last year\",\n\tNextWeek: \"Next week\",\n\tNextMonth: \"Next month\",\n\tNextYear: \"Next year\",\n\tUserYesterday: \"Yesterday\",\n\tUserToday: \"Today\",\n\tUserTomorrow: \"Tomorrow\",\n\tUserThisWeek: \"This week\",\n\tUserThisMonth: \"This month\",\n\tUserThisYear: \"This year\",\n\tUserLastWeek: \"Last week\",\n\tUserLastMonth: \"Last month\",\n\tUserLastYear: \"Last year\",\n\tUserNextWeek: \"Next week\",\n\tUserNextMonth: \"Next month\",\n\tUserNextYear: \"Next year\",\n};\n\nexport function getDynamicValueLabel(value: DynamicValue): string {\n\treturn getLocalizedString(DYNAMIC_VALUE_LABELS[value] ?? value);\n}\n\n/**\n * Returns the dynamic (\"special\") values that can be used with a field. Date\n * fields get the relative dates; string fields can be compared to the current\n * user name.\n */\nexport function getDynamicValuesForField(field: Pick<FieldMetadata, \"type\">): DynamicValue[] {\n\tif (field.type === \"date\" || field.type === \"datetime\") return [...RELATIVE_DATE_VALUES];\n\tif (field.type === \"string\") return [\"CurrentUser\"];\n\n\treturn [];\n}\n\n/** Operators that can compare a field with another column. */\nconst COLUMN_OPERATORS: FilterOperator[] = [...EQUALITY, ...RANGE, ...COMPARISONS];\n\n/** Whether an operator can take another column as its value. */\nexport function supportsColumnValue(operator: FilterOperator): boolean {\n\treturn COLUMN_OPERATORS.includes(operator);\n}\n\n/** Dates and datetimes compare with each other; other types only with their own. */\nfunction getComparableType(type: FilterFieldType): FilterFieldType {\n\treturn type === \"datetime\" ? \"date\" : type;\n}\n\n/**\n * The columns a field can be compared with: the other fields of the same type,\n * as in the Windows filter editor. Booleans have only true and false checks,\n * so they get none.\n */\nexport function getColumnsForField(\n\tfield: Pick<FieldMetadata, \"name\" | \"type\">,\n\tfields: FieldMetadata[],\n): FieldMetadata[] {\n\tif (field.type === \"boolean\") return [];\n\n\tlet type = getComparableType(field.type);\n\n\treturn fields.filter(\n\t\t(other) => other.name !== field.name && !other.excludeFromFilter && getComparableType(other.type) === type,\n\t);\n}\n\n/**\n * Aliases for names `eOperators.ToString()` can produce that are not operators\n * in their own right. `Not` is the bare negation flag (i.e. \"not equal to\"),\n * `Tweens` shares its numeric value with `NotBetween`, and combined flags are\n * rendered as a comma separated list.\n *\n * @see `FilterElementConverter.FilterExpressionToDictionary`\n */\nconst OPERATOR_ALIASES: Record<string, FilterOperator> = {\n\tnot: \"notequals\",\n\ttweens: \"notbetween\",\n\tnotdate: \"notdatequals\",\n\t// The enum member is `NotDateEquals`, but the wire format drops an \"e\".\n\tnotdateequals: \"notdatequals\",\n};\n\n/**\n * Normalizes an operator string coming from the server or from another filter\n * producer (`AfGrid` emits PascalCase names) to the canonical lower-case form.\n */\nexport function normalizeOperator(operator: string): FilterOperator {\n\tlet normalized = operator.toLowerCase().replace(/[\\s,]/g, \"\");\n\n\treturn OPERATOR_ALIASES[normalized] ?? (normalized as FilterOperator);\n}\n","import {\n\tgetDefaultOperator,\n\tgetOperatorArity,\n\tgetValueType,\n\tnormalizeOperator,\n\tPERIOD_OPERATORS,\n\tsupportsColumnValue,\n} from \"./operators.js\";\nimport type {\n\tFieldMetadata,\n\tFilterExpression,\n\tFilterGroup,\n\tFilterOperator,\n\tFilterRangeEnd,\n\tFilterTree,\n} from \"./types.js\";\n\n/**\n * Path to a node in a filter tree, as a list of item indexes from the root\n * group. The root itself is the empty path.\n *\n * @example\n * ```ts\n * // The second condition of the first group\n * let path: FilterPath = [0, 1];\n * ```\n */\nexport type FilterPath = number[];\n\nexport type FilterNode = FilterGroup | FilterExpression;\n\n/**\n * Creates the default empty filter: a single `and` group with no conditions.\n */\nexport function createEmptyFilter(): FilterTree {\n\treturn createEmptyGroup(\"and\");\n}\n\nexport function createEmptyGroup(mode: FilterGroup[\"mode\"] = \"and\"): FilterGroup {\n\treturn { type: \"group\", mode, items: [] };\n}\n\nexport function isGroup(item: FilterNode): item is FilterGroup {\n\treturn item.type === \"group\";\n}\n\nexport function isExpression(item: FilterNode): item is FilterExpression {\n\treturn item.type === \"expression\";\n}\n\n/**\n * Normalizes any filter value into a tree the editor can render, without\n * changing its structure. An expression is wrapped in a group; a missing filter\n * becomes the default layout.\n */\nexport function toFilterTree(filter: FilterNode | null | undefined): FilterTree {\n\tif (!filter) return createEmptyFilter();\n\tif (isExpression(filter)) return { type: \"group\", mode: \"and\", items: [filter] };\n\n\treturn filter;\n}\n\n/**\n * Returns the `and` group alone when it is the only item of an `or` group.\n *\n * The server parses filter strings into the standard layout, which wraps even\n * a plain list of conditions in an `or` group of one `and` group. That extra\n * level means nothing, so the builder shows the `and` group on its own, as the\n * Windows filter editor does.\n */\nexport function unwrapSingleAndGroup(filter: FilterTree): FilterTree {\n\tif (filter.mode !== \"or\" || filter.items.length !== 1) return filter;\n\n\tlet [only] = filter.items;\n\n\treturn isGroup(only) && only.mode === \"and\" ? only : filter;\n}\n\n/**\n * True when the filter contains no expressions. An empty filter must not be\n * saved or applied.\n */\nexport function isEmptyFilter(filter: FilterNode | null | undefined): boolean {\n\treturn countExpressions(filter) === 0;\n}\n\nexport function countExpressions(filter: FilterNode | null | undefined): number {\n\tif (!filter) return 0;\n\tif (isExpression(filter)) return 1;\n\n\treturn filter.items.reduce((total, item) => total + countExpressions(item), 0);\n}\n\n/**\n * Removes incomplete expressions and empty groups from the whole tree. Returns\n * null when nothing is left. Use before applying a filter to a data object so\n * incomplete rows are not sent.\n */\nexport function pruneFilter(filter: FilterNode | null | undefined): FilterGroup | null {\n\tif (!filter) return null;\n\n\tif (isExpression(filter)) {\n\t\treturn isCompleteExpression(filter) ? { type: \"group\", mode: \"and\", items: [filter] } : null;\n\t}\n\n\tlet items = filter.items\n\t\t.map((item) => (isGroup(item) ? pruneFilter(item) : isCompleteExpression(item) ? item : null))\n\t\t.filter((item): item is FilterNode => item !== null);\n\n\tif (items.length === 0) return null;\n\n\treturn { ...filter, items };\n}\n\n/**\n * True when an expression has everything it needs to be sent to the server.\n */\nexport function isCompleteExpression(item: FilterNode): boolean {\n\tif (isGroup(item)) return item.items.some(isCompleteExpression);\n\tif (!item.column || !item.operator) return false;\n\n\tlet arity = getOperatorArity(normalizeOperator(item.operator));\n\n\tif (arity === \"none\") return true;\n\tif (item.value === null || item.value === undefined || item.value === \"\") return false;\n\n\tif (arity === \"pair\") {\n\t\treturn (\n\t\t\tArray.isArray(item.value) &&\n\t\t\titem.value.length === 2 &&\n\t\t\titem.value.every((v: unknown) => {\n\t\t\t\tlet end = isRangeEnd(v) ? v.value : v;\n\t\t\t\treturn end !== \"\" && end != null;\n\t\t\t})\n\t\t);\n\t}\n\n\tif (arity === \"list\") {\n\t\treturn Array.isArray(item.value) && item.value.length > 0;\n\t}\n\n\treturn true;\n}\n\n/** Whether a range value end is a `FilterRangeEnd` rather than a plain value. */\nexport function isRangeEnd(value: unknown): value is FilterRangeEnd {\n\treturn typeof value === \"object\" && value !== null && !(value instanceof Date) && \"valueType\" in value;\n}\n\n/** Dynamic values the framework has no `eDynamicValue` member for. */\nconst DYNAMIC_VALUES_WITHOUT_FRAMEWORK_SUPPORT = new Set<string>([\"Now\", \"DayAfterTomorrow\", \"TwoWeeksAway\"]);\n\nfunction needsConversion(valueType: string, value: unknown): boolean {\n\tif (valueType === \"column\") return true;\n\n\treturn valueType === \"special\" && typeof value === \"string\" && DYNAMIC_VALUES_WITHOUT_FRAMEWORK_SUPPORT.has(value);\n}\n\n/**\n * True when the filter holds something the framework's data API cannot read\n * from a filter object: another column, a range end that is not a plain value,\n * one of the dates only the Windows editor offers, or a whole-period check. Such a filter must be\n * converted with `convertFilter` and sent as a filter string.\n */\nexport function needsFilterString(filter: FilterNode | null | undefined): boolean {\n\tif (!filter) return false;\n\tif (isGroup(filter)) return filter.items.some(needsFilterString);\n\tif (PERIOD_OPERATORS.includes(normalizeOperator(filter.operator))) return true;\n\tif (needsConversion(filter.valueType, filter.value)) return true;\n\n\treturn Array.isArray(filter.value) && filter.value.some((item: unknown) => isRangeEnd(item));\n}\n\n/**\n * Creates an expression for a field, using the field's default operator and an\n * empty value of the right shape.\n */\nexport function createExpression(field: FieldMetadata, operator?: FilterOperator): FilterExpression {\n\tlet op = operator ?? getDefaultOperator(field);\n\n\treturn {\n\t\ttype: \"expression\",\n\t\tcolumn: field.name,\n\t\toperator: op,\n\t\tvalue: createEmptyValue(op),\n\t\tvalueType: getValueType(field, op),\n\t};\n}\n\nfunction createEmptyValue(operator: FilterOperator): FilterExpression[\"value\"] {\n\tswitch (getOperatorArity(operator)) {\n\t\tcase \"none\":\n\t\t\treturn null;\n\t\tcase \"pair\":\n\t\t\treturn [\"\", \"\"];\n\t\tcase \"list\":\n\t\t\treturn [];\n\t\tdefault:\n\t\t\treturn \"\";\n\t}\n}\n\n/**\n * Returns a copy of the expression with a new operator, adjusting the value to\n * the new operator's arity and the value type accordingly.\n */\nexport function changeOperator(\n\texpression: FilterExpression,\n\toperator: FilterOperator,\n\tfield: Pick<FieldMetadata, \"type\">,\n): FilterExpression {\n\tlet previousArity = getOperatorArity(normalizeOperator(expression.operator));\n\tlet arity = getOperatorArity(operator);\n\tlet value = expression.value;\n\n\t// A column only makes sense for comparisons, not for text checks such as\n\t// \"contains\".\n\tlet holdsColumn =\n\t\texpression.valueType === \"column\" ||\n\t\t(Array.isArray(value) && value.some((item: unknown) => isRangeEnd(item) && item.valueType === \"column\"));\n\n\tif (arity !== previousArity || (holdsColumn && !supportsColumnValue(operator))) {\n\t\tvalue = createEmptyValue(operator);\n\t}\n\n\t// A dynamic value or a column stays valid across operators of the same arity.\n\tlet keepsValueType =\n\t\t(expression.valueType === \"special\" || expression.valueType === \"column\") && value === expression.value;\n\tlet valueType = keepsValueType ? expression.valueType : getValueType(field, operator);\n\n\treturn { ...expression, operator, value, valueType };\n}\n\n/** Returns the node at `path`, or null when the path does not exist. */\nexport function getNodeAtPath(filter: FilterNode, path: FilterPath): FilterNode | null {\n\tlet node: FilterNode | undefined = filter;\n\n\tfor (let index of path) {\n\t\tif (!node || !isGroup(node)) return null;\n\t\tnode = node.items[index];\n\t}\n\n\treturn node ?? null;\n}\n\n/**\n * Returns a copy of the tree with `updater` applied to the node at `path`.\n * Returning null removes the node. An unknown path leaves the tree untouched.\n */\nexport function updateNodeAtPath<T extends FilterNode>(\n\tfilter: T,\n\tpath: FilterPath,\n\tupdater: (node: FilterNode) => FilterNode | null,\n): T {\n\tif (path.length === 0) {\n\t\treturn (updater(filter) as T | null) ?? filter;\n\t}\n\n\tif (!isGroup(filter)) return filter;\n\n\tlet [index, ...rest] = path;\n\tlet child = filter.items[index as number];\n\tif (!child) return filter;\n\n\tlet items = [...filter.items];\n\n\tif (rest.length === 0) {\n\t\tlet updated = updater(child);\n\n\t\tif (updated === null) {\n\t\t\titems.splice(index as number, 1);\n\t\t} else {\n\t\t\titems[index as number] = updated;\n\t\t}\n\t} else {\n\t\titems[index as number] = updateNodeAtPath(child, rest, updater);\n\t}\n\n\treturn { ...filter, items };\n}\n\n/** Returns a copy of the tree with `node` appended to the group at `path`. */\nexport function insertNodeAtPath<T extends FilterNode>(filter: T, path: FilterPath, node: FilterNode): T {\n\treturn updateNodeAtPath(filter, path, (target) =>\n\t\tisGroup(target) ? { ...target, items: [...target.items, node] } : target,\n\t);\n}\n\n/**\n * Returns a copy of the tree with the node at `path` removed. The root is never\n * removed; it is reset to the default layout instead.\n */\nexport function removeNodeAtPath<T extends FilterNode>(filter: T, path: FilterPath): T {\n\tif (path.length === 0) return createEmptyFilter() as T;\n\n\treturn updateNodeAtPath(filter, path, () => null);\n}\n\n/** Returns a copy of the tree with the mode of the group at `path` changed. */\nexport function setGroupMode<T extends FilterNode>(filter: T, path: FilterPath, mode: FilterGroup[\"mode\"]): T {\n\treturn updateNodeAtPath(filter, path, (node) => (isGroup(node) ? { ...node, mode } : node));\n}\n\n/** Returns a copy of the tree with an empty group appended to the group at `path`. */\nexport function addGroupAtPath<T extends FilterNode>(filter: T, path: FilterPath = []): T {\n\tlet parent = getNodeAtPath(filter, path);\n\t// A nested group is only useful when it combines differently to its parent.\n\tlet mode: FilterGroup[\"mode\"] = parent && isGroup(parent) && parent.mode === \"and\" ? \"or\" : \"and\";\n\n\treturn insertNodeAtPath(filter, path, createEmptyGroup(mode));\n}\n","import { toFilterTree } from \"./tree.js\";\nimport type { FilterGroup } from \"./types.js\";\n\nconst CONVERT_URL = \"/api/filter/convert\";\n\nexport type FilterConversionResult = {\n\tfilterObject: FilterGroup | null;\n\tfilterString: string;\n\twhereClause: string;\n\t/** Set when the filter string could not be parsed into a structured filter. */\n\twarning?: string;\n};\n\nexport type ConvertRequest = {\n\tfilterObject?: FilterGroup | null;\n\tfilterString?: string;\n\t/** Optional view name, used server-side to validate field names. */\n\tviewName?: string;\n};\n\n/**\n * Converts between a structured filter and a DevExpress filter string.\n *\n * The conversion is done server-side by `api/filter/convert`, because the\n * criteria string grammar is implemented by the licensed DevExpress parser that\n * the Windows client and the report engine use. Doing it in the browser would\n * risk producing strings the server cannot parse back.\n */\nexport async function convertFilter(request: ConvertRequest, signal?: AbortSignal): Promise<FilterConversionResult> {\n\tlet response = await fetch(CONVERT_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify(request),\n\t\tsignal,\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to convert filter: ${response.status} ${response.statusText}`);\n\t}\n\n\tlet result = (await response.json()) as FilterConversionResult;\n\n\treturn {\n\t\t...result,\n\t\tfilterObject: result.filterObject ? toFilterTree(result.filterObject) : null,\n\t};\n}\n","import type { FilterExpression, FilterGroup } from \"./types.js\";\nimport { getDynamicValueLabel, getOperatorArity, getOperatorLabel, normalizeOperator } from \"./operators.js\";\nimport { isExpression, isRangeEnd } from \"./tree.js\";\nimport type { DynamicValue, FieldMetadata } from \"./types.js\";\n\nexport type DescribeFilterOptions = {\n\t/** Field metadata, used to resolve captions and date operator wording. */\n\tfields?: FieldMetadata[];\n};\n\n/**\n * Builds a human-readable description of a filter, without contacting the\n * server. Use this for read-only summaries; the authoritative filter string is\n * produced by `api/filter/convert`, since only the server can generate the\n * DevExpress criteria syntax.\n */\nexport function describeFilter(\n\tfilter: FilterGroup | FilterExpression | null | undefined,\n\toptions: DescribeFilterOptions = {},\n): string {\n\tif (!filter) return \"\";\n\n\tlet fieldsByName = new Map((options.fields ?? []).map((field) => [field.name.toLowerCase(), field]));\n\n\treturn describe(filter, fieldsByName, 0);\n}\n\nfunction describe(item: FilterGroup | FilterExpression, fields: Map<string, FieldMetadata>, depth: number): string {\n\tif (isExpression(item)) return describeExpression(item, fields);\n\n\tlet parts = item.items.map((child) => describe(child, fields, depth + 1)).filter((part) => part !== \"\");\n\n\tif (parts.length === 0) return \"\";\n\tif (parts.length === 1) return parts[0] as string;\n\n\tlet separator = item.mode === \"or\" ? \" OR \" : \" AND \";\n\tlet joined = parts.join(separator);\n\n\treturn depth === 0 ? joined : `(${joined})`;\n}\n\nfunction describeExpression(expression: FilterExpression, fields: Map<string, FieldMetadata>): string {\n\tif (!expression.column || !expression.operator) return \"\";\n\n\tlet field = fields.get(expression.column.toLowerCase());\n\tlet caption = field?.caption ?? expression.column;\n\tlet operator = normalizeOperator(expression.operator);\n\tlet label = getOperatorLabel(operator, field?.type);\n\tlet arity = getOperatorArity(operator);\n\n\tif (arity === \"none\") return `[${caption}] ${label}`;\n\n\treturn `[${caption}] ${label} ${formatValue(expression, arity, fields)}`;\n}\n\nfunction formatValue(\n\texpression: FilterExpression,\n\tarity: ReturnType<typeof getOperatorArity>,\n\tfields: Map<string, FieldMetadata>,\n): string {\n\tlet { value, valueType } = expression;\n\n\tif (Array.isArray(value)) {\n\t\tlet formatted = value.map((item: unknown) =>\n\t\t\tisRangeEnd(item) ? formatScalar(item.value, item.valueType, fields) : formatScalar(item, valueType, fields),\n\t\t);\n\n\t\treturn arity === \"pair\" ? formatted.join(\" - \") : `(${formatted.join(\", \")})`;\n\t}\n\n\treturn formatScalar(value, valueType, fields);\n}\n\nfunction formatScalar(value: unknown, valueType: string, fields: Map<string, FieldMetadata>): string {\n\tif (valueType === \"special\" && typeof value === \"string\") return getDynamicValueLabel(value as DynamicValue);\n\tif (valueType === \"column\" && typeof value === \"string\") {\n\t\treturn `[${fields.get(value.toLowerCase())?.caption ?? value}]`;\n\t}\n\tif (value === null || value === undefined) return \"\";\n\tif (value instanceof Date) return value.toISOString().slice(0, 10);\n\tif (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n\tif (valueType === \"number\" || valueType === \"boolean\") return String(value);\n\n\treturn `'${String(value)}'`;\n}\n","/**\n * Field metadata for the filter builder.\n *\n * `sviw_WebSiteCMS_FilterBuilder_ViewFields` normalizes the SQL data types into\n * the handful of types the builder groups fields by, so the mapping here is\n * mostly a rename. The loading lives in `useFilterFields` in\n * `@olenbetong/appframe-react`.\n */\n\nimport type { FieldMetadata, FilterFieldType } from \"./types.js\";\n\n/** A raw `sviw_WebSiteCMS_FilterBuilder_ViewFields` record. */\nexport type ViewFieldRecord = {\n\tFieldName?: string | null;\n\t/** Already normalized by the view; anything unexpected is treated as text. */\n\tDataType?: string | null;\n\tDBObjectID?: string | null;\n\tNullable?: boolean | number | null;\n\tExcludeFromFilter?: boolean | number | null;\n\tValueListRecordSource?: string | null;\n};\n\nconst FIELD_TYPES: FilterFieldType[] = [\"string\", \"number\", \"boolean\", \"date\", \"datetime\", \"uniqueidentifier\"];\n\n/** The builder type for a normalized `DataType`. Unknown types are filtered as text. */\nexport function toFilterFieldType(dataType: string | null | undefined): FilterFieldType {\n\tlet normalized = dataType?.trim().toLowerCase();\n\n\treturn FIELD_TYPES.find((type) => type === normalized) ?? \"string\";\n}\n\nfunction toBoolean(value: boolean | number | null | undefined) {\n\treturn value === true || value === 1;\n}\n\n/** Maps a view field record onto the metadata the builder renders. */\nexport function toFieldMetadata(record: ViewFieldRecord): FieldMetadata {\n\treturn {\n\t\tname: record.FieldName ?? \"\",\n\t\ttype: toFilterFieldType(record.DataType),\n\t\tnullable: toBoolean(record.Nullable),\n\t\texcludeFromFilter: toBoolean(record.ExcludeFromFilter),\n\t\tvalueListRecordSource: record.ValueListRecordSource || undefined,\n\t};\n}\n\n/**\n * Maps and sorts view field records. Fields the view marks as excluded are\n * dropped rather than hidden, since nothing else can use them.\n */\nexport function toFieldMetadataList(records: ViewFieldRecord[]): FieldMetadata[] {\n\treturn records\n\t\t.map(toFieldMetadata)\n\t\t.filter((field) => field.name !== \"\" && !field.excludeFromFilter)\n\t\t.sort((a, b) => a.name.localeCompare(b.name));\n}\n","import { isExpression, isGroup } from \"./tree.js\";\nimport type { FilterExpression, FilterGroup, FilterTree } from \"./types.js\";\n\n/**\n * The \"standard layout\" is the simplified shape the Windows filter editor can\n * render in its flow view: an outer `or` group whose items are `and` groups of\n * expressions. Anything else falls back to the advanced (DevExpress) editor\n * there, and to the nested tree editor on the web.\n *\n * @see `afFilterEditor.CanRenderFilter`\n */\nexport function isStandardLayout(filter: FilterGroup | FilterExpression | null | undefined): boolean {\n\tif (!filter || isExpression(filter) || filter.mode !== \"or\") return false;\n\n\treturn filter.items.every((item) => isGroup(item) && item.mode === \"and\" && item.items.every(isExpression));\n}\n\n/**\n * Coerces an arbitrary filter tree into the standard layout.\n *\n * An `or` group's items each become their own `and` group. Anything else is\n * flattened into a single `and` group. This loses nesting, so only use it when\n * the consumer cannot handle a nested tree.\n */\nexport function coerceToStandardLayout(filter: FilterGroup | FilterExpression | null | undefined): FilterTree {\n\tif (!filter) return { type: \"group\", mode: \"or\", items: [{ type: \"group\", mode: \"and\", items: [] }] };\n\n\tif (isExpression(filter)) {\n\t\treturn { type: \"group\", mode: \"or\", items: [{ type: \"group\", mode: \"and\", items: [filter] }] };\n\t}\n\n\tif (filter.mode === \"or\") {\n\t\tlet groups = filter.items.map((item) => {\n\t\t\tlet expressions: FilterExpression[] = [];\n\t\t\tcollectExpressions(item, expressions);\n\n\t\t\treturn { type: \"group\", mode: \"and\", items: expressions } satisfies FilterGroup;\n\t\t});\n\n\t\treturn {\n\t\t\ttype: \"group\",\n\t\t\tmode: \"or\",\n\t\t\titems: groups.length > 0 ? groups : [{ type: \"group\", mode: \"and\", items: [] }],\n\t\t};\n\t}\n\n\tlet expressions: FilterExpression[] = [];\n\tcollectExpressions(filter, expressions);\n\n\treturn { type: \"group\", mode: \"or\", items: [{ type: \"group\", mode: \"and\", items: expressions }] };\n}\n\n/**\n * Collects every expression in a (possibly nested) filter, in document order.\n */\nfunction collectExpressions(item: FilterGroup | FilterExpression, into: FilterExpression[]) {\n\tif (isExpression(item)) {\n\t\tinto.push(item);\n\t\treturn;\n\t}\n\n\tfor (let child of item.items) {\n\t\tcollectExpressions(child, into);\n\t}\n}\n","/**\n * Saved filters — the filters the Windows filter builder stores in\n * `stbl_Database_Filters` and shares between users through\n * `stbl_Database_FiltersPermissions`.\n *\n * This module is the pure part: the shape of a saved filter, how the raw\n * database records map onto it, and who is allowed to do what with one. The\n * loading and the mutations live in `useSavedFilters` in\n * `@olenbetong/appframe-react`.\n */\n\nimport type { FilterAccessLevel } from \"./types.js\";\n\n/**\n * Access a user has to a saved filter.\n *\n * `sviw_Database_MyFilters` only ever reports `Reader`, `Editor` or `Manager`,\n * and a filter the current user created is always reported as `Manager`.\n */\nexport type { FilterAccessLevel };\n\n/**\n * `sviw_Database_MyFilters` unions in a synthetic \"No Filter\" row so the Windows\n * tree can render a \"no filter\" entry. It is not a real filter and must be\n * dropped before the list is shown.\n */\nexport const NO_FILTER_ID = -1;\n\n/** A saved filter, as used by the filter builder UI. */\nexport type SavedFilter = {\n\tfilterId: number;\n\tprimKey: string;\n\tname: string;\n\t/** DevExpress criteria string, e.g. `[Name] Like '%a%'`. */\n\tcriteria: string;\n\tdbObjectId: string;\n\taccessLevel: FilterAccessLevel;\n\t/** True when the current user created the filter. */\n\tcreatedByUser: boolean;\n\tcreatedBy: string;\n\tdomain: string | null;\n\t/** True when the filter is visible in every domain the user has access to. */\n\tcrossDomain: boolean;\n\t/** True when the filter is shared with individual users rather than groups. */\n\tisPersonallyShared: boolean;\n\t/** True when the criteria are hidden from readers of a shared filter. */\n\thideCriteria: boolean;\n\theaderText: string | null;\n\tcomment: string | null;\n\t/** Report parameter values, stored as XML. Only used by report filters. */\n\tparameters: string | null;\n\treportTitle: string | null;\n\tcreated: Date | null;\n\tupdated: Date | null;\n};\n\n/** A raw `sviw_Database_MyFilters` / `sviw_Database_FiltersFromReportID` record. */\nexport type SavedFilterRecord = {\n\tPrimKey?: string | null;\n\tFilterID?: number | null;\n\tDBObjectID?: string | null;\n\tFilterName?: string | null;\n\tFilterCriteria?: string | null;\n\tAccessLevel?: string | null;\n\tCreatedByUser?: string | boolean | null;\n\tCreatedBy?: string | null;\n\tDomain?: string | null;\n\tCrossDomain?: boolean | number | null;\n\tIsPersonallyShared?: boolean | number | null;\n\tHideFilterCriteria?: boolean | number | null;\n\tOptionalHeaderText?: string | null;\n\tOptionalComment?: string | null;\n\tParameters?: string | null;\n\tReportTitle?: string | null;\n\tCreated?: Date | string | null;\n\tUpdated?: Date | string | null;\n};\n\n/**\n * Maps a database record onto a `SavedFilter`.\n *\n * `CreatedByUser` needs care: the view computes it as the *string* `'true'` or\n * `'false'` (and `'False'` on the synthetic row), not as a bit.\n */\nexport function toSavedFilter(record: SavedFilterRecord): SavedFilter {\n\treturn {\n\t\tfilterId: record.FilterID ?? NO_FILTER_ID,\n\t\tprimKey: record.PrimKey ?? \"\",\n\t\tname: record.FilterName ?? \"\",\n\t\tcriteria: record.FilterCriteria ?? \"\",\n\t\tdbObjectId: record.DBObjectID ?? \"\",\n\t\taccessLevel: toAccessLevel(record.AccessLevel),\n\t\tcreatedByUser: toBoolean(record.CreatedByUser),\n\t\tcreatedBy: record.CreatedBy ?? \"\",\n\t\tdomain: record.Domain ?? null,\n\t\tcrossDomain: toBoolean(record.CrossDomain),\n\t\tisPersonallyShared: toBoolean(record.IsPersonallyShared),\n\t\thideCriteria: toBoolean(record.HideFilterCriteria),\n\t\theaderText: record.OptionalHeaderText || null,\n\t\tcomment: record.OptionalComment || null,\n\t\tparameters: record.Parameters || null,\n\t\treportTitle: record.ReportTitle || null,\n\t\tcreated: toDate(record.Created),\n\t\tupdated: toDate(record.Updated),\n\t};\n}\n\nfunction toAccessLevel(value: string | null | undefined): FilterAccessLevel {\n\tif (value === \"Manager\" || value === \"Editor\") return value;\n\n\treturn \"Reader\";\n}\n\nfunction toBoolean(value: string | boolean | number | null | undefined): boolean {\n\tif (typeof value === \"string\") return value.toLowerCase() === \"true\";\n\n\treturn Boolean(value);\n}\n\nfunction toDate(value: Date | string | null | undefined): Date | null {\n\tif (!value) return null;\n\tif (value instanceof Date) return value;\n\n\tlet date = new Date(value);\n\n\treturn Number.isNaN(date.getTime()) ? null : date;\n}\n\n/** Drops the synthetic \"No Filter\" row and sorts by name. */\nexport function toSavedFilters(records: SavedFilterRecord[]): SavedFilter[] {\n\treturn records\n\t\t.filter((record) => (record.FilterID ?? NO_FILTER_ID) !== NO_FILTER_ID)\n\t\t.map(toSavedFilter)\n\t\t.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Splits filters into the two branches the Windows tree shows: the ones the user\n * created, and the ones that have been shared with them.\n */\nexport function partitionSavedFilters(filters: SavedFilter[]): { mine: SavedFilter[]; shared: SavedFilter[] } {\n\tlet mine: SavedFilter[] = [];\n\tlet shared: SavedFilter[] = [];\n\n\tfor (let filter of filters) {\n\t\tif (filter.createdByUser) {\n\t\t\tmine.push(filter);\n\t\t} else {\n\t\t\tshared.push(filter);\n\t\t}\n\t}\n\n\treturn { mine, shared };\n}\n\nexport function findSavedFilter(filters: SavedFilter[], filterId: number | null | undefined) {\n\tif (filterId == null) return undefined;\n\n\treturn filters.find((filter) => filter.filterId === filterId);\n}\n\n/** True when the user may overwrite the filter's criteria. */\nexport function canEditSavedFilter(filter: SavedFilter): boolean {\n\treturn filter.accessLevel === \"Editor\" || filter.accessLevel === \"Manager\";\n}\n\n/** True when the user may change who the filter is shared with. */\nexport function canShareSavedFilter(filter: SavedFilter): boolean {\n\treturn filter.accessLevel === \"Manager\";\n}\n\n/**\n * True when the user may rename or delete the filter.\n *\n * `sstp_WebSiteCMS_FilterBuilder_RenameFilter` and `…_DeleteFilter` both require\n * `CreatedBy = SUSER_SNAME()` — the manager-access branch is commented out in\n * both procedures, deliberately, because the UI only ever offers these for a\n * user's own filters.\n */\nexport function canRenameSavedFilter(filter: SavedFilter): boolean {\n\treturn filter.createdByUser;\n}\n\nexport const canDeleteSavedFilter = canRenameSavedFilter;\n\n/**\n * True when the user may toggle the cross-domain flag.\n *\n * `sstp_Database_Filters_SetCrossDomain` also filters on\n * `CreatedBy = SUSER_SNAME()`.\n */\nexport const canSetCrossDomain = canRenameSavedFilter;\n","export type ImageSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nexport interface getUserImageOptions {\n\t/**\n\t * Prim key of the record in atbl_Personnel_Employees\n\t */\n\tprimKey: string | null;\n\t/**\n\t * Person ID of the employee\n\t */\n\tlogin: string | null;\n\t/**\n\t * The value of the ImageHash column in atbl_Personnel_Employees\n\t */\n\thash: string | number | null;\n\t/**\n\t * The image will be resized according to this value. Default is 'xs':\n\t * - xs: 64px\n\t * - sm: 128px\n\t * - md: 256px\n\t * - lg: 512px\n\t * - xl: image is not resized\n\t */\n\tsize?: ImageSize;\n}\n\nconst sizeMap: Record<ImageSize, number | undefined> = {\n\txs: 64,\n\tsm: 128,\n\tmd: 256,\n\tlg: 512,\n\txl: undefined,\n};\n\n/**\n * Returns the absolute path of a users profile picture. Uses the EmployeesLookup\n * data resource to enable caching across applications.\n *\n * If one of primKey or hash are not provided, or are null, an empty string will be returned.\n */\nexport function getUserImage(\n\tprimKey: string | null,\n\tlogin: string | null,\n\thash: string | number | null,\n\tsize?: ImageSize,\n): string;\nexport function getUserImage(options: getUserImageOptions): string;\nexport function getUserImage(\n\tprimKeyOrOptions: string | null | getUserImageOptions,\n\tlogin?: string | null,\n\thash?: string | number | null,\n\tsize: ImageSize = \"xs\",\n) {\n\tif (primKeyOrOptions && typeof primKeyOrOptions === \"object\") {\n\t\treturn getUserImage(primKeyOrOptions.primKey, primKeyOrOptions.login, primKeyOrOptions.hash, primKeyOrOptions.size);\n\t}\n\n\tif (!primKeyOrOptions || !hash) {\n\t\treturn \"\";\n\t}\n\n\tlet sizePx = sizeMap[size];\n\treturn `/api/data/blob/EmployeesLookup/${primKeyOrOptions}/Image/${encodeURIComponent(login ?? \"\")}${\n\t\tlogin ? \".\" : \"\"\n\t}${encodeURIComponent(hash)}.jpg${sizePx ? `?width=${sizePx}` : \"\"}`;\n}\n","import { getLocalizedString } from \"./getLocalizedString.js\";\nimport { invariant } from \"./invariant.js\";\n\n/**\n * Localization function for tagged templates. Replaces the arguments\n * with $n, where n is the index, before asking Appframe to translate\n * the string.\n *\n * For example, if you call localize`Page ${2} or ${5}`, the string\n * \"Page $0 of $1\" will be sent to Appframe, and for norwegian,\n * \"Side 2 av 5\" will be returned.\n *\n * @param stringParts\n * @param args\n * @returns Translated string\n */\nexport function localize(stringParts: TemplateStringsArray, ...args: any[]): string {\n\tinvariant(\n\t\tArray.isArray(stringParts) && stringParts.length === args.length + 1,\n\t\t\"Method 'localize' must be used as a tag function\",\n\t);\n\n\tlet stringToLocalize = stringParts.map((s, i) => (i === stringParts.length - 1 ? s : `${s}$${i}`)).join(\"\");\n\tlet localized = getLocalizedString(stringToLocalize);\n\n\targs.forEach((arg, index) => {\n\t\tlocalized = localized.replace(`$${index}`, arg);\n\t});\n\n\treturn localized;\n}\n","/**\n * Reading the `afRecordSource` XML the Windows designer stores on reports and\n * on report parameters.\n *\n * A record source is a serialized .NET object, so the document is machine\n * generated and its shape is fixed. Only a handful of elements matter here, and\n * parsing them with a reader rather than `DOMParser` keeps this module usable\n * outside a browser.\n */\n\nimport type { ReportRecordSource, ReportRecordSourceColumn } from \"./types.js\";\n\nconst XML_ENTITIES: Record<string, string> = {\n\tamp: \"&\",\n\tlt: \"<\",\n\tgt: \">\",\n\tquot: '\"',\n\tapos: \"'\",\n};\n\n/** Resolves the XML entities the .NET serializer emits. */\nexport function decodeXml(value: string) {\n\treturn value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (match, entity: string) => {\n\t\tif (entity.startsWith(\"#x\") || entity.startsWith(\"#X\")) {\n\t\t\treturn String.fromCodePoint(Number.parseInt(entity.slice(2), 16));\n\t\t}\n\n\t\tif (entity.startsWith(\"#\")) {\n\t\t\treturn String.fromCodePoint(Number.parseInt(entity.slice(1), 10));\n\t\t}\n\n\t\treturn XML_ENTITIES[entity.toLowerCase()] ?? match;\n\t});\n}\n\n/** Escapes a value for use as XML text or as an attribute value. */\nexport function encodeXml(value: string) {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\n/**\n * The text content of the first `<name>` element, or `null` when the element is\n * missing or self-closing (`<FilterString />`, which the serializer writes for\n * an empty value).\n */\nexport function readElement(xml: string, name: string): string | null {\n\tlet match = new RegExp(`<${name}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)</${name}>`).exec(xml);\n\n\tif (!match) return null;\n\n\tlet value = decodeXml(match[1]).trim();\n\n\treturn value === \"\" ? null : value;\n}\n\n/** The text content of every `<name>` element, in document order. */\nexport function readElements(xml: string, name: string): string[] {\n\tlet matches = xml.matchAll(new RegExp(`<${name}(?:\\\\s[^>]*)?>([\\\\s\\\\S]*?)</${name}>`, \"g\"));\n\n\treturn [...matches].map((match) => decodeXml(match[1]).trim());\n}\n\n/**\n * The CLR types the designer writes on a select column. Anything else — and a\n * column with no `DataType` at all, which is what it writes for strings — is\n * treated as text.\n */\nconst COLUMN_TYPES: Record<string, ReportRecordSourceColumn[\"type\"]> = {\n\tByte: \"number\",\n\tDecimal: \"number\",\n\tDouble: \"number\",\n\tInt16: \"number\",\n\tInt32: \"number\",\n\tInt64: \"number\",\n\tSingle: \"number\",\n\tBoolean: \"boolean\",\n\tDateTime: \"date\",\n};\n\n/** The columns of a `<SelectColumns>` block, in document order. */\nfunction readSelectColumns(xml: string): ReportRecordSourceColumn[] {\n\tlet block = /<SelectColumns>([\\s\\S]*?)<\\/SelectColumns>/.exec(xml)?.[1] ?? \"\";\n\tlet columns: ReportRecordSourceColumn[] = [];\n\n\tfor (let match of block.matchAll(/<afSelectColumn>([\\s\\S]*?)<\\/afSelectColumn>/g)) {\n\t\tlet name = readElement(match[1], \"Name\");\n\t\tif (!name) continue;\n\n\t\tcolumns.push({ name, type: COLUMN_TYPES[readElement(match[1], \"DataType\") ?? \"\"] ?? \"string\" });\n\t}\n\n\treturn columns;\n}\n\n/**\n * The record source a document holds. Parameter lookups store one `<afRecordSource>`; a report\n * stores its record sources as `<ArrayOfAfReportRecordSource>` with one `<afReportRecordSource>`\n * per data source, of which the one with the ID `Master` feeds the report (else the first).\n */\nfunction pickRecordSource(document: string | null | undefined): string | null {\n\tif (!document) return null;\n\tlet sources = [...document.matchAll(/<(afRecordSource|afReportRecordSource)(?:\\s[^>]*)?>([\\s\\S]*?)<\\/\\1>/g)].map(\n\t\t(match) => match[2],\n\t);\n\tif (sources.length === 0) return null;\n\treturn sources.find((source) => readElement(source, \"ID\") === \"Master\") ?? sources[0];\n}\n\n/**\n * Reads the parts of an `afRecordSource` a web client can act on: the view, the\n * selected columns and the design-time filter.\n *\n * Returns `null` for anything that is not a record source, so a missing or\n * empty `RecordSource` column simply means \"no lookup\".\n */\nexport function parseRecordSource(document: string | null | undefined): ReportRecordSource | null {\n\tlet xml = pickRecordSource(document);\n\tif (!xml) return null;\n\n\tlet viewName = readElement(xml, \"ViewName\");\n\tif (!viewName) return null;\n\n\tlet maxRecords = readElement(xml, \"MaxRecords\");\n\n\treturn {\n\t\tviewName,\n\t\tcolumns: readSelectColumns(xml),\n\t\twhereClause: readElement(xml, \"WhereClause\"),\n\t\tfilterString: readElement(xml, \"FilterString\"),\n\t\t// The designer writes 0 for \"no limit\"; the web data API spells that -1.\n\t\tmaxRecords: maxRecords === null || maxRecords === \"0\" ? null : Number(maxRecords),\n\t};\n}\n","/**\n * Report parameters: mapping the metadata records onto something renderable,\n * and the XML the parameter values are stored as on a saved filter.\n */\n\nimport { encodeXml, parseRecordSource, readElement } from \"./recordSource.js\";\nimport type {\n\tReportParameter,\n\tReportParameterEditor,\n\tReportParameterRecord,\n\tReportParameterType,\n\tReportParameterValue,\n\tReportParameterValues,\n} from \"./types.js\";\n\nconst PARAMETER_TYPES: ReportParameterType[] = [\"String\", \"Integer\", \"Double\", \"Date\", \"Boolean\"];\n\n/**\n * A default value naming a stored procedure, e.g. `astp_Reports_CurrentPeriod`.\n *\n * The Windows launcher executes the procedure and uses the first column of the\n * first row. The web client cannot execute an arbitrary procedure, so these are\n * resolved through the report endpoint instead.\n */\nconst PROCEDURE_DEFAULT = /^[a-z]stp_/i;\n\n/** True when a `DefaultValue` names a stored procedure rather than a literal. */\nexport function isProcedureDefault(defaultValue: string | null | undefined): boolean {\n\treturn typeof defaultValue === \"string\" && PROCEDURE_DEFAULT.test(defaultValue.trim());\n}\n\n/** The choices of a parameter, one per line of its `Items` column. */\nexport function parseParameterItems(items: string | null | undefined): string[] {\n\tif (!items) return [];\n\n\treturn items\n\t\t.split(/\\r\\n|\\r|\\n/)\n\t\t.map((item) => item.trim())\n\t\t.filter((item) => item !== \"\");\n}\n\nfunction toParameterType(dataType: string | null | undefined): ReportParameterType {\n\tlet match = PARAMETER_TYPES.find((type) => type.toLowerCase() === dataType?.trim().toLowerCase());\n\n\t// The designer only offers the five known types, but an unknown one is still\n\t// editable as text.\n\treturn match ?? \"String\";\n}\n\n/**\n * The editor a parameter should use. A lookup or a fixed item list wins over the\n * data type, which is what the Windows launcher does as well.\n */\nfunction toParameterEditor(type: ReportParameterType, hasLookup: boolean, hasItems: boolean): ReportParameterEditor {\n\tif (hasLookup) return \"lookup\";\n\tif (hasItems) return \"select\";\n\n\tswitch (type) {\n\t\tcase \"Integer\":\n\t\tcase \"Double\":\n\t\t\treturn \"number\";\n\t\tcase \"Date\":\n\t\t\treturn \"date\";\n\t\tcase \"Boolean\":\n\t\t\treturn \"boolean\";\n\t\tdefault:\n\t\t\treturn \"text\";\n\t}\n}\n\n/** Maps a `sviw_WinClient_ReportsParameters` record onto a `ReportParameter`. */\nexport function toReportParameter(record: ReportParameterRecord): ReportParameter {\n\tlet name = record.Name ?? \"\";\n\tlet type = toParameterType(record.DataType);\n\tlet items = parseParameterItems(record.Items);\n\tlet recordSource = parseRecordSource(record.RecordSource);\n\tlet valueMember = record.ValueMember ?? null;\n\n\tif (recordSource && !valueMember) {\n\t\tvalueMember = recordSource.columns[0]?.name ?? null;\n\t}\n\n\t/*\n\t * The designer has no display member: the Windows lookup grid shows every\n\t * selected column. On the web a combobox shows one, so the first column that\n\t * is not the value is used.\n\t */\n\tlet displayMember = recordSource?.columns.find((column) => column.name !== valueMember)?.name ?? null;\n\tlet hasLookup = Boolean(recordSource && valueMember);\n\n\treturn {\n\t\tname,\n\t\tlabel: record.Label?.trim() || name,\n\t\tdescription: record.Description?.trim() || null,\n\t\ttype,\n\t\teditor: toParameterEditor(type, hasLookup, items.length > 0),\n\t\tdefaultValue: record.DefaultValue ?? null,\n\t\titems,\n\t\trecordSource: hasLookup ? recordSource : null,\n\t\tvalueMember: hasLookup ? valueMember : null,\n\t\tdisplayMember: hasLookup ? displayMember : null,\n\t\tsortOrder: record.SortOrder ?? 0,\n\t\tlookupWidth: record.LookupWidth ?? null,\n\t\tlookupHeight: record.LookupHeight ?? null,\n\t};\n}\n\n/** Maps and sorts a report's parameter records. */\nexport function toReportParameters(records: ReportParameterRecord[]): ReportParameter[] {\n\treturn records\n\t\t.filter((record) => record.Name)\n\t\t.map(toReportParameter)\n\t\t.sort((a, b) => a.sortOrder - b.sortOrder || a.label.localeCompare(b.label));\n}\n\n/** Converts a raw value to the type a parameter expects. */\nexport function coerceParameterValue(\n\tparameter: ReportParameter,\n\tvalue: ReportParameterValue | undefined,\n): ReportParameterValue {\n\tif (value === null || value === undefined || value === \"\") {\n\t\treturn parameter.type === \"Boolean\" ? false : null;\n\t}\n\n\tswitch (parameter.type) {\n\t\tcase \"Boolean\":\n\t\t\treturn typeof value === \"boolean\" ? value : /^(true|1|ja|yes)$/i.test(String(value));\n\t\tcase \"Integer\": {\n\t\t\tlet parsed = Number.parseInt(String(value), 10);\n\n\t\t\treturn Number.isNaN(parsed) ? null : parsed;\n\t\t}\n\t\tcase \"Double\": {\n\t\t\t// Norwegian keyboards produce a comma, which `Number` does not accept.\n\t\t\tlet parsed = Number(String(value).replace(\",\", \".\"));\n\n\t\t\treturn Number.isNaN(parsed) ? null : parsed;\n\t\t}\n\t\tdefault:\n\t\t\treturn String(value);\n\t}\n}\n\n/**\n * The initial values for a set of parameters.\n *\n * Defaults naming a stored procedure are left unset: they have to be resolved\n * server-side, and the caller fills them in once they are.\n */\nexport function getDefaultParameterValues(parameters: ReportParameter[]): ReportParameterValues {\n\tlet values: ReportParameterValues = {};\n\n\tfor (let parameter of parameters) {\n\t\tvalues[parameter.name] = isProcedureDefault(parameter.defaultValue)\n\t\t\t? coerceParameterValue(parameter, null)\n\t\t\t: coerceParameterValue(parameter, parameter.defaultValue);\n\t}\n\n\treturn values;\n}\n\nfunction toXmlValue(parameter: ReportParameter, value: ReportParameterValue): string {\n\tif (value === null || value === undefined) return \"\";\n\t// The Windows client writes booleans the way .NET does.\n\tif (typeof value === \"boolean\") return value ? \"True\" : \"False\";\n\n\treturn String(value);\n}\n\n/**\n * Serializes parameter values the way the Windows filter builder does, so that\n * a filter saved on the web is readable in the Windows client and the other way\n * around.\n *\n * ```xml\n * <Parameters Version=\"1\">\n * <Parameter Name=\"Period\" Type=\"Integer\">202601</Parameter>\n * </Parameters>\n * ```\n */\nexport function serializeParameters(parameters: ReportParameter[], values: ReportParameterValues): string {\n\tlet entries = parameters.map((parameter) => {\n\t\tlet value = toXmlValue(parameter, values[parameter.name] ?? null);\n\n\t\treturn ` <Parameter Name=\"${encodeXml(parameter.name)}\" Type=\"${parameter.type}\">${encodeXml(value)}</Parameter>`;\n\t});\n\n\treturn `<Parameters Version=\"1\">\\n${entries.join(\"\\n\")}\\n</Parameters>`;\n}\n\n/**\n * Reads the values back out of the XML stored on a saved filter. Values are\n * returned as strings and coerced against the report's own parameters by\n * `restoreParameterValues`, since the XML may name parameters the report no\n * longer has.\n */\nexport function parseParameters(xml: string | null | undefined): Record<string, string> {\n\tif (!xml) return {};\n\n\tlet values: Record<string, string> = {};\n\n\tfor (let match of xml.matchAll(/<Parameter\\s+([^>]*?)\\/?>(?:([\\s\\S]*?)<\\/Parameter>)?/g)) {\n\t\tlet name = /Name\\s*=\\s*\"([^\"]*)\"/.exec(match[1])?.[1];\n\t\tif (!name) continue;\n\n\t\tvalues[name] = readElement(match[0], \"Parameter\") ?? \"\";\n\t}\n\n\treturn values;\n}\n\n/**\n * Applies the values stored on a saved filter to a report's parameters, keeping\n * the defaults for anything the filter does not mention.\n */\nexport function restoreParameterValues(\n\tparameters: ReportParameter[],\n\txml: string | null | undefined,\n): ReportParameterValues {\n\tlet stored = parseParameters(xml);\n\tlet values = getDefaultParameterValues(parameters);\n\n\tfor (let parameter of parameters) {\n\t\tif (parameter.name in stored) {\n\t\t\tvalues[parameter.name] = coerceParameterValue(parameter, stored[parameter.name]);\n\t\t}\n\t}\n\n\treturn values;\n}\n","/**\n * Resolving report parameter defaults that name a stored procedure.\n *\n * A `DefaultValue` matching `^[a-z]stp_` names a procedure that produces the\n * default rather than being the default itself. Executing it is a server\n * concern, and the framework already exposes it through the `WebsiteCMS`\n * `api/reporting/filters` route.\n */\n\nimport { isProcedureDefault } from \"./parameters.js\";\nimport type { ReportParameter, ReportParameterValue, ReportParameterValues } from \"./types.js\";\n\n/** A result set as the reporting route serializes it. */\ntype ProcedureResult = Record<string, unknown[]> | unknown[];\n\n/**\n * Runs the stored procedure a parameter's default names and returns the value\n * it produces — the first column of the first row, the way the Windows launcher\n * reads it.\n *\n * Returns `null` when the parameter has no procedure default or the procedure\n * produced no rows.\n */\nexport async function fetchParameterDefault(\n\treportId: string,\n\tparameter: ReportParameter,\n\tsignal?: AbortSignal,\n): Promise<ReportParameterValue> {\n\tif (!parameter.defaultValue || !isProcedureDefault(parameter.defaultValue)) return null;\n\n\tlet response = await fetch(`/api/reporting/filters/${encodeURIComponent(reportId)}/GetParameterDefaultValue`, {\n\t\tbody: JSON.stringify({ parameterName: parameter.name }),\n\t\tcredentials: \"same-origin\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t// The route rejects anything that is not an ajax request, as CSRF\n\t\t\t// protection.\n\t\t\t\"X-Requested-With\": \"XMLHttpRequest\",\n\t\t},\n\t\tmethod: \"POST\",\n\t\tsignal,\n\t});\n\n\tif (!response.ok) throw new Error(`${response.status} ${response.statusText}`);\n\n\tlet result = (await response.json()) as ProcedureResult;\n\n\treturn firstValue(result);\n}\n\n/**\n * Resolves every procedure default of a report in parallel and merges the\n * results over the literal defaults.\n *\n * A parameter whose procedure fails is left at its literal default rather than\n * failing the whole form — the user can still type a value.\n */\nexport async function resolveParameterDefaults(\n\treportId: string,\n\tparameters: ReportParameter[],\n\tliteralDefaults: ReportParameterValues,\n\tsignal?: AbortSignal,\n): Promise<ReportParameterValues> {\n\tlet dynamic = parameters.filter((parameter) => isProcedureDefault(parameter.defaultValue ?? \"\"));\n\n\tif (dynamic.length === 0) return literalDefaults;\n\n\tlet resolved = await Promise.all(\n\t\tdynamic.map(async (parameter) => {\n\t\t\ttry {\n\t\t\t\treturn [parameter.name, await fetchParameterDefault(reportId, parameter, signal)] as const;\n\t\t\t} catch {\n\t\t\t\treturn [parameter.name, null] as const;\n\t\t\t}\n\t\t}),\n\t);\n\n\tlet values: ReportParameterValues = { ...literalDefaults };\n\n\tfor (let [name, value] of resolved) {\n\t\tif (value !== null) values[name] = value;\n\t}\n\n\treturn values;\n}\n\n/** The first column of the first row of whichever result set came back. */\nfunction firstValue(result: ProcedureResult): ReportParameterValue {\n\tlet rows = Array.isArray(result) ? result : Object.values(result).find((value) => Array.isArray(value));\n\tlet row = rows?.[0];\n\n\tif (row === undefined || row === null) return null;\n\tif (Array.isArray(row)) return (row[0] as ReportParameterValue) ?? null;\n\tif (typeof row === \"object\") {\n\t\tlet values = Object.values(row as Record<string, unknown>);\n\n\t\treturn (values[0] as ReportParameterValue) ?? null;\n\t}\n\n\treturn row as ReportParameterValue;\n}\n","/**\n * Rendering a report through the framework's report route.\n *\n * The route is a CMS code module owned by the Appframe framework\n * (`WebsiteCMS.Reports`), registered on\n * `api/report/{ReportID}/{FileName}.{Extension}`. Everything here builds\n * requests for it — there is no report endpoint of our own.\n */\n\nimport { downloadRequest, type DownloadItem } from \"../downloadRequest.js\";\nimport type { ReportFormat, ReportParameterValues } from \"./types.js\";\n\n/**\n * The file extension each format is requested with. The route only accepts\n * `(pdf|csv|xlsx?|html|xbm|mht|rtf|txt)`, which is why `TEXT` is `txt` and\n * `IMAGE` is `xbm` rather than the format name.\n */\nconst REPORT_EXTENSIONS: Record<ReportFormat, string> = {\n\tCSV: \"csv\",\n\tHTML: \"html\",\n\tIMAGE: \"xbm\",\n\tMHT: \"mht\",\n\tPDF: \"pdf\",\n\tRTF: \"rtf\",\n\tTEXT: \"txt\",\n\tXLS: \"xls\",\n\tXLSX: \"xlsx\",\n};\n\n/** The media type each format is served as, for previewing a rendered report. */\nconst REPORT_MEDIA_TYPES: Record<ReportFormat, string> = {\n\tCSV: \"text/csv\",\n\tHTML: \"text/html\",\n\tIMAGE: \"image/x-xbitmap\",\n\tMHT: \"message/rfc822\",\n\tPDF: \"application/pdf\",\n\tRTF: \"text/enriched\",\n\tTEXT: \"text/plain\",\n\tXLS: \"application/vnd.ms-excel\",\n\tXLSX: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n};\n\nexport type ReportRenderOptions = {\n\treportId: string;\n\tformat: ReportFormat;\n\t/** File name without an extension. Defaults to the report ID. */\n\tfileName?: string;\n\t/** The filter string the report is run with. */\n\tfilter?: string | null;\n\t/** Parameter values by name. Only non-empty values are sent. */\n\tparameters?: ReportParameterValues | null;\n\t/** Text for the report's optional header band. */\n\theaderText?: string | null;\n\t/** Hides the filter string printed on the report. */\n\thideFilterString?: boolean;\n\t/** Runs the report in another domain than the current one. */\n\tdomain?: string | null;\n\t/** Timezone offset in hours or minutes. Defaults to the browser's. */\n\ttimezone?: number | null;\n\tsignal?: AbortSignal;\n};\n\n/** The extension a format is requested with. */\nexport function getReportExtension(format: ReportFormat): string {\n\treturn REPORT_EXTENSIONS[format];\n}\n\n/** The media type a format is served as. */\nexport function getReportMediaType(format: ReportFormat): string {\n\treturn REPORT_MEDIA_TYPES[format];\n}\n\n/**\n * Characters that must not reach the file name, because it is a path segment of\n * the route: separators, the extension delimiter, the query and fragment\n * delimiters, and control characters.\n */\n// oxlint-disable-next-line no-control-regex -- control characters are the point\nconst UNSAFE_FILE_NAME_CHARACTERS = /[\\\\/:*?\"<>|.#&+%\\u0000-\\u001f]/g;\n\n/**\n * The name a rendered report is downloaded as, including its extension.\n *\n * The file name is part of the route rather than a query parameter, so anything\n * that would change the path is replaced.\n */\nexport function getReportFileName(options: Pick<ReportRenderOptions, \"reportId\" | \"format\" | \"fileName\">): string {\n\tlet name = (options.fileName ?? options.reportId).trim();\n\tlet safe = name.replace(UNSAFE_FILE_NAME_CHARACTERS, \"-\").replace(/-{2,}/g, \"-\").replace(/^-|-$/g, \"\").trim();\n\n\treturn `${safe === \"\" ? options.reportId : safe}.${getReportExtension(options.format)}`;\n}\n\n/**\n * The route is always on the same origin, but a `Request` needs an absolute\n * URL, so the path is resolved against the document when there is one.\n */\nfunction toRequestUrl(path: string): string {\n\tlet base = globalThis.location?.href;\n\n\treturn base ? new URL(path, base).toString() : path;\n}\n\n/**\n * The request that renders a report.\n *\n * A report without parameters is a plain `GET`, so it can be opened directly in\n * a new tab. As soon as there are parameters or a header the options go in a\n * JSON body, which the route only reads from a `POST`.\n *\n * `filter` is always sent alongside `parameters`: the route reads the filter out\n * of the body *guarded by the presence of `parameters`*, and reads it with an\n * indexer that throws when the key is missing.\n */\nexport function getReportRequest(options: ReportRenderOptions): Request {\n\tlet { domain, filter, headerText, hideFilterString, parameters, reportId, signal, timezone } = options;\n\tlet fileName = getReportFileName(options);\n\tlet url = `/api/report/${encodeURIComponent(reportId)}/${encodeURIComponent(fileName)}`;\n\tlet query = new URLSearchParams();\n\tlet entries = toParameterEntries(parameters);\n\tlet needsBody = entries.length > 0 || Boolean(headerText) || hideFilterString === true;\n\n\tif (domain) query.set(\"Domain\", domain);\n\tquery.set(\"timezone\", String(timezone ?? -new Date().getTimezoneOffset() / 60));\n\n\tif (!needsBody) {\n\t\tif (filter) query.set(\"Filter\", filter);\n\n\t\treturn new Request(toRequestUrl(`${url}?${query}`), { credentials: \"same-origin\", signal });\n\t}\n\n\tlet body: Record<string, unknown> = {\n\t\tfilter: filter ?? \"\",\n\t\tparameters: entries,\n\t};\n\n\tif (headerText) body.optionalheader = headerText;\n\t// The route assigns `showfiltercriteria` straight to the report's\n\t// `HideFilterString`, so despite its name it *hides* the filter string.\n\tif (hideFilterString === true) body.showfiltercriteria = \"true\";\n\n\treturn new Request(toRequestUrl(`${url}?${query}`), {\n\t\tbody: JSON.stringify(body),\n\t\tcredentials: \"same-origin\",\n\t\theaders: { \"Content-Type\": \"application/json\", \"X-Requested-With\": \"XMLHttpRequest\" },\n\t\tmethod: \"POST\",\n\t\tsignal,\n\t});\n}\n\n/**\n * Renders a report and resolves with the rendered file.\n *\n * The route answers with a JSON body carrying an `error` when rendering fails,\n * rather than with a failing status, so a JSON response is turned into an error\n * here.\n */\nexport async function renderReport(options: ReportRenderOptions): Promise<Blob> {\n\tlet response = await fetch(getReportRequest(options));\n\tlet contentType = response.headers.get(\"content-type\") ?? \"\";\n\n\tif (contentType.includes(\"application/json\")) {\n\t\tlet data = (await response.json()) as { error?: string };\n\n\t\tthrow new Error(data.error ?? \"Failed to render report\");\n\t}\n\n\tif (!response.ok) throw new Error(`${response.status} ${response.statusText}`);\n\n\treturn response.blob();\n}\n\n/** Renders a report and saves it, reporting progress as it downloads. */\nexport function downloadReport(\n\toptions: ReportRenderOptions & { onProgress?: (progress: DownloadItem) => void },\n): Promise<void> {\n\treturn downloadRequest(getReportRequest(options), {\n\t\tfileName: getReportFileName(options),\n\t\tonProgress: options.onProgress,\n\t\tsignal: options.signal,\n\t});\n}\n\n/**\n * Renders a report and opens it in a new tab.\n *\n * The blob is rendered first rather than pointing a tab at the route, so a\n * failure surfaces as an error here instead of as a tab showing JSON, and so\n * reports with parameters — which have to be posted — can be previewed too.\n */\nexport async function openReport(options: ReportRenderOptions): Promise<void> {\n\tlet blob = await renderReport(options);\n\t// The route serves some formats as types a browser downloads rather than\n\t// displays; the format's own media type is what decides how it opens.\n\tlet url = URL.createObjectURL(blob.type ? blob : new Blob([blob], { type: getReportMediaType(options.format) }));\n\t// Not opened with the `noopener` feature: `open` then returns null even when\n\t// the tab opens, which read as a blocked pop-up and revoked the URL before\n\t// the tab could load it. The opener is cut off by hand instead.\n\tlet opened = globalThis.open(url, \"_blank\");\n\n\tif (!opened) {\n\t\tURL.revokeObjectURL(url);\n\t\tthrow new Error(\"The report could not be opened. Allow pop-ups for this site and try again.\");\n\t}\n\n\topened.opener = null;\n\n\t// Revoking immediately would cancel the load in some browsers, and never\n\t// revoking keeps the whole rendered report in memory.\n\tglobalThis.setTimeout(() => URL.revokeObjectURL(url), 60_000);\n}\n\n/**\n * Renders a report and opens the browser's print dialog for it.\n *\n * The report is loaded into an offscreen iframe rather than a new tab, so the\n * print dialog opens over the launcher and the user is not left with a stray\n * tab afterwards. Browsers that will not print an embedded document fall back\n * to opening it, which leaves the user one keystroke from printing.\n */\nexport async function printReport(options: ReportRenderOptions): Promise<void> {\n\tlet document_ = globalThis.document;\n\tif (!document_?.body) return openReport(options);\n\n\tlet blob = await renderReport(options);\n\tlet url = URL.createObjectURL(blob.type ? blob : new Blob([blob], { type: getReportMediaType(options.format) }));\n\tlet frame = document_.createElement(\"iframe\");\n\t// Hidden with `visibility` rather than `display: none`: a frame that is not\n\t// laid out has nothing to print in some browsers.\n\tframe.setAttribute(\"aria-hidden\", \"true\");\n\tframe.style.cssText =\n\t\t\"position:fixed;inset-block-start:0;inset-inline-start:0;inline-size:1px;block-size:1px;border:0;visibility:hidden\";\n\n\tlet cleanUp = () => {\n\t\tframe.remove();\n\t\tURL.revokeObjectURL(url);\n\t};\n\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tframe.addEventListener(\"load\", () => resolve(), { once: true });\n\t\t\tframe.addEventListener(\"error\", () => reject(new Error(\"The report could not be loaded for printing\")), {\n\t\t\t\tonce: true,\n\t\t\t});\n\t\t\tframe.src = url;\n\t\t\tdocument_.body.append(frame);\n\t\t});\n\n\t\tframe.contentWindow?.focus();\n\t\tframe.contentWindow?.print();\n\t} catch {\n\t\tcleanUp();\n\n\t\treturn openReport(options);\n\t}\n\n\t// The print dialog is modal but not awaitable, and revoking the URL or\n\t// removing the frame while it is open cancels the job.\n\tglobalThis.setTimeout(cleanUp, 60_000);\n}\n\n/**\n * Parameters in the shape the route expects — `{ name, value }` objects.\n *\n * Empty values are dropped: the route skips them anyway, and a report falls back\n * to its own default when a parameter is not sent.\n */\nfunction toParameterEntries(parameters: ReportParameterValues | null | undefined) {\n\tif (!parameters) return [];\n\n\treturn Object.entries(parameters)\n\t\t.filter(([, value]) => value !== null && value !== undefined && value !== \"\")\n\t\t.map(([name, value]) => ({ name, value: typeof value === \"boolean\" ? String(value) : value }));\n}\n","/**\n * Mapping a `sviw_WinClient_MyReports` record onto the report model, including\n * which output formats the report offers.\n */\n\nimport { parseRecordSource } from \"./recordSource.js\";\nimport type { Report, ReportFormat, ReportRecord } from \"./types.js\";\n\nfunction isSet(value: boolean | number | null | undefined) {\n\treturn value === true || value === 1;\n}\n\n/**\n * The formats a report may be rendered to, in the order the launcher offers\n * them.\n *\n * `FormatCSV` covers the whole spreadsheet family: the Windows launcher enables\n * \"save as XLS\" and \"save as XLSX\" from the same flag, because they all come out\n * of the same DevExpress export.\n */\nexport function getReportFormats(record: ReportRecord): ReportFormat[] {\n\tlet formats: ReportFormat[] = [];\n\n\tif (isSet(record.FormatPDF)) formats.push(\"PDF\");\n\tif (isSet(record.FormatCSV)) formats.push(\"XLSX\", \"XLS\", \"CSV\");\n\tif (isSet(record.FormatHTML)) formats.push(\"HTML\");\n\tif (isSet(record.FormatRTF)) formats.push(\"RTF\");\n\tif (isSet(record.FormatMHT)) formats.push(\"MHT\");\n\tif (isSet(record.FormatTEXT)) formats.push(\"TEXT\");\n\tif (isSet(record.FormatIMAGE)) formats.push(\"IMAGE\");\n\n\treturn formats;\n}\n\n/** Maps a report record onto the model the launcher renders. */\nexport function toReport(record: ReportRecord): Report {\n\tlet recordSource = parseRecordSource(record.RecordSource);\n\n\treturn {\n\t\treportId: record.ReportID ?? \"\",\n\t\tshortId: record.ShortID ?? null,\n\t\ttitle: record.Title?.trim() || null,\n\t\tdescription: record.Description?.trim() || null,\n\t\tviewName: recordSource?.viewName ?? null,\n\t\tinitialFilter: recordSource?.filterString ?? null,\n\t\thasTitle: isSet(record.HasTitle),\n\t\thasHeader: isSet(record.HasHeader),\n\t\tformats: getReportFormats(record),\n\t\ttimeout: record.Timeout ?? null,\n\t};\n}\n","import { getLocalizedString } from \"./getLocalizedString.js\";\n\nconst template = `\n\t<dialog class=\"alert-dialog\">\n\t\t\t<form method=\"dialog\">\n\t\t\t\t\t<article></article>\n\t\t\t\t\t<footer>\n\t\t\t\t\t\t\t<menu></menu>\n\t\t\t\t\t</footer>\n\t\t\t</form>\n\t</dialog>\n`;\n\n/*\n * The module can be loaded from `<head>`, where there is no body to append to\n * yet. Waiting for the DOM in that case keeps the whole bundle from throwing on\n * load — everything else it exports would be lost with it.\n */\nfunction addDialogTemplate() {\n\tif (document.querySelector(\"#AlertDialogTemplate\")) return;\n\n\tlet $template = document.createElement(\"template\");\n\t$template.id = \"AlertDialogTemplate\";\n\t$template.innerHTML = template;\n\tdocument.body.append($template);\n}\n\nif (globalThis.document) {\n\tif (document.body) {\n\t\taddDialogTemplate();\n\t} else {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", addDialogTemplate, { once: true });\n\t}\n}\n\nexport function waitForDialog(dialog: HTMLDialogElement, cancelValue = \"No\"): Promise<string> {\n\treturn new Promise((resolve) => {\n\t\tlet controller = new AbortController();\n\n\t\tdialog.addEventListener(\n\t\t\t\"close\",\n\t\t\t() => {\n\t\t\t\tresolve(dialog.returnValue);\n\t\t\t\tcontroller.abort();\n\t\t\t},\n\t\t\t{ signal: controller.signal },\n\t\t);\n\n\t\tdialog.addEventListener(\n\t\t\t\"cancel\",\n\t\t\t() => {\n\t\t\t\tresolve(cancelValue);\n\t\t\t\tcontroller.abort();\n\t\t\t},\n\t\t\t{ signal: controller.signal },\n\t\t);\n\t});\n}\nexport type AlertDialogButton = {\n\tlabel: string;\n\tvalue: string;\n\tprimary?: boolean;\n};\n\nexport type AlertDialogOptions = {\n\tbuttons: AlertDialogButton[];\n\tcontent: string | HTMLElement;\n\tclassName?: string;\n\tid?: string;\n\ttitle?: string;\n};\n\nconst alertDialogPromiseMap = new Map<string, Promise<string>>();\n\nexport async function openAlertDialog({ buttons, content, className, id, title }: AlertDialogOptions) {\n\tif (id && alertDialogPromiseMap.has(id)) {\n\t\treturn await alertDialogPromiseMap.get(id);\n\t}\n\n\tlet $template = document.querySelector<HTMLTemplateElement>(\"#AlertDialogTemplate\");\n\tif (!$template) throw Error(\"No alert dialog template found\");\n\n\tlet $dialog = $template.content.firstElementChild?.cloneNode(true) as HTMLDialogElement | null;\n\tlet $content = $dialog?.querySelector(\"article\");\n\tlet $menu = $dialog?.querySelector(\"footer menu\");\n\n\tif (!$dialog || !$content || !$menu) throw Error(\"Invalid alert dialog template\");\n\n\tif (className) {\n\t\tfor (const className_ of className.split(/\\s+/)) {\n\t\t\t$dialog?.classList.add(className_);\n\t\t}\n\t}\n\n\tif (title) {\n\t\tlet $header = document.createElement(\"header\");\n\t\t$header.append(title);\n\t\t$content.parentNode?.insertBefore($header, $content);\n\t}\n\n\t$content.appendChild(typeof content === \"string\" ? document.createTextNode(content) : content);\n\tfor (let { label, value, primary } of buttons) {\n\t\tlet listItem = document.createElement(\"li\");\n\t\tlet buttonElem = document.createElement(\"button\");\n\t\tbuttonElem.value = value;\n\t\tif (primary) {\n\t\t\tbuttonElem.classList.add(\"primary\");\n\t\t}\n\t\tbuttonElem.textContent = label;\n\t\tlistItem.appendChild(buttonElem);\n\t\t$menu.appendChild(buttonElem);\n\t}\n\n\tdocument.body.appendChild($dialog);\n\t$dialog.showModal();\n\n\tlet dialogPromise = waitForDialog($dialog).then((returnValue) => {\n\t\tif (id && alertDialogPromiseMap.has(id)) {\n\t\t\talertDialogPromiseMap.delete(id);\n\t\t}\n\t\tif ($dialog) document.body.removeChild($dialog);\n\t\treturn returnValue;\n\t});\n\n\tif (id) {\n\t\talertDialogPromiseMap.set(id, dialogPromise);\n\t}\n\n\treturn await dialogPromise;\n}\n\nexport enum ConfirmButtons {\n\tYesNo = 0,\n\tOkCancel = 1,\n}\n\nexport type ConfirmOptions = {\n\tbuttons?: ConfirmButtons;\n\ttitle?: string;\n};\n\nexport async function confirm(prompt: string, options: ConfirmOptions = {}): Promise<boolean> {\n\tlet { buttons: optionButtons = ConfirmButtons.YesNo, title } = options;\n\tlet buttons: AlertDialogButton[] =\n\t\toptionButtons === ConfirmButtons.OkCancel\n\t\t\t? [\n\t\t\t\t\t{ label: getLocalizedString(\"OK\"), value: \"yes\", primary: true },\n\t\t\t\t\t{ label: getLocalizedString(\"Cancel\"), value: \"no\" },\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t{ label: getLocalizedString(\"Yes\"), value: \"yes\", primary: true },\n\t\t\t\t\t{ label: getLocalizedString(\"No\"), value: \"no\" },\n\t\t\t\t];\n\n\tlet result = await openAlertDialog({ content: prompt, buttons, className: \"alert-dialog-confirm\", title });\n\treturn result === \"yes\";\n}\n\nexport type AlertOptions = {\n\ttitle?: string;\n};\n\nexport async function alert(prompt: string, options: AlertOptions = {}): Promise<void> {\n\tawait openAlertDialog({\n\t\tcontent: prompt,\n\t\tclassName: \"alert-dialog-alert\",\n\t\tbuttons: [{ label: getLocalizedString(\"OK\"), value: \"ok\", primary: true }],\n\t\t...options,\n\t});\n}\n","function getKey(article?: string) {\n\tconst articleKey =\n\t\tarticle ?? globalThis.af?.article?.id ?? globalThis.window?.location.pathname.substring(1).split(\"/\")[0];\n\treturn `${articleKey}.${globalThis.af?.userSession.login ?? \"anonymous\"}`;\n}\n\nexport function getSettings<T>(setting: string, article?: string): T | null {\n\tif (!globalThis.af?.userSession) return null;\n\n\tconst key = getKey(article);\n\tconst settings = JSON.parse(localStorage.getItem(key) || \"{}\");\n\n\tif (setting) {\n\t\treturn settings[setting] || null;\n\t}\n\n\treturn settings;\n}\n\nexport function saveSettings<T>(setting: string, value: T | null, article?: string): void {\n\tif (!globalThis.af?.userSession) return;\n\n\tconst key = getKey(article);\n\tlet userSettings = JSON.parse(localStorage.getItem(key) || \"{}\");\n\n\tif (typeof value !== \"undefined\") {\n\t\tuserSettings[setting] = value;\n\t} else {\n\t\tuserSettings = setting;\n\t}\n\n\tlocalStorage.setItem(key, JSON.stringify(userSettings));\n}\n","import type { alert, confirm, openAlertDialog } from \"./alert.js\";\nimport { UserSession } from \"./UserSession.js\";\nimport \"./errorHandler.js\";\nimport type { expose } from \"./expose.js\";\nimport type { getLocalizedString } from \"./getLocalizedString.js\";\nimport type { localize } from \"./localize.js\";\nimport afLocalStorage from \"./localStorage.js\";\nimport type { getSessionData, logActivity, logError, startActivity, time } from \"./log.js\";\n\nexport { default as mitt } from \"mitt\";\nexport { toLocalDateTime, toLocalISODate, toLocalISODateTime } from \"./date.js\";\nexport { downloadBlob } from \"./downloadBlob.js\";\nexport type { DownloadItem, DownloadParameters } from \"./downloadRequest.js\";\nexport { downloadRequest } from \"./downloadRequest.js\";\nexport { expose } from \"./expose.js\";\nexport * from \"./filter/index.js\";\nexport { createLocalizer, getLocalizedString, type LocalizeOptions } from \"./getLocalizedString.js\";\nexport type { getUserImageOptions } from \"./getUserImage.js\";\nexport { getUserImage } from \"./getUserImage.js\";\nexport { invariant } from \"./invariant.js\";\nexport { localize } from \"./localize.js\";\nexport { afLocalStorage as localStorage };\nexport { getSessionData, logActivity, logError, startActivity, time } from \"./log.js\";\nexport * from \"./report/index.js\";\nexport { default as SessionHandler } from \"./SessionHandler.js\";\nexport { UserSession };\nexport type { AlertDialogButton, AlertDialogOptions, AlertOptions, ConfirmOptions } from \"./alert.js\";\nexport { alert, ConfirmButtons, confirm, openAlertDialog, waitForDialog } from \"./alert.js\";\nexport { getSettings, saveSettings } from \"./settings.js\";\n\n// oxlint-disable-next-line no-use-before-define -- this is injected build time\nexport const version = __VERSION__;\n\ndeclare global {\n\tvar __VERSION__: string;\n\tvar __DEV__: number;\n\tvar af: AfCore;\n\n\tinterface Window {\n\t\taf: AfCore;\n\t}\n}\n\nexport interface AfArticle {\n\thostName: string;\n\ti18n: Record<string, string>;\n\tid: string;\n\tversion?: string;\n}\n\nexport interface AfCore {\n\tarticle: AfArticle;\n\t/**\n\t * Strings of globalization projects other than the article's, by project name, for\n\t * code that runs on every page without belonging to an article (the site chrome).\n\t * Rendered server side or filled by the caller; see `getLocalizedString`'s `project`.\n\t */\n\ti18n?: Record<string, Record<string, string>>;\n\tcommon: {\n\t\tgetLocalizedString: typeof getLocalizedString;\n\t\texpose: typeof expose;\n\t\tgetSessionData: typeof getSessionData;\n\t\tlocalize: typeof localize;\n\t\tlocalStorage: typeof afLocalStorage;\n\t\tlogActivity: typeof logActivity;\n\t\tlogError: typeof logError;\n\t\tstartActivity: typeof startActivity;\n\t\talert: typeof alert;\n\t\tconfirm: typeof confirm;\n\t\topenAlertDialog: typeof openAlertDialog;\n\t\ttime: typeof time;\n\t\tversion?: string;\n\t};\n\tcontrols?: {\n\t\talert?: (message: string, callback?: () => void) => void;\n\t\tconfirm?: (options: {\n\t\t\ttitle: string;\n\t\t\tmessage: string;\n\t\t\tbuttons: string[];\n\t\t\tcallback: (button: number) => void;\n\t\t}) => void;\n\t};\n\tuserSession: UserSession;\n}\n"],"ignoreList":[0],"mappings":"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"}
|