Browse Source

Work on atomicQuery. These changes allow the web view to talk directly to the running TS in the editor and allow the editor to respond back to the web view that initiated the request w/o needing the C++ to need to get involved other than just passing the message back and forth. Made a distinction between having a request and just a notification. Also added types to the atomicQuery response.

Shaddock Heath 8 years ago
parent
commit
c391083ed0

+ 7 - 7
Script/AtomicEditor/hostExtensions/HostExtensionServices.ts

@@ -29,7 +29,7 @@ import ResourceOps = require("../resources/ResourceOps");
 /**
 /**
  * Generic registry for storing Editor Extension Services
  * Generic registry for storing Editor Extension Services
  */
  */
-export class ServicesProvider<T extends Editor.Extensions.ServiceEventListener> implements Editor.Extensions.ServicesProvider<T> {
+export class ServicesProvider<T extends Editor.Extensions.ServiceEventListener> extends Atomic.ScriptObject implements Editor.Extensions.ServicesProvider<T> {
     registeredServices: T[] = [];
     registeredServices: T[] = [];
 
 
     /**
     /**
@@ -392,7 +392,7 @@ export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.U
 
 
     /**
     /**
      * Will load a resource editor or navigate to an already loaded resource editor by path
      * Will load a resource editor or navigate to an already loaded resource editor by path
-     * @param resourcePath full path to resource to load 
+     * @param resourcePath full path to resource to load
      * @param lineNumber optional line number to navigate to
      * @param lineNumber optional line number to navigate to
      * @return {Editor.ResourceEditor}
      * @return {Editor.ResourceEditor}
      */
      */
@@ -608,16 +608,16 @@ export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.U
      * Hooks into web messages coming in from web views
      * Hooks into web messages coming in from web views
      * @param  {[String|Object]} data
      * @param  {[String|Object]} data
      */
      */
-    handleWebMessage(data: WebView.WebMessageEvent) {
+    handleWebMessage(webMessage: WebView.WebMessageEvent) {
         let messageType;
         let messageType;
         let messageObject;
         let messageObject;
 
 
         try {
         try {
-            messageObject = JSON.parse(data.request);
+            messageObject = JSON.parse(webMessage.request);
             messageType = messageObject.message;
             messageType = messageObject.message;
         } catch (e) {
         } catch (e) {
             // not JSON, we are just getting a notification message of some sort
             // not JSON, we are just getting a notification message of some sort
-            messageType = data.request;
+            messageType = webMessage.request;
         }
         }
 
 
         // run through and find any services that can handle this.
         // run through and find any services that can handle this.
@@ -625,7 +625,7 @@ export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.U
             try {
             try {
                 // Verify that the service contains the appropriate methods and that it can save
                 // Verify that the service contains the appropriate methods and that it can save
                 if (service.handleWebMessage) {
                 if (service.handleWebMessage) {
-                    service.handleWebMessage(messageType, messageObject);
+                    service.handleWebMessage(webMessage, messageType, messageObject);
                 }
                 }
             } catch (e) {
             } catch (e) {
                 EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
                 EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
@@ -639,6 +639,6 @@ export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.U
      */
      */
     subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
     subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
         // Placeholder for when UI events published by the editor need to be listened for
         // Placeholder for when UI events published by the editor need to be listened for
-        eventDispatcher.subscribeToEvent(WebView.WebMessageEvent((ev) => this.handleWebMessage(ev)));
+        this.subscribeToEvent(WebView.WebMessageEvent((ev) => this.handleWebMessage(ev)));
     }
     }
 }
 }

+ 2 - 1
Script/AtomicEditor/hostExtensions/languageExtensions/TypescriptLanguageExtension.ts

@@ -350,10 +350,11 @@ export default class TypescriptLanguageExtension extends Atomic.ScriptObject imp
      * @param message The message type that was submitted to be used to determine what the data contains if present
      * @param message The message type that was submitted to be used to determine what the data contains if present
      * @param data any additional data that needs to be submitted with the message
      * @param data any additional data that needs to be submitted with the message
      */
      */
-    handleWebMessage(messageType: string, data: any) {
+    handleWebMessage(webMessage: WebView.WebMessageEvent, messageType: string, data: any) {
         switch (messageType) {
         switch (messageType) {
             case "TypeScript.DisplayCompileResults":
             case "TypeScript.DisplayCompileResults":
                 this.displayCompileResults(data);
                 this.displayCompileResults(data);
+                webMessage.handler.success();
                 break;
                 break;
         }
         }
     }
     }

+ 1 - 1
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageExtension.ts

@@ -462,7 +462,7 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
         let messageArray = results.annotations.map((result) => {
         let messageArray = results.annotations.map((result) => {
             return `${result.text} at line ${result.row} col ${result.column} in ${result.file}`;
             return `${result.text} at line ${result.row} col ${result.column} in ${result.file}`;
         });
         });
-        window.atomicQueryPromise("TypeScript.DisplayCompileResults", results);
+        atomicHostEvent("TypeScript.DisplayCompileResults", results);
     }
     }
 }
 }
 
 

+ 8 - 37
Script/AtomicWebViewEditor/interop.ts

@@ -21,6 +21,7 @@
 //
 //
 
 
 // This is the interop file, exposing functions that can be called by the host game engine
 // This is the interop file, exposing functions that can be called by the host game engine
+import "./utils/WindowExt";
 import * as editorCommands from "./editor/editorCommands";
 import * as editorCommands from "./editor/editorCommands";
 
 
 /**
 /**
@@ -35,38 +36,6 @@ const DEBUG_PORT = 3335;
  */
  */
 const DEBUG_ALERT = false;
 const DEBUG_ALERT = false;
 
 
-/**
- * Promise version of atomic query
- * @param  {string} messageType the message type to pass to atomicQuery.  If there is no payload, this will be passed directly, otherwise it will be passed in a data object
- * @param  {any} data optional data to send
- * @return {Promise}
- */
-window.atomicQueryPromise = function(messageType: string, data?: {}): Promise<{}> {
-    return new Promise(function(resolve, reject) {
-
-        let queryMessage;
-
-        // if we have a data element, then we need to structure the message so that the host understands it
-        // by adding the message to the object and then stringify-ing the whole thing
-        if (data) {
-            // stringify and reparse since we need to modify the data, but don't want to modify the passed in object
-            queryMessage = JSON.parse(JSON.stringify(data));
-            queryMessage.message = messageType;
-        } else {
-            queryMessage = {
-                message: messageType
-            };
-        }
-
-        window.atomicQuery({
-            request: JSON.stringify(queryMessage),
-            persistent: false,
-            onSuccess: resolve,
-            onFailure: (error_code, error_message) => reject({ error_code: error_code, error_message: error_message })
-        });
-    });
-};
-
 export default class HostInteropType {
 export default class HostInteropType {
 
 
     private static _inst: HostInteropType = null;
     private static _inst: HostInteropType = null;
@@ -109,7 +78,7 @@ export default class HostInteropType {
         // get the code
         // get the code
         this.getResource(codeUrl).then((src: string) => {
         this.getResource(codeUrl).then((src: string) => {
             editorCommands.loadCodeIntoEditor(src, filename, fileExt);
             editorCommands.loadCodeIntoEditor(src, filename, fileExt);
-            return window.atomicQueryPromise(HostInteropType.EDITOR_GET_USER_PREFS);
+            return atomicHostEvent(HostInteropType.EDITOR_GET_USER_PREFS);
         }).then(() => {
         }).then(() => {
             this.setCodeLoaded();
             this.setCodeLoaded();
         }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
         }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
@@ -123,7 +92,7 @@ export default class HostInteropType {
      */
      */
     saveCode(): Promise<any> {
     saveCode(): Promise<any> {
         let source = editorCommands.getSourceText();
         let source = editorCommands.getSourceText();
-        return window.atomicQueryPromise(HostInteropType.EDITOR_SAVE_CODE, {
+        return atomicHostEvent(HostInteropType.EDITOR_SAVE_CODE, {
             payload: source
             payload: source
         }).then(() => {
         }).then(() => {
             editorCommands.codeSaved(this.fileName, this.fileExt, source);
             editorCommands.codeSaved(this.fileName, this.fileExt, source);
@@ -137,7 +106,7 @@ export default class HostInteropType {
      * @return {Promise}
      * @return {Promise}
      */
      */
     saveFile(filename: string, fileContents: string): Promise<any> {
     saveFile(filename: string, fileContents: string): Promise<any> {
-        return window.atomicQueryPromise(HostInteropType.EDITOR_SAVE_FILE, {
+        return atomicHostEvent(HostInteropType.EDITOR_SAVE_FILE, {
             filename: filename,
             filename: filename,
             payload: fileContents
             payload: fileContents
         });
         });
@@ -152,7 +121,9 @@ export default class HostInteropType {
             alert(`Attach chrome dev tools to this instance by navigating to http://localhost:${DEBUG_PORT}`);
             alert(`Attach chrome dev tools to this instance by navigating to http://localhost:${DEBUG_PORT}`);
         }
         }
         editorCommands.editorLoaded();
         editorCommands.editorLoaded();
-        window.atomicQueryPromise(HostInteropType.EDITOR_LOAD_COMPLETE);
+        atomicHostEvent(HostInteropType.EDITOR_LOAD_COMPLETE);
+
+        atomicHostRequest<string>("foo").then((d) => alert(d));
     }
     }
 
 
     /**
     /**
@@ -186,7 +157,7 @@ export default class HostInteropType {
      * Notify the host that the contents of the editor has changed
      * Notify the host that the contents of the editor has changed
      */
      */
     notifyEditorChange() {
     notifyEditorChange() {
-        window.atomicQueryPromise(HostInteropType.EDITOR_CHANGE).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
+        atomicHostEvent(HostInteropType.EDITOR_CHANGE).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
             console.log("Error on change: " + e.error_message);
             console.log("Error on change: " + e.error_message);
         });
         });
     }
     }

+ 1 - 0
Script/AtomicWebViewEditor/tsconfig.json

@@ -43,6 +43,7 @@
         "./typings/systemjs.d.ts",
         "./typings/systemjs.d.ts",
         "./typings/webworkers.d.ts",
         "./typings/webworkers.d.ts",
         "./typings/WindowExt.d.ts",
         "./typings/WindowExt.d.ts",
+        "./utils/WindowExt.ts",
         "../TypeScript/Atomic.d.ts",
         "../TypeScript/Atomic.d.ts",
         "../TypeScript/AtomicApp.d.ts",
         "../TypeScript/AtomicApp.d.ts",
         "../TypeScript/AtomicNETScript.d.ts",
         "../TypeScript/AtomicNETScript.d.ts",

+ 37 - 11
Script/AtomicWebViewEditor/typings/WindowExt.d.ts

@@ -20,31 +20,53 @@
 // THE SOFTWARE.
 // THE SOFTWARE.
 //
 //
 
 
+interface HostMessage {
+    message: string;
+}
+
 /**
 /**
  * Defines the interface to what is available for the host to call or for the client to call on the window object
  * Defines the interface to what is available for the host to call or for the client to call on the window object
  */
  */
 interface Window {
 interface Window {
-    atomicQuery: (message: {
+    atomicQuery(message: {
         request: any,
         request: any,
         persistent: boolean,
         persistent: boolean,
-        onSuccess: () => void,
-        onFailure: (error_code, error_message) => void
-    }) => void;
+        onSuccess: (result?: string) => void,
+        onFailure: (error_code: number, error_message: string) => void
+    }): void;
 
 
     /**
     /**
-     * Used to call into the host and provide messages
+     * Used to send a notification message to the host
      * @param {string} messageType
      * @param {string} messageType
      * @param {object} data
      * @param {object} data
+     * @return {Promise} will resolve when the host has handled the message
+     * @deprecated use window.hostNotify
+     */
+    atomicQueryPromise(messageType: string, data?: {}): Promise<void>;
+
+    /**
+     * Used to send a notification message to the host
+     * @param {string} messageType
+     * @param {object} data
+     * @return {Promise} will resolve when the host has handled the message
+     * @deprecated
+     */
+    atomicHostEvent(messageType: string, data?: {}): Promise<void>;
+
+    /**
+     * Used to send a request to the server.  The server will send back the results in the promise
+     * @param  {string} messageType
+     * @param  {object} data
      * @return {Promise}
      * @return {Promise}
      */
      */
-    atomicQueryPromise: (messageType: string, data?: {}) => Promise<{}>;
+    atomicHostRequest<T>(messageType: string, data?: {}): Promise<T>;
 
 
-    HOST_loadCode: (codeUrl) => void;
-    HOST_saveCode: () => void;
+    HOST_loadCode(codeUrl): void;
+    HOST_saveCode(): void;
 
 
-    HOST_resourceRenamed: (path: string, newPath: string) => void;
-    HOST_resourceDeleted: (path: string) => void;
-    HOST_preferencesChanged: (jsonProjectPrefs: string, jsonApplicationPrefs: string) => void;
+    HOST_resourceRenamed(path: string, newPath: string): void;
+    HOST_resourceDeleted(path: string): void;
+    HOST_preferencesChanged(jsonProjectPrefs: string, jsonApplicationPrefs: string): void;
 
 
     /**
     /**
      * Preferences set by the host.  Each preference category is a JSON string of all the prefs
      * Preferences set by the host.  Each preference category is a JSON string of all the prefs
@@ -54,3 +76,7 @@ interface Window {
         ProjectPreferences: string
         ProjectPreferences: string
     };
     };
 }
 }
+
+// globally expose these
+declare function atomicHostEvent(messageType: string, data?: {}): Promise<void>;
+declare function atomicHostRequest<T>(messageType: string, data?: {}): Promise<T>;

+ 95 - 0
Script/AtomicWebViewEditor/utils/WindowExt.ts

@@ -0,0 +1,95 @@
+//
+// Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+class WindowExt {
+
+    /**
+     * Used to send a notification message to the host
+     * @param {string} messageType
+     * @param {object} data
+     * @return {Promise} will resolve when the host has handled the message
+     */
+    static atomicHostEvent(messageType: string, data?: {}): Promise<void> {
+        let queryMessage: HostMessage;
+
+        // if we have a data element, then we need to structure the message so that the host understands it
+        // by adding the message to the object and then stringify-ing the whole thing
+        if (data) {
+            // stringify and reparse since we need to modify the data, but don't want to modify the passed in object
+            queryMessage = JSON.parse(JSON.stringify(data));
+            queryMessage.message = messageType;
+        } else {
+            queryMessage = {
+                message: messageType
+            };
+        }
+
+        return new Promise<void>(function(resolve, reject) {
+            window.atomicQuery({
+                request: JSON.stringify(queryMessage),
+                persistent: false,
+                onSuccess: (result?: string) => resolve(),
+                onFailure: (error_code, error_message) => reject({ error_code: error_code, error_message: error_message })
+            });
+        });
+    }
+
+    /**
+     * Used to send a request to the server.  The server will send back the results in the promise
+     * @param  {string} messageType
+     * @param  {object} data
+     * @return {Promise}
+     */
+    static atomicHostRequest<T>(messageType: string, data?: {}): Promise<T> {
+        let queryMessage: HostMessage;
+
+        // if we have a data element, then we need to structure the message so that the host understands it
+        // by adding the message to the object and then stringify-ing the whole thing
+        if (data) {
+            // stringify and reparse since we need to modify the data, but don't want to modify the passed in object
+            queryMessage = JSON.parse(JSON.stringify(data));
+            queryMessage.message = messageType;
+        } else {
+            queryMessage = {
+                message: messageType
+            };
+        }
+
+        return new Promise<T>(function(resolve, reject) {
+            window.atomicQuery({
+                request: JSON.stringify(queryMessage),
+                persistent: false,
+                onSuccess: (s) => {
+                    // unwrap the message that was returned
+                    let o = JSON.parse(s) as WebView.WebMessageEventResponse<T>;
+                    resolve(o.response);
+                },
+                onFailure: (error_code, error_message) => reject({ error_code: error_code, error_message: error_message })
+            });
+        });
+    }
+}
+
+// set up the window
+window.atomicQueryPromise = WindowExt.atomicHostEvent; // deprecated
+window.atomicHostEvent = WindowExt.atomicHostEvent;
+window.atomicHostRequest = WindowExt.atomicHostRequest;

+ 1 - 1
Script/Packages/WebView/WebView.json

@@ -2,5 +2,5 @@
 	"name" : "WebView",
 	"name" : "WebView",
 	"includes" : ["<Atomic/Graphics/DebugRenderer.h>"],
 	"includes" : ["<Atomic/Graphics/DebugRenderer.h>"],
 	"sources" : ["Source/AtomicWebView"],
 	"sources" : ["Source/AtomicWebView"],
-	"classes" : ["WebBrowserHost", "WebClient", "WebRenderHandler", "WebTexture2D", "UIWebView"]
+	"classes" : ["WebBrowserHost", "WebClient", "WebRenderHandler", "WebTexture2D", "UIWebView", "WebMessageHandler"]
 }
 }

+ 10 - 0
Script/TypeScript/AtomicWork.d.ts

@@ -202,3 +202,13 @@ declare module ToolCore {
     export function getAssetDatabase(): AssetDatabase;
     export function getAssetDatabase(): AssetDatabase;
     export function getLicenseSystem(): LicenseSystem;
     export function getLicenseSystem(): LicenseSystem;
 }
 }
+
+declare module WebView {
+
+    /**
+     * interface for sending data to the web view in a standard way.
+     */
+    export interface WebMessageEventResponse<T> {
+        response: T
+    }
+}

+ 3 - 2
Script/TypeScript/EditorWork.d.ts

@@ -120,7 +120,7 @@ declare module Editor.Extensions {
         canHandleResource(resourcePath: string) : boolean;
         canHandleResource(resourcePath: string) : boolean;
         /**
         /**
          * Generates a resource editor for the provided resource type
          * Generates a resource editor for the provided resource type
-         * @param  resourceFrame 
+         * @param  resourceFrame
          * @param  resourcePath
          * @param  resourcePath
          * @param  tabContainer
          * @param  tabContainer
          * @param  lineNumber
          * @param  lineNumber
@@ -238,10 +238,11 @@ declare module Editor.HostExtensions {
 
 
         /**
         /**
          * Handle messages that are submitted via Atomic.Query from within a web view editor.
          * Handle messages that are submitted via Atomic.Query from within a web view editor.
+         * @param webMessage The original message coming in from the browser
          * @param message The message type that was submitted to be used to determine what the data contains if present
          * @param message The message type that was submitted to be used to determine what the data contains if present
          * @param data any additional data that needs to be submitted with the message
          * @param data any additional data that needs to be submitted with the message
          */
          */
-        handleWebMessage?(messageType: string, data?: any): void;
+        handleWebMessage?(webMessage: WebView.WebMessageEvent, messageType: string, data?: any): void;
     }
     }
 
 
     export interface UIServicesProvider extends Editor.Extensions.ServicesProvider<UIServicesEventListener> {
     export interface UIServicesProvider extends Editor.Extensions.ServicesProvider<UIServicesEventListener> {

+ 3 - 1
Source/AtomicEditor/Editors/JSResourceEditor.cpp

@@ -131,6 +131,7 @@ void JSResourceEditor::HandleWebMessage(StringHash eventType, VariantMap& eventD
         String message = jvalue["message"].GetString();
         String message = jvalue["message"].GetString();
         if (message == EDITOR_CHANGE) {
         if (message == EDITOR_CHANGE) {
             SetModified(true);
             SetModified(true);
+            handler->Success();
         }
         }
         else if (message == EDITOR_SAVE_CODE)
         else if (message == EDITOR_SAVE_CODE)
         {
         {
@@ -138,6 +139,7 @@ void JSResourceEditor::HandleWebMessage(StringHash eventType, VariantMap& eventD
             File file(context_, fullpath_, FILE_WRITE);
             File file(context_, fullpath_, FILE_WRITE);
             file.Write((void*) code.CString(), code.Length());
             file.Write((void*) code.CString(), code.Length());
             file.Close();
             file.Close();
+            handler->Success();
         }
         }
         else if (message == EDITOR_SAVE_FILE)
         else if (message == EDITOR_SAVE_FILE)
         {
         {
@@ -158,10 +160,10 @@ void JSResourceEditor::HandleWebMessage(StringHash eventType, VariantMap& eventD
             } else {
             } else {
                 ATOMIC_LOGWARNING("Ignoring attempt to write file: " + fn);
                 ATOMIC_LOGWARNING("Ignoring attempt to write file: " + fn);
             }
             }
+            handler->Success();
         }
         }
     }
     }
 
 
-    handler->Success();
 
 
 }
 }
 
 

+ 1 - 1
Source/AtomicWebView/WebViewEvents.h

@@ -96,7 +96,7 @@ ATOMIC_EVENT(E_WEBMESSAGE, WebMessage)
     ATOMIC_PARAM(P_CEFBROWSER, Browser);       // CefBrowser*
     ATOMIC_PARAM(P_CEFBROWSER, Browser);       // CefBrowser*
     ATOMIC_PARAM(P_CEFFRAME, Frame);           // CefFrame*
     ATOMIC_PARAM(P_CEFFRAME, Frame);           // CefFrame*
 
 
-    ATOMIC_PARAM(P_DEFERRED, Deferred);        // Return Value: Bool
+    ATOMIC_PARAM(P_DEFERRED, Deferred);        // Bool Return Value
 }
 }