@powerportalspro/react 5.0.0-beta.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/index.cjs +2333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2524 -0
- package/dist/index.d.ts +2524 -0
- package/dist/index.js +2263 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2263 @@
|
|
|
1
|
+
import { createContext, useContext, useCallback, useMemo, useEffect, useSyncExternalStore, useState, useRef, useReducer, useId } from 'react';
|
|
2
|
+
import { Transport, AuthClient, PowerPortalsProClient, LoginResult, SwitchIdentityResult } from '@powerportalspro/core';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/context.ts
|
|
6
|
+
var AuthStatus = {
|
|
7
|
+
Loading: "loading",
|
|
8
|
+
Anonymous: "anonymous",
|
|
9
|
+
Authenticated: "authenticated"
|
|
10
|
+
};
|
|
11
|
+
var PowerPortalsProContext = createContext(null);
|
|
12
|
+
|
|
13
|
+
// src/caches/in-memory-cache.ts
|
|
14
|
+
var InMemoryCache = class {
|
|
15
|
+
constructor(fetcher) {
|
|
16
|
+
this.fetcher = fetcher;
|
|
17
|
+
}
|
|
18
|
+
fetcher;
|
|
19
|
+
resolved = /* @__PURE__ */ new Map();
|
|
20
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
21
|
+
async getAsync(key) {
|
|
22
|
+
const cached = this.resolved.get(key);
|
|
23
|
+
if (cached !== void 0) return cached;
|
|
24
|
+
const pending = this.inFlight.get(key);
|
|
25
|
+
if (pending) return pending;
|
|
26
|
+
const promise = (async () => {
|
|
27
|
+
try {
|
|
28
|
+
const result = await this.fetcher(key);
|
|
29
|
+
if (result !== null) this.resolved.set(key, result);
|
|
30
|
+
return result;
|
|
31
|
+
} finally {
|
|
32
|
+
this.inFlight.delete(key);
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
this.inFlight.set(key, promise);
|
|
36
|
+
return promise;
|
|
37
|
+
}
|
|
38
|
+
invalidate(key) {
|
|
39
|
+
this.resolved.delete(key);
|
|
40
|
+
}
|
|
41
|
+
clear() {
|
|
42
|
+
this.resolved.clear();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var InMemoryTableMetadataCache = class {
|
|
46
|
+
cache;
|
|
47
|
+
constructor(ppp) {
|
|
48
|
+
this.cache = new InMemoryCache(
|
|
49
|
+
(tableLogicalName) => ppp.retrieveTableMetadataAsync(tableLogicalName)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
getAsync(tableLogicalName) {
|
|
53
|
+
return this.cache.getAsync(tableLogicalName);
|
|
54
|
+
}
|
|
55
|
+
invalidate(tableLogicalName) {
|
|
56
|
+
this.cache.invalidate(tableLogicalName);
|
|
57
|
+
}
|
|
58
|
+
clear() {
|
|
59
|
+
this.cache.clear();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var InMemoryViewMetadataCache = class {
|
|
63
|
+
cache;
|
|
64
|
+
constructor(ppp) {
|
|
65
|
+
this.cache = new InMemoryCache(
|
|
66
|
+
(viewId) => ppp.retrieveViewMetadataAsync(viewId)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
getAsync(viewId) {
|
|
70
|
+
return this.cache.getAsync(viewId);
|
|
71
|
+
}
|
|
72
|
+
invalidate(viewId) {
|
|
73
|
+
this.cache.invalidate(viewId);
|
|
74
|
+
}
|
|
75
|
+
clear() {
|
|
76
|
+
this.cache.clear();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var InMemoryEnvironmentFileSettingsCache = class _InMemoryEnvironmentFileSettingsCache {
|
|
80
|
+
static KEY = "environment-file-settings";
|
|
81
|
+
cache;
|
|
82
|
+
constructor(ppp) {
|
|
83
|
+
this.cache = new InMemoryCache(
|
|
84
|
+
() => ppp.getEnvironmentFileSettingsAsync()
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
getAsync() {
|
|
88
|
+
return this.cache.getAsync(_InMemoryEnvironmentFileSettingsCache.KEY);
|
|
89
|
+
}
|
|
90
|
+
invalidate() {
|
|
91
|
+
this.cache.invalidate(_InMemoryEnvironmentFileSettingsCache.KEY);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var InMemoryViewsForTableCache = class {
|
|
95
|
+
cache;
|
|
96
|
+
constructor(ppp) {
|
|
97
|
+
this.cache = new InMemoryCache(async (tableLogicalName) => {
|
|
98
|
+
const result = await ppp.retrieveViewsForTableAsync(tableLogicalName);
|
|
99
|
+
return result ?? [];
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async getAsync(tableLogicalName) {
|
|
103
|
+
const result = await this.cache.getAsync(tableLogicalName);
|
|
104
|
+
return result ?? [];
|
|
105
|
+
}
|
|
106
|
+
invalidate(tableLogicalName) {
|
|
107
|
+
this.cache.invalidate(tableLogicalName);
|
|
108
|
+
}
|
|
109
|
+
clear() {
|
|
110
|
+
this.cache.clear();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
function usePowerPortalsPro() {
|
|
114
|
+
const ctx = useContext(PowerPortalsProContext);
|
|
115
|
+
if (!ctx) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"usePowerPortalsPro must be used inside <PowerPortalsProProvider>. Wrap your app at or near the root."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return ctx;
|
|
121
|
+
}
|
|
122
|
+
var LocalizerContext = createContext(null);
|
|
123
|
+
LocalizerContext.displayName = "Localizer";
|
|
124
|
+
function useLocalizer() {
|
|
125
|
+
const ctx = useContext(LocalizerContext);
|
|
126
|
+
if (!ctx) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"useLocalizer must be used inside a <LocalizerProvider>. PowerPortalsProProvider auto-mounts a default localizer; ensure your tree is wrapped."
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return ctx;
|
|
132
|
+
}
|
|
133
|
+
function useT() {
|
|
134
|
+
return useLocalizer().t;
|
|
135
|
+
}
|
|
136
|
+
function usePrefixedT(prefix) {
|
|
137
|
+
const t = useT();
|
|
138
|
+
return useCallback(
|
|
139
|
+
(key, args) => t(`${prefix}.${key}`, args),
|
|
140
|
+
[t, prefix]
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
var LocalizationPrefetcherContext = createContext(null);
|
|
144
|
+
LocalizationPrefetcherContext.displayName = "LocalizationPrefetcher";
|
|
145
|
+
function useLocalizationPrefetcher() {
|
|
146
|
+
return useContext(LocalizationPrefetcherContext);
|
|
147
|
+
}
|
|
148
|
+
var NOOP_UNSUBSCRIBE = () => void 0;
|
|
149
|
+
function useLocalization(prefixes) {
|
|
150
|
+
const prefetcher = useLocalizationPrefetcher();
|
|
151
|
+
const prefixesKey = useMemo(() => {
|
|
152
|
+
if (prefixes.length === 0) return "";
|
|
153
|
+
const unique = Array.from(new Set(prefixes));
|
|
154
|
+
unique.sort();
|
|
155
|
+
return unique.join("\n");
|
|
156
|
+
}, [prefixes]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!prefetcher) return;
|
|
159
|
+
if (prefixesKey === "") return;
|
|
160
|
+
const tokens = prefixesKey.split("\n");
|
|
161
|
+
prefetcher.prefetch(tokens);
|
|
162
|
+
return prefetcher.subscribe(() => {
|
|
163
|
+
prefetcher.prefetch(tokens);
|
|
164
|
+
});
|
|
165
|
+
}, [prefetcher, prefixesKey]);
|
|
166
|
+
}
|
|
167
|
+
function useLocalizationLoaded(prefixes) {
|
|
168
|
+
const prefetcher = useLocalizationPrefetcher();
|
|
169
|
+
const subscribe = useCallback(
|
|
170
|
+
(listener) => prefetcher?.subscribe(listener) ?? NOOP_UNSUBSCRIBE,
|
|
171
|
+
[prefetcher]
|
|
172
|
+
);
|
|
173
|
+
const getSnapshot = useCallback(
|
|
174
|
+
() => prefetcher ? prefetcher.isLoaded(prefixes) : true,
|
|
175
|
+
[prefetcher, prefixes]
|
|
176
|
+
);
|
|
177
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshotAlwaysLoaded);
|
|
178
|
+
}
|
|
179
|
+
var getServerSnapshotAlwaysLoaded = () => true;
|
|
180
|
+
function LocalizerProvider({
|
|
181
|
+
value,
|
|
182
|
+
children
|
|
183
|
+
}) {
|
|
184
|
+
return /* @__PURE__ */ jsx(LocalizerContext.Provider, { value, children });
|
|
185
|
+
}
|
|
186
|
+
function flattenStrings(obj, prefix = "") {
|
|
187
|
+
const out = {};
|
|
188
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
189
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
190
|
+
if (typeof v === "string") {
|
|
191
|
+
out[key] = v;
|
|
192
|
+
} else if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
193
|
+
Object.assign(out, flattenStrings(v, key));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
function interpolate(template, args) {
|
|
199
|
+
if (!args || args.length === 0) return template;
|
|
200
|
+
return template.replace(/\{(\d+)(?::[^}]*)?\}/g, (match, indexStr) => {
|
|
201
|
+
const idx = Number(indexStr);
|
|
202
|
+
if (idx < 0 || idx >= args.length) return match;
|
|
203
|
+
return String(args[idx]);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function createLocalizer(strings, options) {
|
|
207
|
+
const warnedKeys = /* @__PURE__ */ new Set();
|
|
208
|
+
return {
|
|
209
|
+
t(key, args) {
|
|
210
|
+
const template = strings[key];
|
|
211
|
+
if (template === void 0) {
|
|
212
|
+
if (options?.onMissingKey && !warnedKeys.has(key)) {
|
|
213
|
+
warnedKeys.add(key);
|
|
214
|
+
options.onMissingKey(key);
|
|
215
|
+
}
|
|
216
|
+
return key;
|
|
217
|
+
}
|
|
218
|
+
return interpolate(template, args);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async function fetchLocalizationJson(url) {
|
|
223
|
+
const response = await fetch(url);
|
|
224
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} fetching ${url}`);
|
|
225
|
+
return await response.json();
|
|
226
|
+
}
|
|
227
|
+
function resolveLocalizationUrl(template, locale) {
|
|
228
|
+
return template.replace(/\{locale\}/g, locale);
|
|
229
|
+
}
|
|
230
|
+
var ASP_NET_CULTURE_COOKIE = ".AspNetCore.Culture";
|
|
231
|
+
function getAspNetCultureCookie() {
|
|
232
|
+
if (typeof document === "undefined" || !document.cookie) return null;
|
|
233
|
+
for (const part of document.cookie.split("; ")) {
|
|
234
|
+
const eq = part.indexOf("=");
|
|
235
|
+
if (eq < 0) continue;
|
|
236
|
+
const name = part.substring(0, eq);
|
|
237
|
+
if (name !== ASP_NET_CULTURE_COOKIE) continue;
|
|
238
|
+
const value = decodeURIComponent(part.substring(eq + 1));
|
|
239
|
+
const cMatch = value.match(/c=([^|]+)/);
|
|
240
|
+
if (cMatch?.[1]) return cMatch[1];
|
|
241
|
+
return value || null;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
function setAspNetCultureCookie(culture, options) {
|
|
246
|
+
if (typeof document === "undefined") return;
|
|
247
|
+
const path = options?.path ?? "/";
|
|
248
|
+
const maxAge = options?.maxAgeSeconds ?? 31536e3;
|
|
249
|
+
const value = encodeURIComponent(`c=${culture}|uic=${culture}`);
|
|
250
|
+
document.cookie = `${ASP_NET_CULTURE_COOKIE}=${value}; path=${path}; max-age=${maxAge}; samesite=lax`;
|
|
251
|
+
}
|
|
252
|
+
var DEFAULT_URL_LOCALE_PATTERN = /^\/([a-z]{2}(?:-[a-zA-Z]{2,4})?)(?:\/|$)/;
|
|
253
|
+
function getLocaleFromUrlPath(pattern = DEFAULT_URL_LOCALE_PATTERN) {
|
|
254
|
+
if (typeof window === "undefined") return null;
|
|
255
|
+
const match = window.location.pathname.match(pattern);
|
|
256
|
+
return match?.[1] ?? null;
|
|
257
|
+
}
|
|
258
|
+
function detectLocale(options) {
|
|
259
|
+
const {
|
|
260
|
+
matchUrl = true,
|
|
261
|
+
urlPattern = DEFAULT_URL_LOCALE_PATTERN,
|
|
262
|
+
matchBrowser = true,
|
|
263
|
+
fallback = "en"
|
|
264
|
+
} = options ?? {};
|
|
265
|
+
if (matchUrl) {
|
|
266
|
+
const fromUrl = getLocaleFromUrlPath(urlPattern);
|
|
267
|
+
if (fromUrl) return fromUrl;
|
|
268
|
+
}
|
|
269
|
+
const fromCookie = getAspNetCultureCookie();
|
|
270
|
+
if (fromCookie) return fromCookie;
|
|
271
|
+
if (matchBrowser && typeof navigator !== "undefined" && navigator.language) {
|
|
272
|
+
const primary = navigator.language.split("-")[0];
|
|
273
|
+
if (primary) return primary;
|
|
274
|
+
}
|
|
275
|
+
return fallback;
|
|
276
|
+
}
|
|
277
|
+
var MISSING_KEY_FLUSH_DELAY_MS = 75;
|
|
278
|
+
var DEFAULT_BUNDLE_SENTINEL = "defaultBundle";
|
|
279
|
+
function coerceKeyToToken(key) {
|
|
280
|
+
if (!key) return null;
|
|
281
|
+
const lower = key.toLowerCase();
|
|
282
|
+
if (!lower.startsWith("tables.")) return null;
|
|
283
|
+
const afterTables = lower.substring("tables.".length);
|
|
284
|
+
const firstDot = afterTables.indexOf(".");
|
|
285
|
+
if (firstDot < 0) return `tables.${afterTables}`;
|
|
286
|
+
const tableName = afterTables.substring(0, firstDot);
|
|
287
|
+
const afterTable = afterTables.substring(firstDot + 1);
|
|
288
|
+
if (afterTable.startsWith("views.")) {
|
|
289
|
+
const afterViews = afterTable.substring("views.".length);
|
|
290
|
+
const nextDot = afterViews.indexOf(".");
|
|
291
|
+
const viewId = nextDot < 0 ? afterViews : afterViews.substring(0, nextDot);
|
|
292
|
+
if (!viewId) return null;
|
|
293
|
+
return `views.${viewId}`;
|
|
294
|
+
}
|
|
295
|
+
return `tables.${tableName}`;
|
|
296
|
+
}
|
|
297
|
+
function coercePrefixToToken(prefix) {
|
|
298
|
+
if (!prefix) return null;
|
|
299
|
+
const lower = prefix.toLowerCase();
|
|
300
|
+
if (lower.startsWith("views.")) {
|
|
301
|
+
const afterViews = lower.substring("views.".length);
|
|
302
|
+
const nextDot = afterViews.indexOf(".");
|
|
303
|
+
const viewId = nextDot < 0 ? afterViews : afterViews.substring(0, nextDot);
|
|
304
|
+
return viewId ? `views.${viewId}` : null;
|
|
305
|
+
}
|
|
306
|
+
return coerceKeyToToken(prefix);
|
|
307
|
+
}
|
|
308
|
+
var LocaleContext = createContext(null);
|
|
309
|
+
LocaleContext.displayName = "Locale";
|
|
310
|
+
var LocalizationManifestContext = createContext(null);
|
|
311
|
+
LocalizationManifestContext.displayName = "LocalizationManifest";
|
|
312
|
+
function useLocale() {
|
|
313
|
+
const ctx = useContext(LocaleContext);
|
|
314
|
+
return ctx ?? { locale: "en", setLocale: noopSetLocale };
|
|
315
|
+
}
|
|
316
|
+
var noopSetLocale = (_) => void 0;
|
|
317
|
+
function LocaleProvider({
|
|
318
|
+
initialLocale = "en",
|
|
319
|
+
autoDetect = true,
|
|
320
|
+
urlPattern,
|
|
321
|
+
persistToCookie = true,
|
|
322
|
+
locale: controlledLocale,
|
|
323
|
+
onLocaleChange,
|
|
324
|
+
children
|
|
325
|
+
}) {
|
|
326
|
+
const [internalLocale, setInternalLocale] = useState(() => {
|
|
327
|
+
if (!autoDetect) return initialLocale;
|
|
328
|
+
return detectLocale({
|
|
329
|
+
matchUrl: urlPattern !== null,
|
|
330
|
+
...urlPattern instanceof RegExp && { urlPattern },
|
|
331
|
+
fallback: initialLocale
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
const isControlled = controlledLocale !== void 0;
|
|
335
|
+
const locale = isControlled ? controlledLocale : internalLocale;
|
|
336
|
+
const setLocale = useCallback(
|
|
337
|
+
(next) => {
|
|
338
|
+
if (!isControlled) setInternalLocale(next);
|
|
339
|
+
if (persistToCookie) setAspNetCultureCookie(next);
|
|
340
|
+
onLocaleChange?.(next);
|
|
341
|
+
},
|
|
342
|
+
[isControlled, persistToCookie, onLocaleChange]
|
|
343
|
+
);
|
|
344
|
+
const value = useMemo(() => ({ locale, setLocale }), [locale, setLocale]);
|
|
345
|
+
return /* @__PURE__ */ jsx(LocaleContext.Provider, { value, children });
|
|
346
|
+
}
|
|
347
|
+
function defaultMissingKeyWarning(key) {
|
|
348
|
+
console.warn(`PowerPortalsPro: missing localization key "${key}"`);
|
|
349
|
+
}
|
|
350
|
+
function DefaultLocalizerProvider({
|
|
351
|
+
urls,
|
|
352
|
+
url,
|
|
353
|
+
onMissingKey = defaultMissingKeyWarning,
|
|
354
|
+
prefixes,
|
|
355
|
+
children
|
|
356
|
+
}) {
|
|
357
|
+
const [cache, setCache] = useState({});
|
|
358
|
+
const [supportedLocales, setSupportedLocales] = useState(void 0);
|
|
359
|
+
const { locale } = useLocale();
|
|
360
|
+
const { ppp } = usePowerPortalsPro();
|
|
361
|
+
const localeRef = useRef(locale);
|
|
362
|
+
localeRef.current = locale;
|
|
363
|
+
const pppRef = useRef(ppp);
|
|
364
|
+
pppRef.current = ppp;
|
|
365
|
+
const resolvedLocaleRef = useRef(null);
|
|
366
|
+
const thumbprintsRef = useRef(null);
|
|
367
|
+
const pendingTokensRef = useRef(/* @__PURE__ */ new Set());
|
|
368
|
+
const triedTokensRef = useRef(/* @__PURE__ */ new Set());
|
|
369
|
+
const pendingTimerRef = useRef(null);
|
|
370
|
+
const settledTokensRef = useRef(/* @__PURE__ */ new Set());
|
|
371
|
+
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
372
|
+
const baselinePrefixesRef = useRef([]);
|
|
373
|
+
const baselineTokensRef = useRef([]);
|
|
374
|
+
const baselinePrefixesKey = useMemo(() => {
|
|
375
|
+
const input = prefixes ?? [];
|
|
376
|
+
if (input.length === 0) {
|
|
377
|
+
baselinePrefixesRef.current = [];
|
|
378
|
+
baselineTokensRef.current = [];
|
|
379
|
+
return "";
|
|
380
|
+
}
|
|
381
|
+
const unique = Array.from(new Set(input));
|
|
382
|
+
unique.sort();
|
|
383
|
+
baselinePrefixesRef.current = unique;
|
|
384
|
+
const tokens = [];
|
|
385
|
+
for (const p of unique) {
|
|
386
|
+
const t = coercePrefixToToken(p);
|
|
387
|
+
if (t !== null) tokens.push(t);
|
|
388
|
+
}
|
|
389
|
+
baselineTokensRef.current = tokens;
|
|
390
|
+
return unique.join("\n");
|
|
391
|
+
}, [prefixes]);
|
|
392
|
+
const notifyListeners = useCallback(() => {
|
|
393
|
+
const snapshot = Array.from(listenersRef.current);
|
|
394
|
+
for (const listener of snapshot) listener();
|
|
395
|
+
}, []);
|
|
396
|
+
const previousRunRef = useRef(null);
|
|
397
|
+
const flushPendingRef = useRef(() => void 0);
|
|
398
|
+
const overlayTemplates = urls ?? (url !== void 0 ? [url] : []);
|
|
399
|
+
const resolvedOverlayUrls = overlayTemplates.map((t) => resolveLocalizationUrl(t, locale));
|
|
400
|
+
const overlayUrlsKey = resolvedOverlayUrls.join("\n");
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
let cancelled = false;
|
|
403
|
+
const previous = previousRunRef.current;
|
|
404
|
+
const isLocaleOrUrlChange = previous !== null && (previous.locale !== locale || previous.overlayKey !== overlayUrlsKey);
|
|
405
|
+
previousRunRef.current = { locale, overlayKey: overlayUrlsKey };
|
|
406
|
+
if (isLocaleOrUrlChange) {
|
|
407
|
+
setCache({});
|
|
408
|
+
setSupportedLocales(void 0);
|
|
409
|
+
pendingTokensRef.current = /* @__PURE__ */ new Set();
|
|
410
|
+
triedTokensRef.current = /* @__PURE__ */ new Set();
|
|
411
|
+
if (pendingTimerRef.current !== null) {
|
|
412
|
+
clearTimeout(pendingTimerRef.current);
|
|
413
|
+
pendingTimerRef.current = null;
|
|
414
|
+
}
|
|
415
|
+
if (settledTokensRef.current.size > 0) {
|
|
416
|
+
settledTokensRef.current = /* @__PURE__ */ new Set();
|
|
417
|
+
notifyListeners();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
resolvedLocaleRef.current = null;
|
|
421
|
+
thumbprintsRef.current = null;
|
|
422
|
+
const overlayList = overlayUrlsKey.split("\n").filter((u) => u.length > 0);
|
|
423
|
+
(async () => {
|
|
424
|
+
try {
|
|
425
|
+
const manifest = await pppRef.current.retrieveLocalizationBundleManifestAsync();
|
|
426
|
+
if (cancelled) return;
|
|
427
|
+
setSupportedLocales(manifest.supportedLocales);
|
|
428
|
+
const resolvedLocale = manifest.supportedLocales.includes(locale) ? locale : manifest.supportedLocales[0] ?? locale;
|
|
429
|
+
const thumbs = await pppRef.current.retrieveLocalizationThumbprintsAsync(resolvedLocale);
|
|
430
|
+
if (cancelled) return;
|
|
431
|
+
resolvedLocaleRef.current = resolvedLocale;
|
|
432
|
+
thumbprintsRef.current = thumbs;
|
|
433
|
+
const [bundleData, ...overlayDatas] = await Promise.all([
|
|
434
|
+
pppRef.current.retrieveLocalizationBundleAsync(resolvedLocale, thumbs.bundle),
|
|
435
|
+
...overlayList.map(fetchLocalizationJson)
|
|
436
|
+
]);
|
|
437
|
+
if (cancelled) return;
|
|
438
|
+
const merged = {};
|
|
439
|
+
Object.assign(merged, flattenStrings(bundleData));
|
|
440
|
+
for (const data of overlayDatas) {
|
|
441
|
+
Object.assign(merged, flattenStrings(data));
|
|
442
|
+
}
|
|
443
|
+
setCache(merged);
|
|
444
|
+
} catch (e) {
|
|
445
|
+
if (cancelled) return;
|
|
446
|
+
console.error(
|
|
447
|
+
"PowerPortalsPro: failed to load localization bundle; staying on key-fallback localizer.",
|
|
448
|
+
e
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (cancelled) return;
|
|
452
|
+
settledTokensRef.current.add(DEFAULT_BUNDLE_SENTINEL);
|
|
453
|
+
for (const token of baselineTokensRef.current) {
|
|
454
|
+
if (triedTokensRef.current.has(token)) continue;
|
|
455
|
+
if (pendingTokensRef.current.has(token)) continue;
|
|
456
|
+
pendingTokensRef.current.add(token);
|
|
457
|
+
}
|
|
458
|
+
if (pendingTokensRef.current.size > 0 && pendingTimerRef.current === null) {
|
|
459
|
+
pendingTimerRef.current = setTimeout(
|
|
460
|
+
() => flushPendingRef.current(),
|
|
461
|
+
MISSING_KEY_FLUSH_DELAY_MS
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
notifyListeners();
|
|
465
|
+
})();
|
|
466
|
+
return () => {
|
|
467
|
+
cancelled = true;
|
|
468
|
+
if (pendingTimerRef.current !== null) {
|
|
469
|
+
clearTimeout(pendingTimerRef.current);
|
|
470
|
+
pendingTimerRef.current = null;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
}, [locale, overlayUrlsKey]);
|
|
474
|
+
const flushPending = useCallback(async () => {
|
|
475
|
+
pendingTimerRef.current = null;
|
|
476
|
+
const tokensToFetch = Array.from(pendingTokensRef.current);
|
|
477
|
+
if (tokensToFetch.length === 0) return;
|
|
478
|
+
pendingTokensRef.current = /* @__PURE__ */ new Set();
|
|
479
|
+
for (const tok of tokensToFetch) {
|
|
480
|
+
triedTokensRef.current.add(tok);
|
|
481
|
+
}
|
|
482
|
+
const thumbs = thumbprintsRef.current;
|
|
483
|
+
const requestLocale = resolvedLocaleRef.current;
|
|
484
|
+
if (!thumbs || !requestLocale) {
|
|
485
|
+
for (const tok of tokensToFetch) {
|
|
486
|
+
triedTokensRef.current.delete(tok);
|
|
487
|
+
pendingTokensRef.current.add(tok);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const fetches = tokensToFetch.map(async (token) => {
|
|
492
|
+
try {
|
|
493
|
+
if (token.startsWith("tables.")) {
|
|
494
|
+
const tableName = token.substring("tables.".length);
|
|
495
|
+
const thumb = thumbs.tables[tableName];
|
|
496
|
+
if (!thumb) return null;
|
|
497
|
+
return await pppRef.current.retrieveLocalizationTableBundleAsync(
|
|
498
|
+
tableName,
|
|
499
|
+
requestLocale,
|
|
500
|
+
thumb
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
if (token.startsWith("views.")) {
|
|
504
|
+
const viewId = token.substring("views.".length);
|
|
505
|
+
const thumb = thumbs.views[viewId];
|
|
506
|
+
if (!thumb) return null;
|
|
507
|
+
return await pppRef.current.retrieveLocalizationViewBundleAsync(
|
|
508
|
+
viewId,
|
|
509
|
+
requestLocale,
|
|
510
|
+
thumb
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
} catch (e) {
|
|
515
|
+
console.warn(`PowerPortalsPro: localization fetch failed for token ${token}`, e);
|
|
516
|
+
triedTokensRef.current.delete(token);
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
const responses = await Promise.all(fetches);
|
|
521
|
+
if (requestLocale !== resolvedLocaleRef.current) return;
|
|
522
|
+
let settledChanged = false;
|
|
523
|
+
for (const tok of tokensToFetch) {
|
|
524
|
+
if (!settledTokensRef.current.has(tok)) {
|
|
525
|
+
settledTokensRef.current.add(tok);
|
|
526
|
+
settledChanged = true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const merged = {};
|
|
530
|
+
for (const data of responses) {
|
|
531
|
+
if (data === null) continue;
|
|
532
|
+
Object.assign(merged, flattenStrings(data));
|
|
533
|
+
}
|
|
534
|
+
if (Object.keys(merged).length > 0) {
|
|
535
|
+
setCache((prev) => ({ ...prev, ...merged }));
|
|
536
|
+
}
|
|
537
|
+
if (settledChanged) {
|
|
538
|
+
notifyListeners();
|
|
539
|
+
}
|
|
540
|
+
}, [notifyListeners]);
|
|
541
|
+
flushPendingRef.current = flushPending;
|
|
542
|
+
useEffect(() => {
|
|
543
|
+
if (!settledTokensRef.current.has(DEFAULT_BUNDLE_SENTINEL)) return;
|
|
544
|
+
let added = false;
|
|
545
|
+
for (const token of baselineTokensRef.current) {
|
|
546
|
+
if (triedTokensRef.current.has(token)) continue;
|
|
547
|
+
if (pendingTokensRef.current.has(token)) continue;
|
|
548
|
+
pendingTokensRef.current.add(token);
|
|
549
|
+
added = true;
|
|
550
|
+
}
|
|
551
|
+
if (pendingTokensRef.current.size > 0 && pendingTimerRef.current === null) {
|
|
552
|
+
pendingTimerRef.current = setTimeout(flushPending, MISSING_KEY_FLUSH_DELAY_MS);
|
|
553
|
+
}
|
|
554
|
+
if (added) notifyListeners();
|
|
555
|
+
}, [baselinePrefixesKey, flushPending, notifyListeners]);
|
|
556
|
+
const queueMissingKey = useCallback(
|
|
557
|
+
(key) => {
|
|
558
|
+
const token = coerceKeyToToken(key);
|
|
559
|
+
if (token === null) return;
|
|
560
|
+
if (triedTokensRef.current.has(token)) return;
|
|
561
|
+
if (pendingTokensRef.current.has(token)) return;
|
|
562
|
+
pendingTokensRef.current.add(token);
|
|
563
|
+
if (pendingTimerRef.current === null) {
|
|
564
|
+
pendingTimerRef.current = setTimeout(flushPending, MISSING_KEY_FLUSH_DELAY_MS);
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
[flushPending]
|
|
568
|
+
);
|
|
569
|
+
const onMissingKeyCombined = useCallback(
|
|
570
|
+
(key) => {
|
|
571
|
+
onMissingKey(key);
|
|
572
|
+
queueMissingKey(key);
|
|
573
|
+
},
|
|
574
|
+
[onMissingKey, queueMissingKey]
|
|
575
|
+
);
|
|
576
|
+
const localizer = useMemo(
|
|
577
|
+
() => createLocalizer(cache, { onMissingKey: onMissingKeyCombined }),
|
|
578
|
+
[cache, onMissingKeyCombined]
|
|
579
|
+
);
|
|
580
|
+
const prefetcher = useMemo(
|
|
581
|
+
() => ({
|
|
582
|
+
prefetch(prefixes2) {
|
|
583
|
+
for (const prefix of prefixes2) {
|
|
584
|
+
const token = coercePrefixToToken(prefix);
|
|
585
|
+
if (token === null) continue;
|
|
586
|
+
if (triedTokensRef.current.has(token)) continue;
|
|
587
|
+
if (pendingTokensRef.current.has(token)) continue;
|
|
588
|
+
pendingTokensRef.current.add(token);
|
|
589
|
+
}
|
|
590
|
+
if (pendingTokensRef.current.size > 0 && pendingTimerRef.current === null) {
|
|
591
|
+
pendingTimerRef.current = setTimeout(flushPending, MISSING_KEY_FLUSH_DELAY_MS);
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
isLoaded(prefixes2) {
|
|
595
|
+
if (!settledTokensRef.current.has(DEFAULT_BUNDLE_SENTINEL)) return false;
|
|
596
|
+
for (const token of baselineTokensRef.current) {
|
|
597
|
+
if (!settledTokensRef.current.has(token)) return false;
|
|
598
|
+
}
|
|
599
|
+
for (const prefix of prefixes2) {
|
|
600
|
+
const token = coercePrefixToToken(prefix);
|
|
601
|
+
if (token === null) continue;
|
|
602
|
+
if (!settledTokensRef.current.has(token)) return false;
|
|
603
|
+
}
|
|
604
|
+
return true;
|
|
605
|
+
},
|
|
606
|
+
subscribe(listener) {
|
|
607
|
+
listenersRef.current.add(listener);
|
|
608
|
+
return () => {
|
|
609
|
+
listenersRef.current.delete(listener);
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
}),
|
|
613
|
+
[flushPending]
|
|
614
|
+
);
|
|
615
|
+
const manifestContextValue = useMemo(
|
|
616
|
+
() => ({ supportedLocales }),
|
|
617
|
+
[supportedLocales]
|
|
618
|
+
);
|
|
619
|
+
return /* @__PURE__ */ jsx(LocalizationManifestContext.Provider, { value: manifestContextValue, children: /* @__PURE__ */ jsx(LocalizationPrefetcherContext.Provider, { value: prefetcher, children: /* @__PURE__ */ jsx(LocalizerProvider, { value: localizer, children }) }) });
|
|
620
|
+
}
|
|
621
|
+
var UnsavedChangesRegistryContext = createContext(null);
|
|
622
|
+
UnsavedChangesRegistryContext.displayName = "UnsavedChangesRegistry";
|
|
623
|
+
function UnsavedChangesRegistryProvider({ children }) {
|
|
624
|
+
const reportersRef = useRef(/* @__PURE__ */ new Set());
|
|
625
|
+
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
626
|
+
const notify = useCallback(() => {
|
|
627
|
+
for (const listener of listenersRef.current) listener();
|
|
628
|
+
}, []);
|
|
629
|
+
const register = useCallback(
|
|
630
|
+
(reporter) => {
|
|
631
|
+
reportersRef.current.add(reporter);
|
|
632
|
+
notify();
|
|
633
|
+
return () => {
|
|
634
|
+
reportersRef.current.delete(reporter);
|
|
635
|
+
notify();
|
|
636
|
+
};
|
|
637
|
+
},
|
|
638
|
+
[notify]
|
|
639
|
+
);
|
|
640
|
+
const subscribe = useCallback((listener) => {
|
|
641
|
+
listenersRef.current.add(listener);
|
|
642
|
+
return () => {
|
|
643
|
+
listenersRef.current.delete(listener);
|
|
644
|
+
};
|
|
645
|
+
}, []);
|
|
646
|
+
const isAnyDirty = useCallback(() => {
|
|
647
|
+
for (const reporter of reportersRef.current) {
|
|
648
|
+
if (reporter.isDirty()) return true;
|
|
649
|
+
}
|
|
650
|
+
return false;
|
|
651
|
+
}, []);
|
|
652
|
+
const registry = useMemo(
|
|
653
|
+
() => ({ register, subscribe, isAnyDirty, notifyDirtyChanged: notify }),
|
|
654
|
+
[register, subscribe, isAnyDirty, notify]
|
|
655
|
+
);
|
|
656
|
+
return /* @__PURE__ */ jsx(UnsavedChangesRegistryContext.Provider, { value: registry, children });
|
|
657
|
+
}
|
|
658
|
+
function useUnsavedChangesRegistry() {
|
|
659
|
+
return useContext(UnsavedChangesRegistryContext);
|
|
660
|
+
}
|
|
661
|
+
function useIsAnyDirty() {
|
|
662
|
+
const registry = useUnsavedChangesRegistry();
|
|
663
|
+
const subscribe = useCallback(
|
|
664
|
+
(listener) => {
|
|
665
|
+
if (!registry) return () => {
|
|
666
|
+
};
|
|
667
|
+
return registry.subscribe(listener);
|
|
668
|
+
},
|
|
669
|
+
[registry]
|
|
670
|
+
);
|
|
671
|
+
const getSnapshot = useCallback(() => {
|
|
672
|
+
if (!registry) return false;
|
|
673
|
+
return registry.isAnyDirty();
|
|
674
|
+
}, [registry]);
|
|
675
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
676
|
+
}
|
|
677
|
+
function PowerPortalsProProvider({
|
|
678
|
+
children,
|
|
679
|
+
transport,
|
|
680
|
+
localizationUrl,
|
|
681
|
+
localizationUrls,
|
|
682
|
+
localizationPrefixes,
|
|
683
|
+
tableMetadataCache,
|
|
684
|
+
viewMetadataCache,
|
|
685
|
+
viewsForTableCache,
|
|
686
|
+
environmentFileSettingsCache
|
|
687
|
+
}) {
|
|
688
|
+
const transportRef = useRef(null);
|
|
689
|
+
if (transportRef.current === null) {
|
|
690
|
+
transportRef.current = transport ?? new Transport();
|
|
691
|
+
}
|
|
692
|
+
const transportInstance = transportRef.current;
|
|
693
|
+
const auth = useMemo(() => new AuthClient(transportInstance), [transportInstance]);
|
|
694
|
+
const ppp = useMemo(() => new PowerPortalsProClient(transportInstance), [transportInstance]);
|
|
695
|
+
const resolvedTableMetadataCache = useMemo(
|
|
696
|
+
() => tableMetadataCache ?? new InMemoryTableMetadataCache(ppp),
|
|
697
|
+
[ppp, tableMetadataCache]
|
|
698
|
+
);
|
|
699
|
+
const resolvedViewMetadataCache = useMemo(
|
|
700
|
+
() => viewMetadataCache ?? new InMemoryViewMetadataCache(ppp),
|
|
701
|
+
[ppp, viewMetadataCache]
|
|
702
|
+
);
|
|
703
|
+
const resolvedViewsForTableCache = useMemo(
|
|
704
|
+
() => viewsForTableCache ?? new InMemoryViewsForTableCache(ppp),
|
|
705
|
+
[ppp, viewsForTableCache]
|
|
706
|
+
);
|
|
707
|
+
const resolvedEnvironmentFileSettingsCache = useMemo(
|
|
708
|
+
() => environmentFileSettingsCache ?? new InMemoryEnvironmentFileSettingsCache(ppp),
|
|
709
|
+
[ppp, environmentFileSettingsCache]
|
|
710
|
+
);
|
|
711
|
+
const [authState, setAuthState] = useState({ status: AuthStatus.Loading });
|
|
712
|
+
const refreshAuth = useCallback(async () => {
|
|
713
|
+
try {
|
|
714
|
+
const me = await auth.getCurrentUserAsync();
|
|
715
|
+
if (me.isAuthenticated) {
|
|
716
|
+
setAuthState({ status: AuthStatus.Authenticated, user: me });
|
|
717
|
+
} else {
|
|
718
|
+
setAuthState({ status: AuthStatus.Anonymous });
|
|
719
|
+
}
|
|
720
|
+
} catch {
|
|
721
|
+
setAuthState({ status: AuthStatus.Anonymous });
|
|
722
|
+
}
|
|
723
|
+
}, [auth]);
|
|
724
|
+
const login = useCallback(
|
|
725
|
+
async (request) => {
|
|
726
|
+
const response = await auth.loginAsync(request);
|
|
727
|
+
if (response.result === LoginResult.Success) {
|
|
728
|
+
await refreshAuth();
|
|
729
|
+
}
|
|
730
|
+
return response;
|
|
731
|
+
},
|
|
732
|
+
[auth, refreshAuth]
|
|
733
|
+
);
|
|
734
|
+
const logout = useCallback(async () => {
|
|
735
|
+
await auth.logoutAsync();
|
|
736
|
+
setAuthState({ status: AuthStatus.Anonymous });
|
|
737
|
+
}, [auth]);
|
|
738
|
+
const switchIdentity = useCallback(async () => {
|
|
739
|
+
const response = await auth.switchIdentityAsync();
|
|
740
|
+
if (response.result === SwitchIdentityResult.Switched) {
|
|
741
|
+
await refreshAuth();
|
|
742
|
+
}
|
|
743
|
+
return response;
|
|
744
|
+
}, [auth, refreshAuth]);
|
|
745
|
+
useEffect(() => {
|
|
746
|
+
refreshAuth();
|
|
747
|
+
}, [refreshAuth]);
|
|
748
|
+
const value = useMemo(
|
|
749
|
+
() => ({
|
|
750
|
+
transport: transportInstance,
|
|
751
|
+
auth,
|
|
752
|
+
ppp,
|
|
753
|
+
tableMetadataCache: resolvedTableMetadataCache,
|
|
754
|
+
viewMetadataCache: resolvedViewMetadataCache,
|
|
755
|
+
viewsForTableCache: resolvedViewsForTableCache,
|
|
756
|
+
environmentFileSettingsCache: resolvedEnvironmentFileSettingsCache,
|
|
757
|
+
authState,
|
|
758
|
+
refreshAuth,
|
|
759
|
+
login,
|
|
760
|
+
logout,
|
|
761
|
+
switchIdentity
|
|
762
|
+
}),
|
|
763
|
+
[
|
|
764
|
+
transportInstance,
|
|
765
|
+
auth,
|
|
766
|
+
ppp,
|
|
767
|
+
resolvedTableMetadataCache,
|
|
768
|
+
resolvedViewMetadataCache,
|
|
769
|
+
resolvedViewsForTableCache,
|
|
770
|
+
resolvedEnvironmentFileSettingsCache,
|
|
771
|
+
authState,
|
|
772
|
+
refreshAuth,
|
|
773
|
+
login,
|
|
774
|
+
logout,
|
|
775
|
+
switchIdentity
|
|
776
|
+
]
|
|
777
|
+
);
|
|
778
|
+
let localizationContent;
|
|
779
|
+
if (localizationUrl === false) {
|
|
780
|
+
localizationContent = children;
|
|
781
|
+
} else {
|
|
782
|
+
const overlayUrls = [];
|
|
783
|
+
if (typeof localizationUrl === "string") overlayUrls.push(localizationUrl);
|
|
784
|
+
if (localizationUrls) overlayUrls.push(...localizationUrls);
|
|
785
|
+
localizationContent = /* @__PURE__ */ jsxs(LocaleProvider, { children: [
|
|
786
|
+
/* @__PURE__ */ jsx(CacheLocaleSync, {}),
|
|
787
|
+
/* @__PURE__ */ jsx(
|
|
788
|
+
DefaultLocalizerProvider,
|
|
789
|
+
{
|
|
790
|
+
...overlayUrls.length > 0 && { urls: overlayUrls },
|
|
791
|
+
...localizationPrefixes !== void 0 && { prefixes: localizationPrefixes },
|
|
792
|
+
children
|
|
793
|
+
}
|
|
794
|
+
)
|
|
795
|
+
] });
|
|
796
|
+
}
|
|
797
|
+
return /* @__PURE__ */ jsx(PowerPortalsProContext.Provider, { value, children: /* @__PURE__ */ jsx(UnsavedChangesRegistryProvider, { children: localizationContent }) });
|
|
798
|
+
}
|
|
799
|
+
function CacheLocaleSync() {
|
|
800
|
+
const { locale } = useLocale();
|
|
801
|
+
const {
|
|
802
|
+
tableMetadataCache,
|
|
803
|
+
viewMetadataCache,
|
|
804
|
+
viewsForTableCache,
|
|
805
|
+
environmentFileSettingsCache
|
|
806
|
+
} = usePowerPortalsPro();
|
|
807
|
+
const isFirstRun = useRef(true);
|
|
808
|
+
useEffect(() => {
|
|
809
|
+
if (isFirstRun.current) {
|
|
810
|
+
isFirstRun.current = false;
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
tableMetadataCache.clear();
|
|
814
|
+
viewMetadataCache.clear();
|
|
815
|
+
viewsForTableCache.clear();
|
|
816
|
+
environmentFileSettingsCache.invalidate();
|
|
817
|
+
}, [
|
|
818
|
+
locale,
|
|
819
|
+
tableMetadataCache,
|
|
820
|
+
viewMetadataCache,
|
|
821
|
+
viewsForTableCache,
|
|
822
|
+
environmentFileSettingsCache
|
|
823
|
+
]);
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/use-auth.ts
|
|
828
|
+
function useAuth() {
|
|
829
|
+
const { authState, refreshAuth, login, logout, switchIdentity } = usePowerPortalsPro();
|
|
830
|
+
return {
|
|
831
|
+
...authState,
|
|
832
|
+
refresh: refreshAuth,
|
|
833
|
+
login,
|
|
834
|
+
logout,
|
|
835
|
+
switchIdentity
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function AuthorizedView({ children, roles, fallback = null }) {
|
|
839
|
+
const auth = useAuth();
|
|
840
|
+
if (auth.status !== AuthStatus.Authenticated) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
841
|
+
if (roles !== void 0) {
|
|
842
|
+
const required = typeof roles === "string" ? [roles] : roles;
|
|
843
|
+
if (required.length > 0) {
|
|
844
|
+
const userRoles = auth.user.roles ?? [];
|
|
845
|
+
const hasAny = required.some((r) => userRoles.includes(r));
|
|
846
|
+
if (!hasAny) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
850
|
+
}
|
|
851
|
+
var QueryStatus = {
|
|
852
|
+
Loading: "loading",
|
|
853
|
+
Success: "success",
|
|
854
|
+
Error: "error"
|
|
855
|
+
};
|
|
856
|
+
function useAsyncResource(fetcher, deps, enabled = true, keepPreviousData = false) {
|
|
857
|
+
const [state, setState] = useState({
|
|
858
|
+
status: enabled ? QueryStatus.Loading : QueryStatus.Success,
|
|
859
|
+
data: void 0,
|
|
860
|
+
error: void 0,
|
|
861
|
+
isFetching: enabled
|
|
862
|
+
});
|
|
863
|
+
const [refetchTick, setRefetchTick] = useState(0);
|
|
864
|
+
const pendingResolversRef = useRef([]);
|
|
865
|
+
useEffect(() => {
|
|
866
|
+
if (!enabled) {
|
|
867
|
+
setState({
|
|
868
|
+
status: QueryStatus.Success,
|
|
869
|
+
data: void 0,
|
|
870
|
+
error: void 0,
|
|
871
|
+
isFetching: false
|
|
872
|
+
});
|
|
873
|
+
const resolvers = pendingResolversRef.current;
|
|
874
|
+
pendingResolversRef.current = [];
|
|
875
|
+
for (const r of resolvers) r.resolve();
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const controller = new AbortController();
|
|
879
|
+
setState((prev) => {
|
|
880
|
+
if (keepPreviousData && prev.status === QueryStatus.Success && prev.data !== void 0) {
|
|
881
|
+
if (prev.isFetching && prev.error === void 0) return prev;
|
|
882
|
+
return { ...prev, error: void 0, isFetching: true };
|
|
883
|
+
}
|
|
884
|
+
if (prev.status === QueryStatus.Loading && prev.data === void 0 && prev.error === void 0 && prev.isFetching) {
|
|
885
|
+
return prev;
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
status: QueryStatus.Loading,
|
|
889
|
+
data: void 0,
|
|
890
|
+
error: void 0,
|
|
891
|
+
isFetching: true
|
|
892
|
+
};
|
|
893
|
+
});
|
|
894
|
+
fetcher(controller.signal).then(
|
|
895
|
+
(data) => {
|
|
896
|
+
if (!controller.signal.aborted) {
|
|
897
|
+
setState({
|
|
898
|
+
status: QueryStatus.Success,
|
|
899
|
+
data,
|
|
900
|
+
error: void 0,
|
|
901
|
+
isFetching: false
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
const resolvers = pendingResolversRef.current;
|
|
905
|
+
pendingResolversRef.current = [];
|
|
906
|
+
for (const r of resolvers) r.resolve();
|
|
907
|
+
},
|
|
908
|
+
(err) => {
|
|
909
|
+
if (controller.signal.aborted || err instanceof Error && err.name === "AbortError") {
|
|
910
|
+
const resolvers2 = pendingResolversRef.current;
|
|
911
|
+
pendingResolversRef.current = [];
|
|
912
|
+
for (const r of resolvers2) r.resolve();
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
setState({
|
|
916
|
+
status: QueryStatus.Error,
|
|
917
|
+
data: void 0,
|
|
918
|
+
error: err,
|
|
919
|
+
isFetching: false
|
|
920
|
+
});
|
|
921
|
+
const resolvers = pendingResolversRef.current;
|
|
922
|
+
pendingResolversRef.current = [];
|
|
923
|
+
for (const r of resolvers) r.reject(err);
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
return () => {
|
|
927
|
+
controller.abort();
|
|
928
|
+
const resolvers = pendingResolversRef.current;
|
|
929
|
+
pendingResolversRef.current = [];
|
|
930
|
+
for (const r of resolvers) r.resolve();
|
|
931
|
+
};
|
|
932
|
+
}, [...deps, enabled, refetchTick]);
|
|
933
|
+
const refetch = useCallback(() => {
|
|
934
|
+
return new Promise((resolve, reject) => {
|
|
935
|
+
pendingResolversRef.current.push({ resolve, reject });
|
|
936
|
+
setRefetchTick((n) => n + 1);
|
|
937
|
+
});
|
|
938
|
+
}, []);
|
|
939
|
+
return { ...state, refetch };
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/use-record.ts
|
|
943
|
+
function useRecord(tableLogicalName, recordId, options) {
|
|
944
|
+
const { ppp } = usePowerPortalsPro();
|
|
945
|
+
const columns = options?.columns;
|
|
946
|
+
const enabled = options?.enabled ?? true;
|
|
947
|
+
return useAsyncResource(
|
|
948
|
+
(signal) => ppp.retrieveRecordAsync(tableLogicalName, recordId, columns, signal),
|
|
949
|
+
// Joining the columns array is the cheapest way to get a stable dep without
|
|
950
|
+
// forcing the consumer to memoize. Column lists are short (typically <20).
|
|
951
|
+
[ppp, tableLogicalName, recordId, columns?.join(",") ?? ""],
|
|
952
|
+
enabled
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// src/use-fetch-records.ts
|
|
957
|
+
function useFetchRecords(fetchXml, options) {
|
|
958
|
+
const { ppp } = usePowerPortalsPro();
|
|
959
|
+
const enabled = options?.enabled ?? true;
|
|
960
|
+
return useAsyncResource(
|
|
961
|
+
(signal) => ppp.retrieveRecordsAsync(fetchXml, signal),
|
|
962
|
+
[ppp, fetchXml],
|
|
963
|
+
enabled
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/use-grid-data.ts
|
|
968
|
+
function useGridData(request, options) {
|
|
969
|
+
const { ppp } = usePowerPortalsPro();
|
|
970
|
+
const enabled = options?.enabled ?? true;
|
|
971
|
+
const keepPreviousData = options?.keepPreviousData ?? false;
|
|
972
|
+
const requestKey = JSON.stringify(request);
|
|
973
|
+
return useAsyncResource(
|
|
974
|
+
(signal) => ppp.loadGridAsync(request, signal),
|
|
975
|
+
[ppp, requestKey],
|
|
976
|
+
enabled,
|
|
977
|
+
keepPreviousData
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
function reduce(state, action) {
|
|
981
|
+
switch (action.type) {
|
|
982
|
+
// Filter / sort / search / pageSize changes invalidate the current
|
|
983
|
+
// page — the new result set has different rows, the user expects to
|
|
984
|
+
// land on page 1. Mirrors what `<MainGrid>` does for standalone
|
|
985
|
+
// mode. Highlight changes DON'T reset the page (purely client-side).
|
|
986
|
+
case "setFilter":
|
|
987
|
+
return { ...state, filter: action.filter, page: 1 };
|
|
988
|
+
case "setSort":
|
|
989
|
+
return { ...state, sort: action.sort, page: 1 };
|
|
990
|
+
case "setSearch":
|
|
991
|
+
return { ...state, search: action.search, page: 1 };
|
|
992
|
+
case "setPage":
|
|
993
|
+
return { ...state, page: action.page };
|
|
994
|
+
case "setPageSize":
|
|
995
|
+
return { ...state, pageSize: action.pageSize, page: 1 };
|
|
996
|
+
case "setHighlight":
|
|
997
|
+
return { ...state, highlight: action.highlight };
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function useViewDataSource(options) {
|
|
1001
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
1002
|
+
const enabled = options.enabled ?? true;
|
|
1003
|
+
const keepPreviousData = options.keepPreviousData ?? true;
|
|
1004
|
+
const [state, dispatch] = useReducer(reduce, void 0, () => ({
|
|
1005
|
+
filter: options.initialFilter ?? EMPTY_FILTERS,
|
|
1006
|
+
sort: options.initialSort ?? EMPTY_SORTS,
|
|
1007
|
+
search: options.initialSearch ?? "",
|
|
1008
|
+
page: options.initialPage ?? 1,
|
|
1009
|
+
pageSize: options.initialPageSize ?? 50,
|
|
1010
|
+
highlight: options.initialHighlight
|
|
1011
|
+
}));
|
|
1012
|
+
const aggregationFingerprint = useMemo(() => {
|
|
1013
|
+
if (!options.aggregations || options.aggregations.length === 0) return null;
|
|
1014
|
+
return JSON.stringify({
|
|
1015
|
+
viewId: options.viewId,
|
|
1016
|
+
fetchXml: options.fetchXml,
|
|
1017
|
+
staticFilters: options.staticFilters,
|
|
1018
|
+
filter: state.filter,
|
|
1019
|
+
search: state.search,
|
|
1020
|
+
aggregations: options.aggregations
|
|
1021
|
+
});
|
|
1022
|
+
}, [
|
|
1023
|
+
options.viewId,
|
|
1024
|
+
options.fetchXml,
|
|
1025
|
+
options.staticFilters,
|
|
1026
|
+
options.aggregations,
|
|
1027
|
+
state.filter,
|
|
1028
|
+
state.search
|
|
1029
|
+
]);
|
|
1030
|
+
const lastFetchedAggregationFingerprintRef = useRef(null);
|
|
1031
|
+
const cachedAggregationResultsRef = useRef(EMPTY_AGGREGATIONS);
|
|
1032
|
+
const request = useMemo(() => {
|
|
1033
|
+
const filters = [
|
|
1034
|
+
...options.staticFilters ?? [],
|
|
1035
|
+
...state.filter
|
|
1036
|
+
];
|
|
1037
|
+
const includeAggregations = aggregationFingerprint !== null && aggregationFingerprint !== lastFetchedAggregationFingerprintRef.current;
|
|
1038
|
+
return {
|
|
1039
|
+
...options.viewId !== void 0 && { viewId: options.viewId },
|
|
1040
|
+
...options.fetchXml !== void 0 && { fetchXml: options.fetchXml },
|
|
1041
|
+
sorts: state.sort,
|
|
1042
|
+
pageNumber: state.page,
|
|
1043
|
+
pageSize: state.pageSize,
|
|
1044
|
+
...state.search !== "" && { searchText: state.search },
|
|
1045
|
+
...filters.length > 0 && { filters },
|
|
1046
|
+
...includeAggregations && options.aggregations && {
|
|
1047
|
+
aggregations: options.aggregations
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
}, [
|
|
1051
|
+
options.viewId,
|
|
1052
|
+
options.fetchXml,
|
|
1053
|
+
options.staticFilters,
|
|
1054
|
+
options.aggregations,
|
|
1055
|
+
state.filter,
|
|
1056
|
+
state.sort,
|
|
1057
|
+
state.search,
|
|
1058
|
+
state.page,
|
|
1059
|
+
state.pageSize,
|
|
1060
|
+
aggregationFingerprint
|
|
1061
|
+
]);
|
|
1062
|
+
const debouncedRequest = useDebouncedValue(request, debounceMs);
|
|
1063
|
+
const query = useGridData(debouncedRequest, { enabled, keepPreviousData });
|
|
1064
|
+
const setFilter = useCallback((filter) => {
|
|
1065
|
+
dispatch({ type: "setFilter", filter });
|
|
1066
|
+
}, []);
|
|
1067
|
+
const setSort = useCallback((sort) => {
|
|
1068
|
+
dispatch({ type: "setSort", sort });
|
|
1069
|
+
}, []);
|
|
1070
|
+
const setSearch = useCallback((search) => {
|
|
1071
|
+
dispatch({ type: "setSearch", search });
|
|
1072
|
+
}, []);
|
|
1073
|
+
const setPage = useCallback((page) => {
|
|
1074
|
+
dispatch({ type: "setPage", page });
|
|
1075
|
+
}, []);
|
|
1076
|
+
const setPageSize = useCallback((pageSize) => {
|
|
1077
|
+
dispatch({ type: "setPageSize", pageSize });
|
|
1078
|
+
}, []);
|
|
1079
|
+
const setHighlight = useCallback((highlight) => {
|
|
1080
|
+
dispatch({ type: "setHighlight", highlight });
|
|
1081
|
+
}, []);
|
|
1082
|
+
const refresh = useCallback(async () => {
|
|
1083
|
+
await query.refetch();
|
|
1084
|
+
}, [query.refetch]);
|
|
1085
|
+
const data = query.data;
|
|
1086
|
+
const rows = data?.tableRecords ?? EMPTY_RECORDS;
|
|
1087
|
+
const columns = data?.columns ?? EMPTY_COLUMNS;
|
|
1088
|
+
const totalCount = data?.pagingInfo?.totalRecordCount;
|
|
1089
|
+
const tablePermissions = data?.tablePermissions;
|
|
1090
|
+
const aggregations = options.aggregations ?? EMPTY_AGGREGATION_SPEC;
|
|
1091
|
+
const isFetchingAggregations = query.isFetching && debouncedRequest.aggregations !== void 0 && debouncedRequest.aggregations !== null && debouncedRequest.aggregations.length > 0;
|
|
1092
|
+
const lastSeenDataRef = useRef(void 0);
|
|
1093
|
+
useEffect(() => {
|
|
1094
|
+
if (data === lastSeenDataRef.current) return;
|
|
1095
|
+
lastSeenDataRef.current = data;
|
|
1096
|
+
if (data?.aggregations) {
|
|
1097
|
+
cachedAggregationResultsRef.current = data.aggregations;
|
|
1098
|
+
lastFetchedAggregationFingerprintRef.current = aggregationFingerprint;
|
|
1099
|
+
}
|
|
1100
|
+
}, [data, aggregationFingerprint]);
|
|
1101
|
+
const aggregationResults = (options.aggregations?.length ?? 0) === 0 ? EMPTY_AGGREGATIONS : data?.aggregations ?? cachedAggregationResultsRef.current;
|
|
1102
|
+
return useMemo(
|
|
1103
|
+
() => ({
|
|
1104
|
+
filter: state.filter,
|
|
1105
|
+
sort: state.sort,
|
|
1106
|
+
search: state.search,
|
|
1107
|
+
page: state.page,
|
|
1108
|
+
pageSize: state.pageSize,
|
|
1109
|
+
highlight: state.highlight,
|
|
1110
|
+
aggregations,
|
|
1111
|
+
status: query.status,
|
|
1112
|
+
rows,
|
|
1113
|
+
totalCount,
|
|
1114
|
+
columns,
|
|
1115
|
+
tablePermissions,
|
|
1116
|
+
aggregationResults,
|
|
1117
|
+
error: query.error,
|
|
1118
|
+
isFetching: query.isFetching,
|
|
1119
|
+
isFetchingAggregations,
|
|
1120
|
+
setFilter,
|
|
1121
|
+
setSort,
|
|
1122
|
+
setSearch,
|
|
1123
|
+
setPage,
|
|
1124
|
+
setPageSize,
|
|
1125
|
+
setHighlight,
|
|
1126
|
+
refresh
|
|
1127
|
+
}),
|
|
1128
|
+
[
|
|
1129
|
+
state.filter,
|
|
1130
|
+
state.sort,
|
|
1131
|
+
state.search,
|
|
1132
|
+
state.page,
|
|
1133
|
+
state.pageSize,
|
|
1134
|
+
state.highlight,
|
|
1135
|
+
aggregations,
|
|
1136
|
+
query.status,
|
|
1137
|
+
rows,
|
|
1138
|
+
totalCount,
|
|
1139
|
+
columns,
|
|
1140
|
+
tablePermissions,
|
|
1141
|
+
aggregationResults,
|
|
1142
|
+
query.error,
|
|
1143
|
+
query.isFetching,
|
|
1144
|
+
isFetchingAggregations,
|
|
1145
|
+
setFilter,
|
|
1146
|
+
setSort,
|
|
1147
|
+
setSearch,
|
|
1148
|
+
setPage,
|
|
1149
|
+
setPageSize,
|
|
1150
|
+
setHighlight,
|
|
1151
|
+
refresh
|
|
1152
|
+
]
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
var EMPTY_FILTERS = [];
|
|
1156
|
+
var EMPTY_SORTS = [];
|
|
1157
|
+
var EMPTY_RECORDS = [];
|
|
1158
|
+
var EMPTY_COLUMNS = [];
|
|
1159
|
+
var EMPTY_AGGREGATIONS = [];
|
|
1160
|
+
var EMPTY_AGGREGATION_SPEC = [];
|
|
1161
|
+
function useDebouncedValue(value, delayMs) {
|
|
1162
|
+
const [debounced, setDebounced] = useState(value);
|
|
1163
|
+
const valueKey = JSON.stringify(value);
|
|
1164
|
+
const previousKeyRef = useRef(valueKey);
|
|
1165
|
+
useEffect(() => {
|
|
1166
|
+
if (delayMs === 0) {
|
|
1167
|
+
setDebounced(value);
|
|
1168
|
+
previousKeyRef.current = valueKey;
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
if (previousKeyRef.current === valueKey) return;
|
|
1172
|
+
const timer = setTimeout(() => {
|
|
1173
|
+
setDebounced(value);
|
|
1174
|
+
previousKeyRef.current = valueKey;
|
|
1175
|
+
}, delayMs);
|
|
1176
|
+
return () => clearTimeout(timer);
|
|
1177
|
+
}, [valueKey, delayMs]);
|
|
1178
|
+
return debounced;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/use-table-metadata.ts
|
|
1182
|
+
function useTableMetadata(tableLogicalName, options) {
|
|
1183
|
+
const { tableMetadataCache } = usePowerPortalsPro();
|
|
1184
|
+
const enabled = options?.enabled ?? true;
|
|
1185
|
+
return useAsyncResource(
|
|
1186
|
+
async () => {
|
|
1187
|
+
const result = await tableMetadataCache.getAsync(tableLogicalName);
|
|
1188
|
+
return result ?? void 0;
|
|
1189
|
+
},
|
|
1190
|
+
[tableMetadataCache, tableLogicalName],
|
|
1191
|
+
enabled
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/use-table-permissions.ts
|
|
1196
|
+
function useTablePermissions(tableLogicalName, options) {
|
|
1197
|
+
const { ppp } = usePowerPortalsPro();
|
|
1198
|
+
const enabled = (options?.enabled ?? true) && tableLogicalName.length > 0;
|
|
1199
|
+
return useAsyncResource(
|
|
1200
|
+
async () => ppp.retrieveTablePermissionsAsync(tableLogicalName),
|
|
1201
|
+
[ppp, tableLogicalName],
|
|
1202
|
+
enabled
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/use-view-metadata.ts
|
|
1207
|
+
function useViewMetadata(viewId, options) {
|
|
1208
|
+
const { viewMetadataCache } = usePowerPortalsPro();
|
|
1209
|
+
const enabled = options?.enabled ?? true;
|
|
1210
|
+
return useAsyncResource(
|
|
1211
|
+
async () => {
|
|
1212
|
+
const result = await viewMetadataCache.getAsync(viewId);
|
|
1213
|
+
return result ?? void 0;
|
|
1214
|
+
},
|
|
1215
|
+
[viewMetadataCache, viewId],
|
|
1216
|
+
enabled
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/use-views-for-table.ts
|
|
1221
|
+
function useViewsForTable(tableLogicalName, options) {
|
|
1222
|
+
const { viewsForTableCache } = usePowerPortalsPro();
|
|
1223
|
+
const enabled = options?.enabled ?? true;
|
|
1224
|
+
return useAsyncResource(
|
|
1225
|
+
() => viewsForTableCache.getAsync(tableLogicalName),
|
|
1226
|
+
[viewsForTableCache, tableLogicalName],
|
|
1227
|
+
enabled
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/use-environment-file-settings.ts
|
|
1232
|
+
function useEnvironmentFileSettings(options) {
|
|
1233
|
+
const { environmentFileSettingsCache } = usePowerPortalsPro();
|
|
1234
|
+
const enabled = options?.enabled ?? true;
|
|
1235
|
+
return useAsyncResource(
|
|
1236
|
+
async () => {
|
|
1237
|
+
const result = await environmentFileSettingsCache.getAsync();
|
|
1238
|
+
return result ?? void 0;
|
|
1239
|
+
},
|
|
1240
|
+
[environmentFileSettingsCache],
|
|
1241
|
+
enabled
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
function useSupportedLocales(options) {
|
|
1245
|
+
const { ppp } = usePowerPortalsPro();
|
|
1246
|
+
const enabled = options?.enabled ?? true;
|
|
1247
|
+
const manifestState = useContext(LocalizationManifestContext);
|
|
1248
|
+
const providerScoped = manifestState !== null;
|
|
1249
|
+
const fallback = useAsyncResource(
|
|
1250
|
+
async () => {
|
|
1251
|
+
const manifest = await ppp.retrieveLocalizationBundleManifestAsync();
|
|
1252
|
+
return manifest.supportedLocales;
|
|
1253
|
+
},
|
|
1254
|
+
[ppp],
|
|
1255
|
+
enabled && !providerScoped
|
|
1256
|
+
);
|
|
1257
|
+
if (providerScoped) {
|
|
1258
|
+
if (manifestState.supportedLocales === void 0) {
|
|
1259
|
+
return {
|
|
1260
|
+
status: enabled ? QueryStatus.Loading : QueryStatus.Success,
|
|
1261
|
+
data: void 0,
|
|
1262
|
+
error: void 0,
|
|
1263
|
+
isFetching: enabled,
|
|
1264
|
+
refetch: fallback.refetch
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
return {
|
|
1268
|
+
status: QueryStatus.Success,
|
|
1269
|
+
data: manifestState.supportedLocales,
|
|
1270
|
+
error: void 0,
|
|
1271
|
+
isFetching: false,
|
|
1272
|
+
refetch: fallback.refetch
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
return fallback;
|
|
1276
|
+
}
|
|
1277
|
+
function useQueryParam(name) {
|
|
1278
|
+
const [value, setValue] = useState(() => readParam(name));
|
|
1279
|
+
useEffect(() => {
|
|
1280
|
+
if (typeof window === "undefined") return void 0;
|
|
1281
|
+
installHistoryPatch();
|
|
1282
|
+
setValue(readParam(name));
|
|
1283
|
+
const handler = () => setValue(readParam(name));
|
|
1284
|
+
window.addEventListener("popstate", handler);
|
|
1285
|
+
window.addEventListener(PUSH_EVENT, handler);
|
|
1286
|
+
window.addEventListener(REPLACE_EVENT, handler);
|
|
1287
|
+
return () => {
|
|
1288
|
+
window.removeEventListener("popstate", handler);
|
|
1289
|
+
window.removeEventListener(PUSH_EVENT, handler);
|
|
1290
|
+
window.removeEventListener(REPLACE_EVENT, handler);
|
|
1291
|
+
};
|
|
1292
|
+
}, [name]);
|
|
1293
|
+
return value;
|
|
1294
|
+
}
|
|
1295
|
+
var PUSH_EVENT = "ppp:pushstate";
|
|
1296
|
+
var REPLACE_EVENT = "ppp:replacestate";
|
|
1297
|
+
var historyPatched = false;
|
|
1298
|
+
function installHistoryPatch() {
|
|
1299
|
+
if (historyPatched) return;
|
|
1300
|
+
if (typeof window === "undefined" || typeof window.history === "undefined") return;
|
|
1301
|
+
historyPatched = true;
|
|
1302
|
+
const origPush = window.history.pushState.bind(window.history);
|
|
1303
|
+
const origReplace = window.history.replaceState.bind(window.history);
|
|
1304
|
+
window.history.pushState = function(...args) {
|
|
1305
|
+
origPush(...args);
|
|
1306
|
+
window.dispatchEvent(new Event(PUSH_EVENT));
|
|
1307
|
+
};
|
|
1308
|
+
window.history.replaceState = function(...args) {
|
|
1309
|
+
origReplace(...args);
|
|
1310
|
+
window.dispatchEvent(new Event(REPLACE_EVENT));
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
function readParam(name) {
|
|
1314
|
+
if (typeof window === "undefined") return null;
|
|
1315
|
+
try {
|
|
1316
|
+
const params = new URLSearchParams(window.location.search);
|
|
1317
|
+
return params.get(name);
|
|
1318
|
+
} catch {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
function useBeforeUnloadWhen(condition) {
|
|
1323
|
+
useEffect(() => {
|
|
1324
|
+
if (!condition) return;
|
|
1325
|
+
const handler = (e) => {
|
|
1326
|
+
e.preventDefault();
|
|
1327
|
+
e.returnValue = "";
|
|
1328
|
+
};
|
|
1329
|
+
window.addEventListener("beforeunload", handler);
|
|
1330
|
+
return () => window.removeEventListener("beforeunload", handler);
|
|
1331
|
+
}, [condition]);
|
|
1332
|
+
}
|
|
1333
|
+
var MainContextContext = createContext(null);
|
|
1334
|
+
MainContextContext.displayName = "MainContext";
|
|
1335
|
+
function MainContext({
|
|
1336
|
+
children,
|
|
1337
|
+
onBeforeSave,
|
|
1338
|
+
warnOnUnsavedChanges,
|
|
1339
|
+
forceSuccessfulValidationBeforeSave
|
|
1340
|
+
}) {
|
|
1341
|
+
const parentMainContext = useContext(MainContextContext);
|
|
1342
|
+
const isTopMost = parentMainContext === null;
|
|
1343
|
+
const resolvedWarnOnUnsavedChanges = warnOnUnsavedChanges ?? parentMainContext?.warnOnUnsavedChanges ?? true;
|
|
1344
|
+
const resolvedForceSuccessfulValidationBeforeSave = forceSuccessfulValidationBeforeSave ?? parentMainContext?.forceSuccessfulValidationBeforeSave ?? true;
|
|
1345
|
+
const descendantsRef = useRef(/* @__PURE__ */ new Set());
|
|
1346
|
+
const [version, setVersion] = useState(0);
|
|
1347
|
+
const { ppp } = usePowerPortalsPro();
|
|
1348
|
+
const refreshAll = useCallback(async () => {
|
|
1349
|
+
for (const descendant of descendantsRef.current) {
|
|
1350
|
+
await descendant.refresh();
|
|
1351
|
+
}
|
|
1352
|
+
}, []);
|
|
1353
|
+
const resetState = useCallback(() => {
|
|
1354
|
+
for (const descendant of descendantsRef.current) {
|
|
1355
|
+
descendant.resetState();
|
|
1356
|
+
}
|
|
1357
|
+
}, []);
|
|
1358
|
+
const validate = useCallback(async () => {
|
|
1359
|
+
for (const descendant of descendantsRef.current) {
|
|
1360
|
+
const ok = await descendant.validate();
|
|
1361
|
+
if (!ok) return false;
|
|
1362
|
+
}
|
|
1363
|
+
return true;
|
|
1364
|
+
}, []);
|
|
1365
|
+
const saveAll = useCallback(async () => {
|
|
1366
|
+
if (onBeforeSave) await onBeforeSave();
|
|
1367
|
+
if (resolvedForceSuccessfulValidationBeforeSave) {
|
|
1368
|
+
const valid = await validate();
|
|
1369
|
+
if (!valid) return;
|
|
1370
|
+
}
|
|
1371
|
+
const requests = [];
|
|
1372
|
+
for (const descendant of descendantsRef.current) {
|
|
1373
|
+
requests.push(...descendant.getRequests());
|
|
1374
|
+
}
|
|
1375
|
+
if (requests.length === 0) return;
|
|
1376
|
+
await ppp.executeMultipleAsync(requests, { returnResponses: false });
|
|
1377
|
+
await refreshAll();
|
|
1378
|
+
}, [onBeforeSave, validate, ppp, refreshAll, resolvedForceSuccessfulValidationBeforeSave]);
|
|
1379
|
+
const registerDescendant = useCallback((descendant) => {
|
|
1380
|
+
descendantsRef.current.add(descendant);
|
|
1381
|
+
setVersion((v) => v + 1);
|
|
1382
|
+
return () => {
|
|
1383
|
+
descendantsRef.current.delete(descendant);
|
|
1384
|
+
setVersion((v) => v + 1);
|
|
1385
|
+
};
|
|
1386
|
+
}, []);
|
|
1387
|
+
const notifyDescendantDirtyChanged = useCallback(() => {
|
|
1388
|
+
setVersion((v) => v + 1);
|
|
1389
|
+
}, []);
|
|
1390
|
+
const isDirty = useMemo(() => {
|
|
1391
|
+
for (const descendant of descendantsRef.current) {
|
|
1392
|
+
if (descendant.isDirty()) return true;
|
|
1393
|
+
}
|
|
1394
|
+
return false;
|
|
1395
|
+
}, [version]);
|
|
1396
|
+
const getRequests = useCallback(() => {
|
|
1397
|
+
const all = [];
|
|
1398
|
+
for (const descendant of descendantsRef.current) {
|
|
1399
|
+
all.push(...descendant.getRequests());
|
|
1400
|
+
}
|
|
1401
|
+
return all;
|
|
1402
|
+
}, []);
|
|
1403
|
+
const value = useMemo(
|
|
1404
|
+
() => ({
|
|
1405
|
+
saveAll,
|
|
1406
|
+
refreshAll,
|
|
1407
|
+
resetState,
|
|
1408
|
+
validate,
|
|
1409
|
+
getRequests,
|
|
1410
|
+
registerDescendant,
|
|
1411
|
+
notifyDescendantDirtyChanged,
|
|
1412
|
+
isDirty,
|
|
1413
|
+
warnOnUnsavedChanges: resolvedWarnOnUnsavedChanges,
|
|
1414
|
+
forceSuccessfulValidationBeforeSave: resolvedForceSuccessfulValidationBeforeSave
|
|
1415
|
+
}),
|
|
1416
|
+
[
|
|
1417
|
+
saveAll,
|
|
1418
|
+
refreshAll,
|
|
1419
|
+
resetState,
|
|
1420
|
+
validate,
|
|
1421
|
+
getRequests,
|
|
1422
|
+
registerDescendant,
|
|
1423
|
+
notifyDescendantDirtyChanged,
|
|
1424
|
+
isDirty,
|
|
1425
|
+
resolvedWarnOnUnsavedChanges,
|
|
1426
|
+
resolvedForceSuccessfulValidationBeforeSave
|
|
1427
|
+
]
|
|
1428
|
+
);
|
|
1429
|
+
useEffect(() => {
|
|
1430
|
+
if (!parentMainContext) return;
|
|
1431
|
+
return parentMainContext.registerDescendant({
|
|
1432
|
+
getRequests,
|
|
1433
|
+
validate,
|
|
1434
|
+
refresh: refreshAll,
|
|
1435
|
+
resetState,
|
|
1436
|
+
isDirty: () => isDirtyRef.current
|
|
1437
|
+
});
|
|
1438
|
+
}, [parentMainContext?.registerDescendant, getRequests, validate, refreshAll, resetState]);
|
|
1439
|
+
const parentMainContextRef = useRef(parentMainContext);
|
|
1440
|
+
parentMainContextRef.current = parentMainContext;
|
|
1441
|
+
useEffect(() => {
|
|
1442
|
+
parentMainContextRef.current?.notifyDescendantDirtyChanged();
|
|
1443
|
+
}, [isDirty]);
|
|
1444
|
+
useBeforeUnloadWhen(isTopMost && resolvedWarnOnUnsavedChanges && isDirty);
|
|
1445
|
+
const unsavedChangesRegistry = useUnsavedChangesRegistry();
|
|
1446
|
+
const isDirtyRef = useRef(isDirty);
|
|
1447
|
+
isDirtyRef.current = isDirty;
|
|
1448
|
+
const resolvedWarnRef = useRef(resolvedWarnOnUnsavedChanges);
|
|
1449
|
+
resolvedWarnRef.current = resolvedWarnOnUnsavedChanges;
|
|
1450
|
+
useEffect(() => {
|
|
1451
|
+
if (!isTopMost) return;
|
|
1452
|
+
if (!unsavedChangesRegistry) return;
|
|
1453
|
+
return unsavedChangesRegistry.register({
|
|
1454
|
+
isDirty: () => resolvedWarnRef.current && isDirtyRef.current
|
|
1455
|
+
});
|
|
1456
|
+
}, [isTopMost, unsavedChangesRegistry]);
|
|
1457
|
+
useEffect(() => {
|
|
1458
|
+
if (!isTopMost) return;
|
|
1459
|
+
if (!unsavedChangesRegistry) return;
|
|
1460
|
+
unsavedChangesRegistry.notifyDirtyChanged();
|
|
1461
|
+
}, [isTopMost, unsavedChangesRegistry, isDirty, resolvedWarnOnUnsavedChanges]);
|
|
1462
|
+
return /* @__PURE__ */ jsx(MainContextContext.Provider, { value, children });
|
|
1463
|
+
}
|
|
1464
|
+
function useMainContext() {
|
|
1465
|
+
return useContext(MainContextContext);
|
|
1466
|
+
}
|
|
1467
|
+
function MainContextProvider({
|
|
1468
|
+
value,
|
|
1469
|
+
children
|
|
1470
|
+
}) {
|
|
1471
|
+
return /* @__PURE__ */ jsx(MainContextContext.Provider, { value, children });
|
|
1472
|
+
}
|
|
1473
|
+
var OverlayContext = createContext(null);
|
|
1474
|
+
OverlayContext.displayName = "OverlayService";
|
|
1475
|
+
function useOverlayService() {
|
|
1476
|
+
return useContext(OverlayContext)?.service ?? null;
|
|
1477
|
+
}
|
|
1478
|
+
function useOverlayForTarget(targetId) {
|
|
1479
|
+
const ctx = useContext(OverlayContext);
|
|
1480
|
+
if (!ctx) return null;
|
|
1481
|
+
const stack = ctx.overlaysByTarget.get(targetId ?? "") ?? [];
|
|
1482
|
+
return stack[stack.length - 1] ?? null;
|
|
1483
|
+
}
|
|
1484
|
+
function OverlayServiceProvider({
|
|
1485
|
+
value,
|
|
1486
|
+
children
|
|
1487
|
+
}) {
|
|
1488
|
+
return /* @__PURE__ */ jsx(OverlayContext.Provider, { value, children });
|
|
1489
|
+
}
|
|
1490
|
+
function useOverlayServiceState() {
|
|
1491
|
+
const [overlaysByTarget, setOverlaysByTarget] = useState(() => /* @__PURE__ */ new Map());
|
|
1492
|
+
const nextIdRef = useRef(1);
|
|
1493
|
+
const service = useMemo(
|
|
1494
|
+
() => ({
|
|
1495
|
+
show(options) {
|
|
1496
|
+
const id = `overlay-${nextIdRef.current++}`;
|
|
1497
|
+
const instance = { id, ...options };
|
|
1498
|
+
setOverlaysByTarget((prev) => {
|
|
1499
|
+
const key = options?.targetId ?? "";
|
|
1500
|
+
const next = new Map(prev);
|
|
1501
|
+
next.set(key, [...next.get(key) ?? [], instance]);
|
|
1502
|
+
return next;
|
|
1503
|
+
});
|
|
1504
|
+
return instance;
|
|
1505
|
+
},
|
|
1506
|
+
hide(overlay) {
|
|
1507
|
+
setOverlaysByTarget((prev) => {
|
|
1508
|
+
const key = overlay.targetId ?? "";
|
|
1509
|
+
const stack = prev.get(key) ?? [];
|
|
1510
|
+
const idx = stack.findIndex((o) => o.id === overlay.id);
|
|
1511
|
+
if (idx < 0) return prev;
|
|
1512
|
+
const next = new Map(prev);
|
|
1513
|
+
if (stack.length === 1) {
|
|
1514
|
+
next.delete(key);
|
|
1515
|
+
} else {
|
|
1516
|
+
next.set(key, [...stack.slice(0, idx), ...stack.slice(idx + 1)]);
|
|
1517
|
+
}
|
|
1518
|
+
return next;
|
|
1519
|
+
});
|
|
1520
|
+
},
|
|
1521
|
+
hideAll() {
|
|
1522
|
+
setOverlaysByTarget(/* @__PURE__ */ new Map());
|
|
1523
|
+
}
|
|
1524
|
+
}),
|
|
1525
|
+
[]
|
|
1526
|
+
);
|
|
1527
|
+
return useMemo(() => ({ service, overlaysByTarget }), [service, overlaysByTarget]);
|
|
1528
|
+
}
|
|
1529
|
+
async function runWithOverlayAsync(service, options, operation) {
|
|
1530
|
+
if (!service) return operation();
|
|
1531
|
+
const handle = service.show(options);
|
|
1532
|
+
try {
|
|
1533
|
+
return await operation();
|
|
1534
|
+
} finally {
|
|
1535
|
+
service.hide(handle);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
function useRunWithOverlay() {
|
|
1539
|
+
const service = useOverlayService();
|
|
1540
|
+
return useCallback(
|
|
1541
|
+
(options, operation) => runWithOverlayAsync(service, options, operation),
|
|
1542
|
+
[service]
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
var ValidationContextContext = createContext(null);
|
|
1546
|
+
ValidationContextContext.displayName = "ValidationContext";
|
|
1547
|
+
function useValidationContext() {
|
|
1548
|
+
return useContext(ValidationContextContext);
|
|
1549
|
+
}
|
|
1550
|
+
function useColumnValidationErrors(columnName) {
|
|
1551
|
+
const ctx = useValidationContext();
|
|
1552
|
+
if (!ctx) return void 0;
|
|
1553
|
+
return ctx.errors.get(columnName);
|
|
1554
|
+
}
|
|
1555
|
+
function ValidationProvider({ children }) {
|
|
1556
|
+
const validatorsRef = useRef(/* @__PURE__ */ new Map());
|
|
1557
|
+
const [errors, setErrors] = useState(
|
|
1558
|
+
() => /* @__PURE__ */ new Map()
|
|
1559
|
+
);
|
|
1560
|
+
const register = useCallback(
|
|
1561
|
+
(columnName, validator) => {
|
|
1562
|
+
validatorsRef.current.set(columnName, validator);
|
|
1563
|
+
return () => {
|
|
1564
|
+
validatorsRef.current.delete(columnName);
|
|
1565
|
+
setErrors((prev) => {
|
|
1566
|
+
if (!prev.has(columnName)) return prev;
|
|
1567
|
+
const next = new Map(prev);
|
|
1568
|
+
next.delete(columnName);
|
|
1569
|
+
return next;
|
|
1570
|
+
});
|
|
1571
|
+
};
|
|
1572
|
+
},
|
|
1573
|
+
[]
|
|
1574
|
+
);
|
|
1575
|
+
const validateColumn = useCallback(async (columnName) => {
|
|
1576
|
+
const validator = validatorsRef.current.get(columnName);
|
|
1577
|
+
if (!validator) return;
|
|
1578
|
+
const fieldErrors = await validator();
|
|
1579
|
+
setErrors((prev) => {
|
|
1580
|
+
const existing = prev.get(columnName) ?? [];
|
|
1581
|
+
if (existing.length === fieldErrors.length && existing.every((e, i) => e === fieldErrors[i])) {
|
|
1582
|
+
return prev;
|
|
1583
|
+
}
|
|
1584
|
+
const next = new Map(prev);
|
|
1585
|
+
if (fieldErrors.length === 0) {
|
|
1586
|
+
next.delete(columnName);
|
|
1587
|
+
} else {
|
|
1588
|
+
next.set(columnName, fieldErrors);
|
|
1589
|
+
}
|
|
1590
|
+
return next;
|
|
1591
|
+
});
|
|
1592
|
+
}, []);
|
|
1593
|
+
const validateAll = useCallback(async () => {
|
|
1594
|
+
const next = /* @__PURE__ */ new Map();
|
|
1595
|
+
for (const [col, validator] of validatorsRef.current) {
|
|
1596
|
+
const fieldErrors = await validator();
|
|
1597
|
+
if (fieldErrors.length > 0) {
|
|
1598
|
+
next.set(col, fieldErrors);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
setErrors(next);
|
|
1602
|
+
return next.size === 0;
|
|
1603
|
+
}, []);
|
|
1604
|
+
const clearErrors = useCallback(() => {
|
|
1605
|
+
setErrors(/* @__PURE__ */ new Map());
|
|
1606
|
+
}, []);
|
|
1607
|
+
const isValid = errors.size === 0;
|
|
1608
|
+
const value = useMemo(
|
|
1609
|
+
() => ({ register, validateColumn, validateAll, clearErrors, isValid, errors }),
|
|
1610
|
+
[register, validateColumn, validateAll, clearErrors, isValid, errors]
|
|
1611
|
+
);
|
|
1612
|
+
return /* @__PURE__ */ jsx(ValidationContextContext.Provider, { value, children });
|
|
1613
|
+
}
|
|
1614
|
+
function valuesEqual(a, b) {
|
|
1615
|
+
const aEmpty = a === null || a === void 0;
|
|
1616
|
+
const bEmpty = b === null || b === void 0;
|
|
1617
|
+
if (aEmpty && bEmpty) return true;
|
|
1618
|
+
if (aEmpty !== bEmpty) return false;
|
|
1619
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
1620
|
+
}
|
|
1621
|
+
var GUID_PATTERN = /^\{?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}?$/i;
|
|
1622
|
+
function parseQueryDefault(metadata, rawValue) {
|
|
1623
|
+
switch (metadata.$type) {
|
|
1624
|
+
// StringMetadata + MemoMetadata both ride on StringValue. In Blazor
|
|
1625
|
+
// MemoMetadata derives from StringMetadata so a single `StringMetadata`
|
|
1626
|
+
// pattern catches both; in TS the discriminators are distinct so we
|
|
1627
|
+
// list both arms.
|
|
1628
|
+
case 14:
|
|
1629
|
+
case 7:
|
|
1630
|
+
return { $type: 14, value: rawValue };
|
|
1631
|
+
case 5: {
|
|
1632
|
+
const n = Number(rawValue);
|
|
1633
|
+
return Number.isFinite(n) && Number.isInteger(n) ? { $type: 5, value: n } : null;
|
|
1634
|
+
}
|
|
1635
|
+
case 18: {
|
|
1636
|
+
const n = Number(rawValue);
|
|
1637
|
+
return Number.isFinite(n) && Number.isInteger(n) ? { $type: 18, value: n } : null;
|
|
1638
|
+
}
|
|
1639
|
+
case 3: {
|
|
1640
|
+
const n = Number(rawValue);
|
|
1641
|
+
return Number.isFinite(n) ? { $type: 3, value: n } : null;
|
|
1642
|
+
}
|
|
1643
|
+
case 4: {
|
|
1644
|
+
const n = Number(rawValue);
|
|
1645
|
+
return Number.isFinite(n) ? { $type: 4, value: n } : null;
|
|
1646
|
+
}
|
|
1647
|
+
case 8: {
|
|
1648
|
+
const n = Number(rawValue);
|
|
1649
|
+
return Number.isFinite(n) ? { $type: 8, value: n } : null;
|
|
1650
|
+
}
|
|
1651
|
+
case 0: {
|
|
1652
|
+
const lower = rawValue.trim().toLowerCase();
|
|
1653
|
+
if (lower === "true") return { $type: 0, value: true };
|
|
1654
|
+
if (lower === "false") return { $type: 0, value: false };
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
case 2: {
|
|
1658
|
+
const ts = Date.parse(rawValue);
|
|
1659
|
+
if (Number.isNaN(ts)) return null;
|
|
1660
|
+
return { $type: 2, value: new Date(ts).toISOString() };
|
|
1661
|
+
}
|
|
1662
|
+
case 11: {
|
|
1663
|
+
const n = Number(rawValue);
|
|
1664
|
+
return Number.isFinite(n) && Number.isInteger(n) ? { $type: 11, value: n } : null;
|
|
1665
|
+
}
|
|
1666
|
+
case 40: {
|
|
1667
|
+
const parts = rawValue.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1668
|
+
const values = [];
|
|
1669
|
+
for (const p of parts) {
|
|
1670
|
+
const n = Number(p);
|
|
1671
|
+
if (Number.isFinite(n) && Number.isInteger(n)) values.push({ value: n });
|
|
1672
|
+
}
|
|
1673
|
+
return values.length > 0 ? { $type: 40, value: values } : null;
|
|
1674
|
+
}
|
|
1675
|
+
case 6: {
|
|
1676
|
+
try {
|
|
1677
|
+
const parsed = JSON.parse(rawValue);
|
|
1678
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
1679
|
+
const findKey = (target) => Object.keys(parsed).find(
|
|
1680
|
+
(k) => k.toLowerCase() === target.toLowerCase()
|
|
1681
|
+
);
|
|
1682
|
+
const obj = parsed;
|
|
1683
|
+
const tableNameKey = findKey("tableName");
|
|
1684
|
+
const valueKey = findKey("value");
|
|
1685
|
+
const nameKey = findKey("name");
|
|
1686
|
+
const tableName = tableNameKey ? obj[tableNameKey] : void 0;
|
|
1687
|
+
const value = valueKey ? obj[valueKey] : void 0;
|
|
1688
|
+
const name = nameKey ? obj[nameKey] : null;
|
|
1689
|
+
if (typeof tableName !== "string" || tableName.length === 0) return null;
|
|
1690
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
1691
|
+
return {
|
|
1692
|
+
$type: 6,
|
|
1693
|
+
tableName,
|
|
1694
|
+
value,
|
|
1695
|
+
name: typeof name === "string" ? name : null
|
|
1696
|
+
};
|
|
1697
|
+
} catch {
|
|
1698
|
+
return null;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
case 15: {
|
|
1702
|
+
if (!GUID_PATTERN.test(rawValue)) return null;
|
|
1703
|
+
return { $type: 15, value: rawValue.replace(/[{}]/g, "") };
|
|
1704
|
+
}
|
|
1705
|
+
default:
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
function parseQueryDefaultsFromUrl(search, tableMetadata) {
|
|
1710
|
+
const out = {};
|
|
1711
|
+
if (!search) return out;
|
|
1712
|
+
const params = new URLSearchParams(search);
|
|
1713
|
+
for (const [columnName, rawValue] of params) {
|
|
1714
|
+
if (!columnName) continue;
|
|
1715
|
+
const meta = tableMetadata.columns.find(
|
|
1716
|
+
(c) => c.columnName === columnName
|
|
1717
|
+
);
|
|
1718
|
+
if (!meta) continue;
|
|
1719
|
+
try {
|
|
1720
|
+
const parsed = parseQueryDefault(meta, rawValue);
|
|
1721
|
+
if (parsed) out[columnName] = parsed;
|
|
1722
|
+
} catch {
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return out;
|
|
1726
|
+
}
|
|
1727
|
+
var RecordContextContext = createContext(null);
|
|
1728
|
+
RecordContextContext.displayName = "RecordContext";
|
|
1729
|
+
function RecordContext(props) {
|
|
1730
|
+
return /* @__PURE__ */ jsx(ValidationProvider, { children: /* @__PURE__ */ jsx(RecordContextInner, { ...props }) });
|
|
1731
|
+
}
|
|
1732
|
+
function RecordContextInner({
|
|
1733
|
+
children,
|
|
1734
|
+
table,
|
|
1735
|
+
id: idProp,
|
|
1736
|
+
queryParameterName,
|
|
1737
|
+
initialRecord,
|
|
1738
|
+
loadedRecord: loadedRecordProp,
|
|
1739
|
+
initialPendingChanges,
|
|
1740
|
+
columns,
|
|
1741
|
+
onRecordLoaded,
|
|
1742
|
+
onBeforeSave,
|
|
1743
|
+
onBeforeDelete,
|
|
1744
|
+
warnOnUnsavedChanges,
|
|
1745
|
+
forceSuccessfulValidationBeforeSave
|
|
1746
|
+
}) {
|
|
1747
|
+
const queryId = useQueryParam(queryParameterName ?? "");
|
|
1748
|
+
const resolvedId = queryParameterName && queryId !== null && queryId.length > 0 ? queryId : idProp;
|
|
1749
|
+
const propIsEmptyId = !resolvedId || resolvedId === "00000000-0000-0000-0000-000000000000";
|
|
1750
|
+
const [effectiveId, setEffectiveId] = useState(
|
|
1751
|
+
propIsEmptyId ? void 0 : resolvedId
|
|
1752
|
+
);
|
|
1753
|
+
useEffect(() => {
|
|
1754
|
+
setEffectiveId(propIsEmptyId ? void 0 : resolvedId);
|
|
1755
|
+
}, [resolvedId, propIsEmptyId]);
|
|
1756
|
+
const isCreateMode = effectiveId === void 0;
|
|
1757
|
+
const isSkipFetchMode = !isCreateMode && loadedRecordProp !== void 0;
|
|
1758
|
+
const { ppp } = usePowerPortalsPro();
|
|
1759
|
+
const mainContext = useMainContext();
|
|
1760
|
+
const validation = useValidationContext();
|
|
1761
|
+
const isTopMost = mainContext === null;
|
|
1762
|
+
const resolvedWarnOnUnsavedChanges = warnOnUnsavedChanges ?? mainContext?.warnOnUnsavedChanges ?? true;
|
|
1763
|
+
const resolvedForceSuccessfulValidationBeforeSave = forceSuccessfulValidationBeforeSave ?? mainContext?.forceSuccessfulValidationBeforeSave ?? true;
|
|
1764
|
+
const overlayService = useOverlayService();
|
|
1765
|
+
const localizer = useLocalizer();
|
|
1766
|
+
const overlayTargetId = useId();
|
|
1767
|
+
const query = useRecord(table, effectiveId ?? "", {
|
|
1768
|
+
...columns ? { columns } : {},
|
|
1769
|
+
enabled: !isCreateMode && !isSkipFetchMode
|
|
1770
|
+
});
|
|
1771
|
+
const metadataQuery = useTableMetadata(table);
|
|
1772
|
+
const [loadedRecord, setLoadedRecord] = useState(
|
|
1773
|
+
() => isSkipFetchMode ? loadedRecordProp : void 0
|
|
1774
|
+
);
|
|
1775
|
+
const [pendingChanges, setPendingChanges] = useState(
|
|
1776
|
+
() => /* @__PURE__ */ new Map()
|
|
1777
|
+
);
|
|
1778
|
+
const onRecordLoadedRef = useRef(onRecordLoaded);
|
|
1779
|
+
onRecordLoadedRef.current = onRecordLoaded;
|
|
1780
|
+
const onBeforeSaveRef = useRef(onBeforeSave);
|
|
1781
|
+
onBeforeSaveRef.current = onBeforeSave;
|
|
1782
|
+
const refetchRef = useRef(query.refetch);
|
|
1783
|
+
refetchRef.current = query.refetch;
|
|
1784
|
+
const recordRef = useRef(void 0);
|
|
1785
|
+
const loadedRecordRef = useRef(void 0);
|
|
1786
|
+
const ownDirtyRef = useRef(false);
|
|
1787
|
+
const pendingChangesRef = useRef(/* @__PURE__ */ new Map());
|
|
1788
|
+
pendingChangesRef.current = pendingChanges;
|
|
1789
|
+
const validationRef = useRef(validation);
|
|
1790
|
+
validationRef.current = validation;
|
|
1791
|
+
const isCreateModeRef = useRef(isCreateMode);
|
|
1792
|
+
isCreateModeRef.current = isCreateMode;
|
|
1793
|
+
const forceSuccessfulValidationBeforeSaveRef = useRef(
|
|
1794
|
+
resolvedForceSuccessfulValidationBeforeSave
|
|
1795
|
+
);
|
|
1796
|
+
forceSuccessfulValidationBeforeSaveRef.current = resolvedForceSuccessfulValidationBeforeSave;
|
|
1797
|
+
const queryParameterNameRef = useRef(queryParameterName);
|
|
1798
|
+
queryParameterNameRef.current = queryParameterName;
|
|
1799
|
+
const isTopMostRef = useRef(mainContext === null);
|
|
1800
|
+
isTopMostRef.current = mainContext === null;
|
|
1801
|
+
useEffect(() => {
|
|
1802
|
+
if (!isCreateMode) return;
|
|
1803
|
+
const baseSeed = initialRecord ?? { tableName: table, properties: {} };
|
|
1804
|
+
const seeded = baseSeed.id !== void 0 ? baseSeed : { ...baseSeed, _idForCreate: crypto.randomUUID() };
|
|
1805
|
+
setLoadedRecord(seeded);
|
|
1806
|
+
setPendingChanges(/* @__PURE__ */ new Map());
|
|
1807
|
+
}, [isCreateMode, initialRecord, table]);
|
|
1808
|
+
useEffect(() => {
|
|
1809
|
+
if (!isCreateMode) return;
|
|
1810
|
+
if (!queryParameterName) return;
|
|
1811
|
+
if (!metadataQuery.data) return;
|
|
1812
|
+
if (!loadedRecord) return;
|
|
1813
|
+
if (pendingChangesRef.current.size > 0) return;
|
|
1814
|
+
const search = typeof window !== "undefined" ? window.location.search : "";
|
|
1815
|
+
const defaults = parseQueryDefaultsFromUrl(search, metadataQuery.data);
|
|
1816
|
+
const cols = Object.keys(defaults);
|
|
1817
|
+
if (cols.length === 0) return;
|
|
1818
|
+
let changed = false;
|
|
1819
|
+
const nextProperties = {
|
|
1820
|
+
...loadedRecord.properties ?? {}
|
|
1821
|
+
};
|
|
1822
|
+
for (const col of cols) {
|
|
1823
|
+
const next = defaults[col];
|
|
1824
|
+
if (!valuesEqual(nextProperties[col], next)) {
|
|
1825
|
+
nextProperties[col] = next;
|
|
1826
|
+
changed = true;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
if (!changed) return;
|
|
1830
|
+
setLoadedRecord(
|
|
1831
|
+
(current) => current ? { ...current, properties: nextProperties } : current
|
|
1832
|
+
);
|
|
1833
|
+
}, [isCreateMode, queryParameterName, metadataQuery.data, loadedRecord]);
|
|
1834
|
+
useEffect(() => {
|
|
1835
|
+
if (!isSkipFetchMode || !loadedRecordProp) return;
|
|
1836
|
+
setLoadedRecord(loadedRecordProp);
|
|
1837
|
+
setPendingChanges(
|
|
1838
|
+
initialPendingChanges && initialPendingChanges.size > 0 ? new Map(initialPendingChanges) : /* @__PURE__ */ new Map()
|
|
1839
|
+
);
|
|
1840
|
+
validationRef.current?.clearErrors();
|
|
1841
|
+
onRecordLoadedRef.current?.(loadedRecordProp);
|
|
1842
|
+
}, [isSkipFetchMode, loadedRecordProp, initialPendingChanges]);
|
|
1843
|
+
useEffect(() => {
|
|
1844
|
+
if (query.status === "success" && query.data) {
|
|
1845
|
+
setLoadedRecord(query.data);
|
|
1846
|
+
setPendingChanges(
|
|
1847
|
+
initialPendingChanges && initialPendingChanges.size > 0 ? new Map(initialPendingChanges) : /* @__PURE__ */ new Map()
|
|
1848
|
+
);
|
|
1849
|
+
validationRef.current?.clearErrors();
|
|
1850
|
+
onRecordLoadedRef.current?.(query.data);
|
|
1851
|
+
}
|
|
1852
|
+
}, [query.status, query.data, initialPendingChanges]);
|
|
1853
|
+
loadedRecordRef.current = loadedRecord;
|
|
1854
|
+
const record = useMemo(() => {
|
|
1855
|
+
if (!loadedRecord) return void 0;
|
|
1856
|
+
if (pendingChanges.size === 0) return loadedRecord;
|
|
1857
|
+
const properties = { ...loadedRecord.properties ?? {} };
|
|
1858
|
+
for (const [col, val] of pendingChanges) {
|
|
1859
|
+
if (val === null) {
|
|
1860
|
+
delete properties[col];
|
|
1861
|
+
} else {
|
|
1862
|
+
properties[col] = val;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return { ...loadedRecord, properties, isDirty: true };
|
|
1866
|
+
}, [loadedRecord, pendingChanges]);
|
|
1867
|
+
const dirtyColumns = useMemo(
|
|
1868
|
+
() => new Set(pendingChanges.keys()),
|
|
1869
|
+
[pendingChanges]
|
|
1870
|
+
);
|
|
1871
|
+
const ownDirty = pendingChanges.size > 0;
|
|
1872
|
+
recordRef.current = record;
|
|
1873
|
+
ownDirtyRef.current = ownDirty;
|
|
1874
|
+
const nestedRef = useRef(/* @__PURE__ */ new Set());
|
|
1875
|
+
const [nestedVersion, setNestedVersion] = useState(0);
|
|
1876
|
+
const nestedDirty = useMemo(() => {
|
|
1877
|
+
for (const d of nestedRef.current) {
|
|
1878
|
+
if (d.isDirty()) return true;
|
|
1879
|
+
}
|
|
1880
|
+
return false;
|
|
1881
|
+
}, [nestedVersion]);
|
|
1882
|
+
const isDirty = ownDirty || nestedDirty;
|
|
1883
|
+
const registerNested = useCallback((descendant) => {
|
|
1884
|
+
nestedRef.current.add(descendant);
|
|
1885
|
+
setNestedVersion((v) => v + 1);
|
|
1886
|
+
return () => {
|
|
1887
|
+
nestedRef.current.delete(descendant);
|
|
1888
|
+
setNestedVersion((v) => v + 1);
|
|
1889
|
+
};
|
|
1890
|
+
}, []);
|
|
1891
|
+
const notifyNestedDirtyChanged = useCallback(() => {
|
|
1892
|
+
setNestedVersion((v) => v + 1);
|
|
1893
|
+
}, []);
|
|
1894
|
+
const buildOwnSaveRequest = useCallback(() => {
|
|
1895
|
+
if (!recordRef.current || !ownDirtyRef.current) return null;
|
|
1896
|
+
const current = recordRef.current;
|
|
1897
|
+
if (isCreateModeRef.current) {
|
|
1898
|
+
const allProperties = {
|
|
1899
|
+
...current.properties
|
|
1900
|
+
};
|
|
1901
|
+
return {
|
|
1902
|
+
$type: "CreateRequest",
|
|
1903
|
+
record: {
|
|
1904
|
+
tableName: current.tableName,
|
|
1905
|
+
// Include the client-pre-generated GUID. The seed effect writes
|
|
1906
|
+
// it into `_idForCreate` (Blazor parity); the server's in-batch
|
|
1907
|
+
// resolver matches AssociateRequest / child-FK references
|
|
1908
|
+
// against either `id` or `_idForCreate`, so a Create + Associate
|
|
1909
|
+
// pair within one `ExecuteMultiple` resolves to the same record.
|
|
1910
|
+
// `id` is also forwarded when the consumer supplied an explicit
|
|
1911
|
+
// one via `initialRecord` (rare — pre-assigned ids on data
|
|
1912
|
+
// import).
|
|
1913
|
+
...current.id !== void 0 && { id: current.id },
|
|
1914
|
+
...current._idForCreate !== void 0 && current._idForCreate !== null && {
|
|
1915
|
+
_idForCreate: current._idForCreate
|
|
1916
|
+
},
|
|
1917
|
+
properties: allProperties
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
const partialProperties = {};
|
|
1922
|
+
for (const [col, val] of pendingChangesRef.current) {
|
|
1923
|
+
partialProperties[col] = val;
|
|
1924
|
+
}
|
|
1925
|
+
const partialRecord = {
|
|
1926
|
+
tableName: current.tableName,
|
|
1927
|
+
properties: partialProperties,
|
|
1928
|
+
...current.id !== void 0 && { id: current.id }
|
|
1929
|
+
};
|
|
1930
|
+
return { $type: "UpdateRequest", record: partialRecord };
|
|
1931
|
+
}, []);
|
|
1932
|
+
const getRequests = useCallback(() => {
|
|
1933
|
+
const all = [];
|
|
1934
|
+
const own = buildOwnSaveRequest();
|
|
1935
|
+
if (own) all.push(own);
|
|
1936
|
+
for (const descendant of nestedRef.current) {
|
|
1937
|
+
all.push(...descendant.getRequests());
|
|
1938
|
+
}
|
|
1939
|
+
return all;
|
|
1940
|
+
}, [buildOwnSaveRequest]);
|
|
1941
|
+
const validate = useCallback(async () => {
|
|
1942
|
+
for (const descendant of nestedRef.current) {
|
|
1943
|
+
if (!await descendant.validate()) return false;
|
|
1944
|
+
}
|
|
1945
|
+
return true;
|
|
1946
|
+
}, []);
|
|
1947
|
+
const saveAll = useCallback(async () => {
|
|
1948
|
+
if (!loadedRecordRef.current) {
|
|
1949
|
+
throw new Error("RecordContext.save called before the record loaded.");
|
|
1950
|
+
}
|
|
1951
|
+
await onBeforeSaveRef.current?.();
|
|
1952
|
+
if (forceSuccessfulValidationBeforeSaveRef.current) {
|
|
1953
|
+
const valid = await validate();
|
|
1954
|
+
if (!valid) return;
|
|
1955
|
+
}
|
|
1956
|
+
const ownReq = buildOwnSaveRequest();
|
|
1957
|
+
const descendantReqs = [];
|
|
1958
|
+
for (const descendant of nestedRef.current) {
|
|
1959
|
+
descendantReqs.push(...descendant.getRequests());
|
|
1960
|
+
}
|
|
1961
|
+
const requests = ownReq ? [ownReq, ...descendantReqs] : descendantReqs;
|
|
1962
|
+
if (requests.length > 0) {
|
|
1963
|
+
const responses = await ppp.executeMultipleAsync(requests, { returnResponses: true });
|
|
1964
|
+
if (ownReq?.$type === "CreateRequest") {
|
|
1965
|
+
const ownResp = responses[0];
|
|
1966
|
+
if (ownResp?.$type === "CreateResponse" && ownResp.id) {
|
|
1967
|
+
setEffectiveId(ownResp.id);
|
|
1968
|
+
const qpn = queryParameterNameRef.current;
|
|
1969
|
+
if (qpn && isTopMostRef.current && typeof window !== "undefined") {
|
|
1970
|
+
const existing = new URLSearchParams(window.location.search).get(
|
|
1971
|
+
qpn
|
|
1972
|
+
);
|
|
1973
|
+
if (!existing) {
|
|
1974
|
+
const newUrl = new URL(window.location.href);
|
|
1975
|
+
newUrl.searchParams.set(qpn, ownResp.id);
|
|
1976
|
+
window.history.replaceState(
|
|
1977
|
+
window.history.state,
|
|
1978
|
+
"",
|
|
1979
|
+
newUrl.toString()
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
setPendingChanges(/* @__PURE__ */ new Map());
|
|
1986
|
+
}
|
|
1987
|
+
if (!isCreateModeRef.current) {
|
|
1988
|
+
void refetchRef.current();
|
|
1989
|
+
}
|
|
1990
|
+
for (const descendant of nestedRef.current) {
|
|
1991
|
+
await descendant.refresh();
|
|
1992
|
+
}
|
|
1993
|
+
}, [ppp, validate, buildOwnSaveRequest]);
|
|
1994
|
+
const reload = useCallback(async () => {
|
|
1995
|
+
refetchRef.current();
|
|
1996
|
+
for (const descendant of nestedRef.current) {
|
|
1997
|
+
await descendant.refresh();
|
|
1998
|
+
}
|
|
1999
|
+
}, []);
|
|
2000
|
+
const resetState = useCallback(() => {
|
|
2001
|
+
setPendingChanges(/* @__PURE__ */ new Map());
|
|
2002
|
+
validationRef.current?.clearErrors();
|
|
2003
|
+
for (const descendant of nestedRef.current) {
|
|
2004
|
+
descendant.resetState();
|
|
2005
|
+
}
|
|
2006
|
+
}, []);
|
|
2007
|
+
const setColumnValue = useCallback(
|
|
2008
|
+
(columnName, value2) => {
|
|
2009
|
+
setPendingChanges((prev) => {
|
|
2010
|
+
if (!loadedRecord) return prev;
|
|
2011
|
+
const original = loadedRecord.properties?.[columnName];
|
|
2012
|
+
const newValue = value2 ?? null;
|
|
2013
|
+
if (valuesEqual(original, newValue)) {
|
|
2014
|
+
if (!prev.has(columnName)) return prev;
|
|
2015
|
+
const next2 = new Map(prev);
|
|
2016
|
+
next2.delete(columnName);
|
|
2017
|
+
return next2;
|
|
2018
|
+
}
|
|
2019
|
+
const existing = prev.has(columnName) ? prev.get(columnName) ?? null : void 0;
|
|
2020
|
+
if (existing !== void 0 && valuesEqual(existing, newValue)) return prev;
|
|
2021
|
+
const next = new Map(prev);
|
|
2022
|
+
next.set(columnName, newValue);
|
|
2023
|
+
return next;
|
|
2024
|
+
});
|
|
2025
|
+
},
|
|
2026
|
+
[loadedRecord]
|
|
2027
|
+
);
|
|
2028
|
+
const hydrateColumnValue = useCallback(
|
|
2029
|
+
(columnName, value2) => {
|
|
2030
|
+
setLoadedRecord((current) => {
|
|
2031
|
+
if (!current) return current;
|
|
2032
|
+
if (pendingChanges.has(columnName)) return current;
|
|
2033
|
+
const previous = current.properties?.[columnName];
|
|
2034
|
+
if (value2 === null) {
|
|
2035
|
+
if (previous === void 0) return current;
|
|
2036
|
+
const nextProperties = { ...current.properties ?? {} };
|
|
2037
|
+
delete nextProperties[columnName];
|
|
2038
|
+
return { ...current, properties: nextProperties };
|
|
2039
|
+
}
|
|
2040
|
+
if (valuesEqual(previous, value2)) return current;
|
|
2041
|
+
return {
|
|
2042
|
+
...current,
|
|
2043
|
+
properties: { ...current.properties ?? {}, [columnName]: value2 }
|
|
2044
|
+
};
|
|
2045
|
+
});
|
|
2046
|
+
},
|
|
2047
|
+
[pendingChanges]
|
|
2048
|
+
);
|
|
2049
|
+
const loadingOverlayRef = useRef(null);
|
|
2050
|
+
useEffect(() => {
|
|
2051
|
+
if (!overlayService) return;
|
|
2052
|
+
if (query.status === "loading") {
|
|
2053
|
+
if (!loadingOverlayRef.current) {
|
|
2054
|
+
loadingOverlayRef.current = overlayService.show({
|
|
2055
|
+
targetId: overlayTargetId,
|
|
2056
|
+
description: localizer.t("app.messages.record-loading")
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
} else if (loadingOverlayRef.current) {
|
|
2060
|
+
overlayService.hide(loadingOverlayRef.current);
|
|
2061
|
+
loadingOverlayRef.current = null;
|
|
2062
|
+
}
|
|
2063
|
+
return () => {
|
|
2064
|
+
if (loadingOverlayRef.current) {
|
|
2065
|
+
overlayService.hide(loadingOverlayRef.current);
|
|
2066
|
+
loadingOverlayRef.current = null;
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
}, [overlayService, query.status, overlayTargetId, localizer]);
|
|
2070
|
+
const isDirtyRef = useRef(isDirty);
|
|
2071
|
+
isDirtyRef.current = isDirty;
|
|
2072
|
+
const mainContextRef = useRef(mainContext);
|
|
2073
|
+
mainContextRef.current = mainContext;
|
|
2074
|
+
useEffect(() => {
|
|
2075
|
+
if (!mainContext) return;
|
|
2076
|
+
return mainContext.registerDescendant({
|
|
2077
|
+
getRequests,
|
|
2078
|
+
validate,
|
|
2079
|
+
refresh: reload,
|
|
2080
|
+
resetState,
|
|
2081
|
+
isDirty: () => isDirtyRef.current
|
|
2082
|
+
});
|
|
2083
|
+
}, [mainContext?.registerDescendant, getRequests, validate, reload, resetState]);
|
|
2084
|
+
useEffect(() => {
|
|
2085
|
+
mainContextRef.current?.notifyDescendantDirtyChanged();
|
|
2086
|
+
}, [isDirty]);
|
|
2087
|
+
useBeforeUnloadWhen(isTopMost && resolvedWarnOnUnsavedChanges && isDirty);
|
|
2088
|
+
const unsavedChangesRegistry = useUnsavedChangesRegistry();
|
|
2089
|
+
const resolvedWarnRef = useRef(resolvedWarnOnUnsavedChanges);
|
|
2090
|
+
resolvedWarnRef.current = resolvedWarnOnUnsavedChanges;
|
|
2091
|
+
useEffect(() => {
|
|
2092
|
+
if (!isTopMost) return;
|
|
2093
|
+
if (!unsavedChangesRegistry) return;
|
|
2094
|
+
return unsavedChangesRegistry.register({
|
|
2095
|
+
isDirty: () => resolvedWarnRef.current && isDirtyRef.current
|
|
2096
|
+
});
|
|
2097
|
+
}, [isTopMost, unsavedChangesRegistry]);
|
|
2098
|
+
useEffect(() => {
|
|
2099
|
+
if (!isTopMost) return;
|
|
2100
|
+
if (!unsavedChangesRegistry) return;
|
|
2101
|
+
unsavedChangesRegistry.notifyDirtyChanged();
|
|
2102
|
+
}, [isTopMost, unsavedChangesRegistry, isDirty, resolvedWarnOnUnsavedChanges]);
|
|
2103
|
+
const innerMainContext = useMemo(
|
|
2104
|
+
() => ({
|
|
2105
|
+
saveAll,
|
|
2106
|
+
refreshAll: reload,
|
|
2107
|
+
resetState,
|
|
2108
|
+
validate,
|
|
2109
|
+
getRequests,
|
|
2110
|
+
registerDescendant: registerNested,
|
|
2111
|
+
notifyDescendantDirtyChanged: notifyNestedDirtyChanged,
|
|
2112
|
+
isDirty,
|
|
2113
|
+
warnOnUnsavedChanges: resolvedWarnOnUnsavedChanges,
|
|
2114
|
+
forceSuccessfulValidationBeforeSave: resolvedForceSuccessfulValidationBeforeSave
|
|
2115
|
+
}),
|
|
2116
|
+
[
|
|
2117
|
+
saveAll,
|
|
2118
|
+
reload,
|
|
2119
|
+
resetState,
|
|
2120
|
+
validate,
|
|
2121
|
+
getRequests,
|
|
2122
|
+
registerNested,
|
|
2123
|
+
notifyNestedDirtyChanged,
|
|
2124
|
+
isDirty,
|
|
2125
|
+
resolvedWarnOnUnsavedChanges,
|
|
2126
|
+
resolvedForceSuccessfulValidationBeforeSave
|
|
2127
|
+
]
|
|
2128
|
+
);
|
|
2129
|
+
const onBeforeDeleteRef = useRef(onBeforeDelete);
|
|
2130
|
+
onBeforeDeleteRef.current = onBeforeDelete;
|
|
2131
|
+
const runBeforeDelete = useCallback(async () => {
|
|
2132
|
+
const handler = onBeforeDeleteRef.current;
|
|
2133
|
+
if (handler) await handler();
|
|
2134
|
+
}, []);
|
|
2135
|
+
const value = useMemo(
|
|
2136
|
+
() => ({
|
|
2137
|
+
status: query.status,
|
|
2138
|
+
record,
|
|
2139
|
+
error: query.error,
|
|
2140
|
+
reload,
|
|
2141
|
+
save: saveAll,
|
|
2142
|
+
getRequests,
|
|
2143
|
+
resetState,
|
|
2144
|
+
setColumnValue,
|
|
2145
|
+
hydrateColumnValue,
|
|
2146
|
+
tableMetadata: metadataQuery.data,
|
|
2147
|
+
isDirty,
|
|
2148
|
+
dirtyColumns,
|
|
2149
|
+
overlayTargetId,
|
|
2150
|
+
runBeforeDelete
|
|
2151
|
+
}),
|
|
2152
|
+
[
|
|
2153
|
+
query.status,
|
|
2154
|
+
query.error,
|
|
2155
|
+
record,
|
|
2156
|
+
reload,
|
|
2157
|
+
saveAll,
|
|
2158
|
+
getRequests,
|
|
2159
|
+
resetState,
|
|
2160
|
+
setColumnValue,
|
|
2161
|
+
hydrateColumnValue,
|
|
2162
|
+
metadataQuery.data,
|
|
2163
|
+
isDirty,
|
|
2164
|
+
dirtyColumns,
|
|
2165
|
+
overlayTargetId,
|
|
2166
|
+
runBeforeDelete
|
|
2167
|
+
]
|
|
2168
|
+
);
|
|
2169
|
+
return /* @__PURE__ */ jsx(MainContextProvider, { value: innerMainContext, children: /* @__PURE__ */ jsx(RecordContextContext.Provider, { value, children }) });
|
|
2170
|
+
}
|
|
2171
|
+
function useRecordContext() {
|
|
2172
|
+
const ctx = useContext(RecordContextContext);
|
|
2173
|
+
if (!ctx) {
|
|
2174
|
+
throw new Error(
|
|
2175
|
+
"useRecordContext must be used inside <RecordContext>. Wrap the component tree where you need the record."
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
return ctx;
|
|
2179
|
+
}
|
|
2180
|
+
function useRecordContextOptional() {
|
|
2181
|
+
return useContext(RecordContextContext);
|
|
2182
|
+
}
|
|
2183
|
+
var EMPTY_GUID = "00000000-0000-0000-0000-000000000000";
|
|
2184
|
+
function LookupRecordContext({
|
|
2185
|
+
columnName,
|
|
2186
|
+
onBeforeSave,
|
|
2187
|
+
children,
|
|
2188
|
+
isVisible = true
|
|
2189
|
+
}) {
|
|
2190
|
+
const parent = useRecordContext();
|
|
2191
|
+
if (!isVisible) return null;
|
|
2192
|
+
const raw = parent.record?.properties?.[columnName];
|
|
2193
|
+
if (!raw?.value || raw.value === EMPTY_GUID || !raw.tableName) {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
return /* @__PURE__ */ jsx(
|
|
2197
|
+
RecordContext,
|
|
2198
|
+
{
|
|
2199
|
+
table: raw.tableName,
|
|
2200
|
+
id: raw.value,
|
|
2201
|
+
...onBeforeSave && { onBeforeSave },
|
|
2202
|
+
children
|
|
2203
|
+
}
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// src/use-column-metadata.ts
|
|
2208
|
+
function useColumnMetadata(columnName) {
|
|
2209
|
+
const { tableMetadata } = useRecordContext();
|
|
2210
|
+
return tableMetadata?.columns.find((c) => c.columnName === columnName);
|
|
2211
|
+
}
|
|
2212
|
+
var DialogServiceContext = createContext(null);
|
|
2213
|
+
DialogServiceContext.displayName = "DialogService";
|
|
2214
|
+
function useDialogService() {
|
|
2215
|
+
const ctx = useContext(DialogServiceContext);
|
|
2216
|
+
if (!ctx) {
|
|
2217
|
+
throw new Error(
|
|
2218
|
+
"useDialogService must be used inside a <DialogServiceProvider>. Wrap your app in <FluentDialogProvider> from @powerportalspro/react-fluent (or your own implementation) so framework dialogs have somewhere to render."
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
return ctx;
|
|
2222
|
+
}
|
|
2223
|
+
function DialogServiceProvider({
|
|
2224
|
+
value,
|
|
2225
|
+
children
|
|
2226
|
+
}) {
|
|
2227
|
+
return /* @__PURE__ */ jsx(DialogServiceContext.Provider, { value, children });
|
|
2228
|
+
}
|
|
2229
|
+
async function warnAboutUnsavedChangesAsync(dialog, localizer) {
|
|
2230
|
+
const result = await dialog.showConfirmationAsync(localizer.t("app.messages.unsaved-changes"), {
|
|
2231
|
+
title: localizer.t("app.messages.unsaved-changes-title"),
|
|
2232
|
+
primaryText: localizer.t("app.buttons.yes.label"),
|
|
2233
|
+
secondaryText: localizer.t("app.buttons.no.label")
|
|
2234
|
+
});
|
|
2235
|
+
return !result.cancelled;
|
|
2236
|
+
}
|
|
2237
|
+
function useWarnAboutUnsavedChanges() {
|
|
2238
|
+
const dialog = useDialogService();
|
|
2239
|
+
const localizer = useLocalizer();
|
|
2240
|
+
return useCallback(
|
|
2241
|
+
() => warnAboutUnsavedChangesAsync(dialog, localizer),
|
|
2242
|
+
[dialog, localizer]
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// src/unsaved-changes-warning.ts
|
|
2247
|
+
function useUnsavedChangesWarning() {
|
|
2248
|
+
const ctx = useMainContext();
|
|
2249
|
+
const isDirty = ctx?.isDirty ?? false;
|
|
2250
|
+
useBeforeUnloadWhen(isDirty);
|
|
2251
|
+
}
|
|
2252
|
+
function useUnsavedChangesGuard() {
|
|
2253
|
+
const isAnyDirty = useIsAnyDirty();
|
|
2254
|
+
const warn = useWarnAboutUnsavedChanges();
|
|
2255
|
+
return { isAnyDirty, warn };
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
// src/index.ts
|
|
2259
|
+
var VERSION = "0.1.0";
|
|
2260
|
+
|
|
2261
|
+
export { ASP_NET_CULTURE_COOKIE, AuthStatus, AuthorizedView, DEFAULT_URL_LOCALE_PATTERN, DefaultLocalizerProvider, DialogServiceProvider, InMemoryEnvironmentFileSettingsCache, InMemoryTableMetadataCache, InMemoryViewMetadataCache, InMemoryViewsForTableCache, LocaleProvider, LocalizerProvider, LookupRecordContext, MainContext, MainContextProvider, OverlayServiceProvider, PowerPortalsProContext, PowerPortalsProProvider, QueryStatus, RecordContext, UnsavedChangesRegistryProvider, VERSION, ValidationProvider, createLocalizer, detectLocale, flattenStrings, getAspNetCultureCookie, getLocaleFromUrlPath, interpolate, resolveLocalizationUrl, runWithOverlayAsync, setAspNetCultureCookie, useAuth, useColumnMetadata, useColumnValidationErrors, useDialogService, useEnvironmentFileSettings, useFetchRecords, useGridData, useIsAnyDirty, useLocale, useLocalization, useLocalizationLoaded, useLocalizationPrefetcher, useLocalizer, useMainContext, useOverlayForTarget, useOverlayService, useOverlayServiceState, usePowerPortalsPro, usePrefixedT, useQueryParam, useRecord, useRecordContext, useRecordContextOptional, useRunWithOverlay, useSupportedLocales, useT, useTableMetadata, useTablePermissions, useUnsavedChangesGuard, useUnsavedChangesRegistry, useUnsavedChangesWarning, useValidationContext, useViewDataSource, useViewMetadata, useViewsForTable, useWarnAboutUnsavedChanges, warnAboutUnsavedChangesAsync };
|
|
2262
|
+
//# sourceMappingURL=index.js.map
|
|
2263
|
+
//# sourceMappingURL=index.js.map
|