Browse Source

Add ESLint to the project

pull/9028/head
Max Katz 3 years ago
parent
commit
185dd96a17
  1. 47
      src/Web/Avalonia.Web/webapp/.eslintrc.json
  2. 8
      src/Web/Avalonia.Web/webapp/build.js
  3. 11
      src/Web/Avalonia.Web/webapp/modules/avalonia.ts
  4. 137
      src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
  5. 22
      src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts
  6. 11
      src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
  7. 51
      src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
  8. 4
      src/Web/Avalonia.Web/webapp/modules/storage.ts
  9. 2330
      src/Web/Avalonia.Web/webapp/package-lock.json
  10. 15
      src/Web/Avalonia.Web/webapp/package.json

47
src/Web/Avalonia.Web/webapp/.eslintrc.json

@ -0,0 +1,47 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "standard-with-typescript",
"overrides": [],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": [
"tsconfig.json"
]
},
"rules": {
"indent": [
"warn",
4
],
"@typescript-eslint/indent": [
"warn",
4
],
"quotes": ["warn", "double"],
"semi": ["error", "always"],
"@typescript-eslint/quotes": ["warn", "double"],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-extraneous-class": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/semi": ["error", "always"],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
]
},
"ignorePatterns": ["types/*"]
}

8
src/Web/Avalonia.Web/webapp/build.js

@ -10,7 +10,7 @@ require("esbuild").build({
target: "es2016",
platform: "browser",
sourcemap: "linked",
loader: {".ts": "ts"}
})
.then(() => console.log("⚡ Done"))
.catch(() => process.exit(1));
loader: { ".ts": "ts" }
})
.then(() => console.log("⚡ Done"))
.catch(() => process.exit(1));

11
src/Web/Avalonia.Web/webapp/modules/avalonia.ts

@ -1,10 +1,8 @@
import { RuntimeAPI } from "../types/dotnet";
import { SizeWatcher } from "./avalonia/canvas";
import { DpiWatcher } from "./avalonia/canvas";
import { Canvas } from "./avalonia/canvas";
import { RuntimeAPI } from "../types/dotnet";
import { SizeWatcher, DpiWatcher, Canvas } from "./avalonia/canvas";
import { InputHelper } from "./avalonia/input";
import { AvaloniaDOM } from "./avalonia/dom";
import { CaretHelper } from "./avalonia/CaretHelper"
export async function createAvaloniaRuntime(api: RuntimeAPI): Promise<void> {
api.setModuleImports("avalonia.ts", {
@ -12,7 +10,6 @@ export async function createAvaloniaRuntime(api: RuntimeAPI): Promise<void> {
InputHelper,
SizeWatcher,
DpiWatcher,
AvaloniaDOM,
CaretHelper
AvaloniaDOM
});
}

137
src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts

@ -1,6 +1,4 @@
declare let Module: EmscriptenModule;
type SKGLViewInfo = {
interface SKGLViewInfo {
context: WebGLRenderingContext | WebGL2RenderingContext | undefined;
fboId: number;
stencil: number;
@ -9,8 +7,8 @@ type SKGLViewInfo = {
}
type CanvasElement = {
Canvas: Canvas | undefined
} & HTMLCanvasElement
Canvas: Canvas | undefined;
} & HTMLCanvasElement;
export class Canvas {
static elements: Map<string, HTMLCanvasElement>;
@ -24,22 +22,24 @@ export class Canvas {
newHeight?: number;
public static initGL(element: HTMLCanvasElement, elementId: string, renderFrameCallback: () => void): SKGLViewInfo | null {
var view = Canvas.init(true, element, elementId, renderFrameCallback);
if (!view || !view.glInfo)
const view = Canvas.init(true, element, elementId, renderFrameCallback);
if (!view || !view.glInfo) {
return null;
}
return view.glInfo;
}
static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, renderFrameCallback: () => void): Canvas | null {
var htmlCanvas = element as CanvasElement;
const htmlCanvas = element as CanvasElement;
if (!htmlCanvas) {
console.error(`No canvas element was provided.`);
console.error("No canvas element was provided.");
return null;
}
if (!Canvas.elements)
if (!Canvas.elements) {
Canvas.elements = new Map<string, HTMLCanvasElement>();
}
Canvas.elements.set(elementId, element);
const view = new Canvas(useGL, element, renderFrameCallback);
@ -56,16 +56,16 @@ export class Canvas {
if (useGL) {
const ctx = Canvas.createWebGLContext(element);
if (!ctx) {
console.error(`Failed to create WebGL context: err ${ctx}`);
console.error("Failed to create WebGL context");
return;
}
var GL = (globalThis as any).AvaloniaGL;
const GL = (globalThis as any).AvaloniaGL;
// make current
GL.makeContextCurrent(ctx);
var GLctx = GL.currentContext.GLctx as WebGLRenderingContext;
const GLctx = GL.currentContext.GLctx as WebGLRenderingContext;
// read values
const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING);
@ -75,17 +75,17 @@ export class Canvas {
fboId: fbo ? fbo.id : 0,
stencil: GLctx.getParameter(GLctx.STENCIL_BITS),
sample: 0, // TODO: GLctx.getParameter(GLctx.SAMPLES)
depth: GLctx.getParameter(GLctx.DEPTH_BITS),
depth: GLctx.getParameter(GLctx.DEPTH_BITS)
};
}
}
public setEnableRenderLoop(enable: boolean) {
public setEnableRenderLoop(enable: boolean): void {
this.renderLoopEnabled = enable;
// either start the new frame or cancel the existing one
if (enable) {
//console.info(`Enabling render loop with callback ${this.renderFrameCallback._id}...`);
// console.info(`Enabling render loop with callback ${this.renderFrameCallback._id}...`);
this.requestAnimationFrame();
} else if (this.renderLoopRequest !== 0) {
window.cancelAnimationFrame(this.renderLoopRequest);
@ -93,71 +93,76 @@ export class Canvas {
}
}
public requestAnimationFrame(renderLoop?: boolean) {
public requestAnimationFrame(renderLoop?: boolean): void {
// optionally update the render loop
if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop)
if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) {
this.setEnableRenderLoop(renderLoop);
}
// skip because we have a render loop
if (this.renderLoopRequest !== 0)
if (this.renderLoopRequest !== 0) {
return;
}
// add the draw to the next frame
this.renderLoopRequest = window.requestAnimationFrame(() => {
if (this.glInfo) {
var GL = (globalThis as any).AvaloniaGL;
const GL = (globalThis as any).AvaloniaGL;
// make current
GL.makeContextCurrent(this.glInfo.context);
}
if (this.htmlCanvas.width != this.newWidth) {
this.htmlCanvas.width = this.newWidth || 0;
if (this.htmlCanvas.width !== this.newWidth) {
this.htmlCanvas.width = this.newWidth ?? 0;
}
if (this.htmlCanvas.height != this.newHeight) {
this.htmlCanvas.height = this.newHeight || 0;
if (this.htmlCanvas.height !== this.newHeight) {
this.htmlCanvas.height = this.newHeight ?? 0;
}
this.renderFrameCallback();
this.renderLoopRequest = 0;
// we may want to draw the next frame
if (this.renderLoopEnabled)
if (this.renderLoopEnabled) {
this.requestAnimationFrame();
}
});
}
public setCanvasSize(width: number, height: number) {
public setCanvasSize(width: number, height: number): void {
this.newWidth = width;
this.newHeight = height;
if (this.htmlCanvas.width != this.newWidth) {
if (this.htmlCanvas.width !== this.newWidth) {
this.htmlCanvas.width = this.newWidth;
}
if (this.htmlCanvas.height != this.newHeight) {
if (this.htmlCanvas.height !== this.newHeight) {
this.htmlCanvas.height = this.newHeight;
}
if (this.glInfo) {
var GL = (globalThis as any).AvaloniaGL;
const GL = (globalThis as any).AvaloniaGL;
// make current
GL.makeContextCurrent(this.glInfo.context);
}
}
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number) {
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number): void {
const htmlCanvas = element as CanvasElement;
if (!htmlCanvas || !htmlCanvas.Canvas)
if (!htmlCanvas || !htmlCanvas.Canvas) {
return;
}
htmlCanvas.Canvas.setCanvasSize(width, height);
}
public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) {
public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean): void {
const htmlCanvas = element as CanvasElement;
if (!htmlCanvas || !htmlCanvas.Canvas)
if (!htmlCanvas || !htmlCanvas.Canvas) {
return;
}
htmlCanvas.Canvas.requestAnimationFrame(renderLoop);
}
@ -176,15 +181,15 @@ export class Canvas {
minorVersion: 0,
enableExtensionsByDefault: 1,
explicitSwapControl: 0,
renderViaOffscreenBackBuffer: 1,
renderViaOffscreenBackBuffer: 1
};
var GL = (globalThis as any).AvaloniaGL;
const GL = (globalThis as any).AvaloniaGL;
let ctx: WebGLRenderingContext = GL.createContext(htmlCanvas, contextAttributes);
if (!ctx && contextAttributes.majorVersion > 1) {
console.warn('Falling back to WebGL 1.0');
console.warn("Falling back to WebGL 1.0");
contextAttributes.majorVersion = 1;
contextAttributes.minorVersion = 0;
ctx = GL.createContext(htmlCanvas, contextAttributes);
@ -196,9 +201,9 @@ export class Canvas {
type SizeWatcherElement = {
SizeWatcher: SizeWatcherInstance;
} & HTMLElement
} & HTMLElement;
type SizeWatcherInstance = {
interface SizeWatcherInstance {
callback: (width: number, height: number) => void;
}
@ -206,17 +211,16 @@ export class SizeWatcher {
static observer: ResizeObserver;
static elements: Map<string, HTMLElement>;
public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void) {
if (!element || !callback)
public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void): void {
if (!element || !callback) {
return;
//console.info(`Adding size watcher observation with callback ${callback._id}...`);
}
SizeWatcher.init();
const watcherElement = element as SizeWatcherElement;
watcherElement.SizeWatcher = {
callback: callback
callback
};
SizeWatcher.elements.set(elementId, element);
@ -225,38 +229,38 @@ export class SizeWatcher {
SizeWatcher.invoke(element);
}
public static unobserve(elementId: string) {
if (!elementId || !SizeWatcher.observer)
public static unobserve(elementId: string): void {
if (!elementId || !SizeWatcher.observer) {
return;
}
//console.info('Removing size watcher observation...');
const element = SizeWatcher.elements.get(elementId)!;
SizeWatcher.elements.delete(elementId);
SizeWatcher.observer.unobserve(element);
const element = SizeWatcher.elements.get(elementId);
if (element) {
SizeWatcher.elements.delete(elementId);
SizeWatcher.observer.unobserve(element);
}
}
static init() {
if (SizeWatcher.observer)
static init(): void {
if (SizeWatcher.observer) {
return;
//console.info('Starting size watcher...');
}
SizeWatcher.elements = new Map<string, HTMLElement>();
SizeWatcher.observer = new ResizeObserver((entries) => {
for (let entry of entries) {
for (const entry of entries) {
SizeWatcher.invoke(entry.target);
}
});
}
static invoke(element: Element) {
static invoke(element: Element): void {
const watcherElement = element as SizeWatcherElement;
const instance = watcherElement.SizeWatcher;
if (!instance || !instance.callback)
if (!instance || !instance.callback) {
return;
}
return instance.callback(element.clientWidth, element.clientHeight);
}
@ -267,13 +271,11 @@ export class DpiWatcher {
static timerId: number;
static callback: (old: number, newdpi: number) => void;
public static getDpi() {
public static getDpi(): number {
return window.devicePixelRatio;
}
public static start(callback: (old: number, newdpi: number) => void) : number {
//console.info(`Starting DPI watcher with callback ${callback._id}...`);
public static start(callback: (old: number, newdpi: number) => void): number {
DpiWatcher.lastDpi = window.devicePixelRatio;
DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000);
DpiWatcher.callback = callback;
@ -281,17 +283,14 @@ export class DpiWatcher {
return DpiWatcher.lastDpi;
}
public static stop() {
//console.info(`Stopping DPI watcher with callback ${DpiWatcher.callback._id}...`);
public static stop(): void {
window.clearInterval(DpiWatcher.timerId);
//DpiWatcher.callback = undefined;
}
static update() {
if (!DpiWatcher.callback)
static update(): void {
if (!DpiWatcher.callback) {
return;
}
const currentDpi = window.devicePixelRatio;
const lastDpi = DpiWatcher.lastDpi;

22
src/Web/Avalonia.Web/webapp/modules/avalonia/CaretHelper.ts → src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts

@ -1,4 +1,4 @@
// Based on https://github.com/component/textarea-caret-position/blob/master/index.js
// Based on https://github.com/component/textarea-caret-position/blob/master/index.js
export class CaretHelper {
public static getCaretCoordinates(
element: HTMLInputElement | HTMLTextAreaElement,
@ -11,7 +11,7 @@ export class CaretHelper {
);
}
const debug = (options && options.debug) || false;
const debug = options?.debug ?? false;
if (debug) {
const el = document.querySelector(
"#input-textarea-caret-position-mirror-div"
@ -27,7 +27,7 @@ export class CaretHelper {
const style = div.style;
const computed = window.getComputedStyle
? window.getComputedStyle(element)
: ((element as any)["currentStyle"] as CSSStyleDeclaration); // currentStyle for IE < 9
: ((element as any).currentStyle as CSSStyleDeclaration); // currentStyle for IE < 9
const isInput = element.nodeName === "INPUT";
// Default textarea styles
@ -51,7 +51,7 @@ export class CaretHelper {
parseInt(computed.borderBottomWidth);
const targetHeight = outerHeight + parseInt(computed.lineHeight);
if (height > targetHeight) {
style.lineHeight = height - outerHeight + "px";
style.lineHeight = `${height - outerHeight}px`;
} else if (height === targetHeight) {
style.lineHeight = computed.lineHeight;
} else {
@ -67,8 +67,9 @@ export class CaretHelper {
if (isFirefox) {
// Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
if (element.scrollHeight > parseInt(computed.height))
if (element.scrollHeight > parseInt(computed.height)) {
style.overflowY = "scroll";
}
} else {
style.overflow = "hidden"; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
}
@ -88,9 +89,9 @@ export class CaretHelper {
div.appendChild(span);
const coordinates = {
top: span.offsetTop + parseInt(computed["borderTopWidth"]),
left: span.offsetLeft + parseInt(computed["borderLeftWidth"]),
height: parseInt(computed["lineHeight"]),
top: span.offsetTop + parseInt(computed.borderTopWidth),
left: span.offsetLeft + parseInt(computed.borderLeftWidth),
height: parseInt(computed.lineHeight)
};
if (debug) {
@ -103,8 +104,7 @@ export class CaretHelper {
}
}
var properties = [
const properties = [
"direction", // RTL support
"boxSizing",
"width", // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
@ -142,7 +142,7 @@ var properties = [
"wordSpacing",
"tabSize",
"MozTabSize",
"MozTabSize"
];
const isBrowser = typeof window !== "undefined";

11
src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts

@ -1,6 +1,5 @@
export class AvaloniaDOM {
public static addClass(element: HTMLElement, className: string) {
public static addClass(element: HTMLElement, className: string): void {
element.classList.add(className);
}
@ -12,15 +11,15 @@ export class AvaloniaDOM {
// Rendering target canvas
const canvas = document.createElement("canvas");
canvas.classList.add('avalonia-canvas');
canvas.classList.add("avalonia-canvas");
canvas.style.backgroundColor = "#ccc";
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.position = "absolute";
// Native controls host
const nativeHost = document.createElement("div");
nativeHost.classList.add('avalonia-native-host');
nativeHost.classList.add("avalonia-native-host");
nativeHost.style.left = "0px";
nativeHost.style.top = "0px";
nativeHost.style.width = "100%";
@ -29,7 +28,7 @@ export class AvaloniaDOM {
// IME
const inputElement = document.createElement("input");
inputElement.classList.add('avalonia-input-element');
inputElement.classList.add("avalonia-input-element");
inputElement.autocapitalize = "none";
inputElement.type = "text";
inputElement.spellcheck = false;

51
src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts

@ -1,7 +1,6 @@
import { CaretHelper } from "./CaretHelper"
import { CaretHelper } from "./caretHelper";
enum RawInputModifiers
{
enum RawInputModifiers {
None = 0,
Alt = 1,
Control = 2,
@ -24,12 +23,7 @@ export class InputHelper {
public static subscribeKeyEvents(
element: HTMLInputElement,
keyDownCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean,
keyUpCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean,
inputCallback: (type: string, data: string|null) => boolean,
compositionStartCallback: (args: CompositionEvent) => boolean,
compositionUpdateCallback: (args: CompositionEvent) => boolean,
compositionEndCallback: (args: CompositionEvent) => boolean)
{
keyUpCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean) {
const keyDownHandler = (args: KeyboardEvent) => {
if (keyDownCallback(args.code, args.key, this.getModifiers(args))) {
args.preventDefault();
@ -52,14 +46,13 @@ export class InputHelper {
}
public static subscribeTextEvents(
element: HTMLInputElement,
element: HTMLInputElement,
inputCallback: (type: string, data: string | null) => boolean,
compositionStartCallback: (args: CompositionEvent) => boolean,
compositionUpdateCallback: (args: CompositionEvent) => boolean,
compositionEndCallback: (args: CompositionEvent) => boolean) {
const inputHandler = (args: Event) => {
var inputEvent = args as InputEvent;
const inputEvent = args as InputEvent;
// todo check cast
if (inputCallback(inputEvent.type, inputEvent.data)) {
@ -102,31 +95,27 @@ export class InputHelper {
pointerMoveCallback: (args: PointerEvent) => boolean,
pointerDownCallback: (args: PointerEvent) => boolean,
pointerUpCallback: (args: PointerEvent) => boolean,
wheelCallback: (args: WheelEvent) => boolean,
wheelCallback: (args: WheelEvent) => boolean
) {
const pointerMoveHandler = (args: PointerEvent) => {
if (pointerMoveCallback(args)) {
args.preventDefault();
}
};
const pointerDownHandler = (args: PointerEvent) => {
if (pointerDownCallback(args)) {
args.preventDefault();
}
};
const pointerUpHandler = (args: PointerEvent) => {
if (pointerUpCallback(args)) {
args.preventDefault();
}
};
const wheelHandler = (args: WheelEvent) => {
if (wheelCallback(args)) {
args.preventDefault();
}
@ -137,8 +126,6 @@ export class InputHelper {
element.addEventListener("pointerup", pointerUpHandler);
element.addEventListener("wheel", wheelHandler);
return () => {
element.removeEventListener("pointerover", pointerMoveHandler);
element.removeEventListener("pointerdown", pointerDownHandler);
@ -146,7 +133,7 @@ export class InputHelper {
element.removeEventListener("wheel", wheelHandler);
};
}
public static subscribeInputEvents(
element: HTMLInputElement,
inputCallback: (value: string) => boolean
@ -179,18 +166,18 @@ export class InputHelper {
inputElement.style.left = (x).toFixed(0) + "px";
inputElement.style.top = (y).toFixed(0) + "px";
let { height, left, top } = CaretHelper.getCaretCoordinates(inputElement, caret);
const { left, top } = CaretHelper.getCaretCoordinates(inputElement, caret);
inputElement.style.left = (x - left).toFixed(0) + "px";
inputElement.style.top = (y - top).toFixed(0) + "px";
}
public static hide(inputElement: HTMLInputElement) {
inputElement.style.display = 'none';
inputElement.style.display = "none";
}
public static show(inputElement: HTMLInputElement) {
inputElement.style.display = 'block';
inputElement.style.display = "block";
}
public static setSurroundingText(inputElement: HTMLInputElement, text: string, start: number, end: number) {
@ -201,20 +188,16 @@ export class InputHelper {
inputElement.value = text;
inputElement.setSelectionRange(start, end);
inputElement.style.width = "20px";
inputElement.style.width = inputElement.scrollWidth + "px";
inputElement.style.width = `${inputElement.scrollWidth}px`;
}
private static getModifiers(args: KeyboardEvent): RawInputModifiers {
var modifiers = RawInputModifiers.None;
if (args.ctrlKey)
modifiers |= RawInputModifiers.Control;
if (args.altKey)
modifiers |= RawInputModifiers.Alt;
if (args.shiftKey)
modifiers |= RawInputModifiers.Shift;
if (args.metaKey)
modifiers |= RawInputModifiers.Meta;
let modifiers = RawInputModifiers.None;
if (args.ctrlKey) { modifiers |= RawInputModifiers.Control; }
if (args.altKey) { modifiers |= RawInputModifiers.Alt; }
if (args.shiftKey) { modifiers |= RawInputModifiers.Shift; }
if (args.metaKey) { modifiers |= RawInputModifiers.Meta; }
return modifiers;
}

4
src/Web/Avalonia.Web/webapp/modules/storage.ts

@ -1,5 +1,5 @@
export class StorageProvider {
export class StorageProvider {
static isFileApiSupported(): boolean {
return (globalThis as any).showOpenFilePicker !== undefined;
}
}
}

2330
src/Web/Avalonia.Web/webapp/package-lock.json

File diff suppressed because it is too large

15
src/Web/Avalonia.Web/webapp/package.json

@ -1,13 +1,22 @@
{
"name": "avalonia.web",
"scripts": {
"prebuild": "node ./node_modules/typescript/bin/tsc -noEmit",
"typecheck": "npx tsc -noEmit",
"eslint": "npx eslint . --fix",
"prebuild": "npm-run-all typecheck eslint",
"build": "node build.js"
},
"devDependencies": {
"@types/emscripten": "^1.39.6",
"@types/wicg-file-system-access": "^2020.9.5",
"typescript": "^4.7.4",
"esbuild": "^0.15.7"
"@typescript-eslint/eslint-plugin": "^5.38.1",
"esbuild": "^0.15.7",
"eslint": "^8.24.0",
"eslint-config-standard-with-typescript": "^23.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.3.0",
"eslint-plugin-promise": "^6.0.1",
"npm-run-all": "^4.1.5",
"typescript": "^4.8.3"
}
}

Loading…
Cancel
Save