diff --git a/Directory.Build.props b/Directory.Build.props
index e73a76e6..90dcc8e0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,7 +5,7 @@
2.0.0.0
2.0.0.0
- 5.120.1
+ 5.123.0
OutSystems
ReactView
Copyright © OutSystems 2023
@@ -14,6 +14,9 @@
.npm-install-stamp
+
+
+ true
diff --git a/ReactViewResources/AMDLoader/AMDLoader.ts b/ReactViewResources/AMDLoader/AMDLoader.ts
index ffc074d9..ffffd8b3 100644
--- a/ReactViewResources/AMDLoader/AMDLoader.ts
+++ b/ReactViewResources/AMDLoader/AMDLoader.ts
@@ -11,6 +11,7 @@ namespace AMDLoader {
export let timeout = 5000;
export function getOrCreateDependencyPromise(name: string): Promise {
+ console.log("AMDLoader :: getOrCreateDependencyPromise: ", name);
name = name.replace(/^.\//, "").toLowerCase();
if (!promises[name]) {
promises[name] = new Promise((resolve, reject) => {
@@ -29,12 +30,14 @@ namespace AMDLoader {
}
export function resolve(name: string, value: any): void {
+ console.log("AMDLoader :: resolve: ", name);1
getOrCreateDependencyPromise(name); // create promise if necessary
resolves[name](value);
defines[name] = true;
}
export function require(deps: string[], definition: Function): void {
+ console.log("AMDLoader :: require: ", deps);
if (!deps || deps.length === 0) {
definition.apply(null, []);
return;
@@ -49,6 +52,7 @@ namespace AMDLoader {
}
const define = function (name: string, deps: string[], definition: Function): void {
+ console.log("AMDLoader :: define: ", name);
if (typeof name !== "string") {
throw new Error("Unnamed modules are not supported");
}
diff --git a/ReactViewResources/Loader/Internal/ComponentsRenderCache.ts b/ReactViewResources/Loader/Internal/ComponentsRenderCache.ts
index 19dc2c35..516e349f 100644
--- a/ReactViewResources/Loader/Internal/ComponentsRenderCache.ts
+++ b/ReactViewResources/Loader/Internal/ComponentsRenderCache.ts
@@ -7,27 +7,29 @@ export interface IRenderCacheEntry {
}
export async function renderCachedView(view: ViewMetadata, componentSource: string, componentPropertiesHash: string): Promise {
- if (!view.isMain) {
- // disable render from cache for inner views, since react does not currently support portals hydration
- return null;
- }
-
- const componentCacheKey = componentSource + "|" + componentPropertiesHash;
-
- const cachedComponentHtml = localStorage.getItem(componentCacheKey);
- if (cachedComponentHtml) {
- // render cached component html to reduce time to first render
- view.root!.innerHTML = cachedComponentHtml;
- await waitForNextPaint();
-
- // already on cache, skip storing on cache
- return null;
- }
-
- return {
- cacheKey: componentCacheKey,
- componentSource: componentSource
- };
+ return null;
+ //
+ // if (!view.isMain) {
+ // // disable render from cache for inner views, since react does not currently support portals hydration
+ // return null;
+ // }
+ //
+ // const componentCacheKey = componentSource + "|" + componentPropertiesHash;
+ //
+ // const cachedComponentHtml = localStorage.getItem(componentCacheKey);
+ // if (cachedComponentHtml) {
+ // // render cached component html to reduce time to first render
+ // view.root!.innerHTML = cachedComponentHtml;
+ // await waitForNextPaint();
+ //
+ // // already on cache, skip storing on cache
+ // return null;
+ // }
+ //
+ // return {
+ // cacheKey: componentCacheKey,
+ // componentSource: componentSource
+ // };
}
export function storeViewRenderInCache(view: ViewMetadata, cacheEntry: IRenderCacheEntry, maxPreRenderedCacheEntries: number): Promise {
diff --git a/ReactViewResources/Loader/Internal/Loader.View.tsx b/ReactViewResources/Loader/Internal/Loader.View.tsx
index 0c7f425e..e944358f 100644
--- a/ReactViewResources/Loader/Internal/Loader.View.tsx
+++ b/ReactViewResources/Loader/Internal/Loader.View.tsx
@@ -9,19 +9,36 @@ import { ViewMetadata } from "./ViewMetadata";
import { ViewPortalsCollection } from "./ViewPortalsCollection";
import { addView, deleteView } from "./ViewsCollection";
-export function createView(componentClass: any, properties: {}, view: ViewMetadata, componentName: string) {
- componentClass.contextType = PluginsContext;
+export function createView(componentClass: any, properties: {}, view: ViewMetadata, componentName: string, componentNativeObject: any, componentNativeObjectName: string) {
+ return ;
+}
+interface IViewProps {
+ componentClass: any;
+ properties: {};
+ view: ViewMetadata;
+ componentName: string
+ componentNativeObject: any;
+ componentNativeObjectName: string
+}
+
+const View = ({ componentClass, properties, view, componentName, componentNativeObject, componentNativeObjectName }: IViewProps) => {
+ componentClass.contextType = PluginsContext;
const makeResourceUrl = (resourceKey: string, ...params: string[]) => formatUrl(view.name, resourceKey, ...params);
+ const pluginsContext = React.useRef(new PluginsContextHolder(Array.from(view.modules.values())));
+
+ React.useEffect(() => {
+ return () => {
+ pluginsContext.current.dispose();
+ pluginsContext.current = null!;
+ }
+ }, []);
+
return (
-
+
-
{React.createElement(componentClass, { ref: e => view.modules.set(componentName, e), ...properties })}
diff --git a/ReactViewResources/Loader/Internal/NativeAPI.ts b/ReactViewResources/Loader/Internal/NativeAPI.ts
index 2d0c8d5a..8a390087 100644
--- a/ReactViewResources/Loader/Internal/NativeAPI.ts
+++ b/ReactViewResources/Loader/Internal/NativeAPI.ts
@@ -33,5 +33,7 @@ export function notifyViewLoaded(viewName: string, id: string): void {
}
export function notifyViewDestroyed(viewName: string): void {
- withAPI(api => api.notifyViewDestroyed(viewName));
-}
\ No newline at end of file
+ withAPI(api => {
+ api.notifyViewDestroyed(viewName)
+ });
+}
diff --git a/ReactViewResources/Loader/Internal/ResourcesLoader.ts b/ReactViewResources/Loader/Internal/ResourcesLoader.ts
index a97e4de1..59e89bc7 100644
--- a/ReactViewResources/Loader/Internal/ResourcesLoader.ts
+++ b/ReactViewResources/Loader/Internal/ResourcesLoader.ts
@@ -4,21 +4,47 @@ import { Task } from "./Task";
import { ViewMetadata } from "./ViewMetadata";
import { getView } from "./ViewsCollection";
+console.log("Loading resources loader file...");
+const LoadedScriptsKey = "LOADED_SCRIPTS_KEY";
+const ScriptLoadTasksKey = "SCRIPT_LOAD_TASKS_KEY";
+let loadedScripts: Set = window[LoadedScriptsKey];
+let scriptLoadTasks: Map> = window[ScriptLoadTasksKey];
+
+if (!loadedScripts) {
+ console.log("Create new 'loadedScripts' SET !!");
+ loadedScripts = new Set();
+ window[LoadedScriptsKey] = loadedScripts;
+}
+
+if (!scriptLoadTasks) {
+ console.log("Create new 'scriptLoadTasks' MAP !!");
+ scriptLoadTasks = new Map>();
+ window[LoadedScriptsKey] = loadedScripts;
+}
+
export function loadScript(scriptSrc: string, view: ViewMetadata): Promise {
return new Promise(async (resolve) => {
- const frameScripts = view.scriptsLoadTasks;
-
// check if script was already added, fallback to main frame
- const scriptLoadTask = frameScripts.get(scriptSrc) || !view.isMain ? getView(mainFrameName).scriptsLoadTasks.get(scriptSrc) : null;
+ const scriptLoadTask = scriptLoadTasks.get(scriptSrc) || null;
if (scriptLoadTask) {
+ console.log("Wait loading TASK for: " + scriptSrc);
// wait for script to be loaded
await scriptLoadTask.promise;
+ scriptLoadTasks.delete(scriptSrc);
+ resolve();
+ return;
+ }
+
+ if(loadedScripts.has(scriptSrc)) {
+ console.log("Load skipped for: " + scriptSrc);
resolve();
return;
}
+ console.log("Load script Task: " + scriptSrc);
+ loadedScripts.add(scriptSrc);
const loadTask = new Task();
- frameScripts.set(scriptSrc, loadTask);
+ scriptLoadTasks.set(scriptSrc, loadTask);
const script = document.createElement("script");
script.src = scriptSrc;
@@ -54,18 +80,51 @@ export function loadStyleSheet(stylesheet: string, containerElement: Element, ma
}
function waitForLoad(element: T, url: string, timeout: number): Promise {
- return new Promise((resolve) => {
- const timeoutHandle = setTimeout(
- () => {
- if (isDebugModeEnabled) {
- showWarningMessage(`Timeout loading resouce: '${url}'. If you paused the application to debug, you may disregard this message.`);
- }
- },
- timeout);
-
- element.addEventListener("load", () => {
- clearTimeout(timeoutHandle);
+ return new Promise((resolve, reject) => {
+ // const timeoutHandle = setTimeout(
+ // () => {
+ // if (isDebugModeEnabled) {
+ // showWarningMessage(`Timeout loading resouce: '${url}'. If you paused the application to debug, you may disregard this message.`);
+ // }
+ // },
+ // timeout);
+
+ // element.addEventListener("load", () => {
+ // clearTimeout(timeoutHandle);
+ // resolve(element);
+ // });
+
+ let timeoutHandle: number | undefined;
+
+ // Define handlers ahead of time
+ const loadHandler = () => {
+ cleanup();
resolve(element);
- });
+ };
+
+ const errorHandler = () => {
+ cleanup();
+ reject(new Error(`Failed to load resource: '${url}'`));
+ };
+
+ // This central function is key to disposing of everything
+ const cleanup = () => {
+ clearTimeout(timeoutHandle);
+ element.removeEventListener("load", loadHandler);
+ element.removeEventListener("error", errorHandler);
+ };
+
+ // Set the timeout to reject the promise and clean up
+ timeoutHandle = setTimeout(() => {
+ cleanup();
+ const message = `Timeout loading resource: '${url}'.`;
+ if (isDebugModeEnabled) {
+ showWarningMessage(`${message} If you paused the application to debug, you may disregard this message.`);
+ }
+ reject(new Error(message));
+ }, timeout);
+
+ element.addEventListener("load", loadHandler);
+ element.addEventListener("error", errorHandler);
});
}
\ No newline at end of file
diff --git a/ReactViewResources/Loader/Internal/ViewMetadata.ts b/ReactViewResources/Loader/Internal/ViewMetadata.ts
index 53426d56..68c37596 100644
--- a/ReactViewResources/Loader/Internal/ViewMetadata.ts
+++ b/ReactViewResources/Loader/Internal/ViewMetadata.ts
@@ -9,7 +9,6 @@ export type ViewMetadata = {
placeholder: Element; // element were the view is mounted (where the shadow root is mounted in case of child views)
root?: Element; // view root element
head?: Element; // view head element
- scriptsLoadTasks: Map>; // maps scripts urls to load tasks
pluginsLoadTask: Task; // plugins load task
viewLoadTask: Task; // resolved when view is loaded
modules: Map; // maps module name to module instance
@@ -33,7 +32,6 @@ export function newView(id: number, name: string, isMain: boolean, placeholder:
nativeObjectNames: [],
pluginsLoadTask: new Task(),
viewLoadTask: new Task(),
- scriptsLoadTasks: new Map>(),
childViews: new ObservableListCollection(),
context: null,
parentView: null!
diff --git a/ReactViewResources/Loader/Internal/ViewPortal.tsx b/ReactViewResources/Loader/Internal/ViewPortal.tsx
index 239e7728..e8841e6d 100644
--- a/ReactViewResources/Loader/Internal/ViewPortal.tsx
+++ b/ReactViewResources/Loader/Internal/ViewPortal.tsx
@@ -28,12 +28,13 @@ interface IViewPortalState {
* */
export class ViewPortal extends React.Component {
- private head: Element;
- private shadowRoot: HTMLElement;
+ private head: Element | null = null;
+ private shadowRoot: HTMLElement | null = null;
constructor(props: IViewPortalProps, context: any) {
super(props, context);
+ debugger;
this.state = { component: null! };
this.shadowRoot = props.view.placeholder.attachShadow({ mode: "open" }).getRootNode() as HTMLElement;
@@ -42,6 +43,7 @@ export class ViewPortal extends React.Component
{component}
@@ -56,21 +58,24 @@ export class ViewPortal extends React.Component s.dataset.sticky === "true");
- stylesheets.forEach(s => this.head.appendChild(document.importNode(s, true)));
+ stylesheets.forEach(s => this.head!.appendChild(document.importNode(s, true)));
this.props.viewMounted(this.props.view);
}
public componentWillUnmount() {
+ this.head = null;
+ this.shadowRoot = null;
this.props.viewUnmounted(this.props.view);
}
@@ -90,6 +95,6 @@ export class ViewPortal extends React.Component