mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
Migrate runtime to TypeScript
This commit is contained in:
parent
d5a8f1e323
commit
95b4bfc253
40 changed files with 3317 additions and 2233 deletions
|
|
@ -1,5 +0,0 @@
|
|||
events.test.js
|
||||
node_modules
|
||||
types/drag.d.ts
|
||||
types/contextmenu.d.ts
|
||||
types/log.d.ts
|
||||
|
|
@ -3,36 +3,47 @@
|
|||
"type": "module",
|
||||
"version": "3.0.0-alpha.56",
|
||||
"description": "Wails Runtime",
|
||||
"types": "types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"require": "./src/index.js",
|
||||
"import": "./src/index.js"
|
||||
}
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wailsapp/wails.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild:types": "rimraf ./types",
|
||||
"build:types": "npx -p typescript tsc src/index.js --declaration --allowJs --emitDeclarationOnly --outDir types",
|
||||
"postbuild:types": "task generate:events",
|
||||
"build:docs": "npx typedoc ./src/index.js",
|
||||
"build:docs:md": "npx typedoc ./src/index.js"
|
||||
"url": "git+https://github.com/wailsapp/wails.git",
|
||||
"directory": "v3/internal/runtime/desktop/@wailsio/runtime"
|
||||
},
|
||||
"author": "The Wails Team",
|
||||
"license": "MIT",
|
||||
"homepage": "https://v3.wails.io",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wailsapp/wails/issues"
|
||||
},
|
||||
"homepage": "https://wails.io",
|
||||
"private": false,
|
||||
"files": [
|
||||
"./dist",
|
||||
"./types"
|
||||
],
|
||||
"sideEffects": [
|
||||
"./dist/index.js",
|
||||
"./dist/contextmenu.js",
|
||||
"./dist/drag.js"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "npx rimraf ./dist ./docs ./types ./tsconfig.tsbuildinfo",
|
||||
"generate:events": "task generate:events",
|
||||
"generate": "npm run generate:events",
|
||||
"prebuild": "npm run clean && npm run generate",
|
||||
"build:code": "npx tsc",
|
||||
"build:docs": "npx typedoc --plugin typedoc-plugin-mdn-links --plugin typedoc-plugin-missing-exports ./src/index.ts",
|
||||
"build:docs:md": "npx typedoc --plugin typedoc-plugin-markdown --plugin typedoc-plugin-mdn-links --plugin typedoc-plugin-missing-exports ./src/index.ts",
|
||||
"build": "npm run build:code & npm run build:docs & wait",
|
||||
"prepack": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rimraf": "^5.0.5",
|
||||
"typedoc": "^0.25.7",
|
||||
"typedoc-plugin-markdown": "^3.17.1",
|
||||
"typescript": "^5.3.3"
|
||||
"typedoc": "^0.27.7",
|
||||
"typedoc-plugin-markdown": "^4.4.2",
|
||||
"typedoc-plugin-mdn-links": "^4.0.13",
|
||||
"typedoc-plugin-missing-exports": "^3.1.0",
|
||||
"typescript": "^5.7.3",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,8 @@ The electron alternative for Go
|
|||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
import { newRuntimeCallerWithID, objectNames } from "./runtime";
|
||||
const call = newRuntimeCallerWithID(objectNames.Application, '');
|
||||
import { newRuntimeCaller, objectNames } from "./runtime.js";
|
||||
const call = newRuntimeCaller(objectNames.Application);
|
||||
|
||||
const HideMethod = 0;
|
||||
const ShowMethod = 1;
|
||||
|
|
@ -19,28 +17,21 @@ const QuitMethod = 2;
|
|||
|
||||
/**
|
||||
* Hides a certain method by calling the HideMethod function.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*
|
||||
*/
|
||||
export function Hide() {
|
||||
export function Hide(): Promise<void> {
|
||||
return call(HideMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the ShowMethod and returns the result.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
export function Show() {
|
||||
export function Show(): Promise<void> {
|
||||
return call(ShowMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the QuitMethod to terminate the program.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
export function Quit() {
|
||||
export function Quit(): Promise<void> {
|
||||
return call(QuitMethod);
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
import {newRuntimeCallerWithID, objectNames} from "./runtime";
|
||||
|
||||
const call = newRuntimeCallerWithID(objectNames.Browser, '');
|
||||
const BrowserOpenURL = 0;
|
||||
|
||||
/**
|
||||
* Open a browser window to the given URL
|
||||
* @param {string} url - The URL to open
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function OpenURL(url) {
|
||||
return call(BrowserOpenURL, {url});
|
||||
}
|
||||
24
v3/internal/runtime/desktop/@wailsio/runtime/src/browser.ts
Normal file
24
v3/internal/runtime/desktop/@wailsio/runtime/src/browser.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import { newRuntimeCaller, objectNames } from "./runtime.js";
|
||||
|
||||
const call = newRuntimeCaller(objectNames.Browser);
|
||||
|
||||
const BrowserOpenURL = 0;
|
||||
|
||||
/**
|
||||
* Open a browser window to the given URL.
|
||||
*
|
||||
* @param url - The URL to open
|
||||
*/
|
||||
export function OpenURL(url: string | URL): Promise<void> {
|
||||
return call(BrowserOpenURL, {url: url.toString()});
|
||||
}
|
||||
125
v3/internal/runtime/desktop/@wailsio/runtime/src/callable.ts
Normal file
125
v3/internal/runtime/desktop/@wailsio/runtime/src/callable.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// Source: https://github.com/inspect-js/is-callable
|
||||
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2015 Jordan Harband
|
||||
//
|
||||
// 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.
|
||||
|
||||
var fnToStr = Function.prototype.toString;
|
||||
var reflectApply: typeof Reflect.apply | false | null = typeof Reflect === 'object' && Reflect !== null && Reflect.apply;
|
||||
var badArrayLike: any;
|
||||
var isCallableMarker: any;
|
||||
if (typeof reflectApply === 'function' && typeof Object.defineProperty === 'function') {
|
||||
try {
|
||||
badArrayLike = Object.defineProperty({}, 'length', {
|
||||
get: function () {
|
||||
throw isCallableMarker;
|
||||
}
|
||||
});
|
||||
isCallableMarker = {};
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
reflectApply(function () { throw 42; }, null, badArrayLike);
|
||||
} catch (_) {
|
||||
if (_ !== isCallableMarker) {
|
||||
reflectApply = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reflectApply = null;
|
||||
}
|
||||
|
||||
var constructorRegex = /^\s*class\b/;
|
||||
var isES6ClassFn = function isES6ClassFunction(value: any): boolean {
|
||||
try {
|
||||
var fnStr = fnToStr.call(value);
|
||||
return constructorRegex.test(fnStr);
|
||||
} catch (e) {
|
||||
return false; // not a function
|
||||
}
|
||||
};
|
||||
|
||||
var tryFunctionObject = function tryFunctionToStr(value: any): boolean {
|
||||
try {
|
||||
if (isES6ClassFn(value)) { return false; }
|
||||
fnToStr.call(value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
var toStr = Object.prototype.toString;
|
||||
var objectClass = '[object Object]';
|
||||
var fnClass = '[object Function]';
|
||||
var genClass = '[object GeneratorFunction]';
|
||||
var ddaClass = '[object HTMLAllCollection]'; // IE 11
|
||||
var ddaClass2 = '[object HTML document.all class]';
|
||||
var ddaClass3 = '[object HTMLCollection]'; // IE 9-10
|
||||
var hasToStringTag = typeof Symbol === 'function' && !!Symbol.toStringTag; // better: use `has-tostringtag`
|
||||
|
||||
var isIE68 = !(0 in [,]); // eslint-disable-line no-sparse-arrays, comma-spacing
|
||||
|
||||
var isDDA: (value: any) => boolean = function isDocumentDotAll() { return false; };
|
||||
if (typeof document === 'object') {
|
||||
// Firefox 3 canonicalizes DDA to undefined when it's not accessed directly
|
||||
var all = document.all;
|
||||
if (toStr.call(all) === toStr.call(document.all)) {
|
||||
isDDA = function isDocumentDotAll(value) {
|
||||
/* globals document: false */
|
||||
// in IE 6-8, typeof document.all is "object" and it's truthy
|
||||
if ((isIE68 || !value) && (typeof value === 'undefined' || typeof value === 'object')) {
|
||||
try {
|
||||
var str = toStr.call(value);
|
||||
return (
|
||||
str === ddaClass
|
||||
|| str === ddaClass2
|
||||
|| str === ddaClass3 // opera 12.16
|
||||
|| str === objectClass // IE 6-8
|
||||
) && value('') == null; // eslint-disable-line eqeqeq
|
||||
} catch (e) { /**/ }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function isCallableRefApply<T>(value: T | unknown): value is (...args: any[]) => any {
|
||||
if (isDDA(value)) { return true; }
|
||||
if (!value) { return false; }
|
||||
if (typeof value !== 'function' && typeof value !== 'object') { return false; }
|
||||
try {
|
||||
(reflectApply as any)(value, null, badArrayLike);
|
||||
} catch (e) {
|
||||
if (e !== isCallableMarker) { return false; }
|
||||
}
|
||||
return !isES6ClassFn(value) && tryFunctionObject(value);
|
||||
}
|
||||
|
||||
function isCallableNoRefApply<T>(value: T | unknown): value is (...args: any[]) => any {
|
||||
if (isDDA(value)) { return true; }
|
||||
if (!value) { return false; }
|
||||
if (typeof value !== 'function' && typeof value !== 'object') { return false; }
|
||||
if (hasToStringTag) { return tryFunctionObject(value); }
|
||||
if (isES6ClassFn(value)) { return false; }
|
||||
var strClass = toStr.call(value);
|
||||
if (strClass !== fnClass && strClass !== genClass && !(/^\[object HTML/).test(strClass)) { return false; }
|
||||
return tryFunctionObject(value);
|
||||
};
|
||||
|
||||
export default reflectApply ? isCallableRefApply : isCallableNoRefApply;
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
import { newRuntimeCallerWithID, objectNames } from "./runtime";
|
||||
import { nanoid } from './nanoid.js';
|
||||
|
||||
// Setup
|
||||
window._wails = window._wails || {};
|
||||
window._wails.callResultHandler = resultHandler;
|
||||
window._wails.callErrorHandler = errorHandler;
|
||||
|
||||
|
||||
const CallBinding = 0;
|
||||
const call = newRuntimeCallerWithID(objectNames.Call, '');
|
||||
const cancelCall = newRuntimeCallerWithID(objectNames.CancelCall, '');
|
||||
let callResponses = new Map();
|
||||
|
||||
/**
|
||||
* Generates a unique ID using the nanoid library.
|
||||
*
|
||||
* @return {string} - A unique ID that does not exist in the callResponses set.
|
||||
*/
|
||||
function generateID() {
|
||||
let result;
|
||||
do {
|
||||
result = nanoid();
|
||||
} while (callResponses.has(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the result of a call request.
|
||||
*
|
||||
* @param {string} id - The id of the request to handle the result for.
|
||||
* @param {string} data - The result data of the request.
|
||||
* @param {boolean} isJSON - Indicates whether the data is JSON or not.
|
||||
*
|
||||
* @return {undefined} - This method does not return any value.
|
||||
*/
|
||||
function resultHandler(id, data, isJSON) {
|
||||
const promiseHandler = getAndDeleteResponse(id);
|
||||
if (promiseHandler) {
|
||||
if (!data) {
|
||||
promiseHandler.resolve();
|
||||
} else if (!isJSON) {
|
||||
promiseHandler.resolve(data);
|
||||
} else {
|
||||
try {
|
||||
promiseHandler.resolve(JSON.parse(data));
|
||||
} catch (err) {
|
||||
promiseHandler.reject(new TypeError("could not parse result: " + err.message, { cause: err }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the error from a call request.
|
||||
*
|
||||
* @param {string} id - The id of the promise handler.
|
||||
* @param {string} data - The error data to reject the promise handler with.
|
||||
* @param {boolean} isJSON - Indicates whether the data is JSON or not.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function errorHandler(id, data, isJSON) {
|
||||
const promiseHandler = getAndDeleteResponse(id);
|
||||
if (promiseHandler) {
|
||||
if (!isJSON) {
|
||||
promiseHandler.reject(new Error(data));
|
||||
} else {
|
||||
let error;
|
||||
try {
|
||||
error = JSON.parse(data);
|
||||
} catch (err) {
|
||||
promiseHandler.reject(new TypeError("could not parse error: " + err.message, { cause: err }));
|
||||
return;
|
||||
}
|
||||
|
||||
let options = {};
|
||||
if (error.cause) {
|
||||
options.cause = error.cause;
|
||||
}
|
||||
|
||||
let exception;
|
||||
switch (error.kind) {
|
||||
case "ReferenceError":
|
||||
exception = new ReferenceError(error.message, options);
|
||||
break;
|
||||
case "TypeError":
|
||||
exception = new TypeError(error.message, options);
|
||||
break;
|
||||
case "RuntimeError":
|
||||
exception = new RuntimeError(error.message, options);
|
||||
break;
|
||||
default:
|
||||
exception = new Error(error.message, options);
|
||||
break;
|
||||
}
|
||||
|
||||
promiseHandler.reject(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves and removes the response associated with the given ID from the callResponses map.
|
||||
*
|
||||
* @param {any} id - The ID of the response to be retrieved and removed.
|
||||
*
|
||||
* @returns {any} The response object associated with the given ID.
|
||||
*/
|
||||
function getAndDeleteResponse(id) {
|
||||
const response = callResponses.get(id);
|
||||
callResponses.delete(id);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all required information for a binding call.
|
||||
*
|
||||
* @typedef {Object} CallOptions
|
||||
* @property {number} [methodID] - The numeric ID of the bound method to call.
|
||||
* @property {string} [methodName] - The fully qualified name of the bound method to call.
|
||||
* @property {any[]} args - Arguments to be passed into the bound method.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exception class that will be thrown in case the bound method returns an error.
|
||||
* The value of the {@link RuntimeError#name} property is "RuntimeError".
|
||||
*/
|
||||
export class RuntimeError extends Error {
|
||||
/**
|
||||
* Constructs a new RuntimeError instance.
|
||||
*
|
||||
* @param {string} message - The error message.
|
||||
* @param {any[]} args - Optional arguments for the Error constructor.
|
||||
*/
|
||||
constructor(message, ...args) {
|
||||
super(message, ...args);
|
||||
this.name = "RuntimeError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a bound method according to the given call options.
|
||||
*
|
||||
* In case of failure, the returned promise will reject with an exception
|
||||
* among ReferenceError (unknown method), TypeError (wrong argument count or type),
|
||||
* {@link RuntimeError} (method returned an error), or other (network or internal errors).
|
||||
* The exception might have a "cause" field with the value returned
|
||||
* by the application- or service-level error marshaling functions.
|
||||
*
|
||||
* @param {CallOptions} options - A method call descriptor.
|
||||
* @returns {Promise<any>} - The result of the call.
|
||||
*/
|
||||
export function Call(options) {
|
||||
const id = generateID();
|
||||
const doCancel = () => { return cancelCall(type, {"call-id": id}) };
|
||||
let queuedCancel = false, callRunning = false;
|
||||
let p = new Promise((resolve, reject) => {
|
||||
options["call-id"] = id;
|
||||
callResponses.set(id, { resolve, reject });
|
||||
call(CallBinding, options).then((_) => {
|
||||
callRunning = true;
|
||||
if (queuedCancel) {
|
||||
return doCancel();
|
||||
}
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
callResponses.delete(id);
|
||||
});
|
||||
});
|
||||
p.cancel = () => {
|
||||
if (callRunning) {
|
||||
return doCancel();
|
||||
} else {
|
||||
queuedCancel = true;
|
||||
}
|
||||
};
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a bound method by name with the specified arguments.
|
||||
* See {@link Call} for details.
|
||||
*
|
||||
* @param {string} methodName - The name of the method in the format 'package.struct.method'.
|
||||
* @param {any[]} args - The arguments to pass to the method.
|
||||
* @returns {Promise<any>} The result of the method call.
|
||||
*/
|
||||
export function ByName(methodName, ...args) {
|
||||
return Call({
|
||||
methodName,
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method by its numeric ID with the specified arguments.
|
||||
* See {@link Call} for details.
|
||||
*
|
||||
* @param {number} methodID - The ID of the method to call.
|
||||
* @param {any[]} args - The arguments to pass to the method.
|
||||
* @return {Promise<any>} - The result of the method call.
|
||||
*/
|
||||
export function ByID(methodID, ...args) {
|
||||
return Call({
|
||||
methodID,
|
||||
args
|
||||
});
|
||||
}
|
||||
233
v3/internal/runtime/desktop/@wailsio/runtime/src/calls.ts
Normal file
233
v3/internal/runtime/desktop/@wailsio/runtime/src/calls.ts
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import { CancellablePromise, type CancellablePromiseWithResolvers } from "./cancellable.js";
|
||||
import { newRuntimeCaller, objectNames } from "./runtime.js";
|
||||
import { nanoid } from "./nanoid.js";
|
||||
|
||||
// Setup
|
||||
window._wails = window._wails || {};
|
||||
window._wails.callResultHandler = resultHandler;
|
||||
window._wails.callErrorHandler = errorHandler;
|
||||
|
||||
type PromiseResolvers = Omit<CancellablePromiseWithResolvers<any>, "promise" | "oncancelled">
|
||||
|
||||
const call = newRuntimeCaller(objectNames.Call);
|
||||
const cancelCall = newRuntimeCaller(objectNames.CancelCall);
|
||||
const callResponses = new Map<string, PromiseResolvers>();
|
||||
|
||||
const CallBinding = 0;
|
||||
const CancelMethod = 0
|
||||
|
||||
/**
|
||||
* Holds all required information for a binding call.
|
||||
* May provide either a method ID or a method name, but not both.
|
||||
*/
|
||||
export type CallOptions = {
|
||||
/** The numeric ID of the bound method to call. */
|
||||
methodID: number;
|
||||
/** The fully qualified name of the bound method to call. */
|
||||
methodName?: never;
|
||||
/** Arguments to be passed into the bound method. */
|
||||
args: any[];
|
||||
} | {
|
||||
/** The numeric ID of the bound method to call. */
|
||||
methodID?: never;
|
||||
/** The fully qualified name of the bound method to call. */
|
||||
methodName: string;
|
||||
/** Arguments to be passed into the bound method. */
|
||||
args: any[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception class that will be thrown in case the bound method returns an error.
|
||||
* The value of the {@link RuntimeError#name} property is "RuntimeError".
|
||||
*/
|
||||
export class RuntimeError extends Error {
|
||||
/**
|
||||
* Constructs a new RuntimeError instance.
|
||||
* @param message - The error message.
|
||||
* @param options - Options to be forwarded to the Error constructor.
|
||||
*/
|
||||
constructor(message?: string, options?: ErrorOptions) {
|
||||
super(message, options);
|
||||
this.name = "RuntimeError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the result of a call request.
|
||||
*
|
||||
* @param id - The id of the request to handle the result for.
|
||||
* @param data - The result data of the request.
|
||||
* @param isJSON - Indicates whether the data is JSON or not.
|
||||
*/
|
||||
function resultHandler(id: string, data: string, isJSON: boolean): void {
|
||||
const resolvers = getAndDeleteResponse(id);
|
||||
if (!resolvers) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
resolvers.resolve(undefined);
|
||||
} else if (!isJSON) {
|
||||
resolvers.resolve(data);
|
||||
} else {
|
||||
try {
|
||||
resolvers.resolve(JSON.parse(data));
|
||||
} catch (err: any) {
|
||||
resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the error from a call request.
|
||||
*
|
||||
* @param id - The id of the promise handler.
|
||||
* @param data - The error data to reject the promise handler with.
|
||||
* @param isJSON - Indicates whether the data is JSON or not.
|
||||
*/
|
||||
function errorHandler(id: string, data: string, isJSON: boolean): void {
|
||||
const resolvers = getAndDeleteResponse(id);
|
||||
if (!resolvers) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isJSON) {
|
||||
resolvers.reject(new Error(data));
|
||||
} else {
|
||||
let error: any;
|
||||
try {
|
||||
error = JSON.parse(data);
|
||||
} catch (err: any) {
|
||||
resolvers.reject(new TypeError("could not parse error: " + err.message, { cause: err }));
|
||||
return;
|
||||
}
|
||||
|
||||
let options: ErrorOptions = {};
|
||||
if (error.cause) {
|
||||
options.cause = error.cause;
|
||||
}
|
||||
|
||||
let exception;
|
||||
switch (error.kind) {
|
||||
case "ReferenceError":
|
||||
exception = new ReferenceError(error.message, options);
|
||||
break;
|
||||
case "TypeError":
|
||||
exception = new TypeError(error.message, options);
|
||||
break;
|
||||
case "RuntimeError":
|
||||
exception = new RuntimeError(error.message, options);
|
||||
break;
|
||||
default:
|
||||
exception = new Error(error.message, options);
|
||||
break;
|
||||
}
|
||||
|
||||
resolvers.reject(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves and removes the response associated with the given ID from the callResponses map.
|
||||
*
|
||||
* @param id - The ID of the response to be retrieved and removed.
|
||||
* @returns The response object associated with the given ID, if any.
|
||||
*/
|
||||
function getAndDeleteResponse(id: string): PromiseResolvers | undefined {
|
||||
const response = callResponses.get(id);
|
||||
callResponses.delete(id);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique ID using the nanoid library.
|
||||
*
|
||||
* @returns A unique ID that does not exist in the callResponses set.
|
||||
*/
|
||||
function generateID(): string {
|
||||
let result;
|
||||
do {
|
||||
result = nanoid();
|
||||
} while (callResponses.has(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a bound method according to the given call options.
|
||||
*
|
||||
* In case of failure, the returned promise will reject with an exception
|
||||
* among ReferenceError (unknown method), TypeError (wrong argument count or type),
|
||||
* {@link RuntimeError} (method returned an error), or other (network or internal errors).
|
||||
* The exception might have a "cause" field with the value returned
|
||||
* by the application- or service-level error marshaling functions.
|
||||
*
|
||||
* @param options - A method call descriptor.
|
||||
* @returns The result of the call.
|
||||
*/
|
||||
export function Call(options: CallOptions): CancellablePromise<any> {
|
||||
const id = generateID();
|
||||
|
||||
const result = CancellablePromise.withResolvers<any>();
|
||||
callResponses.set(id, { resolve: result.resolve, reject: result.reject });
|
||||
|
||||
const request = call(CallBinding, Object.assign({ "call-id": id }, options));
|
||||
let running = false;
|
||||
|
||||
request.then(() => {
|
||||
running = true;
|
||||
}, (err) => {
|
||||
callResponses.delete(id);
|
||||
result.reject(err);
|
||||
});
|
||||
|
||||
const cancel = () => {
|
||||
callResponses.delete(id);
|
||||
return cancelCall(CancelMethod, {"call-id": id}).catch((err) => {
|
||||
console.log("Error while requesting binding call cancellation:", err);
|
||||
});
|
||||
};
|
||||
|
||||
result.oncancelled = () => {
|
||||
if (running) {
|
||||
return cancel();
|
||||
} else {
|
||||
return request.then(cancel);
|
||||
}
|
||||
};
|
||||
|
||||
return result.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a bound method by name with the specified arguments.
|
||||
* See {@link Call} for details.
|
||||
*
|
||||
* @param methodName - The name of the method in the format 'package.struct.method'.
|
||||
* @param args - The arguments to pass to the method.
|
||||
* @returns The result of the method call.
|
||||
*/
|
||||
export function ByName(methodName: string, ...args: any[]): CancellablePromise<any> {
|
||||
return Call({ methodName, args });
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method by its numeric ID with the specified arguments.
|
||||
* See {@link Call} for details.
|
||||
*
|
||||
* @param methodID - The ID of the method to call.
|
||||
* @param args - The arguments to pass to the method.
|
||||
* @return The result of the method call.
|
||||
*/
|
||||
export function ByID(methodID: number, ...args: any[]): CancellablePromise<any> {
|
||||
return Call({ methodID, args });
|
||||
}
|
||||
923
v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts
Normal file
923
v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts
Normal file
|
|
@ -0,0 +1,923 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import isCallable from "./callable.js";
|
||||
|
||||
/**
|
||||
* Exception class that will be used as rejection reason
|
||||
* in case a {@link CancellablePromise} is cancelled successfully.
|
||||
*
|
||||
* The value of the {@link name} property is the string `"CancelError"`.
|
||||
* The value of the {@link cause} property is the cause passed to the cancel method, if any.
|
||||
*/
|
||||
export class CancelError extends Error {
|
||||
/**
|
||||
* Constructs a new `CancelError` instance.
|
||||
* @param message - The error message.
|
||||
* @param options - Options to be forwarded to the Error constructor.
|
||||
*/
|
||||
constructor(message?: string, options?: ErrorOptions) {
|
||||
super(message, options);
|
||||
this.name = "CancelError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception class that will be reported as an unhandled rejection
|
||||
* in case a {@link CancellablePromise} rejects after being cancelled,
|
||||
* or when the `oncancelled` callback throws or rejects.
|
||||
*
|
||||
* The value of the {@link name} property is the string `"CancelledRejectionError"`.
|
||||
* The value of the {@link cause} property is the reason the promise rejected with.
|
||||
*
|
||||
* Because the original promise was cancelled,
|
||||
* a wrapper promise will be passed to the unhandled rejection listener instead.
|
||||
* The {@link promise} property holds a reference to the original promise.
|
||||
*/
|
||||
export class CancelledRejectionError extends Error {
|
||||
/**
|
||||
* Holds a reference to the promise that was cancelled and then rejected.
|
||||
*/
|
||||
promise: CancellablePromise<unknown>;
|
||||
|
||||
/**
|
||||
* Constructs a new `CancelledRejectionError` instance.
|
||||
* @param promise - The promise that caused the error originally.
|
||||
* @param reason - The rejection reason.
|
||||
* @param info - An optional informative message specifying the circumstances in which the error was thrown.
|
||||
* Defaults to the string `"Unhandled rejection in cancelled promise."`.
|
||||
*/
|
||||
constructor(promise: CancellablePromise<unknown>, reason?: any, info?: string) {
|
||||
super((info ?? "Unhandled rejection in cancelled promise.") + " Reason: " + errorMessage(reason), { cause: reason });
|
||||
this.promise = promise;
|
||||
this.name = "CancelledRejectionError";
|
||||
}
|
||||
}
|
||||
|
||||
type CancellablePromiseResolver<T> = (value: T | PromiseLike<T> | CancellablePromiseLike<T>) => void;
|
||||
type CancellablePromiseRejector = (reason?: any) => void;
|
||||
type CancellablePromiseCanceller = (cause?: any) => void | PromiseLike<void>;
|
||||
type CancellablePromiseExecutor<T> = (resolve: CancellablePromiseResolver<T>, reject: CancellablePromiseRejector) => void;
|
||||
|
||||
export interface CancellablePromiseLike<T> {
|
||||
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1> | CancellablePromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2> | CancellablePromiseLike<TResult2>) | undefined | null): CancellablePromiseLike<TResult1 | TResult2>;
|
||||
cancel(cause?: any): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a cancellable promise along with its resolution methods.
|
||||
* The `oncancelled` field will be null initially but may be set to provide a custom cancellation function.
|
||||
*/
|
||||
export interface CancellablePromiseWithResolvers<T> {
|
||||
promise: CancellablePromise<T>;
|
||||
resolve: CancellablePromiseResolver<T>;
|
||||
reject: CancellablePromiseRejector;
|
||||
oncancelled: CancellablePromiseCanceller | null;
|
||||
}
|
||||
|
||||
interface CancellablePromiseState {
|
||||
readonly root: CancellablePromiseState;
|
||||
resolving: boolean;
|
||||
settled: boolean;
|
||||
reason?: CancelError;
|
||||
}
|
||||
|
||||
// Private field names.
|
||||
const barrierSym = Symbol("barrier");
|
||||
const cancelImplSym = Symbol("cancelImpl");
|
||||
const species = Symbol.species ?? Symbol("speciesPolyfill");
|
||||
|
||||
/**
|
||||
* A promise with an attached method for cancelling long-running operations (see {@link CancellablePromise#cancel}).
|
||||
* Cancellation can optionally be bound to an {@link AbortSignal}
|
||||
* for better composability (see {@link CancellablePromise#cancelOn}).
|
||||
*
|
||||
* Cancelling a pending promise will result in an immediate rejection
|
||||
* with an instance of {@link CancelError} as reason,
|
||||
* but whoever started the promise will be responsible
|
||||
* for actually aborting the underlying operation.
|
||||
* To this purpose, the constructor and all chaining methods
|
||||
* accept optional cancellation callbacks.
|
||||
*
|
||||
* If a `CancellablePromise` still resolves after having been cancelled,
|
||||
* the result will be discarded. If it rejects, the reason
|
||||
* will be reported as an unhandled rejection,
|
||||
* wrapped in a {@link CancelledRejectionError} instance.
|
||||
* To facilitate the handling of cancellation requests,
|
||||
* cancelled `CancellablePromise`s will _not_ report unhandled `CancelError`s
|
||||
* whose `cause` field is the same as the one with which the current promise was cancelled.
|
||||
*
|
||||
* All usual promise methods are defined and return a `CancellablePromise`
|
||||
* whose cancel method will cancel the parent operation as well, propagating the cancellation reason
|
||||
* upwards through promise chains.
|
||||
* Conversely, cancelling a promise will not automatically cancel dependent promises downstream:
|
||||
* ```ts
|
||||
* let root = new CancellablePromise((resolve, reject) => { ... });
|
||||
* let child1 = root.then(() => { ... });
|
||||
* let child2 = child1.then(() => { ... });
|
||||
* let child3 = root.catch(() => { ... });
|
||||
* child1.cancel(); // Cancels child1 and root, but not child2 or child3
|
||||
* ```
|
||||
* Cancelling a promise that has already settled is safe and has no consequence.
|
||||
*
|
||||
* The `cancel` method returns a promise that _always fulfills_
|
||||
* after the whole chain has processed the cancel request
|
||||
* and all attached callbacks up to that moment have run.
|
||||
*
|
||||
* All ES2024 promise methods (static and instance) are defined on CancellablePromise,
|
||||
* but actual availability may vary with OS/webview version.
|
||||
*
|
||||
* In line with the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing,
|
||||
* `CancellablePromise` does not support transparent subclassing.
|
||||
* Extenders should take care to provide their own method implementations.
|
||||
* This might be reconsidered in case the proposal is retired.
|
||||
*
|
||||
* CancellablePromise is a wrapper around the DOM Promise object
|
||||
* and is compliant with the [Promises/A+ specification](https://promisesaplus.com/)
|
||||
* (it passes the [compliance suite](https://github.com/promises-aplus/promises-tests))
|
||||
* if so is the underlying implementation.
|
||||
*/
|
||||
export class CancellablePromise<T> extends Promise<T> implements PromiseLike<T>, CancellablePromiseLike<T> {
|
||||
// Private fields.
|
||||
/** @internal */
|
||||
private [barrierSym]!: Partial<PromiseWithResolvers<void>> | null;
|
||||
/** @internal */
|
||||
private readonly [cancelImplSym]!: (reason: CancelError) => void | PromiseLike<void>;
|
||||
|
||||
/**
|
||||
* Creates a new `CancellablePromise`.
|
||||
*
|
||||
* @param executor - A callback used to initialize the promise. This callback is passed two arguments:
|
||||
* a `resolve` callback used to resolve the promise with a value
|
||||
* or the result of another promise (possibly cancellable),
|
||||
* and a `reject` callback used to reject the promise with a provided reason or error.
|
||||
* If the value provided to the `resolve` callback is a thenable _and_ cancellable object
|
||||
* (it has a `then` _and_ a `cancel` method),
|
||||
* cancellation requests will be forwarded to that object and the oncancelled will not be invoked anymore.
|
||||
* If any one of the two callbacks is called _after_ the promise has been cancelled,
|
||||
* the provided values will be cancelled and resolved as usual,
|
||||
* but their results will be discarded.
|
||||
* However, if the resolution process ultimately ends up in a rejection
|
||||
* that is not due to cancellation, the rejection reason
|
||||
* will be wrapped in a {@link CancelledRejectionError}
|
||||
* and bubbled up as an unhandled rejection.
|
||||
* @param oncancelled - It is the caller's responsibility to ensure that any operation
|
||||
* started by the executor is properly halted upon cancellation.
|
||||
* This optional callback can be used to that purpose.
|
||||
* It will be called _synchronously_ with a cancellation cause
|
||||
* when cancellation is requested, _after_ the promise has already rejected
|
||||
* with a {@link CancelError}, but _before_
|
||||
* any {@link then}/{@link catch}/{@link finally} callback runs.
|
||||
* If the callback returns a thenable, the promise returned from {@link cancel}
|
||||
* will only fulfill after the former has settled.
|
||||
* Unhandled exceptions or rejections from the callback will be wrapped
|
||||
* in a {@link CancelledRejectionError} and bubbled up as unhandled rejections.
|
||||
* If the `resolve` callback is called before cancellation with a cancellable promise,
|
||||
* cancellation requests on this promise will be diverted to that promise,
|
||||
* and the original `oncancelled` callback will be discarded.
|
||||
*/
|
||||
constructor(executor: CancellablePromiseExecutor<T>, oncancelled?: CancellablePromiseCanceller) {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void;
|
||||
let reject!: (reason?: any) => void;
|
||||
super((res, rej) => { resolve = res; reject = rej; });
|
||||
|
||||
if ((this.constructor as any)[species] !== Promise) {
|
||||
throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");
|
||||
}
|
||||
|
||||
let promise: CancellablePromiseWithResolvers<T> = {
|
||||
promise: this,
|
||||
resolve,
|
||||
reject,
|
||||
get oncancelled() { return oncancelled ?? null; },
|
||||
set oncancelled(cb) { oncancelled = cb ?? undefined; }
|
||||
};
|
||||
|
||||
const state: CancellablePromiseState = {
|
||||
get root() { return state; },
|
||||
resolving: false,
|
||||
settled: false
|
||||
};
|
||||
|
||||
// Setup cancellation system.
|
||||
void Object.defineProperties(this, {
|
||||
[barrierSym]: {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: null
|
||||
},
|
||||
[cancelImplSym]: {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: cancellerFor(promise, state)
|
||||
}
|
||||
});
|
||||
|
||||
// Run the actual executor.
|
||||
const rejector = rejectorFor(promise, state);
|
||||
try {
|
||||
executor(resolverFor(promise, state), rejector);
|
||||
} catch (err) {
|
||||
if (state.resolving) {
|
||||
console.log("Unhandled exception in CancellablePromise executor.", err);
|
||||
} else {
|
||||
rejector(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels immediately the execution of the operation associated with this promise.
|
||||
* The promise rejects with a {@link CancelError} instance as reason,
|
||||
* with the {@link CancelError#cause} property set to the given argument, if any.
|
||||
*
|
||||
* Has no effect if called after the promise has already settled;
|
||||
* repeated calls in particular are safe, but only the first one
|
||||
* will set the cancellation cause.
|
||||
*
|
||||
* The `CancelError` exception _need not_ be handled explicitly _on the promises that are being cancelled:_
|
||||
* cancelling a promise with no attached rejection handler does not trigger an unhandled rejection event.
|
||||
* Therefore, the following idioms are all equally correct:
|
||||
* ```ts
|
||||
* new CancellablePromise((resolve, reject) => { ... }).cancel();
|
||||
* new CancellablePromise((resolve, reject) => { ... }).then(...).cancel();
|
||||
* new CancellablePromise((resolve, reject) => { ... }).then(...).catch(...).cancel();
|
||||
* ```
|
||||
* Whenever some cancelled promise in a chain rejects with a `CancelError`
|
||||
* with the same cancellation cause as itself, the error will be discarded silently.
|
||||
* However, the `CancelError` _will still be delivered_ to all attached rejection handlers
|
||||
* added by {@link then} and related methods:
|
||||
* ```ts
|
||||
* let cancellable = new CancellablePromise((resolve, reject) => { ... });
|
||||
* cancellable.then(() => { ... }).catch(console.log);
|
||||
* cancellable.cancel(); // A CancelError is printed to the console.
|
||||
* ```
|
||||
* If the `CancelError` is not handled downstream by the time it reaches
|
||||
* a _non-cancelled_ promise, it _will_ trigger an unhandled rejection event,
|
||||
* just like normal rejections would:
|
||||
* ```ts
|
||||
* let cancellable = new CancellablePromise((resolve, reject) => { ... });
|
||||
* let chained = cancellable.then(() => { ... }).then(() => { ... }); // No catch...
|
||||
* cancellable.cancel(); // Unhandled rejection event on chained!
|
||||
* ```
|
||||
* Therefore, it is important to either cancel whole promise chains from their tail,
|
||||
* as shown in the correct idioms above, or take care of handling errors everywhere.
|
||||
*
|
||||
* @returns A cancellable promise that _fulfills_ after the cancel callback (if any)
|
||||
* and all handlers attached up to the call to cancel have run.
|
||||
* If the cancel callback returns a thenable, the promise returned by `cancel`
|
||||
* will also wait for that thenable to settle.
|
||||
* This enables callers to wait for the cancelled operation to terminate
|
||||
* without being forced to handle potential errors at the call site.
|
||||
* ```ts
|
||||
* cancellable.cancel().then(() => {
|
||||
* // Cleanup finished, it's safe to do something else.
|
||||
* }, (err) => {
|
||||
* // Unreachable: the promise returned from cancel will never reject.
|
||||
* });
|
||||
* ```
|
||||
* Note that the returned promise will _not_ handle implicitly any rejection
|
||||
* that might have occurred already in the cancelled chain.
|
||||
* It will just track whether registered handlers have been executed or not.
|
||||
* Therefore, unhandled rejections will never be silently handled by calling cancel.
|
||||
*/
|
||||
cancel(cause?: any): CancellablePromise<void> {
|
||||
return new CancellablePromise<void>((resolve) => {
|
||||
Promise.allSettled([
|
||||
this[cancelImplSym](new CancelError("Promise cancelled.", { cause })),
|
||||
currentBarrier(this)
|
||||
]).then(() => resolve(), () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds promise cancellation to the abort event of the given {@link AbortSignal}.
|
||||
* If the signal has already aborted, the promise will be cancelled immediately.
|
||||
* When either condition is verified, the cancellation cause will be set
|
||||
* to the signal's abort reason (see {@link AbortSignal#reason}).
|
||||
*
|
||||
* Has no effect if called (or if the signal aborts) _after_ the promise has already settled.
|
||||
* Only the first signal to abort will set the cancellation cause.
|
||||
*
|
||||
* For more details about the cancellation process,
|
||||
* see {@link cancel} and the `CancellablePromise` constructor.
|
||||
*
|
||||
* This method enables `await`ing cancellable promises without having
|
||||
* to store them for future cancellation, e.g.:
|
||||
* ```ts
|
||||
* await longRunningOperation().cancelOn(signal);
|
||||
* ```
|
||||
* instead of:
|
||||
* ```ts
|
||||
* let promiseToBeCancelled = longRunningOperation();
|
||||
* await promiseToBeCancelled;
|
||||
* ```
|
||||
*
|
||||
* @returns This promise, for method chaining.
|
||||
*/
|
||||
cancelOn(signal: AbortSignal): CancellablePromise<T> {
|
||||
if (signal.aborted) {
|
||||
void this.cancel(signal.reason)
|
||||
} else {
|
||||
signal.addEventListener('abort', () => void this.cancel(signal.reason), {capture: true});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the `CancellablePromise`.
|
||||
*
|
||||
* The optional `oncancelled` argument will be invoked when the returned promise is cancelled,
|
||||
* with the same semantics as the `oncancelled` argument of the constructor.
|
||||
* When the parent promise rejects or is cancelled, the `onrejected` callback will run,
|
||||
* _even after the returned promise has been cancelled:_
|
||||
* in that case, should it reject or throw, the reason will be wrapped
|
||||
* in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection.
|
||||
*
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A `CancellablePromise` for the completion of whichever callback is executed.
|
||||
* The returned promise is hooked up to propagate cancellation requests up the chain, but not down:
|
||||
*
|
||||
* - if the parent promise is cancelled, the `onrejected` handler will be invoked with a `CancelError`
|
||||
* and the returned promise _will resolve regularly_ with its result;
|
||||
* - conversely, if the returned promise is cancelled, _the parent promise is cancelled too;_
|
||||
* the `onrejected` handler will still be invoked with the parent's `CancelError`,
|
||||
* but its result will be discarded
|
||||
* and the returned promise will reject with a `CancelError` as well.
|
||||
*
|
||||
* The promise returned from {@link cancel} will fulfill only after all attached handlers
|
||||
* up the entire promise chain have been run.
|
||||
*
|
||||
* If either callback returns a cancellable promise,
|
||||
* cancellation requests will be diverted to it,
|
||||
* and the specified `oncancelled` callback will be discarded.
|
||||
*/
|
||||
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1> | CancellablePromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2> | CancellablePromiseLike<TResult2>) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise<TResult1 | TResult2> {
|
||||
if (!(this instanceof CancellablePromise)) {
|
||||
throw new TypeError("CancellablePromise.prototype.then called on an invalid object.");
|
||||
}
|
||||
|
||||
// NOTE: TypeScript's built-in type for then is broken,
|
||||
// as it allows specifying an arbitrary TResult1 != T even when onfulfilled is not a function.
|
||||
// We cannot fix it if we want to CancellablePromise to implement PromiseLike<T>.
|
||||
|
||||
if (!isCallable(onfulfilled)) { onfulfilled = identity as any; }
|
||||
if (!isCallable(onrejected)) { onrejected = thrower; }
|
||||
|
||||
if (onfulfilled === identity && onrejected == thrower) {
|
||||
// Shortcut for trivial arguments.
|
||||
return new CancellablePromise((resolve) => resolve(this as any));
|
||||
}
|
||||
|
||||
const barrier: Partial<PromiseWithResolvers<void>> = {};
|
||||
this[barrierSym] = barrier;
|
||||
|
||||
return new CancellablePromise<TResult1 | TResult2>((resolve, reject) => {
|
||||
void promiseThen.call(this,
|
||||
(value) => {
|
||||
if (this[barrierSym] === barrier) { this[barrierSym] = null; }
|
||||
barrier.resolve?.();
|
||||
|
||||
try {
|
||||
resolve(onfulfilled!(value));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
},
|
||||
(reason?) => {
|
||||
if (this[barrierSym] === barrier) { this[barrierSym] = null; }
|
||||
barrier.resolve?.();
|
||||
|
||||
try {
|
||||
resolve(onrejected!(reason));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}, async (cause?) => {
|
||||
//cancelled = true;
|
||||
try {
|
||||
return oncancelled?.(cause);
|
||||
} finally {
|
||||
await this.cancel(cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a callback for only the rejection of the Promise.
|
||||
*
|
||||
* The optional `oncancelled` argument will be invoked when the returned promise is cancelled,
|
||||
* with the same semantics as the `oncancelled` argument of the constructor.
|
||||
* When the parent promise rejects or is cancelled, the `onrejected` callback will run,
|
||||
* _even after the returned promise has been cancelled:_
|
||||
* in that case, should it reject or throw, the reason will be wrapped
|
||||
* in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection.
|
||||
*
|
||||
* It is equivalent to
|
||||
* ```ts
|
||||
* cancellablePromise.then(undefined, onrejected, oncancelled);
|
||||
* ```
|
||||
* and the same caveats apply.
|
||||
*
|
||||
* @returns A Promise for the completion of the callback.
|
||||
* Cancellation requests on the returned promise
|
||||
* will propagate up the chain to the parent promise,
|
||||
* but not in the other direction.
|
||||
*
|
||||
* The promise returned from {@link cancel} will fulfill only after all attached handlers
|
||||
* up the entire promise chain have been run.
|
||||
*
|
||||
* If `onrejected` returns a cancellable promise,
|
||||
* cancellation requests will be diverted to it,
|
||||
* and the specified `oncancelled` callback will be discarded.
|
||||
* See {@link then} for more details.
|
||||
*/
|
||||
catch<TResult = never>(onrejected?: ((reason: any) => (PromiseLike<TResult> | TResult)) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise<T | TResult> {
|
||||
return this.then(undefined, onrejected, oncancelled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a callback that is invoked when the CancellablePromise is settled (fulfilled or rejected). The
|
||||
* resolved value cannot be accessed or modified from the callback.
|
||||
* The returned promise will settle in the same state as the original one
|
||||
* after the provided callback has completed execution,
|
||||
* unless the callback throws or returns a rejecting promise,
|
||||
* in which case the returned promise will reject as well.
|
||||
*
|
||||
* The optional `oncancelled` argument will be invoked when the returned promise is cancelled,
|
||||
* with the same semantics as the `oncancelled` argument of the constructor.
|
||||
* Once the parent promise settles, the `onfinally` callback will run,
|
||||
* _even after the returned promise has been cancelled:_
|
||||
* in that case, should it reject or throw, the reason will be wrapped
|
||||
* in a {@link CancelledRejectionError} and bubbled up as an unhandled rejection.
|
||||
*
|
||||
* This method is implemented in terms of {@link then} and the same caveats apply.
|
||||
* It is polyfilled, hence available in every OS/webview version.
|
||||
*
|
||||
* @returns A Promise for the completion of the callback.
|
||||
* Cancellation requests on the returned promise
|
||||
* will propagate up the chain to the parent promise,
|
||||
* but not in the other direction.
|
||||
*
|
||||
* The promise returned from {@link cancel} will fulfill only after all attached handlers
|
||||
* up the entire promise chain have been run.
|
||||
*
|
||||
* If `onfinally` returns a cancellable promise,
|
||||
* cancellation requests will be diverted to it,
|
||||
* and the specified `oncancelled` callback will be discarded.
|
||||
* See {@link then} for more details.
|
||||
*/
|
||||
finally(onfinally?: (() => void) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise<T> {
|
||||
if (!(this instanceof CancellablePromise)) {
|
||||
throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");
|
||||
}
|
||||
|
||||
if (!isCallable(onfinally)) {
|
||||
return this.then(onfinally, onfinally, oncancelled);
|
||||
}
|
||||
|
||||
return this.then(
|
||||
(value) => CancellablePromise.resolve(onfinally()).then(() => value),
|
||||
(reason?) => CancellablePromise.resolve(onfinally()).then(() => { throw reason; }),
|
||||
oncancelled,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* We use the `[Symbol.species]` static property, if available,
|
||||
* to disable the built-in automatic subclassing features from {@link Promise}.
|
||||
* It is critical for performance reasons that extenders do not override this.
|
||||
* Once the proposal at https://github.com/tc39/proposal-rm-builtin-subclassing
|
||||
* is either accepted or retired, this implementation will have to be revised accordingly.
|
||||
*
|
||||
* @ignore
|
||||
* @internal
|
||||
*/
|
||||
static get [species]() {
|
||||
return Promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CancellablePromise that is resolved with an array of results
|
||||
* when all of the provided Promises resolve, or rejected when any Promise is rejected.
|
||||
*
|
||||
* Every one of the provided objects that is a thenable _and_ cancellable object
|
||||
* will be cancelled when the returned promise is cancelled, with the same cause.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static all<T>(values: Iterable<T | PromiseLike<T>>): CancellablePromise<Awaited<T>[]>;
|
||||
static all<T extends readonly unknown[] | []>(values: T): CancellablePromise<{ -readonly [P in keyof T]: Awaited<T[P]>; }>;
|
||||
static all<T extends Iterable<unknown> | ArrayLike<unknown>>(values: T): CancellablePromise<unknown> {
|
||||
let collected = Array.from(values);
|
||||
return collected.length === 0
|
||||
? CancellablePromise.resolve(collected)
|
||||
: new CancellablePromise<unknown>((resolve, reject) => {
|
||||
void Promise.all(collected).then(resolve, reject);
|
||||
}, allCanceller(collected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CancellablePromise that is resolved with an array of results
|
||||
* when all of the provided Promises resolve or reject.
|
||||
*
|
||||
* Every one of the provided objects that is a thenable _and_ cancellable object
|
||||
* will be cancelled when the returned promise is cancelled, with the same cause.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static allSettled<T>(values: Iterable<T | PromiseLike<T>>): CancellablePromise<PromiseSettledResult<Awaited<T>>[]>;
|
||||
static allSettled<T extends readonly unknown[] | []>(values: T): CancellablePromise<{ -readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>; }>;
|
||||
static allSettled<T extends Iterable<unknown> | ArrayLike<unknown>>(values: T): CancellablePromise<unknown> {
|
||||
let collected = Array.from(values);
|
||||
return collected.length === 0
|
||||
? CancellablePromise.resolve(collected)
|
||||
: new CancellablePromise<unknown>((resolve, reject) => {
|
||||
void Promise.allSettled(collected).then(resolve, reject);
|
||||
}, allCanceller(collected));
|
||||
}
|
||||
|
||||
/**
|
||||
* The any function returns a promise that is fulfilled by the first given promise to be fulfilled,
|
||||
* or rejected with an AggregateError containing an array of rejection reasons
|
||||
* if all of the given promises are rejected.
|
||||
* It resolves all elements of the passed iterable to promises as it runs this algorithm.
|
||||
*
|
||||
* Every one of the provided objects that is a thenable _and_ cancellable object
|
||||
* will be cancelled when the returned promise is cancelled, with the same cause.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static any<T>(values: Iterable<T | PromiseLike<T>>): CancellablePromise<Awaited<T>>;
|
||||
static any<T extends readonly unknown[] | []>(values: T): CancellablePromise<Awaited<T[number]>>;
|
||||
static any<T extends Iterable<unknown> | ArrayLike<unknown>>(values: T): CancellablePromise<unknown> {
|
||||
let collected = Array.from(values);
|
||||
return collected.length === 0
|
||||
? CancellablePromise.resolve(collected)
|
||||
: new CancellablePromise<unknown>((resolve, reject) => {
|
||||
void Promise.any(collected).then(resolve, reject);
|
||||
}, allCanceller(collected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved or rejected.
|
||||
*
|
||||
* Every one of the provided objects that is a thenable _and_ cancellable object
|
||||
* will be cancelled when the returned promise is cancelled, with the same cause.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static race<T>(values: Iterable<T | PromiseLike<T>>): CancellablePromise<Awaited<T>>;
|
||||
static race<T extends readonly unknown[] | []>(values: T): CancellablePromise<Awaited<T[number]>>;
|
||||
static race<T extends Iterable<unknown> | ArrayLike<unknown>>(values: T): CancellablePromise<unknown> {
|
||||
let collected = Array.from(values);
|
||||
return new CancellablePromise<unknown>((resolve, reject) => {
|
||||
void Promise.race(collected).then(resolve, reject);
|
||||
}, allCanceller(collected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new cancelled CancellablePromise for the provided cause.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static cancel<T = never>(cause?: any): CancellablePromise<T> {
|
||||
const p = new CancellablePromise<T>(() => {});
|
||||
p.cancel(cause);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CancellablePromise that cancels
|
||||
* after the specified timeout, with the provided cause.
|
||||
*
|
||||
* If the {@link AbortSignal.timeout} factory method is available,
|
||||
* it is used to base the timeout on _active_ time rather than _elapsed_ time.
|
||||
* Otherwise, `timeout` falls back to {@link setTimeout}.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static timeout<T = never>(milliseconds: number, cause?: any): CancellablePromise<T> {
|
||||
const promise = new CancellablePromise<T>(() => {});
|
||||
if (AbortSignal && typeof AbortSignal === 'function' && AbortSignal.timeout && typeof AbortSignal.timeout === 'function') {
|
||||
AbortSignal.timeout(milliseconds).addEventListener('abort', () => void promise.cancel(cause));
|
||||
} else {
|
||||
setTimeout(() => void promise.cancel(cause), milliseconds);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CancellablePromise that resolves after the specified timeout.
|
||||
* The returned promise can be cancelled without consequences.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static sleep(milliseconds: number): CancellablePromise<void>;
|
||||
/**
|
||||
* Creates a new CancellablePromise that resolves after
|
||||
* the specified timeout, with the provided value.
|
||||
* The returned promise can be cancelled without consequences.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static sleep<T>(milliseconds: number, value: T): CancellablePromise<T>;
|
||||
static sleep<T = void>(milliseconds: number, value?: T): CancellablePromise<T> {
|
||||
return new CancellablePromise<T>((resolve) => {
|
||||
setTimeout(() => resolve(value!), milliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new rejected CancellablePromise for the provided reason.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static reject<T = never>(reason?: any): CancellablePromise<T> {
|
||||
return new CancellablePromise<T>((_, reject) => reject(reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new resolved CancellablePromise.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static resolve(): CancellablePromise<void>;
|
||||
/**
|
||||
* Creates a new resolved CancellablePromise for the provided value.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static resolve<T>(value: T): CancellablePromise<Awaited<T>>;
|
||||
/**
|
||||
* Creates a new resolved CancellablePromise for the provided value.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static resolve<T>(value: T | PromiseLike<T>): CancellablePromise<Awaited<T>>;
|
||||
static resolve<T = void>(value?: T | PromiseLike<T>): CancellablePromise<Awaited<T>> {
|
||||
if (value instanceof CancellablePromise) {
|
||||
// Optimise for cancellable promises.
|
||||
return value;
|
||||
}
|
||||
return new CancellablePromise<any>((resolve) => resolve(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CancellablePromise and returns it in an object, along with its resolve and reject functions
|
||||
* and a getter/setter for the cancellation callback.
|
||||
*
|
||||
* This method is polyfilled, hence available in every OS/webview version.
|
||||
*
|
||||
* @group Static Methods
|
||||
*/
|
||||
static withResolvers<T>(): CancellablePromiseWithResolvers<T> {
|
||||
let result: CancellablePromiseWithResolvers<T> = { oncancelled: null } as any;
|
||||
result.promise = new CancellablePromise<T>((resolve, reject) => {
|
||||
result.resolve = resolve;
|
||||
result.reject = reject;
|
||||
}, (cause?: any) => { result.oncancelled?.(cause); });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callback that implements the cancellation algorithm for the given cancellable promise.
|
||||
* The promise returned from the resulting function does not reject.
|
||||
*/
|
||||
function cancellerFor<T>(promise: CancellablePromiseWithResolvers<T>, state: CancellablePromiseState) {
|
||||
let cancellationPromise: void | PromiseLike<void> = undefined;
|
||||
|
||||
return (reason: CancelError): void | PromiseLike<void> => {
|
||||
if (!state.settled) {
|
||||
state.settled = true;
|
||||
state.reason = reason;
|
||||
promise.reject(reason);
|
||||
|
||||
// Attach an error handler that ignores this specific rejection reason and nothing else.
|
||||
// In theory, a sane underlying implementation at this point
|
||||
// should always reject with our cancellation reason,
|
||||
// hence the handler will never throw.
|
||||
void promiseThen.call(promise.promise, undefined, (err) => {
|
||||
if (err !== reason) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If reason is not set, the promise resolved regularly, hence we must not call oncancelled.
|
||||
// If oncancelled is unset, no need to go any further.
|
||||
if (!state.reason || !promise.oncancelled) { return; }
|
||||
|
||||
cancellationPromise = new Promise((resolve) => {
|
||||
try {
|
||||
resolve(promise.oncancelled!(state.reason!.cause));
|
||||
} catch (err) {
|
||||
Promise.reject(new CancelledRejectionError(promise.promise, err, "Unhandled exception in oncancelled callback."));
|
||||
}
|
||||
});
|
||||
cancellationPromise.then(undefined, (reason?) => {
|
||||
throw new CancelledRejectionError(promise.promise, reason, "Unhandled rejection in oncancelled callback.");
|
||||
});
|
||||
|
||||
// Unset oncancelled to prevent repeated calls.
|
||||
promise.oncancelled = null;
|
||||
|
||||
return cancellationPromise;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callback that implements the resolution algorithm for the given cancellable promise.
|
||||
*/
|
||||
function resolverFor<T>(promise: CancellablePromiseWithResolvers<T>, state: CancellablePromiseState): CancellablePromiseResolver<T> {
|
||||
return (value) => {
|
||||
if (state.resolving) { return; }
|
||||
state.resolving = true;
|
||||
|
||||
if (value === promise.promise) {
|
||||
if (state.settled) { return; }
|
||||
state.settled = true;
|
||||
promise.reject(new TypeError("A promise cannot be resolved with itself."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (value != null && (typeof value === 'object' || typeof value === 'function')) {
|
||||
let then: any;
|
||||
try {
|
||||
then = (value as any).then;
|
||||
} catch (err) {
|
||||
state.settled = true;
|
||||
promise.reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCallable(then)) {
|
||||
try {
|
||||
let cancel = (value as any).cancel;
|
||||
if (isCallable(cancel)) {
|
||||
const oncancelled = (cause?: any) => {
|
||||
Reflect.apply(cancel, value, [cause]);
|
||||
};
|
||||
if (state.reason) {
|
||||
// If already cancelled, propagate cancellation.
|
||||
// The promise returned from the canceller algorithm does not reject
|
||||
// so it can be discarded safely.
|
||||
void cancellerFor({ ...promise, oncancelled }, state)(state.reason);
|
||||
} else {
|
||||
promise.oncancelled = oncancelled;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const newState: CancellablePromiseState = {
|
||||
root: state.root,
|
||||
resolving: false,
|
||||
get settled() { return this.root.settled },
|
||||
set settled(value) { this.root.settled = value; },
|
||||
get reason() { return this.root.reason }
|
||||
};
|
||||
|
||||
const rejector = rejectorFor(promise, newState);
|
||||
try {
|
||||
Reflect.apply(then, value, [resolverFor(promise, newState), rejector]);
|
||||
} catch (err) {
|
||||
rejector(err);
|
||||
}
|
||||
return; // IMPORTANT!
|
||||
}
|
||||
}
|
||||
|
||||
if (state.settled) { return; }
|
||||
state.settled = true;
|
||||
promise.resolve(value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callback that implements the rejection algorithm for the given cancellable promise.
|
||||
*/
|
||||
function rejectorFor<T>(promise: CancellablePromiseWithResolvers<T>, state: CancellablePromiseState): CancellablePromiseRejector {
|
||||
return (reason?) => {
|
||||
if (state.resolving) { return; }
|
||||
state.resolving = true;
|
||||
|
||||
if (state.settled) {
|
||||
try {
|
||||
if (reason instanceof CancelError && state.reason instanceof CancelError && Object.is(reason.cause, state.reason.cause)) {
|
||||
// Swallow late rejections that are CancelErrors whose cancellation cause is the same as ours.
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
void Promise.reject(new CancelledRejectionError(promise.promise, reason));
|
||||
} else {
|
||||
state.settled = true;
|
||||
promise.reject(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callback that cancels all values in an iterable that look like cancellable thenables.
|
||||
*/
|
||||
function allCanceller(values: Iterable<any>): CancellablePromiseCanceller {
|
||||
return (cause?) => {
|
||||
for (const value of values) {
|
||||
try {
|
||||
if (isCallable(value.then)) {
|
||||
let cancel = value.cancel;
|
||||
if (isCallable(cancel)) {
|
||||
Reflect.apply(cancel, value, [cause]);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns its argument.
|
||||
*/
|
||||
function identity<T>(x: T): T {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws its argument.
|
||||
*/
|
||||
function thrower(reason?: any): never {
|
||||
throw reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts various strategies to convert an error to a string.
|
||||
*/
|
||||
function errorMessage(err: any): string {
|
||||
try {
|
||||
if (err instanceof Error || typeof err !== 'object' || err.toString !== Object.prototype.toString) {
|
||||
return "" + err;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
return JSON.stringify(err);
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
return Object.prototype.toString.call(err);
|
||||
} catch {}
|
||||
|
||||
return "<could not convert error to string>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current barrier promise for the given cancellable promise. If necessary, initialises the barrier.
|
||||
*/
|
||||
function currentBarrier<T>(promise: CancellablePromise<T>): Promise<void> {
|
||||
let pwr: Partial<PromiseWithResolvers<void>> = promise[barrierSym] ?? {};
|
||||
if (!('promise' in pwr)) {
|
||||
Object.assign(pwr, promiseWithResolvers<void>());
|
||||
}
|
||||
if (promise[barrierSym] == null) {
|
||||
pwr.resolve!();
|
||||
promise[barrierSym] = pwr;
|
||||
}
|
||||
return pwr.promise!;
|
||||
}
|
||||
|
||||
// Stop sneaky people from breaking the barrier mechanism.
|
||||
const promiseThen = Promise.prototype.then;
|
||||
Promise.prototype.then = function(...args) {
|
||||
if (this instanceof CancellablePromise) {
|
||||
return this.then(...args);
|
||||
} else {
|
||||
return Reflect.apply(promiseThen, this, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Polyfill Promise.withResolvers.
|
||||
let promiseWithResolvers = Promise.withResolvers;
|
||||
if (promiseWithResolvers && typeof promiseWithResolvers === 'function') {
|
||||
promiseWithResolvers = promiseWithResolvers.bind(Promise);
|
||||
} else {
|
||||
promiseWithResolvers = function <T>(): PromiseWithResolvers<T> {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void;
|
||||
let reject!: (reason?: any) => void;
|
||||
const promise = new Promise<T>((res, rej) => { resolve = res; reject = rej; });
|
||||
return { promise, resolve, reject };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
import {newRuntimeCallerWithID, objectNames} from "./runtime";
|
||||
|
||||
const call = newRuntimeCallerWithID(objectNames.Clipboard, '');
|
||||
const ClipboardSetText = 0;
|
||||
const ClipboardText = 1;
|
||||
|
||||
/**
|
||||
* Sets the text to the Clipboard.
|
||||
*
|
||||
* @param {string} text - The text to be set to the Clipboard.
|
||||
* @return {Promise} - A Promise that resolves when the operation is successful.
|
||||
*/
|
||||
export function SetText(text) {
|
||||
return call(ClipboardSetText, {text});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Clipboard text
|
||||
* @returns {Promise<string>} A promise that resolves with the text from the Clipboard.
|
||||
*/
|
||||
export function Text() {
|
||||
return call(ClipboardText);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import {newRuntimeCaller, objectNames} from "./runtime.js";
|
||||
|
||||
const call = newRuntimeCaller(objectNames.Clipboard);
|
||||
|
||||
const ClipboardSetText = 0;
|
||||
const ClipboardText = 1;
|
||||
|
||||
/**
|
||||
* Sets the text to the Clipboard.
|
||||
*
|
||||
* @param text - The text to be set to the Clipboard.
|
||||
* @return A Promise that resolves when the operation is successful.
|
||||
*/
|
||||
export function SetText(text: string): Promise<void> {
|
||||
return call(ClipboardSetText, {text});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Clipboard text
|
||||
*
|
||||
* @returns A promise that resolves with the text from the Clipboard.
|
||||
*/
|
||||
export function Text(): Promise<string> {
|
||||
return call(ClipboardText);
|
||||
}
|
||||
|
|
@ -8,30 +8,38 @@ The electron alternative for Go
|
|||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
import {newRuntimeCallerWithID, objectNames} from "./runtime";
|
||||
import {IsDebug} from "./system";
|
||||
import { newRuntimeCaller, objectNames } from "./runtime.js";
|
||||
import { IsDebug } from "./system.js";
|
||||
|
||||
// setup
|
||||
window.addEventListener('contextmenu', contextMenuHandler);
|
||||
|
||||
const call = newRuntimeCallerWithID(objectNames.ContextMenu, '');
|
||||
const call = newRuntimeCaller(objectNames.ContextMenu);
|
||||
|
||||
const ContextMenuOpen = 0;
|
||||
|
||||
function openContextMenu(id, x, y, data) {
|
||||
function openContextMenu(id: string, x: number, y: number, data: any): void {
|
||||
void call(ContextMenuOpen, {id, x, y, data});
|
||||
}
|
||||
|
||||
function contextMenuHandler(event) {
|
||||
function contextMenuHandler(event: MouseEvent) {
|
||||
let target: HTMLElement;
|
||||
|
||||
if (event.target instanceof HTMLElement) {
|
||||
target = event.target;
|
||||
} else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) {
|
||||
target = event.target.parentElement ?? document.body;
|
||||
} else {
|
||||
target = document.body;
|
||||
}
|
||||
|
||||
// Check for custom context menu
|
||||
let element = event.target;
|
||||
let customContextMenu = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu");
|
||||
customContextMenu = customContextMenu ? customContextMenu.trim() : "";
|
||||
let customContextMenu = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu").trim();
|
||||
|
||||
if (customContextMenu) {
|
||||
event.preventDefault();
|
||||
let customContextMenuData = window.getComputedStyle(element).getPropertyValue("--custom-contextmenu-data");
|
||||
openContextMenu(customContextMenu, event.clientX, event.clientY, customContextMenuData);
|
||||
let data = window.getComputedStyle(target).getPropertyValue("--custom-contextmenu-data");
|
||||
openContextMenu(customContextMenu, event.clientX, event.clientY, data);
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -46,47 +54,56 @@ function contextMenuHandler(event) {
|
|||
|
||||
This rule is inherited like normal CSS rules, so nesting works as expected
|
||||
*/
|
||||
function processDefaultContextMenu(event) {
|
||||
|
||||
function processDefaultContextMenu(event: MouseEvent) {
|
||||
// Debug builds always show the menu
|
||||
if (IsDebug()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let target: HTMLElement;
|
||||
|
||||
if (event.target instanceof HTMLElement) {
|
||||
target = event.target;
|
||||
} else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) {
|
||||
target = event.target.parentElement ?? document.body;
|
||||
} else {
|
||||
target = document.body;
|
||||
}
|
||||
|
||||
// Process default context menu
|
||||
const element = event.target;
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const defaultContextMenuAction = computedStyle.getPropertyValue("--default-contextmenu").trim();
|
||||
switch (defaultContextMenuAction) {
|
||||
switch (window.getComputedStyle(target).getPropertyValue("--default-contextmenu").trim()) {
|
||||
case "show":
|
||||
return;
|
||||
|
||||
case "hide":
|
||||
event.preventDefault();
|
||||
return;
|
||||
|
||||
default:
|
||||
// Check if contentEditable is true
|
||||
if (element.isContentEditable) {
|
||||
if (target.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if text has been selected
|
||||
const selection = window.getSelection();
|
||||
const hasSelection = (selection.toString().length > 0)
|
||||
const hasSelection = selection && selection.toString().length > 0;
|
||||
if (hasSelection) {
|
||||
for (let i = 0; i < selection.rangeCount; i++) {
|
||||
const range = selection.getRangeAt(i);
|
||||
const rects = range.getClientRects();
|
||||
for (let j = 0; j < rects.length; j++) {
|
||||
const rect = rects[j];
|
||||
if (document.elementFromPoint(rect.left, rect.top) === element) {
|
||||
if (document.elementFromPoint(rect.left, rect.top) === target) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if tagname is input or textarea
|
||||
if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
|
||||
if (hasSelection || (!element.readOnly && !element.disabled)) {
|
||||
|
||||
// Check if tag is input or textarea.
|
||||
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
||||
if (hasSelection || (!target.readOnly && !target.disabled)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,37 +8,27 @@ The electron alternative for Go
|
|||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
/**
|
||||
* Any is a dummy creation function for simple or unknown types.
|
||||
* @template T
|
||||
* @param {any} source
|
||||
* @returns {T}
|
||||
*/
|
||||
export function Any(source) {
|
||||
return /** @type {T} */(source);
|
||||
export function Any<T>(source: any): T {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* ByteSlice is a creation function that replaces
|
||||
* null strings with empty strings.
|
||||
* @param {any} source
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ByteSlice(source) {
|
||||
return /** @type {any} */((source == null) ? "" : source);
|
||||
export function ByteSlice(source: any): string {
|
||||
return ((source == null) ? "" : source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Array takes a creation function for an arbitrary type
|
||||
* and returns an in-place creation function for an array
|
||||
* whose elements are of that type.
|
||||
* @template T
|
||||
* @param {(source: any) => T} element
|
||||
* @returns {(source: any) => T[]}
|
||||
*/
|
||||
export function Array(element) {
|
||||
export function Array<T>(element: (source: any) => T): (source: any) => T[] {
|
||||
if (element === Any) {
|
||||
return (source) => (source === null ? [] : source);
|
||||
}
|
||||
|
|
@ -58,12 +48,8 @@ export function Array(element) {
|
|||
* Map takes creation functions for two arbitrary types
|
||||
* and returns an in-place creation function for an object
|
||||
* whose keys and values are of those types.
|
||||
* @template K, V
|
||||
* @param {(source: any) => K} key
|
||||
* @param {(source: any) => V} value
|
||||
* @returns {(source: any) => { [_: K]: V }}
|
||||
*/
|
||||
export function Map(key, value) {
|
||||
export function Map<V>(key: (source: any) => string, value: (source: any) => V): (source: any) => Record<string, V> {
|
||||
if (value === Any) {
|
||||
return (source) => (source === null ? {} : source);
|
||||
}
|
||||
|
|
@ -82,11 +68,8 @@ export function Map(key, value) {
|
|||
/**
|
||||
* Nullable takes a creation function for an arbitrary type
|
||||
* and returns a creation function for a nullable value of that type.
|
||||
* @template T
|
||||
* @param {(source: any) => T} element
|
||||
* @returns {(source: any) => (T | null)}
|
||||
*/
|
||||
export function Nullable(element) {
|
||||
export function Nullable<T>(element: (source: any) => T): (source: any) => (T | null) {
|
||||
if (element === Any) {
|
||||
return Any;
|
||||
}
|
||||
|
|
@ -97,12 +80,11 @@ export function Nullable(element) {
|
|||
/**
|
||||
* Struct takes an object mapping field names to creation functions
|
||||
* and returns an in-place creation function for a struct.
|
||||
* @template {{ [_: string]: ((source: any) => any) }} T
|
||||
* @template {{ [Key in keyof T]?: ReturnType<T[Key]> }} U
|
||||
* @param {T} createField
|
||||
* @returns {(source: any) => U}
|
||||
*/
|
||||
export function Struct(createField) {
|
||||
export function Struct<
|
||||
T extends { [_: string]: ((source: any) => any) },
|
||||
U extends { [Key in keyof T]?: ReturnType<T[Key]> }
|
||||
>(createField: T): (source: any) => U {
|
||||
let allAny = true;
|
||||
for (const name in createField) {
|
||||
if (createField[name] !== Any) {
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
/**
|
||||
* @typedef {Object} OpenFileDialogOptions
|
||||
* @property {boolean} [CanChooseDirectories] - Indicates if directories can be chosen.
|
||||
* @property {boolean} [CanChooseFiles] - Indicates if files can be chosen.
|
||||
* @property {boolean} [CanCreateDirectories] - Indicates if directories can be created.
|
||||
* @property {boolean} [ShowHiddenFiles] - Indicates if hidden files should be shown.
|
||||
* @property {boolean} [ResolvesAliases] - Indicates if aliases should be resolved.
|
||||
* @property {boolean} [AllowsMultipleSelection] - Indicates if multiple selection is allowed.
|
||||
* @property {boolean} [HideExtension] - Indicates if the extension should be hidden.
|
||||
* @property {boolean} [CanSelectHiddenExtension] - Indicates if hidden extensions can be selected.
|
||||
* @property {boolean} [TreatsFilePackagesAsDirectories] - Indicates if file packages should be treated as directories.
|
||||
* @property {boolean} [AllowsOtherFiletypes] - Indicates if other file types are allowed.
|
||||
* @property {FileFilter[]} [Filters] - Array of file filters.
|
||||
* @property {string} [Title] - Title of the dialog.
|
||||
* @property {string} [Message] - Message to show in the dialog.
|
||||
* @property {string} [ButtonText] - Text to display on the button.
|
||||
* @property {string} [Directory] - Directory to open in the dialog.
|
||||
* @property {boolean} [Detached] - Indicates if the dialog should appear detached from the main window.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Object} SaveFileDialogOptions
|
||||
* @property {string} [Filename] - Default filename to use in the dialog.
|
||||
* @property {boolean} [CanChooseDirectories] - Indicates if directories can be chosen.
|
||||
* @property {boolean} [CanChooseFiles] - Indicates if files can be chosen.
|
||||
* @property {boolean} [CanCreateDirectories] - Indicates if directories can be created.
|
||||
* @property {boolean} [ShowHiddenFiles] - Indicates if hidden files should be shown.
|
||||
* @property {boolean} [ResolvesAliases] - Indicates if aliases should be resolved.
|
||||
* @property {boolean} [AllowsMultipleSelection] - Indicates if multiple selection is allowed.
|
||||
* @property {boolean} [HideExtension] - Indicates if the extension should be hidden.
|
||||
* @property {boolean} [CanSelectHiddenExtension] - Indicates if hidden extensions can be selected.
|
||||
* @property {boolean} [TreatsFilePackagesAsDirectories] - Indicates if file packages should be treated as directories.
|
||||
* @property {boolean} [AllowsOtherFiletypes] - Indicates if other file types are allowed.
|
||||
* @property {FileFilter[]} [Filters] - Array of file filters.
|
||||
* @property {string} [Title] - Title of the dialog.
|
||||
* @property {string} [Message] - Message to show in the dialog.
|
||||
* @property {string} [ButtonText] - Text to display on the button.
|
||||
* @property {string} [Directory] - Directory to open in the dialog.
|
||||
* @property {boolean} [Detached] - Indicates if the dialog should appear detached from the main window.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageDialogOptions
|
||||
* @property {string} [Title] - The title of the dialog window.
|
||||
* @property {string} [Message] - The main message to show in the dialog.
|
||||
* @property {Button[]} [Buttons] - Array of button options to show in the dialog.
|
||||
* @property {boolean} [Detached] - True if the dialog should appear detached from the main window (if applicable).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Button
|
||||
* @property {string} [Label] - Text that appears within the button.
|
||||
* @property {boolean} [IsCancel] - True if the button should cancel an operation when clicked.
|
||||
* @property {boolean} [IsDefault] - True if the button should be the default action when the user presses enter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileFilter
|
||||
* @property {string} [DisplayName] - Display name for the filter, it could be "Text Files", "Images" etc.
|
||||
* @property {string} [Pattern] - Pattern to match for the filter, e.g. "*.txt;*.md" for text markdown files.
|
||||
*/
|
||||
|
||||
// setup
|
||||
window._wails = window._wails || {};
|
||||
window._wails.dialogErrorCallback = dialogErrorCallback;
|
||||
window._wails.dialogResultCallback = dialogResultCallback;
|
||||
|
||||
import {newRuntimeCallerWithID, objectNames} from "./runtime";
|
||||
|
||||
import { nanoid } from './nanoid.js';
|
||||
|
||||
// Define constants from the `methods` object in Title Case
|
||||
const DialogInfo = 0;
|
||||
const DialogWarning = 1;
|
||||
const DialogError = 2;
|
||||
const DialogQuestion = 3;
|
||||
const DialogOpenFile = 4;
|
||||
const DialogSaveFile = 5;
|
||||
|
||||
const call = newRuntimeCallerWithID(objectNames.Dialog, '');
|
||||
const dialogResponses = new Map();
|
||||
|
||||
/**
|
||||
* Generates a unique id that is not present in dialogResponses.
|
||||
* @returns {string} unique id
|
||||
*/
|
||||
function generateID() {
|
||||
let result;
|
||||
do {
|
||||
result = nanoid();
|
||||
} while (dialogResponses.has(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog of specified type with the given options.
|
||||
* @param {number} type - type of dialog
|
||||
* @param {MessageDialogOptions|OpenFileDialogOptions|SaveFileDialogOptions} options - options for the dialog
|
||||
* @returns {Promise} promise that resolves with result of dialog
|
||||
*/
|
||||
function dialog(type, options = {}) {
|
||||
const id = generateID();
|
||||
options["dialog-id"] = id;
|
||||
return new Promise((resolve, reject) => {
|
||||
dialogResponses.set(id, {resolve, reject});
|
||||
call(type, options).catch((error) => {
|
||||
reject(error);
|
||||
dialogResponses.delete(id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the callback from a dialog.
|
||||
*
|
||||
* @param {string} id - The ID of the dialog response.
|
||||
* @param {string} data - The data received from the dialog.
|
||||
* @param {boolean} isJSON - Flag indicating whether the data is in JSON format.
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
function dialogResultCallback(id, data, isJSON) {
|
||||
let p = dialogResponses.get(id);
|
||||
if (p) {
|
||||
dialogResponses.delete(id);
|
||||
if (isJSON) {
|
||||
p.resolve(JSON.parse(data));
|
||||
} else {
|
||||
p.resolve(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for handling errors in dialog.
|
||||
*
|
||||
* @param {string} id - The id of the dialog response.
|
||||
* @param {string} message - The error message.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function dialogErrorCallback(id, message) {
|
||||
let p = dialogResponses.get(id);
|
||||
if (p) {
|
||||
dialogResponses.delete(id);
|
||||
p.reject(new Error(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Replace `methods` with constants in Title Case
|
||||
|
||||
/**
|
||||
* @param {MessageDialogOptions} options - Dialog options
|
||||
* @returns {Promise<string>} - The label of the button pressed
|
||||
*/
|
||||
export const Info = (options) => dialog(DialogInfo, options);
|
||||
|
||||
/**
|
||||
* @param {MessageDialogOptions} options - Dialog options
|
||||
* @returns {Promise<string>} - The label of the button pressed
|
||||
*/
|
||||
export const Warning = (options) => dialog(DialogWarning, options);
|
||||
|
||||
/**
|
||||
* @param {MessageDialogOptions} options - Dialog options
|
||||
* @returns {Promise<string>} - The label of the button pressed
|
||||
*/
|
||||
export const Error = (options) => dialog(DialogError, options);
|
||||
|
||||
/**
|
||||
* @param {MessageDialogOptions} options - Dialog options
|
||||
* @returns {Promise<string>} - The label of the button pressed
|
||||
*/
|
||||
export const Question = (options) => dialog(DialogQuestion, options);
|
||||
|
||||
/**
|
||||
* @param {OpenFileDialogOptions} options - Dialog options
|
||||
* @returns {Promise<string[]|string>} Returns selected file or list of files. Returns blank string if no file is selected.
|
||||
*/
|
||||
export const OpenFile = (options) => dialog(DialogOpenFile, options);
|
||||
|
||||
/**
|
||||
* @param {SaveFileDialogOptions} options - Dialog options
|
||||
* @returns {Promise<string>} Returns the selected file. Returns blank string if no file is selected.
|
||||
*/
|
||||
export const SaveFile = (options) => dialog(DialogSaveFile, options);
|
||||
255
v3/internal/runtime/desktop/@wailsio/runtime/src/dialogs.ts
Normal file
255
v3/internal/runtime/desktop/@wailsio/runtime/src/dialogs.ts
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import {newRuntimeCaller, objectNames} from "./runtime.js";
|
||||
import { nanoid } from './nanoid.js';
|
||||
|
||||
// setup
|
||||
window._wails = window._wails || {};
|
||||
window._wails.dialogErrorCallback = dialogErrorCallback;
|
||||
window._wails.dialogResultCallback = dialogResultCallback;
|
||||
|
||||
type PromiseResolvers = Omit<PromiseWithResolvers<any>, "promise">;
|
||||
|
||||
const call = newRuntimeCaller(objectNames.Dialog);
|
||||
const dialogResponses = new Map<string, PromiseResolvers>();
|
||||
|
||||
// Define constants from the `methods` object in Title Case
|
||||
const DialogInfo = 0;
|
||||
const DialogWarning = 1;
|
||||
const DialogError = 2;
|
||||
const DialogQuestion = 3;
|
||||
const DialogOpenFile = 4;
|
||||
const DialogSaveFile = 5;
|
||||
|
||||
export interface OpenFileDialogOptions {
|
||||
/** Indicates if directories can be chosen. */
|
||||
CanChooseDirectories?: boolean;
|
||||
/** Indicates if files can be chosen. */
|
||||
CanChooseFiles?: boolean;
|
||||
/** Indicates if directories can be created. */
|
||||
CanCreateDirectories?: boolean;
|
||||
/** Indicates if hidden files should be shown. */
|
||||
ShowHiddenFiles?: boolean;
|
||||
/** Indicates if aliases should be resolved. */
|
||||
ResolvesAliases?: boolean;
|
||||
/** Indicates if multiple selection is allowed. */
|
||||
AllowsMultipleSelection?: boolean;
|
||||
/** Indicates if the extension should be hidden. */
|
||||
HideExtension?: boolean;
|
||||
/** Indicates if hidden extensions can be selected. */
|
||||
CanSelectHiddenExtension?: boolean;
|
||||
/** Indicates if file packages should be treated as directories. */
|
||||
TreatsFilePackagesAsDirectories?: boolean;
|
||||
/** Indicates if other file types are allowed. */
|
||||
AllowsOtherFiletypes?: boolean;
|
||||
/** Array of file filters. */
|
||||
Filters?: FileFilter[];
|
||||
/** Title of the dialog. */
|
||||
Title?: string;
|
||||
/** Message to show in the dialog. */
|
||||
Message?: string;
|
||||
/** Text to display on the button. */
|
||||
ButtonText?: string;
|
||||
/** Directory to open in the dialog. */
|
||||
Directory?: string;
|
||||
/** Indicates if the dialog should appear detached from the main window. */
|
||||
Detached?: boolean;
|
||||
}
|
||||
|
||||
export interface SaveFileDialogOptions {
|
||||
/** Default filename to use in the dialog. */
|
||||
Filename?: string;
|
||||
/** Indicates if directories can be chosen. */
|
||||
CanChooseDirectories?: boolean;
|
||||
/** Indicates if files can be chosen. */
|
||||
CanChooseFiles?: boolean;
|
||||
/** Indicates if directories can be created. */
|
||||
CanCreateDirectories?: boolean;
|
||||
/** Indicates if hidden files should be shown. */
|
||||
ShowHiddenFiles?: boolean;
|
||||
/** Indicates if aliases should be resolved. */
|
||||
ResolvesAliases?: boolean;
|
||||
/** Indicates if the extension should be hidden. */
|
||||
HideExtension?: boolean;
|
||||
/** Indicates if hidden extensions can be selected. */
|
||||
CanSelectHiddenExtension?: boolean;
|
||||
/** Indicates if file packages should be treated as directories. */
|
||||
TreatsFilePackagesAsDirectories?: boolean;
|
||||
/** Indicates if other file types are allowed. */
|
||||
AllowsOtherFiletypes?: boolean;
|
||||
/** Array of file filters. */
|
||||
Filters?: FileFilter[];
|
||||
/** Title of the dialog. */
|
||||
Title?: string;
|
||||
/** Message to show in the dialog. */
|
||||
Message?: string;
|
||||
/** Text to display on the button. */
|
||||
ButtonText?: string;
|
||||
/** Directory to open in the dialog. */
|
||||
Directory?: string;
|
||||
/** Indicates if the dialog should appear detached from the main window. */
|
||||
Detached?: boolean;
|
||||
}
|
||||
|
||||
export interface MessageDialogOptions {
|
||||
/** The title of the dialog window. */
|
||||
Title?: string;
|
||||
/** The main message to show in the dialog. */
|
||||
Message?: string;
|
||||
/** Array of button options to show in the dialog. */
|
||||
Buttons?: Button[];
|
||||
/** True if the dialog should appear detached from the main window (if applicable). */
|
||||
Detached?: boolean;
|
||||
}
|
||||
|
||||
export interface Button {
|
||||
/** Text that appears within the button. */
|
||||
Label?: string;
|
||||
/** True if the button should cancel an operation when clicked. */
|
||||
IsCancel?: boolean;
|
||||
/** True if the button should be the default action when the user presses enter. */
|
||||
IsDefault?: boolean;
|
||||
}
|
||||
|
||||
export interface FileFilter {
|
||||
/** Display name for the filter, it could be "Text Files", "Images" etc. */
|
||||
DisplayName?: string;
|
||||
/** Pattern to match for the filter, e.g. "*.txt;*.md" for text markdown files. */
|
||||
Pattern?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the result of a dialog request.
|
||||
*
|
||||
* @param id - The id of the request to handle the result for.
|
||||
* @param data - The result data of the request.
|
||||
* @param isJSON - Indicates whether the data is JSON or not.
|
||||
*/
|
||||
function dialogResultCallback(id: string, data: string, isJSON: boolean): void {
|
||||
let resolvers = getAndDeleteResponse(id);
|
||||
if (!resolvers) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isJSON) {
|
||||
try {
|
||||
resolvers.resolve(JSON.parse(data));
|
||||
} catch (err: any) {
|
||||
resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err }));
|
||||
}
|
||||
} else {
|
||||
resolvers.resolve(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the error from a dialog request.
|
||||
*
|
||||
* @param id - The id of the promise handler.
|
||||
* @param message - An error message.
|
||||
*/
|
||||
function dialogErrorCallback(id: string, message: string): void {
|
||||
getAndDeleteResponse(id)?.reject(new window.Error(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves and removes the response associated with the given ID from the dialogResponses map.
|
||||
*
|
||||
* @param id - The ID of the response to be retrieved and removed.
|
||||
* @returns The response object associated with the given ID, if any.
|
||||
*/
|
||||
function getAndDeleteResponse(id: string): PromiseResolvers | undefined {
|
||||
const response = dialogResponses.get(id);
|
||||
dialogResponses.delete(id);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique ID using the nanoid library.
|
||||
*
|
||||
* @returns A unique ID that does not exist in the dialogResponses set.
|
||||
*/
|
||||
function generateID(): string {
|
||||
let result;
|
||||
do {
|
||||
result = nanoid();
|
||||
} while (dialogResponses.has(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents a dialog of specified type with the given options.
|
||||
*
|
||||
* @param type - Dialog type.
|
||||
* @param options - Options for the dialog.
|
||||
* @returns A promise that resolves with result of dialog.
|
||||
*/
|
||||
function dialog(type: number, options: MessageDialogOptions | OpenFileDialogOptions | SaveFileDialogOptions = {}): Promise<any> {
|
||||
const id = generateID();
|
||||
return new Promise((resolve, reject) => {
|
||||
dialogResponses.set(id, { resolve, reject });
|
||||
call(type, Object.assign({ "dialog-id": id }, options)).catch((err: any) => {
|
||||
dialogResponses.delete(id);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents an info dialog.
|
||||
*
|
||||
* @param options - Dialog options
|
||||
* @returns A promise that resolves with the label of the chosen button.
|
||||
*/
|
||||
export function Info(options: MessageDialogOptions): Promise<string> { return dialog(DialogInfo, options); }
|
||||
|
||||
/**
|
||||
* Presents a warning dialog.
|
||||
*
|
||||
* @param options - Dialog options.
|
||||
* @returns A promise that resolves with the label of the chosen button.
|
||||
*/
|
||||
export function Warning(options: MessageDialogOptions): Promise<string> { return dialog(DialogWarning, options); }
|
||||
|
||||
/**
|
||||
* Presents an error dialog.
|
||||
*
|
||||
* @param options - Dialog options.
|
||||
* @returns A promise that resolves with the label of the chosen button.
|
||||
*/
|
||||
export function Error(options: MessageDialogOptions): Promise<string> { return dialog(DialogError, options); }
|
||||
|
||||
/**
|
||||
* Presents a question dialog.
|
||||
*
|
||||
* @param options - Dialog options.
|
||||
* @returns A promise that resolves with the label of the chosen button.
|
||||
*/
|
||||
export function Question(options: MessageDialogOptions): Promise<string> { return dialog(DialogQuestion, options); }
|
||||
|
||||
/**
|
||||
* Presents a file selection dialog to pick one or more files to open.
|
||||
*
|
||||
* @param options - Dialog options.
|
||||
* @returns Selected file or list of files, or a blank string/empty list if no file has been selected.
|
||||
*/
|
||||
export function OpenFile(options: OpenFileDialogOptions & { AllowsMultipleSelection: true }): Promise<string[]>;
|
||||
export function OpenFile(options: OpenFileDialogOptions & { AllowsMultipleSelection?: false | undefined }): Promise<string>;
|
||||
export function OpenFile(options: OpenFileDialogOptions): Promise<string | string[]>;
|
||||
export function OpenFile(options: OpenFileDialogOptions): Promise<string | string[]> { return dialog(DialogOpenFile, options) ?? []; }
|
||||
|
||||
/**
|
||||
* Presents a file selection dialog to pick a file to save.
|
||||
*
|
||||
* @param options - Dialog options.
|
||||
* @returns Selected file, or a blank string if no file has been selected.
|
||||
*/
|
||||
export function SaveFile(options: SaveFileDialogOptions): Promise<string> { return dialog(DialogSaveFile, options); }
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
/* jshint esversion: 9 */
|
||||
import {invoke, IsWindows} from "./system";
|
||||
import {GetFlag} from "./flags";
|
||||
|
||||
// Setup
|
||||
let shouldDrag = false;
|
||||
let resizable = false;
|
||||
let resizeEdge = null;
|
||||
let defaultCursor = "auto";
|
||||
|
||||
window._wails = window._wails || {};
|
||||
|
||||
window._wails.setResizable = function(value) {
|
||||
resizable = value;
|
||||
};
|
||||
|
||||
window._wails.endDrag = function() {
|
||||
document.body.style.cursor = 'default';
|
||||
shouldDrag = false;
|
||||
};
|
||||
|
||||
window.addEventListener('mousedown', onMouseDown);
|
||||
window.addEventListener('mousemove', onMouseMove);
|
||||
window.addEventListener('mouseup', onMouseUp);
|
||||
|
||||
|
||||
function dragTest(e) {
|
||||
let val = window.getComputedStyle(e.target).getPropertyValue("--wails-draggable");
|
||||
let mousePressed = e.buttons !== undefined ? e.buttons : e.which;
|
||||
if (!val || val === "" || val.trim() !== "drag" || mousePressed === 0) {
|
||||
return false;
|
||||
}
|
||||
return e.detail === 1;
|
||||
}
|
||||
|
||||
function onMouseDown(e) {
|
||||
|
||||
// Check for resizing
|
||||
if (resizeEdge) {
|
||||
invoke("wails:resize:" + resizeEdge);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dragTest(e)) {
|
||||
// This checks for clicks on the scroll bar
|
||||
if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) {
|
||||
return;
|
||||
}
|
||||
shouldDrag = true;
|
||||
} else {
|
||||
shouldDrag = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
shouldDrag = false;
|
||||
}
|
||||
|
||||
function setResize(cursor) {
|
||||
document.documentElement.style.cursor = cursor || defaultCursor;
|
||||
resizeEdge = cursor;
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
if (shouldDrag) {
|
||||
shouldDrag = false;
|
||||
let mousePressed = e.buttons !== undefined ? e.buttons : e.which;
|
||||
if (mousePressed > 0) {
|
||||
invoke("wails:drag");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!resizable || !IsWindows()) {
|
||||
return;
|
||||
}
|
||||
if (defaultCursor == null) {
|
||||
defaultCursor = document.documentElement.style.cursor;
|
||||
}
|
||||
let resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5;
|
||||
let resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5;
|
||||
|
||||
// Extra pixels for the corner areas
|
||||
let cornerExtra = GetFlag("resizeCornerExtra") || 10;
|
||||
|
||||
let rightBorder = window.outerWidth - e.clientX < resizeHandleWidth;
|
||||
let leftBorder = e.clientX < resizeHandleWidth;
|
||||
let topBorder = e.clientY < resizeHandleHeight;
|
||||
let bottomBorder = window.outerHeight - e.clientY < resizeHandleHeight;
|
||||
|
||||
// Adjust for corners
|
||||
let rightCorner = window.outerWidth - e.clientX < (resizeHandleWidth + cornerExtra);
|
||||
let leftCorner = e.clientX < (resizeHandleWidth + cornerExtra);
|
||||
let topCorner = e.clientY < (resizeHandleHeight + cornerExtra);
|
||||
let bottomCorner = window.outerHeight - e.clientY < (resizeHandleHeight + cornerExtra);
|
||||
|
||||
// If we aren't on an edge, but were, reset the cursor to default
|
||||
if (!leftBorder && !rightBorder && !topBorder && !bottomBorder && resizeEdge !== undefined) {
|
||||
setResize();
|
||||
}
|
||||
// Adjusted for corner areas
|
||||
else if (rightCorner && bottomCorner) setResize("se-resize");
|
||||
else if (leftCorner && bottomCorner) setResize("sw-resize");
|
||||
else if (leftCorner && topCorner) setResize("nw-resize");
|
||||
else if (topCorner && rightCorner) setResize("ne-resize");
|
||||
else if (leftBorder) setResize("w-resize");
|
||||
else if (topBorder) setResize("n-resize");
|
||||
else if (bottomBorder) setResize("s-resize");
|
||||
else if (rightBorder) setResize("e-resize");
|
||||
}
|
||||
209
v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts
Normal file
209
v3/internal/runtime/desktop/@wailsio/runtime/src/drag.ts
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import { invoke, IsWindows } from "./system.js";
|
||||
import { GetFlag } from "./flags.js";
|
||||
import { canTrackButtons } from "./utils.js";
|
||||
|
||||
// Setup
|
||||
let canDrag = false;
|
||||
let dragging = false;
|
||||
|
||||
let resizable = false;
|
||||
let canResize = false;
|
||||
let resizing = false;
|
||||
let resizeEdge: string = "";
|
||||
let defaultCursor = "auto";
|
||||
|
||||
let buttons = 0;
|
||||
const buttonsTracked = canTrackButtons();
|
||||
|
||||
window._wails = window._wails || {};
|
||||
window._wails.setResizable = (value: boolean): void => {
|
||||
resizable = value;
|
||||
if (!resizable) {
|
||||
// Stop resizing if in progress.
|
||||
canResize = resizing = false;
|
||||
setResize();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('mousedown', onMouseDown, { capture: true });
|
||||
window.addEventListener('mousemove', onMouseMove, { capture: true });
|
||||
window.addEventListener('mouseup', onMouseUp, { capture: true });
|
||||
for (const ev of ['click', 'contextmenu', 'dblclick', 'pointerdown', 'pointerup']) {
|
||||
window.addEventListener(ev, suppressEvent, { capture: true });
|
||||
}
|
||||
|
||||
function suppressEvent(event: Event) {
|
||||
if (dragging || resizing) {
|
||||
// Suppress all button events during dragging & resizing.
|
||||
event.stopImmediatePropagation();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseDown(event: MouseEvent): void {
|
||||
buttons = buttonsTracked ? event.buttons : (buttons | (1 << event.button));
|
||||
|
||||
if (dragging || resizing) {
|
||||
// After dragging or resizing has started, only lifting the primary button can stop it.
|
||||
// Do not let any other events through.
|
||||
suppressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((canDrag || canResize) && (buttons & 1) && event.button !== 0) {
|
||||
// We were ready before, the primary is pressed and was not released:
|
||||
// still ready, but let events bubble through the window.
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset readiness state.
|
||||
canDrag = false;
|
||||
canResize = false;
|
||||
|
||||
// Check for resizing readiness.
|
||||
if (resizeEdge) {
|
||||
if (event.button === 0 && event.detail === 1) {
|
||||
// Ready to resize if the primary button was pressed for the first time.
|
||||
canResize = true;
|
||||
invoke("wails:resize:" + resizeEdge);
|
||||
}
|
||||
|
||||
// Do not start drag operations within resize edges.
|
||||
return;
|
||||
}
|
||||
|
||||
let target: HTMLElement;
|
||||
|
||||
if (event.target instanceof HTMLElement) {
|
||||
target = event.target;
|
||||
} else if (!(event.target instanceof HTMLElement) && event.target instanceof Node) {
|
||||
target = event.target.parentElement ?? document.body;
|
||||
} else {
|
||||
target = document.body;
|
||||
}
|
||||
|
||||
const style = window.getComputedStyle(target);
|
||||
const setting = style.getPropertyValue("--wails-draggable").trim();
|
||||
if (setting === "drag" && event.button === 0 && event.detail === 1) {
|
||||
// Ready to drag if the primary button was pressed for the first time on a draggable element.
|
||||
// Ignore clicks on the scrollbar.
|
||||
if (
|
||||
event.offsetX - parseFloat(style.paddingLeft) < target.clientWidth
|
||||
&& event.offsetY - parseFloat(style.paddingTop) < target.clientHeight
|
||||
) {
|
||||
canDrag = true;
|
||||
invoke("wails:drag");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseUp(event: MouseEvent) {
|
||||
buttons = buttonsTracked ? event.buttons : (buttons & ~(1 << event.button));
|
||||
|
||||
if (event.button === 0) {
|
||||
if (resizing) {
|
||||
// Let mouseup event bubble when a drag ends, but not when a resize ends.
|
||||
suppressEvent(event);
|
||||
}
|
||||
|
||||
// Stop dragging and resizing when the primary button is lifted.
|
||||
canDrag = false;
|
||||
dragging = false;
|
||||
canResize = false;
|
||||
resizing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// After dragging or resizing has started, only lifting the primary button can stop it.
|
||||
suppressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const cursorForEdge = Object.freeze({
|
||||
"se-resize": "nwse-resize",
|
||||
"sw-resize": "nesw-resize",
|
||||
"nw-resize": "nwse-resize",
|
||||
"ne-resize": "nesw-resize",
|
||||
"w-resize": "ew-resize",
|
||||
"n-resize": "ns-resize",
|
||||
"s-resize": "ns-resize",
|
||||
"e-resize": "ew-resize",
|
||||
})
|
||||
|
||||
function setResize(edge?: keyof typeof cursorForEdge): void {
|
||||
if (edge) {
|
||||
if (!resizeEdge) { defaultCursor = document.body.style.cursor; }
|
||||
document.body.style.cursor = cursorForEdge[edge];
|
||||
} else if (!edge && resizeEdge) {
|
||||
document.body.style.cursor = defaultCursor;
|
||||
}
|
||||
|
||||
resizeEdge = edge || "";
|
||||
}
|
||||
|
||||
function onMouseMove(event: MouseEvent): void {
|
||||
if (canResize && resizeEdge) {
|
||||
// Start resizing.
|
||||
resizing = true;
|
||||
} else if (canDrag) {
|
||||
// Start dragging.
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
if (dragging || resizing) {
|
||||
// Either drag or resize is ongoing,
|
||||
// reset readiness and stop processing.
|
||||
canDrag = canResize = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resizable || !IsWindows()) {
|
||||
if (resizeEdge) { setResize(); }
|
||||
return;
|
||||
}
|
||||
|
||||
const resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5;
|
||||
const resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5;
|
||||
|
||||
// Extra pixels for the corner areas.
|
||||
const cornerExtra = GetFlag("resizeCornerExtra") || 10;
|
||||
|
||||
const rightBorder = (window.outerWidth - event.clientX) < resizeHandleWidth;
|
||||
const leftBorder = event.clientX < resizeHandleWidth;
|
||||
const topBorder = event.clientY < resizeHandleHeight;
|
||||
const bottomBorder = (window.outerHeight - event.clientY) < resizeHandleHeight;
|
||||
|
||||
// Adjust for corner areas.
|
||||
const rightCorner = (window.outerWidth - event.clientX) < (resizeHandleWidth + cornerExtra);
|
||||
const leftCorner = event.clientX < (resizeHandleWidth + cornerExtra);
|
||||
const topCorner = event.clientY < (resizeHandleHeight + cornerExtra);
|
||||
const bottomCorner = (window.outerHeight - event.clientY) < (resizeHandleHeight + cornerExtra);
|
||||
|
||||
if (!leftCorner && !topCorner && !bottomCorner && !rightCorner) {
|
||||
// Optimisation: out of all corner areas implies out of borders.
|
||||
setResize();
|
||||
}
|
||||
// Detect corners.
|
||||
else if (rightCorner && bottomCorner) setResize("se-resize");
|
||||
else if (leftCorner && bottomCorner) setResize("sw-resize");
|
||||
else if (leftCorner && topCorner) setResize("nw-resize");
|
||||
else if (topCorner && rightCorner) setResize("ne-resize");
|
||||
// Detect borders.
|
||||
else if (leftBorder) setResize("w-resize");
|
||||
else if (topBorder) setResize("n-resize");
|
||||
else if (bottomBorder) setResize("s-resize");
|
||||
else if (rightBorder) setResize("e-resize");
|
||||
// Out of border area.
|
||||
else setResize();
|
||||
}
|
||||
|
|
@ -1,6 +1,18 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export const EventTypes = {
|
||||
Windows: {
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export const Types = Object.freeze({
|
||||
Windows: Object.freeze({
|
||||
APMPowerSettingChange: "windows:APMPowerSettingChange",
|
||||
APMPowerStatusChange: "windows:APMPowerStatusChange",
|
||||
APMResumeAutomatic: "windows:APMResumeAutomatic",
|
||||
|
|
@ -45,8 +57,8 @@ export const EventTypes = {
|
|||
WindowUnMinimise: "windows:WindowUnMinimise",
|
||||
WindowMaximise: "windows:WindowMaximise",
|
||||
WindowUnMaximise: "windows:WindowUnMaximise",
|
||||
},
|
||||
Mac: {
|
||||
}),
|
||||
Mac: Object.freeze({
|
||||
ApplicationDidBecomeActive: "mac:ApplicationDidBecomeActive",
|
||||
ApplicationDidChangeBackingProperties: "mac:ApplicationDidChangeBackingProperties",
|
||||
ApplicationDidChangeEffectiveAppearance: "mac:ApplicationDidChangeEffectiveAppearance",
|
||||
|
|
@ -179,8 +191,8 @@ export const EventTypes = {
|
|||
WindowZoomIn: "mac:WindowZoomIn",
|
||||
WindowZoomOut: "mac:WindowZoomOut",
|
||||
WindowZoomReset: "mac:WindowZoomReset",
|
||||
},
|
||||
Linux: {
|
||||
}),
|
||||
Linux: Object.freeze({
|
||||
ApplicationStartup: "linux:ApplicationStartup",
|
||||
SystemThemeChanged: "linux:SystemThemeChanged",
|
||||
WindowDeleteEvent: "linux:WindowDeleteEvent",
|
||||
|
|
@ -189,8 +201,8 @@ export const EventTypes = {
|
|||
WindowFocusIn: "linux:WindowFocusIn",
|
||||
WindowFocusOut: "linux:WindowFocusOut",
|
||||
WindowLoadChanged: "linux:WindowLoadChanged",
|
||||
},
|
||||
Common: {
|
||||
}),
|
||||
Common: Object.freeze({
|
||||
ApplicationOpenedWithFile: "common:ApplicationOpenedWithFile",
|
||||
ApplicationStarted: "common:ApplicationStarted",
|
||||
ThemeChanged: "common:ThemeChanged",
|
||||
|
|
@ -215,5 +227,5 @@ export const EventTypes = {
|
|||
WindowZoomIn: "common:WindowZoomIn",
|
||||
WindowZoomOut: "common:WindowZoomOut",
|
||||
WindowZoomReset: "common:WindowZoomReset",
|
||||
},
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
/**
|
||||
* @typedef {import("./types").WailsEvent} WailsEvent
|
||||
*/
|
||||
import {newRuntimeCallerWithID, objectNames} from "./runtime";
|
||||
|
||||
import {EventTypes} from "./event_types";
|
||||
export const Types = EventTypes;
|
||||
|
||||
// Setup
|
||||
window._wails = window._wails || {};
|
||||
window._wails.dispatchWailsEvent = dispatchWailsEvent;
|
||||
|
||||
const call = newRuntimeCallerWithID(objectNames.Events, '');
|
||||
const EmitMethod = 0;
|
||||
const eventListeners = new Map();
|
||||
|
||||
class Listener {
|
||||
constructor(eventName, callback, maxCallbacks) {
|
||||
this.eventName = eventName;
|
||||
this.maxCallbacks = maxCallbacks || -1;
|
||||
this.Callback = (data) => {
|
||||
callback(data);
|
||||
if (this.maxCallbacks === -1) return false;
|
||||
this.maxCallbacks -= 1;
|
||||
return this.maxCallbacks === 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class WailsEvent {
|
||||
constructor(name, data = null) {
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
export function setup() {
|
||||
}
|
||||
|
||||
function dispatchWailsEvent(event) {
|
||||
let listeners = eventListeners.get(event.name);
|
||||
if (listeners) {
|
||||
let toRemove = listeners.filter(listener => {
|
||||
let remove = listener.Callback(event);
|
||||
if (remove) return true;
|
||||
});
|
||||
if (toRemove.length > 0) {
|
||||
listeners = listeners.filter(l => !toRemove.includes(l));
|
||||
if (listeners.length === 0) eventListeners.delete(event.name);
|
||||
else eventListeners.set(event.name, listeners);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback function to be called multiple times for a specific event.
|
||||
*
|
||||
* @param {string} eventName - The name of the event to register the callback for.
|
||||
* @param {function} callback - The callback function to be called when the event is triggered.
|
||||
* @param {number} maxCallbacks - The maximum number of times the callback can be called for the event. Once the maximum number is reached, the callback will no longer be called.
|
||||
*
|
||||
@return {function} - A function that, when called, will unregister the callback from the event.
|
||||
*/
|
||||
export function OnMultiple(eventName, callback, maxCallbacks) {
|
||||
let listeners = eventListeners.get(eventName) || [];
|
||||
const thisListener = new Listener(eventName, callback, maxCallbacks);
|
||||
listeners.push(thisListener);
|
||||
eventListeners.set(eventName, listeners);
|
||||
return () => listenerOff(thisListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback function to be executed when the specified event occurs.
|
||||
*
|
||||
* @param {string} eventName - The name of the event.
|
||||
* @param {function} callback - The callback function to be executed. It takes no parameters.
|
||||
* @return {function} - A function that, when called, will unregister the callback from the event. */
|
||||
export function On(eventName, callback) { return OnMultiple(eventName, callback, -1); }
|
||||
|
||||
/**
|
||||
* Registers a callback function to be executed only once for the specified event.
|
||||
*
|
||||
* @param {string} eventName - The name of the event.
|
||||
* @param {function} callback - The function to be executed when the event occurs.
|
||||
* @return {function} - A function that, when called, will unregister the callback from the event.
|
||||
*/
|
||||
export function Once(eventName, callback) { return OnMultiple(eventName, callback, 1); }
|
||||
|
||||
/**
|
||||
* Removes the specified listener from the event listeners collection.
|
||||
* If all listeners for the event are removed, the event key is deleted from the collection.
|
||||
*
|
||||
* @param {Object} listener - The listener to be removed.
|
||||
*/
|
||||
function listenerOff(listener) {
|
||||
const eventName = listener.eventName;
|
||||
let listeners = eventListeners.get(eventName).filter(l => l !== listener);
|
||||
if (listeners.length === 0) eventListeners.delete(eventName);
|
||||
else eventListeners.set(eventName, listeners);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes event listeners for the specified event names.
|
||||
*
|
||||
* @param {string} eventName - The name of the event to remove listeners for.
|
||||
* @param {...string} additionalEventNames - Additional event names to remove listeners for.
|
||||
* @return {undefined}
|
||||
*/
|
||||
export function Off(eventName, ...additionalEventNames) {
|
||||
let eventsToRemove = [eventName, ...additionalEventNames];
|
||||
eventsToRemove.forEach(eventName => eventListeners.delete(eventName));
|
||||
}
|
||||
/**
|
||||
* Removes all event listeners.
|
||||
*
|
||||
* @function OffAll
|
||||
* @returns {void}
|
||||
*/
|
||||
export function OffAll() { eventListeners.clear(); }
|
||||
|
||||
/**
|
||||
* Emits an event using the given event name.
|
||||
*
|
||||
* @param {WailsEvent} event - The name of the event to emit.
|
||||
* @returns {any} - The result of the emitted event.
|
||||
*/
|
||||
export function Emit(event) { return call(EmitMethod, event); }
|
||||
135
v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts
Normal file
135
v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import { newRuntimeCaller, objectNames } from "./runtime.js";
|
||||
import { eventListeners, Listener, listenerOff } from "./listener.js";
|
||||
|
||||
// Setup
|
||||
window._wails = window._wails || {};
|
||||
window._wails.dispatchWailsEvent = dispatchWailsEvent;
|
||||
|
||||
const call = newRuntimeCaller(objectNames.Events);
|
||||
const EmitMethod = 0;
|
||||
|
||||
export { Types } from "./event_types.js";
|
||||
|
||||
/**
|
||||
* The type of handlers for a given event.
|
||||
*/
|
||||
export type Callback = (ev: WailsEvent) => void;
|
||||
|
||||
/**
|
||||
* Represents a system event or a custom event emitted through wails-provided facilities.
|
||||
*/
|
||||
export class WailsEvent {
|
||||
/**
|
||||
* The name of the event.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Optional data associated with the emitted event.
|
||||
*/
|
||||
data: any;
|
||||
|
||||
/**
|
||||
* Name of the originating window. Omitted for application events.
|
||||
* Will be overridden if set manually.
|
||||
*/
|
||||
sender?: string;
|
||||
|
||||
constructor(name: string, data: any = null) {
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchWailsEvent(event: any) {
|
||||
let listeners = eventListeners.get(event.name);
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
let wailsEvent = new WailsEvent(event.name, event.data);
|
||||
if ('sender' in event) {
|
||||
wailsEvent.sender = event.sender;
|
||||
}
|
||||
|
||||
listeners = listeners.filter(listener => !listener.dispatch(wailsEvent));
|
||||
if (listeners.length === 0) {
|
||||
eventListeners.delete(event.name);
|
||||
} else {
|
||||
eventListeners.set(event.name, listeners);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback function to be called multiple times for a specific event.
|
||||
*
|
||||
* @param eventName - The name of the event to register the callback for.
|
||||
* @param callback - The callback function to be called when the event is triggered.
|
||||
* @param maxCallbacks - The maximum number of times the callback can be called for the event. Once the maximum number is reached, the callback will no longer be called.
|
||||
* @returns A function that, when called, will unregister the callback from the event.
|
||||
*/
|
||||
export function OnMultiple(eventName: string, callback: Callback, maxCallbacks: number) {
|
||||
let listeners = eventListeners.get(eventName) || [];
|
||||
const thisListener = new Listener(eventName, callback, maxCallbacks);
|
||||
listeners.push(thisListener);
|
||||
eventListeners.set(eventName, listeners);
|
||||
return () => listenerOff(thisListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback function to be executed when the specified event occurs.
|
||||
*
|
||||
* @param eventName - The name of the event to register the callback for.
|
||||
* @param callback - The callback function to be called when the event is triggered.
|
||||
* @returns A function that, when called, will unregister the callback from the event.
|
||||
*/
|
||||
export function On(eventName: string, callback: Callback): () => void {
|
||||
return OnMultiple(eventName, callback, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback function to be executed only once for the specified event.
|
||||
*
|
||||
* @param eventName - The name of the event to register the callback for.
|
||||
* @param callback - The callback function to be called when the event is triggered.
|
||||
* @returns A function that, when called, will unregister the callback from the event.
|
||||
*/
|
||||
export function Once(eventName: string, callback: Callback): () => void {
|
||||
return OnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event listeners for the specified event names.
|
||||
*
|
||||
* @param eventNames - The name of the events to remove listeners for.
|
||||
*/
|
||||
export function Off(...eventNames: [string, ...string[]]): void {
|
||||
eventNames.forEach(eventName => eventListeners.delete(eventName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners.
|
||||
*/
|
||||
export function OffAll(): void {
|
||||
eventListeners.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the given event.
|
||||
*
|
||||
* @param event - The name of the event to emit.
|
||||
* @returns A promise that will be fulfilled once the event has been emitted.
|
||||
*/
|
||||
export function Emit(event: WailsEvent): Promise<void> {
|
||||
return call(EmitMethod, event);
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
/**
|
||||
* Retrieves the value associated with the specified key from the flag map.
|
||||
*
|
||||
* @param {string} keyString - The key to retrieve the value for.
|
||||
* @return {*} - The value associated with the specified key.
|
||||
*/
|
||||
export function GetFlag(keyString) {
|
||||
try {
|
||||
return window._wails.flags[keyString];
|
||||
} catch (e) {
|
||||
throw new Error("Unable to retrieve flag '" + keyString + "': " + e);
|
||||
}
|
||||
}
|
||||
23
v3/internal/runtime/desktop/@wailsio/runtime/src/flags.ts
Normal file
23
v3/internal/runtime/desktop/@wailsio/runtime/src/flags.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the value associated with the specified key from the flag map.
|
||||
*
|
||||
* @param key - The key to retrieve the value for.
|
||||
* @return The value associated with the specified key.
|
||||
*/
|
||||
export function GetFlag(key: string): any {
|
||||
try {
|
||||
return window._wails.flags[key];
|
||||
} catch (e) {
|
||||
throw new Error("Unable to retrieve flag '" + key + "': " + e, { cause: e });
|
||||
}
|
||||
}
|
||||
17
v3/internal/runtime/desktop/@wailsio/runtime/src/global.d.ts
vendored
Normal file
17
v3/internal/runtime/desktop/@wailsio/runtime/src/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
_wails: Record<string, any>;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
// Setup
|
||||
window._wails = window._wails || {};
|
||||
|
||||
import "./contextmenu";
|
||||
import "./drag";
|
||||
|
||||
// Re-export public API
|
||||
import * as Application from "./application";
|
||||
import * as Browser from "./browser";
|
||||
import * as Call from "./calls";
|
||||
import * as Clipboard from "./clipboard";
|
||||
import * as Create from "./create";
|
||||
import * as Dialogs from "./dialogs";
|
||||
import * as Events from "./events";
|
||||
import * as Flags from "./flags";
|
||||
import * as Screens from "./screens";
|
||||
import * as System from "./system";
|
||||
import Window from "./window";
|
||||
import * as WML from "./wml";
|
||||
|
||||
export {
|
||||
Application,
|
||||
Browser,
|
||||
Call,
|
||||
Clipboard,
|
||||
Create,
|
||||
Dialogs,
|
||||
Events,
|
||||
Flags,
|
||||
Screens,
|
||||
System,
|
||||
Window,
|
||||
WML
|
||||
};
|
||||
|
||||
let initialised = false;
|
||||
export function init() {
|
||||
window._wails.invoke = System.invoke;
|
||||
System.invoke("wails:runtime:ready");
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
if (!initialised) {
|
||||
init();
|
||||
}
|
||||
});
|
||||
|
||||
// Notify backend
|
||||
|
||||
57
v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
Normal file
57
v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
// Setup
|
||||
window._wails = window._wails || {};
|
||||
|
||||
import "./contextmenu.js";
|
||||
import "./drag.js";
|
||||
|
||||
// Re-export public API
|
||||
import * as Application from "./application.js";
|
||||
import * as Browser from "./browser.js";
|
||||
import * as Call from "./calls.js";
|
||||
import * as Clipboard from "./clipboard.js";
|
||||
import * as Create from "./create.js";
|
||||
import * as Dialogs from "./dialogs.js";
|
||||
import * as Events from "./events.js";
|
||||
import * as Flags from "./flags.js";
|
||||
import * as Screens from "./screens.js";
|
||||
import * as System from "./system.js";
|
||||
import Window from "./window.js";
|
||||
import * as WML from "./wml.js";
|
||||
|
||||
export {
|
||||
Application,
|
||||
Browser,
|
||||
Call,
|
||||
Clipboard,
|
||||
Dialogs,
|
||||
Events,
|
||||
Flags,
|
||||
Screens,
|
||||
System,
|
||||
Window,
|
||||
WML
|
||||
};
|
||||
|
||||
/**
|
||||
* An internal utility consumed by the binding generator.
|
||||
*
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
export { Create };
|
||||
|
||||
export * from "./cancellable.js";
|
||||
|
||||
// Notify backend
|
||||
window._wails.invoke = System.invoke;
|
||||
System.invoke("wails:runtime:ready");
|
||||
52
v3/internal/runtime/desktop/@wailsio/runtime/src/listener.ts
Normal file
52
v3/internal/runtime/desktop/@wailsio/runtime/src/listener.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
// The following utilities have been factored out of ./events.ts
|
||||
// for testing purposes.
|
||||
|
||||
export const eventListeners = new Map<string, Listener[]>();
|
||||
|
||||
export class Listener {
|
||||
eventName: string;
|
||||
callback: (data: any) => void;
|
||||
maxCallbacks: number;
|
||||
|
||||
constructor(eventName: string, callback: (data: any) => void, maxCallbacks: number) {
|
||||
this.eventName = eventName;
|
||||
this.callback = callback;
|
||||
this.maxCallbacks = maxCallbacks || -1;
|
||||
}
|
||||
|
||||
dispatch(data: any): boolean {
|
||||
try {
|
||||
this.callback(data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (this.maxCallbacks === -1) return false;
|
||||
this.maxCallbacks -= 1;
|
||||
return this.maxCallbacks === 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function listenerOff(listener: Listener): void {
|
||||
let listeners = eventListeners.get(listener.eventName);
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners = listeners.filter(l => l !== listener);
|
||||
if (listeners.length === 0) {
|
||||
eventListeners.delete(listener.eventName);
|
||||
} else {
|
||||
eventListeners.set(listener.eventName, listeners);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,10 +27,10 @@
|
|||
// `'use`, `andom`, and `rict'`
|
||||
// References to the brotli default dictionary:
|
||||
// `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf`
|
||||
let urlAlphabet =
|
||||
const urlAlphabet =
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
|
||||
|
||||
export let nanoid = (size = 21) => {
|
||||
export function nanoid(size: number = 21): string {
|
||||
let id = ''
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`.
|
||||
let i = size | 0
|
||||
|
|
@ -39,4 +39,4 @@ export let nanoid = (size = 21) => {
|
|||
id += urlAlphabet[(Math.random() * 64) | 0]
|
||||
}
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
import { nanoid } from './nanoid.js';
|
||||
|
||||
const runtimeURL = window.location.origin + "/wails/runtime";
|
||||
|
||||
// Object Names
|
||||
export const objectNames = {
|
||||
Call: 0,
|
||||
Clipboard: 1,
|
||||
Application: 2,
|
||||
Events: 3,
|
||||
ContextMenu: 4,
|
||||
Dialog: 5,
|
||||
Window: 6,
|
||||
Screens: 7,
|
||||
System: 8,
|
||||
Browser: 9,
|
||||
CancelCall: 10,
|
||||
}
|
||||
export let clientId = nanoid();
|
||||
|
||||
/**
|
||||
* Creates a runtime caller function that invokes a specified method on a given object within a specified window context.
|
||||
*
|
||||
* @param {Object} object - The object on which the method is to be invoked.
|
||||
* @param {string} windowName - The name of the window context in which the method should be called.
|
||||
* @returns {Function} A runtime caller function that takes the method name and optionally arguments and invokes the method within the specified window context.
|
||||
*/
|
||||
export function newRuntimeCaller(object, windowName) {
|
||||
return function (method, args=null) {
|
||||
return runtimeCall(object + "." + method, windowName, args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new runtime caller with specified ID.
|
||||
*
|
||||
* @param {number} object - The object to invoke the method on.
|
||||
* @param {string} windowName - The name of the window.
|
||||
* @return {Function} - The new runtime caller function.
|
||||
*/
|
||||
export function newRuntimeCallerWithID(object, windowName) {
|
||||
return function (method, args=null) {
|
||||
return runtimeCallWithID(object, method, windowName, args);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function runtimeCall(method, windowName, args) {
|
||||
return runtimeCallWithID(null, method, windowName, args);
|
||||
}
|
||||
|
||||
async function runtimeCallWithID(objectID, method, windowName, args) {
|
||||
let url = new URL(runtimeURL);
|
||||
if (objectID != null) {
|
||||
url.searchParams.append("object", objectID);
|
||||
}
|
||||
if (method != null) {
|
||||
url.searchParams.append("method", method);
|
||||
}
|
||||
let fetchOptions = {
|
||||
headers: {},
|
||||
};
|
||||
if (windowName) {
|
||||
fetchOptions.headers["x-wails-window-name"] = windowName;
|
||||
}
|
||||
if (args) {
|
||||
url.searchParams.append("args", JSON.stringify(args));
|
||||
}
|
||||
fetchOptions.headers["x-wails-client-id"] = clientId;
|
||||
|
||||
let response = await fetch(url, fetchOptions);
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
if (response.headers.get("Content-Type") && response.headers.get("Content-Type").indexOf("application/json") !== -1) {
|
||||
return response.json();
|
||||
} else {
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
67
v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
Normal file
67
v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import { nanoid } from './nanoid.js';
|
||||
|
||||
const runtimeURL = window.location.origin + "/wails/runtime";
|
||||
|
||||
// Object Names
|
||||
export const objectNames = Object.freeze({
|
||||
Call: 0,
|
||||
Clipboard: 1,
|
||||
Application: 2,
|
||||
Events: 3,
|
||||
ContextMenu: 4,
|
||||
Dialog: 5,
|
||||
Window: 6,
|
||||
Screens: 7,
|
||||
System: 8,
|
||||
Browser: 9,
|
||||
CancelCall: 10,
|
||||
});
|
||||
export let clientId = nanoid();
|
||||
|
||||
/**
|
||||
* Creates a new runtime caller with specified ID.
|
||||
*
|
||||
* @param object - The object to invoke the method on.
|
||||
* @param windowName - The name of the window.
|
||||
* @return The new runtime caller function.
|
||||
*/
|
||||
export function newRuntimeCaller(object: number, windowName: string = '') {
|
||||
return function (method: number, args: any = null) {
|
||||
return runtimeCallWithID(object, method, windowName, args);
|
||||
};
|
||||
}
|
||||
|
||||
async function runtimeCallWithID(objectID: number, method: number, windowName: string, args: any): Promise<any> {
|
||||
let url = new URL(runtimeURL);
|
||||
url.searchParams.append("object", objectID.toString());
|
||||
url.searchParams.append("method", method.toString());
|
||||
if (args) { url.searchParams.append("args", JSON.stringify(args)); }
|
||||
|
||||
let headers: Record<string, string> = {
|
||||
["x-wails-client-id"]: clientId
|
||||
}
|
||||
if (windowName) {
|
||||
headers["x-wails-window-name"] = windowName;
|
||||
}
|
||||
|
||||
let response = await fetch(url, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
if ((response.headers.get("Content-Type")?.indexOf("application/json") ?? -1) !== -1) {
|
||||
return response.json();
|
||||
} else {
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
/**
|
||||
* @typedef {Object} Size
|
||||
* @property {number} Width - The width.
|
||||
* @property {number} Height - The height.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Rect
|
||||
* @property {number} X - The X coordinate of the origin.
|
||||
* @property {number} Y - The Y coordinate of the origin.
|
||||
* @property {number} Width - The width of the rectangle.
|
||||
* @property {number} Height - The height of the rectangle.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Screen
|
||||
* @property {string} ID - Unique identifier for the screen.
|
||||
* @property {string} Name - Human readable name of the screen.
|
||||
* @property {number} ScaleFactor - The scale factor of the screen (DPI/96). 1 = standard DPI, 2 = HiDPI (Retina), etc.
|
||||
* @property {number} X - The X coordinate of the screen.
|
||||
* @property {number} Y - The Y coordinate of the screen.
|
||||
* @property {Size} Size - Contains the width and height of the screen.
|
||||
* @property {Rect} Bounds - Contains the bounds of the screen in terms of X, Y, Width, and Height.
|
||||
* @property {Rect} PhysicalBounds - Contains the physical bounds of the screen in terms of X, Y, Width, and Height (before scaling).
|
||||
* @property {Rect} WorkArea - Contains the area of the screen that is actually usable (excluding taskbar and other system UI).
|
||||
* @property {Rect} PhysicalWorkArea - Contains the physical WorkArea of the screen (before scaling).
|
||||
* @property {boolean} IsPrimary - True if this is the primary monitor selected by the user in the operating system.
|
||||
* @property {number} Rotation - The rotation of the screen.
|
||||
*/
|
||||
|
||||
import { newRuntimeCallerWithID, objectNames } from "./runtime";
|
||||
const call = newRuntimeCallerWithID(objectNames.Screens, "");
|
||||
|
||||
const getAll = 0;
|
||||
const getPrimary = 1;
|
||||
const getCurrent = 2;
|
||||
|
||||
/**
|
||||
* Gets all screens.
|
||||
* @returns {Promise<Screen[]>} A promise that resolves to an array of Screen objects.
|
||||
*/
|
||||
export function GetAll() {
|
||||
return call(getAll);
|
||||
}
|
||||
/**
|
||||
* Gets the primary screen.
|
||||
* @returns {Promise<Screen>} A promise that resolves to the primary screen.
|
||||
*/
|
||||
export function GetPrimary() {
|
||||
return call(getPrimary);
|
||||
}
|
||||
/**
|
||||
* Gets the current active screen.
|
||||
*
|
||||
* @returns {Promise<Screen>} A promise that resolves with the current active screen.
|
||||
*/
|
||||
export function GetCurrent() {
|
||||
return call(getCurrent);
|
||||
}
|
||||
88
v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts
Normal file
88
v3/internal/runtime/desktop/@wailsio/runtime/src/screens.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export interface Size {
|
||||
/** The width of a rectangular area. */
|
||||
Width: number;
|
||||
/** The height of a rectangular area. */
|
||||
Height: number;
|
||||
}
|
||||
|
||||
export interface Rect {
|
||||
/** The X coordinate of the origin. */
|
||||
X: number;
|
||||
/** The Y coordinate of the origin. */
|
||||
Y: number;
|
||||
/** The width of the rectangle. */
|
||||
Width: number;
|
||||
/** The height of the rectangle. */
|
||||
Height: number;
|
||||
}
|
||||
|
||||
export interface Screen {
|
||||
/** Unique identifier for the screen. */
|
||||
ID: string;
|
||||
/** Human-readable name of the screen. */
|
||||
Name: string;
|
||||
/** The scale factor of the screen (DPI/96). 1 = standard DPI, 2 = HiDPI (Retina), etc. */
|
||||
ScaleFactor: number;
|
||||
/** The X coordinate of the screen. */
|
||||
X: number;
|
||||
/** The Y coordinate of the screen. */
|
||||
Y: number;
|
||||
/** Contains the width and height of the screen. */
|
||||
Size: Size;
|
||||
/** Contains the bounds of the screen in terms of X, Y, Width, and Height. */
|
||||
Bounds: Rect;
|
||||
/** Contains the physical bounds of the screen in terms of X, Y, Width, and Height (before scaling). */
|
||||
PhysicalBounds: Rect;
|
||||
/** Contains the area of the screen that is actually usable (excluding taskbar and other system UI). */
|
||||
WorkArea: Rect;
|
||||
/** Contains the physical WorkArea of the screen (before scaling). */
|
||||
PhysicalWorkArea: Rect;
|
||||
/** True if this is the primary monitor selected by the user in the operating system. */
|
||||
IsPrimary: boolean;
|
||||
/** The rotation of the screen. */
|
||||
Rotation: number;
|
||||
}
|
||||
|
||||
import { newRuntimeCaller, objectNames } from "./runtime.js";
|
||||
const call = newRuntimeCaller(objectNames.Screens);
|
||||
|
||||
const getAll = 0;
|
||||
const getPrimary = 1;
|
||||
const getCurrent = 2;
|
||||
|
||||
/**
|
||||
* Gets all screens.
|
||||
*
|
||||
* @returns A promise that resolves to an array of Screen objects.
|
||||
*/
|
||||
export function GetAll(): Promise<Screen[]> {
|
||||
return call(getAll);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary screen.
|
||||
*
|
||||
* @returns A promise that resolves to the primary screen.
|
||||
*/
|
||||
export function GetPrimary(): Promise<Screen> {
|
||||
return call(getPrimary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current active screen.
|
||||
*
|
||||
* @returns A promise that resolves with the current active screen.
|
||||
*/
|
||||
export function GetCurrent(): Promise<Screen> {
|
||||
return call(getCurrent);
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
import {newRuntimeCallerWithID, objectNames} from "./runtime";
|
||||
let call = newRuntimeCallerWithID(objectNames.System, '');
|
||||
const systemIsDarkMode = 0;
|
||||
const environment = 1;
|
||||
|
||||
const _invoke = (() => {
|
||||
try {
|
||||
if(window?.chrome?.webview) {
|
||||
return (msg) => window.chrome.webview.postMessage(msg);
|
||||
}
|
||||
if(window?.webkit?.messageHandlers?.external) {
|
||||
return (msg) => window.webkit.messageHandlers.external.postMessage(msg);
|
||||
}
|
||||
} catch(e) {
|
||||
console.warn('\n%c⚠️ Browser Environment Detected %c\n\n%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode.\nMore information at: https://v3.wails.io/learn/build/#using-a-browser-for-development\n',
|
||||
'background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;',
|
||||
'background: transparent;',
|
||||
'color: #ffffff; font-style: italic; font-weight: bold;');
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
|
||||
export function invoke(msg) {
|
||||
if (!_invoke) return;
|
||||
return _invoke(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function
|
||||
* Retrieves the system dark mode status.
|
||||
* @returns {Promise<boolean>} - A promise that resolves to a boolean value indicating if the system is in dark mode.
|
||||
*/
|
||||
export function IsDarkMode() {
|
||||
return call(systemIsDarkMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the capabilities of the application from the server.
|
||||
*
|
||||
* @async
|
||||
* @function Capabilities
|
||||
* @returns {Promise<Object>} A promise that resolves to an object containing the capabilities.
|
||||
*/
|
||||
export function Capabilities() {
|
||||
let response = fetch("/wails/capabilities");
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} OSInfo
|
||||
* @property {string} Branding - The branding of the OS.
|
||||
* @property {string} ID - The ID of the OS.
|
||||
* @property {string} Name - The name of the OS.
|
||||
* @property {string} Version - The version of the OS.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EnvironmentInfo
|
||||
* @property {string} Arch - The architecture of the system.
|
||||
* @property {boolean} Debug - True if the application is running in debug mode, otherwise false.
|
||||
* @property {string} OS - The operating system in use.
|
||||
* @property {OSInfo} OSInfo - Details of the operating system.
|
||||
* @property {Object} PlatformInfo - Additional platform information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* Retrieves environment details.
|
||||
* @returns {Promise<EnvironmentInfo>} - A promise that resolves to an object containing OS and system architecture.
|
||||
*/
|
||||
export function Environment() {
|
||||
return call(environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current operating system is Windows.
|
||||
*
|
||||
* @return {boolean} True if the operating system is Windows, otherwise false.
|
||||
*/
|
||||
export function IsWindows() {
|
||||
return window._wails.environment.OS === "windows";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current operating system is Linux.
|
||||
*
|
||||
* @returns {boolean} Returns true if the current operating system is Linux, false otherwise.
|
||||
*/
|
||||
export function IsLinux() {
|
||||
return window._wails.environment.OS === "linux";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current environment is a macOS operating system.
|
||||
*
|
||||
* @returns {boolean} True if the environment is macOS, false otherwise.
|
||||
*/
|
||||
export function IsMac() {
|
||||
return window._wails.environment.OS === "darwin";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current environment architecture is AMD64.
|
||||
* @returns {boolean} True if the current environment architecture is AMD64, false otherwise.
|
||||
*/
|
||||
export function IsAMD64() {
|
||||
return window._wails.environment.Arch === "amd64";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current architecture is ARM.
|
||||
*
|
||||
* @returns {boolean} True if the current architecture is ARM, false otherwise.
|
||||
*/
|
||||
export function IsARM() {
|
||||
return window._wails.environment.Arch === "arm";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current environment is ARM64 architecture.
|
||||
*
|
||||
* @returns {boolean} - Returns true if the environment is ARM64 architecture, otherwise returns false.
|
||||
*/
|
||||
export function IsARM64() {
|
||||
return window._wails.environment.Arch === "arm64";
|
||||
}
|
||||
|
||||
export function IsDebug() {
|
||||
return window._wails.environment.Debug === true;
|
||||
}
|
||||
|
||||
156
v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts
Normal file
156
v3/internal/runtime/desktop/@wailsio/runtime/src/system.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import { newRuntimeCaller, objectNames } from "./runtime.js";
|
||||
|
||||
let call = newRuntimeCaller(objectNames.System);
|
||||
|
||||
const SystemIsDarkMode = 0;
|
||||
const SystemEnvironment = 1;
|
||||
|
||||
const _invoke = (function () {
|
||||
try {
|
||||
if ((window as any).chrome?.webview?.postMessage) {
|
||||
return (window as any).chrome.webview.postMessage.bind((window as any).chrome.webview);
|
||||
} else if ((window as any).webkit?.messageHandlers?.['external']?.postMessage) {
|
||||
return (window as any).webkit.messageHandlers['external'].postMessage.bind((window as any).webkit.messageHandlers['external']);
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
console.warn('\n%c⚠️ Browser Environment Detected %c\n\n%cOnly UI previews are available in the browser. For full functionality, please run the application in desktop mode.\nMore information at: https://v3.wails.io/learn/build/#using-a-browser-for-development\n',
|
||||
'background: #ffffff; color: #000000; font-weight: bold; padding: 4px 8px; border-radius: 4px; border: 2px solid #000000;',
|
||||
'background: transparent;',
|
||||
'color: #ffffff; font-style: italic; font-weight: bold;');
|
||||
return null;
|
||||
})();
|
||||
|
||||
export function invoke(msg: any): void {
|
||||
return _invoke?.(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the system dark mode status.
|
||||
*
|
||||
* @returns A promise that resolves to a boolean value indicating if the system is in dark mode.
|
||||
*/
|
||||
export function IsDarkMode(): Promise<boolean> {
|
||||
return call(SystemIsDarkMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the capabilities of the application from the server.
|
||||
*
|
||||
* @returns A promise that resolves to an object containing the capabilities.
|
||||
*/
|
||||
export async function Capabilities(): Promise<Record<string, any>> {
|
||||
let response = await fetch("/wails/capabilities");
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
throw new Error("could not fetch capabilities: " + response.statusText);
|
||||
}
|
||||
}
|
||||
|
||||
export interface OSInfo {
|
||||
/** The branding of the OS. */
|
||||
Branding: string;
|
||||
/** The ID of the OS. */
|
||||
ID: string;
|
||||
/** The name of the OS. */
|
||||
Name: string;
|
||||
/** The version of the OS. */
|
||||
Version: string;
|
||||
}
|
||||
|
||||
export interface EnvironmentInfo {
|
||||
/** The architecture of the system. */
|
||||
Arch: string;
|
||||
/** True if the application is running in debug mode, otherwise false. */
|
||||
Debug: boolean;
|
||||
/** The operating system in use. */
|
||||
OS: string;
|
||||
/** Details of the operating system. */
|
||||
OSInfo: OSInfo;
|
||||
/** Additional platform information. */
|
||||
PlatformInfo: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves environment details.
|
||||
*
|
||||
* @returns A promise that resolves to an object containing OS and system architecture.
|
||||
*/
|
||||
export function Environment(): Promise<EnvironmentInfo> {
|
||||
return call(SystemEnvironment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current operating system is Windows.
|
||||
*
|
||||
* @return True if the operating system is Windows, otherwise false.
|
||||
*/
|
||||
export function IsWindows(): boolean {
|
||||
return window._wails.environment.OS === "windows";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current operating system is Linux.
|
||||
*
|
||||
* @returns Returns true if the current operating system is Linux, false otherwise.
|
||||
*/
|
||||
export function IsLinux(): boolean {
|
||||
return window._wails.environment.OS === "linux";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current environment is a macOS operating system.
|
||||
*
|
||||
* @returns True if the environment is macOS, false otherwise.
|
||||
*/
|
||||
export function IsMac(): boolean {
|
||||
return window._wails.environment.OS === "darwin";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current environment architecture is AMD64.
|
||||
*
|
||||
* @returns True if the current environment architecture is AMD64, false otherwise.
|
||||
*/
|
||||
export function IsAMD64(): boolean {
|
||||
return window._wails.environment.Arch === "amd64";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current architecture is ARM.
|
||||
*
|
||||
* @returns True if the current architecture is ARM, false otherwise.
|
||||
*/
|
||||
export function IsARM(): boolean {
|
||||
return window._wails.environment.Arch === "arm";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current environment is ARM64 architecture.
|
||||
*
|
||||
* @returns Returns true if the environment is ARM64 architecture, otherwise returns false.
|
||||
*/
|
||||
export function IsARM64(): boolean {
|
||||
return window._wails.environment.Arch === "arm64";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether the app is being run in debug mode.
|
||||
*
|
||||
* @returns True if the app is being run in debug mode.
|
||||
*/
|
||||
export function IsDebug(): boolean {
|
||||
return Boolean(window._wails.environment.Debug);
|
||||
}
|
||||
|
||||
|
|
@ -10,10 +10,10 @@ The electron alternative for Go
|
|||
|
||||
/**
|
||||
* Logs a message to the console with custom formatting.
|
||||
* @param {string} message - The message to be logged.
|
||||
* @return {void}
|
||||
*
|
||||
* @param message - The message to be logged.
|
||||
*/
|
||||
export function debugLog(message) {
|
||||
export function debugLog(message: any) {
|
||||
// eslint-disable-next-line
|
||||
console.log(
|
||||
'%c wails3 %c ' + message + ' ',
|
||||
|
|
@ -22,11 +22,17 @@ export function debugLog(message) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the webview supports the {@link MouseEvent#buttons} property.
|
||||
* Looking at you macOS High Sierra!
|
||||
*/
|
||||
export function canTrackButtons(): boolean {
|
||||
return (new MouseEvent('mousedown')).buttons === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the browser supports removing listeners by triggering an AbortSignal
|
||||
* (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal)
|
||||
*
|
||||
* @return {boolean}
|
||||
* (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal).
|
||||
*/
|
||||
export function canAbortListeners() {
|
||||
if (!EventTarget || !AbortSignal || !AbortController)
|
||||
|
|
@ -75,9 +81,9 @@ export function canAbortListeners() {
|
|||
***/
|
||||
|
||||
let isReady = false;
|
||||
document.addEventListener('DOMContentLoaded', () => isReady = true);
|
||||
document.addEventListener('DOMContentLoaded', () => { isReady = true });
|
||||
|
||||
export function whenReady(callback) {
|
||||
export function whenReady(callback: () => void) {
|
||||
if (isReady || document.readyState === 'complete') {
|
||||
callback();
|
||||
} else {
|
||||
|
|
@ -1,638 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
// Import screen jsdoc definition from ./screens.js
|
||||
/**
|
||||
* @typedef {import("./screens").Screen} Screen
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A record describing the position of a window.
|
||||
*
|
||||
* @typedef {Object} Position
|
||||
* @property {number} x - The horizontal position of the window
|
||||
* @property {number} y - The vertical position of the window
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A record describing the size of a window.
|
||||
*
|
||||
* @typedef {Object} Size
|
||||
* @property {number} width - The width of the window
|
||||
* @property {number} height - The height of the window
|
||||
*/
|
||||
|
||||
|
||||
import {newRuntimeCallerWithID, objectNames} from "./runtime";
|
||||
|
||||
const PositionMethod = 0;
|
||||
const CenterMethod = 1;
|
||||
const CloseMethod = 2;
|
||||
const DisableSizeConstraintsMethod = 3;
|
||||
const EnableSizeConstraintsMethod = 4;
|
||||
const FocusMethod = 5;
|
||||
const ForceReloadMethod = 6;
|
||||
const FullscreenMethod = 7;
|
||||
const GetScreenMethod = 8;
|
||||
const GetZoomMethod = 9;
|
||||
const HeightMethod = 10;
|
||||
const HideMethod = 11;
|
||||
const IsFocusedMethod = 12;
|
||||
const IsFullscreenMethod = 13;
|
||||
const IsMaximisedMethod = 14;
|
||||
const IsMinimisedMethod = 15;
|
||||
const MaximiseMethod = 16;
|
||||
const MinimiseMethod = 17;
|
||||
const NameMethod = 18;
|
||||
const OpenDevToolsMethod = 19;
|
||||
const RelativePositionMethod = 20;
|
||||
const ReloadMethod = 21;
|
||||
const ResizableMethod = 22;
|
||||
const RestoreMethod = 23;
|
||||
const SetPositionMethod = 24;
|
||||
const SetAlwaysOnTopMethod = 25;
|
||||
const SetBackgroundColourMethod = 26;
|
||||
const SetFramelessMethod = 27;
|
||||
const SetFullscreenButtonEnabledMethod = 28;
|
||||
const SetMaxSizeMethod = 29;
|
||||
const SetMinSizeMethod = 30;
|
||||
const SetRelativePositionMethod = 31;
|
||||
const SetResizableMethod = 32;
|
||||
const SetSizeMethod = 33;
|
||||
const SetTitleMethod = 34;
|
||||
const SetZoomMethod = 35;
|
||||
const ShowMethod = 36;
|
||||
const SizeMethod = 37;
|
||||
const ToggleFullscreenMethod = 38;
|
||||
const ToggleMaximiseMethod = 39;
|
||||
const UnFullscreenMethod = 40;
|
||||
const UnMaximiseMethod = 41;
|
||||
const UnMinimiseMethod = 42;
|
||||
const WidthMethod = 43;
|
||||
const ZoomMethod = 44;
|
||||
const ZoomInMethod = 45;
|
||||
const ZoomOutMethod = 46;
|
||||
const ZoomResetMethod = 47;
|
||||
|
||||
/**
|
||||
* @type {symbol}
|
||||
*/
|
||||
const caller = Symbol();
|
||||
|
||||
export class Window {
|
||||
/**
|
||||
* Initialises a window object with the specified name.
|
||||
*
|
||||
* @private
|
||||
* @param {string} name - The name of the target window.
|
||||
*/
|
||||
constructor(name = '') {
|
||||
/**
|
||||
* @private
|
||||
* @name {@link caller}
|
||||
* @type {(...args: any[]) => any}
|
||||
*/
|
||||
this[caller] = newRuntimeCallerWithID(objectNames.Window, name)
|
||||
|
||||
// bind instance method to make them easily usable in event handlers
|
||||
for (const method of Object.getOwnPropertyNames(Window.prototype)) {
|
||||
if (
|
||||
method !== "constructor"
|
||||
&& typeof this[method] === "function"
|
||||
) {
|
||||
this[method] = this[method].bind(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified window.
|
||||
*
|
||||
* @public
|
||||
* @param {string} name - The name of the window to get.
|
||||
* @return {Window} - The corresponding window object.
|
||||
*/
|
||||
Get(name) {
|
||||
return new Window(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute position of the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<Position>} - The current absolute position of the window.
|
||||
*/
|
||||
Position() {
|
||||
return this[caller](PositionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Centers the window on the screen.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Center() {
|
||||
return this[caller](CenterMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Close() {
|
||||
return this[caller](CloseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables min/max size constraints.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
DisableSizeConstraints() {
|
||||
return this[caller](DisableSizeConstraintsMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables min/max size constraints.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
EnableSizeConstraints() {
|
||||
return this[caller](EnableSizeConstraintsMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Focus() {
|
||||
return this[caller](FocusMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the window to reload the page assets.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
ForceReload() {
|
||||
return this[caller](ForceReloadMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Doc.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Fullscreen() {
|
||||
return this[caller](FullscreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the screen that the window is on.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<Screen>} - The screen the window is currently on
|
||||
*/
|
||||
GetScreen() {
|
||||
return this[caller](GetScreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current zoom level of the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<number>} - The current zoom level
|
||||
*/
|
||||
GetZoom() {
|
||||
return this[caller](GetZoomMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<number>} - The current height of the window
|
||||
*/
|
||||
Height() {
|
||||
return this[caller](HeightMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Hide() {
|
||||
return this[caller](HideMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is focused.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<boolean>} - Whether the window is currently focused
|
||||
*/
|
||||
IsFocused() {
|
||||
return this[caller](IsFocusedMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is fullscreen.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<boolean>} - Whether the window is currently fullscreen
|
||||
*/
|
||||
IsFullscreen() {
|
||||
return this[caller](IsFullscreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is maximised.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<boolean>} - Whether the window is currently maximised
|
||||
*/
|
||||
IsMaximised() {
|
||||
return this[caller](IsMaximisedMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is minimised.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<boolean>} - Whether the window is currently minimised
|
||||
*/
|
||||
IsMinimised() {
|
||||
return this[caller](IsMinimisedMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximises the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Maximise() {
|
||||
return this[caller](MaximiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimises the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Minimise() {
|
||||
return this[caller](MinimiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<string>} - The name of the window
|
||||
*/
|
||||
Name() {
|
||||
return this[caller](NameMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the development tools pane.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
OpenDevTools() {
|
||||
return this[caller](OpenDevToolsMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative position of the window to the screen.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<Position>} - The current relative position of the window
|
||||
*/
|
||||
RelativePosition() {
|
||||
return this[caller](RelativePositionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page assets.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Reload() {
|
||||
return this[caller](ReloadMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is resizable.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<boolean>} - Whether the window is currently resizable
|
||||
*/
|
||||
Resizable() {
|
||||
return this[caller](ResizableMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the window to its previous state if it was previously minimised, maximised or fullscreen.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Restore() {
|
||||
return this[caller](RestoreMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the absolute position of the window.
|
||||
*
|
||||
* @public
|
||||
* @param {number} x - The desired horizontal absolute position of the window
|
||||
* @param {number} y - The desired vertical absolute position of the window
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetPosition(x, y) {
|
||||
return this[caller](SetPositionMethod, { x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the window to be always on top.
|
||||
*
|
||||
* @public
|
||||
* @param {boolean} alwaysOnTop - Whether the window should stay on top
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetAlwaysOnTop(alwaysOnTop) {
|
||||
return this[caller](SetAlwaysOnTopMethod, { alwaysOnTop });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background colour of the window.
|
||||
*
|
||||
* @public
|
||||
* @param {number} r - The desired red component of the window background
|
||||
* @param {number} g - The desired green component of the window background
|
||||
* @param {number} b - The desired blue component of the window background
|
||||
* @param {number} a - The desired alpha component of the window background
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetBackgroundColour(r, g, b, a) {
|
||||
return this[caller](SetBackgroundColourMethod, { r, g, b, a });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the window frame and title bar.
|
||||
*
|
||||
* @public
|
||||
* @param {boolean} frameless - Whether the window should be frameless
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetFrameless(frameless) {
|
||||
return this[caller](SetFramelessMethod, { frameless });
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the system fullscreen button.
|
||||
*
|
||||
* @public
|
||||
* @param {boolean} enabled - Whether the fullscreen button should be enabled
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetFullscreenButtonEnabled(enabled) {
|
||||
return this[caller](SetFullscreenButtonEnabledMethod, { enabled });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum size of the window.
|
||||
*
|
||||
* @public
|
||||
* @param {number} width - The desired maximum width of the window
|
||||
* @param {number} height - The desired maximum height of the window
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetMaxSize(width, height) {
|
||||
return this[caller](SetMaxSizeMethod, { width, height });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum size of the window.
|
||||
*
|
||||
* @public
|
||||
* @param {number} width - The desired minimum width of the window
|
||||
* @param {number} height - The desired minimum height of the window
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetMinSize(width, height) {
|
||||
return this[caller](SetMinSizeMethod, { width, height });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the relative position of the window to the screen.
|
||||
*
|
||||
* @public
|
||||
* @param {number} x - The desired horizontal relative position of the window
|
||||
* @param {number} y - The desired vertical relative position of the window
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetRelativePosition(x, y) {
|
||||
return this[caller](SetRelativePositionMethod, { x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the window is resizable.
|
||||
*
|
||||
* @public
|
||||
* @param {boolean} resizable - Whether the window should be resizable
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetResizable(resizable) {
|
||||
return this[caller](SetResizableMethod, { resizable });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the window.
|
||||
*
|
||||
* @public
|
||||
* @param {number} width - The desired width of the window
|
||||
* @param {number} height - The desired height of the window
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetSize(width, height) {
|
||||
return this[caller](SetSizeMethod, { width, height });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of the window.
|
||||
*
|
||||
* @public
|
||||
* @param {string} title - The desired title of the window
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetTitle(title) {
|
||||
return this[caller](SetTitleMethod, { title });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the zoom level of the window.
|
||||
*
|
||||
* @public
|
||||
* @param {number} zoom - The desired zoom level
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
SetZoom(zoom) {
|
||||
return this[caller](SetZoomMethod, { zoom });
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Show() {
|
||||
return this[caller](ShowMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<Size>} - The current size of the window
|
||||
*/
|
||||
Size() {
|
||||
return this[caller](SizeMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the window between fullscreen and normal.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
ToggleFullscreen() {
|
||||
return this[caller](ToggleFullscreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the window between maximised and normal.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
ToggleMaximise() {
|
||||
return this[caller](ToggleMaximiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-fullscreens the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
UnFullscreen() {
|
||||
return this[caller](UnFullscreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-maximises the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
UnMaximise() {
|
||||
return this[caller](UnMaximiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-minimises the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
UnMinimise() {
|
||||
return this[caller](UnMinimiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<number>} - The current width of the window
|
||||
*/
|
||||
Width() {
|
||||
return this[caller](WidthMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms the window.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
Zoom() {
|
||||
return this[caller](ZoomMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the zoom level of the webview content.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
ZoomIn() {
|
||||
return this[caller](ZoomInMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the zoom level of the webview content.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
ZoomOut() {
|
||||
return this[caller](ZoomOutMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the zoom level of the webview content.
|
||||
*
|
||||
* @public
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
ZoomReset() {
|
||||
return this[caller](ZoomResetMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The window within which the script is running.
|
||||
*
|
||||
* @type {Window}
|
||||
*/
|
||||
const thisWindow = new Window('');
|
||||
|
||||
export default thisWindow;
|
||||
520
v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts
Normal file
520
v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import {newRuntimeCaller, objectNames} from "./runtime.js";
|
||||
import type { Screen } from "./screens.js";
|
||||
|
||||
const PositionMethod = 0;
|
||||
const CenterMethod = 1;
|
||||
const CloseMethod = 2;
|
||||
const DisableSizeConstraintsMethod = 3;
|
||||
const EnableSizeConstraintsMethod = 4;
|
||||
const FocusMethod = 5;
|
||||
const ForceReloadMethod = 6;
|
||||
const FullscreenMethod = 7;
|
||||
const GetScreenMethod = 8;
|
||||
const GetZoomMethod = 9;
|
||||
const HeightMethod = 10;
|
||||
const HideMethod = 11;
|
||||
const IsFocusedMethod = 12;
|
||||
const IsFullscreenMethod = 13;
|
||||
const IsMaximisedMethod = 14;
|
||||
const IsMinimisedMethod = 15;
|
||||
const MaximiseMethod = 16;
|
||||
const MinimiseMethod = 17;
|
||||
const NameMethod = 18;
|
||||
const OpenDevToolsMethod = 19;
|
||||
const RelativePositionMethod = 20;
|
||||
const ReloadMethod = 21;
|
||||
const ResizableMethod = 22;
|
||||
const RestoreMethod = 23;
|
||||
const SetPositionMethod = 24;
|
||||
const SetAlwaysOnTopMethod = 25;
|
||||
const SetBackgroundColourMethod = 26;
|
||||
const SetFramelessMethod = 27;
|
||||
const SetFullscreenButtonEnabledMethod = 28;
|
||||
const SetMaxSizeMethod = 29;
|
||||
const SetMinSizeMethod = 30;
|
||||
const SetRelativePositionMethod = 31;
|
||||
const SetResizableMethod = 32;
|
||||
const SetSizeMethod = 33;
|
||||
const SetTitleMethod = 34;
|
||||
const SetZoomMethod = 35;
|
||||
const ShowMethod = 36;
|
||||
const SizeMethod = 37;
|
||||
const ToggleFullscreenMethod = 38;
|
||||
const ToggleMaximiseMethod = 39;
|
||||
const UnFullscreenMethod = 40;
|
||||
const UnMaximiseMethod = 41;
|
||||
const UnMinimiseMethod = 42;
|
||||
const WidthMethod = 43;
|
||||
const ZoomMethod = 44;
|
||||
const ZoomInMethod = 45;
|
||||
const ZoomOutMethod = 46;
|
||||
const ZoomResetMethod = 47;
|
||||
|
||||
/**
|
||||
* A record describing the position of a window.
|
||||
*/
|
||||
interface Position {
|
||||
/** The horizontal position of the window. */
|
||||
x: number;
|
||||
/** The vertical position of the window. */
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A record describing the size of a window.
|
||||
*/
|
||||
interface Size {
|
||||
/** The width of the window. */
|
||||
width: number;
|
||||
/** The height of the window. */
|
||||
height: number;
|
||||
}
|
||||
|
||||
// Private field names.
|
||||
const callerSym = Symbol("caller");
|
||||
|
||||
class Window {
|
||||
// Private fields.
|
||||
private [callerSym]: (message: number, args?: any) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Initialises a window object with the specified name.
|
||||
*
|
||||
* @private
|
||||
* @param name - The name of the target window.
|
||||
*/
|
||||
constructor(name: string = '') {
|
||||
this[callerSym] = newRuntimeCaller(objectNames.Window, name)
|
||||
|
||||
// bind instance method to make them easily usable in event handlers
|
||||
for (const method of Object.getOwnPropertyNames(Window.prototype)) {
|
||||
if (
|
||||
method !== "constructor"
|
||||
&& typeof (this as any)[method] === "function"
|
||||
) {
|
||||
(this as any)[method] = (this as any)[method].bind(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified window.
|
||||
*
|
||||
* @param name - The name of the window to get.
|
||||
* @returns The corresponding window object.
|
||||
*/
|
||||
Get(name: string): Window {
|
||||
return new Window(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute position of the window.
|
||||
*
|
||||
* @returns The current absolute position of the window.
|
||||
*/
|
||||
Position(): Promise<Position> {
|
||||
return this[callerSym](PositionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Centers the window on the screen.
|
||||
*/
|
||||
Center(): Promise<void> {
|
||||
return this[callerSym](CenterMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the window.
|
||||
*/
|
||||
Close(): Promise<void> {
|
||||
return this[callerSym](CloseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables min/max size constraints.
|
||||
*/
|
||||
DisableSizeConstraints(): Promise<void> {
|
||||
return this[callerSym](DisableSizeConstraintsMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables min/max size constraints.
|
||||
*/
|
||||
EnableSizeConstraints(): Promise<void> {
|
||||
return this[callerSym](EnableSizeConstraintsMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the window.
|
||||
*/
|
||||
Focus(): Promise<void> {
|
||||
return this[callerSym](FocusMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the window to reload the page assets.
|
||||
*/
|
||||
ForceReload(): Promise<void> {
|
||||
return this[callerSym](ForceReloadMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the window to fullscreen mode.
|
||||
*/
|
||||
Fullscreen(): Promise<void> {
|
||||
return this[callerSym](FullscreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the screen that the window is on.
|
||||
*
|
||||
* @returns The screen the window is currently on.
|
||||
*/
|
||||
GetScreen(): Promise<Screen> {
|
||||
return this[callerSym](GetScreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current zoom level of the window.
|
||||
*
|
||||
* @returns The current zoom level.
|
||||
*/
|
||||
GetZoom(): Promise<number> {
|
||||
return this[callerSym](GetZoomMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the window.
|
||||
*
|
||||
* @returns The current height of the window.
|
||||
*/
|
||||
Height(): Promise<number> {
|
||||
return this[callerSym](HeightMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the window.
|
||||
*/
|
||||
Hide(): Promise<void> {
|
||||
return this[callerSym](HideMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is focused.
|
||||
*
|
||||
* @returns Whether the window is currently focused.
|
||||
*/
|
||||
IsFocused(): Promise<boolean> {
|
||||
return this[callerSym](IsFocusedMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is fullscreen.
|
||||
*
|
||||
* @returns Whether the window is currently fullscreen.
|
||||
*/
|
||||
IsFullscreen(): Promise<boolean> {
|
||||
return this[callerSym](IsFullscreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is maximised.
|
||||
*
|
||||
* @returns Whether the window is currently maximised.
|
||||
*/
|
||||
IsMaximised(): Promise<boolean> {
|
||||
return this[callerSym](IsMaximisedMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is minimised.
|
||||
*
|
||||
* @returns Whether the window is currently minimised.
|
||||
*/
|
||||
IsMinimised(): Promise<boolean> {
|
||||
return this[callerSym](IsMinimisedMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximises the window.
|
||||
*/
|
||||
Maximise(): Promise<void> {
|
||||
return this[callerSym](MaximiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimises the window.
|
||||
*/
|
||||
Minimise(): Promise<void> {
|
||||
return this[callerSym](MinimiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the window.
|
||||
*
|
||||
* @returns The name of the window.
|
||||
*/
|
||||
Name(): Promise<string> {
|
||||
return this[callerSym](NameMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the development tools pane.
|
||||
*/
|
||||
OpenDevTools(): Promise<void> {
|
||||
return this[callerSym](OpenDevToolsMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative position of the window to the screen.
|
||||
*
|
||||
* @returns The current relative position of the window.
|
||||
*/
|
||||
RelativePosition(): Promise<Position> {
|
||||
return this[callerSym](RelativePositionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page assets.
|
||||
*/
|
||||
Reload(): Promise<void> {
|
||||
return this[callerSym](ReloadMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the window is resizable.
|
||||
*
|
||||
* @returns Whether the window is currently resizable.
|
||||
*/
|
||||
Resizable(): Promise<boolean> {
|
||||
return this[callerSym](ResizableMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the window to its previous state if it was previously minimised, maximised or fullscreen.
|
||||
*/
|
||||
Restore(): Promise<void> {
|
||||
return this[callerSym](RestoreMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the absolute position of the window.
|
||||
*
|
||||
* @param x - The desired horizontal absolute position of the window.
|
||||
* @param y - The desired vertical absolute position of the window.
|
||||
*/
|
||||
SetPosition(x: number, y: number): Promise<void> {
|
||||
return this[callerSym](SetPositionMethod, { x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the window to be always on top.
|
||||
*
|
||||
* @param alwaysOnTop - Whether the window should stay on top.
|
||||
*/
|
||||
SetAlwaysOnTop(alwaysOnTop: boolean): Promise<void> {
|
||||
return this[callerSym](SetAlwaysOnTopMethod, { alwaysOnTop });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background colour of the window.
|
||||
*
|
||||
* @param r - The desired red component of the window background.
|
||||
* @param g - The desired green component of the window background.
|
||||
* @param b - The desired blue component of the window background.
|
||||
* @param a - The desired alpha component of the window background.
|
||||
*/
|
||||
SetBackgroundColour(r: number, g: number, b: number, a: number): Promise<void> {
|
||||
return this[callerSym](SetBackgroundColourMethod, { r, g, b, a });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the window frame and title bar.
|
||||
*
|
||||
* @param frameless - Whether the window should be frameless.
|
||||
*/
|
||||
SetFrameless(frameless: boolean): Promise<void> {
|
||||
return this[callerSym](SetFramelessMethod, { frameless });
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the system fullscreen button.
|
||||
*
|
||||
* @param enabled - Whether the fullscreen button should be enabled.
|
||||
*/
|
||||
SetFullscreenButtonEnabled(enabled: boolean): Promise<void> {
|
||||
return this[callerSym](SetFullscreenButtonEnabledMethod, { enabled });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum size of the window.
|
||||
*
|
||||
* @param width - The desired maximum width of the window.
|
||||
* @param height - The desired maximum height of the window.
|
||||
*/
|
||||
SetMaxSize(width: number, height: number): Promise<void> {
|
||||
return this[callerSym](SetMaxSizeMethod, { width, height });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum size of the window.
|
||||
*
|
||||
* @param width - The desired minimum width of the window.
|
||||
* @param height - The desired minimum height of the window.
|
||||
*/
|
||||
SetMinSize(width: number, height: number): Promise<void> {
|
||||
return this[callerSym](SetMinSizeMethod, { width, height });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the relative position of the window to the screen.
|
||||
*
|
||||
* @param x - The desired horizontal relative position of the window.
|
||||
* @param y - The desired vertical relative position of the window.
|
||||
*/
|
||||
SetRelativePosition(x: number, y: number): Promise<void> {
|
||||
return this[callerSym](SetRelativePositionMethod, { x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the window is resizable.
|
||||
*
|
||||
* @param resizable - Whether the window should be resizable.
|
||||
*/
|
||||
SetResizable(resizable: boolean): Promise<void> {
|
||||
return this[callerSym](SetResizableMethod, { resizable });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the window.
|
||||
*
|
||||
* @param width - The desired width of the window.
|
||||
* @param height - The desired height of the window.
|
||||
*/
|
||||
SetSize(width: number, height: number): Promise<void> {
|
||||
return this[callerSym](SetSizeMethod, { width, height });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of the window.
|
||||
*
|
||||
* @param title - The desired title of the window.
|
||||
*/
|
||||
SetTitle(title: string): Promise<void> {
|
||||
return this[callerSym](SetTitleMethod, { title });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the zoom level of the window.
|
||||
*
|
||||
* @param zoom - The desired zoom level.
|
||||
*/
|
||||
SetZoom(zoom: number): Promise<void> {
|
||||
return this[callerSym](SetZoomMethod, { zoom });
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the window.
|
||||
*/
|
||||
Show(): Promise<void> {
|
||||
return this[callerSym](ShowMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the window.
|
||||
*
|
||||
* @returns The current size of the window.
|
||||
*/
|
||||
Size(): Promise<Size> {
|
||||
return this[callerSym](SizeMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the window between fullscreen and normal.
|
||||
*/
|
||||
ToggleFullscreen(): Promise<void> {
|
||||
return this[callerSym](ToggleFullscreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the window between maximised and normal.
|
||||
*/
|
||||
ToggleMaximise(): Promise<void> {
|
||||
return this[callerSym](ToggleMaximiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-fullscreens the window.
|
||||
*/
|
||||
UnFullscreen(): Promise<void> {
|
||||
return this[callerSym](UnFullscreenMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-maximises the window.
|
||||
*/
|
||||
UnMaximise(): Promise<void> {
|
||||
return this[callerSym](UnMaximiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-minimises the window.
|
||||
*/
|
||||
UnMinimise(): Promise<void> {
|
||||
return this[callerSym](UnMinimiseMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the window.
|
||||
*
|
||||
* @returns The current width of the window.
|
||||
*/
|
||||
Width(): Promise<number> {
|
||||
return this[callerSym](WidthMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms the window.
|
||||
*/
|
||||
Zoom(): Promise<void> {
|
||||
return this[callerSym](ZoomMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the zoom level of the webview content.
|
||||
*/
|
||||
ZoomIn(): Promise<void> {
|
||||
return this[callerSym](ZoomInMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the zoom level of the webview content.
|
||||
*/
|
||||
ZoomOut(): Promise<void> {
|
||||
return this[callerSym](ZoomOutMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the zoom level of the webview content.
|
||||
*/
|
||||
ZoomReset(): Promise<void> {
|
||||
return this[callerSym](ZoomResetMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The window within which the script is running.
|
||||
*/
|
||||
const thisWindow = new Window('');
|
||||
|
||||
export default thisWindow;
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import {OpenURL} from "./browser";
|
||||
import {Question} from "./dialogs";
|
||||
import {Emit, WailsEvent} from "./events";
|
||||
import {canAbortListeners, whenReady} from "./utils";
|
||||
import Window from "./window";
|
||||
|
||||
/**
|
||||
* Sends an event with the given name and optional data.
|
||||
*
|
||||
* @param {string} eventName - The name of the event to send.
|
||||
* @param {any} [data=null] - Optional data to send along with the event.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function sendEvent(eventName, data=null) {
|
||||
Emit(new WailsEvent(eventName, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method on a specified window.
|
||||
* @param {string} windowName - The name of the window to call the method on.
|
||||
* @param {string} methodName - The name of the method to call.
|
||||
*/
|
||||
function callWindowMethod(windowName, methodName) {
|
||||
const targetWindow = Window.Get(windowName);
|
||||
const method = targetWindow[methodName];
|
||||
|
||||
if (typeof method !== "function") {
|
||||
console.error(`Window method '${methodName}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
method.call(targetWindow);
|
||||
} catch (e) {
|
||||
console.error(`Error calling window method '${methodName}': `, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to a triggering event by running appropriate WML actions for the current target
|
||||
*
|
||||
* @param {Event} ev
|
||||
* @return {void}
|
||||
*/
|
||||
function onWMLTriggered(ev) {
|
||||
const element = ev.currentTarget;
|
||||
|
||||
function runEffect(choice = "Yes") {
|
||||
if (choice !== "Yes")
|
||||
return;
|
||||
|
||||
const eventType = element.getAttribute('data-wml-event');
|
||||
const targetWindow = element.getAttribute('data-wml-target-window') || "";
|
||||
const windowMethod = element.getAttribute('data-wml-window');
|
||||
const url = element.getAttribute('data-wml-openURL');
|
||||
|
||||
if (eventType !== null)
|
||||
sendEvent(eventType);
|
||||
if (windowMethod !== null)
|
||||
callWindowMethod(targetWindow, windowMethod);
|
||||
if (url !== null)
|
||||
void OpenURL(url);
|
||||
}
|
||||
|
||||
const confirm = element.getAttribute('data-wml-confirm');
|
||||
|
||||
if (confirm) {
|
||||
Question({
|
||||
Title: "Confirm",
|
||||
Message: confirm,
|
||||
Detached: false,
|
||||
Buttons: [
|
||||
{ Label: "Yes" },
|
||||
{ Label: "No", IsDefault: true }
|
||||
]
|
||||
}).then(runEffect);
|
||||
} else {
|
||||
runEffect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {symbol}
|
||||
*/
|
||||
const controller = Symbol();
|
||||
|
||||
/**
|
||||
* AbortControllerRegistry does not actually remember active event listeners: instead
|
||||
* it ties them to an AbortSignal and uses an AbortController to remove them all at once.
|
||||
*/
|
||||
class AbortControllerRegistry {
|
||||
constructor() {
|
||||
/**
|
||||
* Stores the AbortController that can be used to remove all currently active listeners.
|
||||
*
|
||||
* @private
|
||||
* @name {@link controller}
|
||||
* @member {AbortController}
|
||||
*/
|
||||
this[controller] = new AbortController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an options object for addEventListener that ties the listener
|
||||
* to the AbortSignal from the current AbortController.
|
||||
*
|
||||
* @param {HTMLElement} element An HTML element
|
||||
* @param {string[]} triggers The list of active WML trigger events for the specified elements
|
||||
* @returns {AddEventListenerOptions}
|
||||
*/
|
||||
set(element, triggers) {
|
||||
return { signal: this[controller].signal };
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all registered event listeners.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
reset() {
|
||||
this[controller].abort();
|
||||
this[controller] = new AbortController();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {symbol}
|
||||
*/
|
||||
const triggerMap = Symbol();
|
||||
|
||||
/**
|
||||
* @type {symbol}
|
||||
*/
|
||||
const elementCount = Symbol();
|
||||
|
||||
/**
|
||||
* WeakMapRegistry maps active trigger events to each DOM element through a WeakMap.
|
||||
* This ensures that the mapping remains private to this module, while still allowing garbage
|
||||
* collection of the involved elements.
|
||||
*/
|
||||
class WeakMapRegistry {
|
||||
constructor() {
|
||||
/**
|
||||
* Stores the current element-to-trigger mapping.
|
||||
*
|
||||
* @private
|
||||
* @name {@link triggerMap}
|
||||
* @member {WeakMap<HTMLElement, string[]>}
|
||||
*/
|
||||
this[triggerMap] = new WeakMap();
|
||||
|
||||
/**
|
||||
* Counts the number of elements with active WML triggers.
|
||||
*
|
||||
* @private
|
||||
* @name {@link elementCount}
|
||||
* @member {number}
|
||||
*/
|
||||
this[elementCount] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active triggers for the specified element.
|
||||
*
|
||||
* @param {HTMLElement} element An HTML element
|
||||
* @param {string[]} triggers The list of active WML trigger events for the specified element
|
||||
* @returns {AddEventListenerOptions}
|
||||
*/
|
||||
set(element, triggers) {
|
||||
this[elementCount] += !this[triggerMap].has(element);
|
||||
this[triggerMap].set(element, triggers);
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all registered event listeners.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
reset() {
|
||||
if (this[elementCount] <= 0)
|
||||
return;
|
||||
|
||||
for (const element of document.body.querySelectorAll('*')) {
|
||||
if (this[elementCount] <= 0)
|
||||
break;
|
||||
|
||||
const triggers = this[triggerMap].get(element);
|
||||
this[elementCount] -= (typeof triggers !== "undefined");
|
||||
|
||||
for (const trigger of triggers || [])
|
||||
element.removeEventListener(trigger, onWMLTriggered);
|
||||
}
|
||||
|
||||
this[triggerMap] = new WeakMap();
|
||||
this[elementCount] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const triggerRegistry = canAbortListeners() ? new AbortControllerRegistry() : new WeakMapRegistry();
|
||||
|
||||
/**
|
||||
* Adds event listeners to the specified element.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @return {void}
|
||||
*/
|
||||
function addWMLListeners(element) {
|
||||
const triggerRegExp = /\S+/g;
|
||||
const triggerAttr = (element.getAttribute('data-wml-trigger') || "click");
|
||||
const triggers = [];
|
||||
|
||||
let match;
|
||||
while ((match = triggerRegExp.exec(triggerAttr)) !== null)
|
||||
triggers.push(match[0]);
|
||||
|
||||
const options = triggerRegistry.set(element, triggers);
|
||||
for (const trigger of triggers)
|
||||
element.addEventListener(trigger, onWMLTriggered, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules an automatic reload of WML to be performed as soon as the document is fully loaded.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
export function Enable() {
|
||||
whenReady(Reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the WML page by adding necessary event listeners and browser listeners.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
export function Reload() {
|
||||
triggerRegistry.reset();
|
||||
document.body.querySelectorAll('[data-wml-event], [data-wml-window], [data-wml-openURL]').forEach(addWMLListeners);
|
||||
}
|
||||
209
v3/internal/runtime/desktop/@wailsio/runtime/src/wml.ts
Normal file
209
v3/internal/runtime/desktop/@wailsio/runtime/src/wml.ts
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
import { OpenURL } from "./browser.js";
|
||||
import { Question } from "./dialogs.js";
|
||||
import { Emit, WailsEvent } from "./events.js";
|
||||
import { canAbortListeners, whenReady } from "./utils.js";
|
||||
import Window from "./window.js";
|
||||
|
||||
/**
|
||||
* Sends an event with the given name and optional data.
|
||||
*
|
||||
* @param eventName - - The name of the event to send.
|
||||
* @param [data=null] - - Optional data to send along with the event.
|
||||
*/
|
||||
function sendEvent(eventName: string, data: any = null): void {
|
||||
Emit(new WailsEvent(eventName, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a method on a specified window.
|
||||
*
|
||||
* @param windowName - The name of the window to call the method on.
|
||||
* @param methodName - The name of the method to call.
|
||||
*/
|
||||
function callWindowMethod(windowName: string, methodName: string) {
|
||||
const targetWindow = Window.Get(windowName);
|
||||
const method = (targetWindow as any)[methodName];
|
||||
|
||||
if (typeof method !== "function") {
|
||||
console.error(`Window method '${methodName}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
method.call(targetWindow);
|
||||
} catch (e) {
|
||||
console.error(`Error calling window method '${methodName}': `, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to a triggering event by running appropriate WML actions for the current target.
|
||||
*/
|
||||
function onWMLTriggered(ev: Event): void {
|
||||
const element = ev.currentTarget as Element;
|
||||
|
||||
function runEffect(choice = "Yes") {
|
||||
if (choice !== "Yes")
|
||||
return;
|
||||
|
||||
const eventType = element.getAttribute('wml-event') || element.getAttribute('data-wml-event');
|
||||
const targetWindow = element.getAttribute('wml-target-window') || element.getAttribute('data-wml-target-window') || "";
|
||||
const windowMethod = element.getAttribute('wml-window') || element.getAttribute('data-wml-window');
|
||||
const url = element.getAttribute('wml-openurl') || element.getAttribute('data-wml-openurl');
|
||||
|
||||
if (eventType !== null)
|
||||
sendEvent(eventType);
|
||||
if (windowMethod !== null)
|
||||
callWindowMethod(targetWindow, windowMethod);
|
||||
if (url !== null)
|
||||
void OpenURL(url);
|
||||
}
|
||||
|
||||
const confirm = element.getAttribute('wml-confirm') || element.getAttribute('data-wml-confirm');
|
||||
|
||||
if (confirm) {
|
||||
Question({
|
||||
Title: "Confirm",
|
||||
Message: confirm,
|
||||
Detached: false,
|
||||
Buttons: [
|
||||
{ Label: "Yes" },
|
||||
{ Label: "No", IsDefault: true }
|
||||
]
|
||||
}).then(runEffect);
|
||||
} else {
|
||||
runEffect();
|
||||
}
|
||||
}
|
||||
|
||||
// Private field names.
|
||||
const controllerSym = Symbol("controller");
|
||||
const triggerMapSym = Symbol("triggerMap");
|
||||
const elementCountSym = Symbol("elementCount");
|
||||
|
||||
/**
|
||||
* AbortControllerRegistry does not actually remember active event listeners: instead
|
||||
* it ties them to an AbortSignal and uses an AbortController to remove them all at once.
|
||||
*/
|
||||
class AbortControllerRegistry {
|
||||
// Private fields.
|
||||
[controllerSym]: AbortController;
|
||||
|
||||
constructor() {
|
||||
this[controllerSym] = new AbortController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an options object for addEventListener that ties the listener
|
||||
* to the AbortSignal from the current AbortController.
|
||||
*
|
||||
* @param element - An HTML element
|
||||
* @param triggers - The list of active WML trigger events for the specified elements
|
||||
*/
|
||||
set(element: Element, triggers: string[]): AddEventListenerOptions {
|
||||
return { signal: this[controllerSym].signal };
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all registered event listeners and resets the registry.
|
||||
*/
|
||||
reset(): void {
|
||||
this[controllerSym].abort();
|
||||
this[controllerSym] = new AbortController();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WeakMapRegistry maps active trigger events to each DOM element through a WeakMap.
|
||||
* This ensures that the mapping remains private to this module, while still allowing garbage
|
||||
* collection of the involved elements.
|
||||
*/
|
||||
class WeakMapRegistry {
|
||||
/** Stores the current element-to-trigger mapping. */
|
||||
[triggerMapSym]: WeakMap<Element, string[]>;
|
||||
/** Counts the number of elements with active WML triggers. */
|
||||
[elementCountSym]: number;
|
||||
|
||||
constructor() {
|
||||
this[triggerMapSym] = new WeakMap();
|
||||
this[elementCountSym] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets active triggers for the specified element.
|
||||
*
|
||||
* @param element - An HTML element
|
||||
* @param triggers - The list of active WML trigger events for the specified element
|
||||
*/
|
||||
set(element: Element, triggers: string[]): AddEventListenerOptions {
|
||||
if (!this[triggerMapSym].has(element)) { this[elementCountSym]++; }
|
||||
this[triggerMapSym].set(element, triggers);
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all registered event listeners.
|
||||
*/
|
||||
reset(): void {
|
||||
if (this[elementCountSym] <= 0)
|
||||
return;
|
||||
|
||||
for (const element of document.body.querySelectorAll('*')) {
|
||||
if (this[elementCountSym] <= 0)
|
||||
break;
|
||||
|
||||
const triggers = this[triggerMapSym].get(element);
|
||||
if (triggers != null) { this[elementCountSym]--; }
|
||||
|
||||
for (const trigger of triggers || [])
|
||||
element.removeEventListener(trigger, onWMLTriggered);
|
||||
}
|
||||
|
||||
this[triggerMapSym] = new WeakMap();
|
||||
this[elementCountSym] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const triggerRegistry = canAbortListeners() ? new AbortControllerRegistry() : new WeakMapRegistry();
|
||||
|
||||
/**
|
||||
* Adds event listeners to the specified element.
|
||||
*/
|
||||
function addWMLListeners(element: Element): void {
|
||||
const triggerRegExp = /\S+/g;
|
||||
const triggerAttr = (element.getAttribute('wml-trigger') || element.getAttribute('data-wml-trigger') || "click");
|
||||
const triggers: string[] = [];
|
||||
|
||||
let match;
|
||||
while ((match = triggerRegExp.exec(triggerAttr)) !== null)
|
||||
triggers.push(match[0]);
|
||||
|
||||
const options = triggerRegistry.set(element, triggers);
|
||||
for (const trigger of triggers)
|
||||
element.addEventListener(trigger, onWMLTriggered, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules an automatic reload of WML to be performed as soon as the document is fully loaded.
|
||||
*/
|
||||
export function Enable(): void {
|
||||
whenReady(Reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the WML page by adding necessary event listeners and browser listeners.
|
||||
*/
|
||||
export function Reload(): void {
|
||||
triggerRegistry.reset();
|
||||
document.body.querySelectorAll('[wml-event], [wml-window], [wml-openurl], [data-wml-event], [data-wml-window], [data-wml-openurl]').forEach(addWMLListeners);
|
||||
}
|
||||
|
|
@ -1,20 +1,36 @@
|
|||
{
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["./src/**/*.test.*"],
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"composite": true,
|
||||
|
||||
"allowJs": false,
|
||||
|
||||
"noEmitOnError": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"declarationMap": false,
|
||||
"declarationDir": "types",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
|
||||
"target": "ES2015",
|
||||
"module": "ES2015",
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"stripInternal": true,
|
||||
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
|
||||
"strict": true,
|
||||
"target": "es6",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,12 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var eventsGo = `package events
|
||||
const eventsGo = `package events
|
||||
|
||||
type ApplicationEventType uint
|
||||
type WindowEventType uint
|
||||
|
|
@ -64,7 +61,7 @@ $$EVENTTOJS}
|
|||
|
||||
`
|
||||
|
||||
var darwinEventsH = `//go:build darwin
|
||||
const darwinEventsH = `//go:build darwin
|
||||
|
||||
#ifndef _events_h
|
||||
#define _events_h
|
||||
|
|
@ -76,7 +73,7 @@ $$CHEADEREVENTS
|
|||
|
||||
#endif`
|
||||
|
||||
var linuxEventsH = `//go:build linux
|
||||
const linuxEventsH = `//go:build linux
|
||||
|
||||
#ifndef _events_h
|
||||
#define _events_h
|
||||
|
|
@ -88,34 +85,32 @@ $$CHEADEREVENTS
|
|||
|
||||
#endif`
|
||||
|
||||
var eventsJS = `
|
||||
export const EventTypes = {
|
||||
Windows: {
|
||||
$$WINDOWSJSEVENTS },
|
||||
Mac: {
|
||||
$$MACJSEVENTS },
|
||||
Linux: {
|
||||
$$LINUXJSEVENTS },
|
||||
Common: {
|
||||
$$COMMONJSEVENTS },
|
||||
};
|
||||
`
|
||||
const eventsTS = `/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ ` + "`" + `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
var eventsTS = `
|
||||
export declare const EventTypes: {
|
||||
Windows: {
|
||||
$$WINDOWSTSEVENTS },
|
||||
Mac: {
|
||||
$$MACTSEVENTS },
|
||||
Linux: {
|
||||
$$LINUXTSEVENTS },
|
||||
Common: {
|
||||
$$COMMONTSEVENTS },
|
||||
};
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export const Types = Object.freeze({
|
||||
Windows: Object.freeze({
|
||||
$$WINDOWSJSEVENTS }),
|
||||
Mac: Object.freeze({
|
||||
$$MACJSEVENTS }),
|
||||
Linux: Object.freeze({
|
||||
$$LINUXJSEVENTS }),
|
||||
Common: Object.freeze({
|
||||
$$COMMONJSEVENTS }),
|
||||
});
|
||||
`
|
||||
|
||||
func main() {
|
||||
|
||||
eventNames, err := os.ReadFile("../../pkg/events/events.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -138,11 +133,6 @@ func main() {
|
|||
commonEventsDecl := bytes.NewBufferString("")
|
||||
commonEventsValues := bytes.NewBufferString("")
|
||||
|
||||
linuxJSEvents := bytes.NewBufferString("")
|
||||
macJSEvents := bytes.NewBufferString("")
|
||||
windowsJSEvents := bytes.NewBufferString("")
|
||||
commonJSEvents := bytes.NewBufferString("")
|
||||
|
||||
linuxTSEvents := bytes.NewBufferString("")
|
||||
macTSEvents := bytes.NewBufferString("")
|
||||
windowsTSEvents := bytes.NewBufferString("")
|
||||
|
|
@ -197,8 +187,7 @@ func main() {
|
|||
}
|
||||
linuxEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
|
||||
linuxEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
|
||||
linuxJSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
linuxTSEvents.WriteString("\t\t" + event + ": string,\n")
|
||||
linuxTSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
maxLinuxEvents = id
|
||||
linuxCHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
|
||||
|
|
@ -212,8 +201,7 @@ func main() {
|
|||
}
|
||||
macEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
|
||||
macEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
|
||||
macJSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
macTSEvents.WriteString("\t\t" + event + ": string,\n")
|
||||
macTSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
macCHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
|
||||
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
maxMacEvents = id
|
||||
|
|
@ -261,8 +249,7 @@ func main() {
|
|||
}
|
||||
commonEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
|
||||
commonEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
|
||||
commonJSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
commonTSEvents.WriteString("\t\t" + event + ": string,\n")
|
||||
commonTSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
case "windows":
|
||||
eventType := "ApplicationEventType"
|
||||
|
|
@ -274,8 +261,7 @@ func main() {
|
|||
}
|
||||
windowsEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
|
||||
windowsEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
|
||||
windowsJSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
windowsTSEvents.WriteString("\t\t" + event + ": string,\n")
|
||||
windowsTSEvents.WriteString("\t\t" + event + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"" + strings.TrimSpace(string(line)) + "\",\n")
|
||||
}
|
||||
}
|
||||
|
|
@ -299,22 +285,12 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
// Save the eventsJS template substituting the values and decls
|
||||
templateToWrite = strings.ReplaceAll(eventsJS, "$$MACJSEVENTS", macJSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSJSEVENTS", windowsJSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$LINUXJSEVENTS", linuxJSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONJSEVENTS", commonJSEvents.String())
|
||||
err = os.WriteFile("../../internal/runtime/desktop/@wailsio/runtime/src/event_types.js", []byte(templateToWrite), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Save the eventsTS template substituting the values and decls
|
||||
templateToWrite = strings.ReplaceAll(eventsTS, "$$MACTSEVENTS", macTSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSTSEVENTS", windowsTSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$LINUXTSEVENTS", linuxTSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONTSEVENTS", commonTSEvents.String())
|
||||
err = os.WriteFile("../../internal/runtime/desktop/@wailsio/runtime/types/event_types.d.ts", []byte(templateToWrite), 0644)
|
||||
templateToWrite = strings.ReplaceAll(eventsTS, "$$MACJSEVENTS", macTSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSJSEVENTS", windowsTSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$LINUXJSEVENTS", linuxTSEvents.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONJSEVENTS", commonTSEvents.String())
|
||||
err = os.WriteFile("../../internal/runtime/desktop/@wailsio/runtime/src/event_types.ts", []byte(templateToWrite), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -402,36 +378,4 @@ func main() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Load the runtime package.json
|
||||
packageJsonFilename := "../../internal/runtime/desktop/@wailsio/runtime/package.json"
|
||||
packageJSON, err := os.ReadFile(packageJsonFilename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version := gjson.Get(string(packageJSON), "version").String()
|
||||
// Parse and increment version
|
||||
v := semver.MustParse(version)
|
||||
prerelease := v.Prerelease()
|
||||
// Split the prerelease by the "." and increment the last part by 1
|
||||
parts := strings.Split(prerelease, ".")
|
||||
prereleaseDigits, err := strconv.Atoi(parts[len(parts)-1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
prereleaseNumber := strconv.Itoa(prereleaseDigits + 1)
|
||||
parts[len(parts)-1] = prereleaseNumber
|
||||
prerelease = strings.Join(parts, ".")
|
||||
newVersion, err := v.SetPrerelease(prerelease)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Set new version using sjson
|
||||
newJSON, err := sjson.Set(string(packageJSON), "version", newVersion.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(packageJsonFilename, []byte(newJSON), 0644)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue