Browse Source

Use npm and typescript without msbuild tasks

pull/8820/head
Max Katz 4 years ago
parent
commit
6b70e56baa
  1. 2
      .gitignore
  2. 33
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj
  3. 41
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.ts
  4. 23
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/InputHelper.ts
  5. 261
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts
  6. 68
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.ts
  7. 7
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/extras.d.ts
  8. 326
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/emscripten/index.d.ts
  9. 14
      src/Web/Avalonia.Web.Blazor/tsconfig.json
  10. 1
      src/Web/Avalonia.Web.Blazor/webapp/modules/Common/Common.ts
  11. 40
      src/Web/Avalonia.Web.Blazor/webapp/modules/Common/DpiWatcher.ts
  12. 79
      src/Web/Avalonia.Web.Blazor/webapp/modules/Common/IndexedDbWrapper.ts
  13. 22
      src/Web/Avalonia.Web.Blazor/webapp/modules/Common/InputHelper.ts
  14. 35
      src/Web/Avalonia.Web.Blazor/webapp/modules/Common/NativeControlHost.ts
  15. 255
      src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SKHtmlCanvas.ts
  16. 67
      src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SizeWatcher.ts
  17. 164
      src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts
  18. 14
      src/Web/Avalonia.Web.Blazor/webapp/package.json
  19. 18
      src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json
  20. 0
      src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts
  21. 0
      src/Web/Avalonia.Web.Blazor/webapp/webpack.config.js

2
.gitignore

@ -212,3 +212,5 @@ coc-settings.json
*.map
src/Web/Avalonia.Web.Blazor/wwwroot/*.js
src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js
node_modules
src/Web/Avalonia.Web.Blazor/webapp/package-lock.json

33
src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj

@ -8,31 +8,16 @@
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
<StaticWebAssetsDisableProjectBuildPropsFileGeneration>true</StaticWebAssetsDisableProjectBuildPropsFileGeneration>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
<PropertyGroup>
<TypescriptOutDir>wwwroot</TypescriptOutDir>
<TypeScriptNoEmitOnError>true</TypeScriptNoEmitOnError>
<TypeScriptNoImplicitReturns>true</TypeScriptNoImplicitReturns>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<TypeScriptRemoveComments>false</TypeScriptRemoveComments>
<TypeScriptSourceMap>true</TypeScriptSourceMap>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<TypeScriptRemoveComments>true</TypeScriptRemoveComments>
<TypeScriptSourceMap>false</TypeScriptSourceMap>
</PropertyGroup>
<Import Project="..\..\..\build\BuildTargets.targets" />
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="..\..\..\build\HarfBuzzSharp.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
<ItemGroup>
<Content Include="*.props">
<Pack>true</Pack>
@ -47,10 +32,24 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.8" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="4.7.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<Target Name="NpmInstall" Inputs="webapp/package.json" Outputs="webapp/node_modules/.install-stamp">
<Exec Command="npm install" WorkingDirectory="webapp" />
<!-- Write the stamp file, so incremental builds work -->
<Touch Files="webapp/node_modules/.install-stamp" AlwaysCreate="true" />
</Target>
<Target Name="NpmRunBuild" DependsOnTargets="NpmInstall" BeforeTargets="BeforeBuild">
<Exec Command="npm run build" WorkingDirectory="webapp" />
</Target>
</Project>

41
src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.ts

@ -1,41 +0,0 @@

export class DpiWatcher {
static lastDpi: number;
static timerId: number;
static callback: DotNet.DotNetObjectReference;
public static getDpi() {
return window.devicePixelRatio;
}
public static start(callback: DotNet.DotNetObjectReference): number {
//console.info(`Starting DPI watcher with callback ${callback._id}...`);
DpiWatcher.lastDpi = window.devicePixelRatio;
DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000);
DpiWatcher.callback = callback;
return DpiWatcher.lastDpi;
}
public static stop() {
//console.info(`Stopping DPI watcher with callback ${DpiWatcher.callback._id}...`);
window.clearInterval(DpiWatcher.timerId);
DpiWatcher.callback = undefined;
}
static update() {
if (!DpiWatcher.callback)
return;
const currentDpi = window.devicePixelRatio;
const lastDpi = DpiWatcher.lastDpi;
DpiWatcher.lastDpi = currentDpi;
if (Math.abs(lastDpi - currentDpi) > 0.001) {
DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi);
}
}
}

23
src/Web/Avalonia.Web.Blazor/Interop/Typescript/InputHelper.ts

@ -1,23 +0,0 @@

export class InputHelper {
public static clear (inputElement: HTMLInputElement){
inputElement.value = "";
}
public static focus (inputElement: HTMLInputElement){
inputElement.focus();
inputElement.setSelectionRange(0, 0);
}
public static setCursor (inputElement: HTMLInputElement, kind: string) {
inputElement.style.cursor = kind;
}
public static hide (inputElement: HTMLInputElement){
inputElement.style.display = 'none';
}
public static show (inputElement: HTMLInputElement){
inputElement.style.display = 'block';
}
}

261
src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts

@ -1,261 +0,0 @@
// aliases for emscripten
declare let GL: any;
declare let GLctx: WebGLRenderingContext;
declare let Module: EmscriptenModule;
// container for gl info
type SKGLViewInfo = {
context: WebGLRenderingContext | WebGL2RenderingContext | undefined;
fboId: number;
stencil: number;
sample: number;
depth: number;
}
// alias for a potential skia html canvas
type SKHtmlCanvasElement = {
SKHtmlCanvas: SKHtmlCanvas
} & HTMLCanvasElement
export class SKHtmlCanvas {
static elements: Map<string, HTMLCanvasElement>;
htmlCanvas: HTMLCanvasElement;
glInfo: SKGLViewInfo;
renderFrameCallback: DotNet.DotNetObjectReference;
renderLoopEnabled: boolean = false;
renderLoopRequest: number = 0;
newWidth: number;
newHeight: number;
public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): SKGLViewInfo {
var view = SKHtmlCanvas.init(true, element, elementId, callback);
if (!view || !view.glInfo)
return null;
return view.glInfo;
}
public static initRaster(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): boolean {
var view = SKHtmlCanvas.init(false, element, elementId, callback);
if (!view)
return false;
return true;
}
static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): SKHtmlCanvas {
var htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas) {
console.error(`No canvas element was provided.`);
return null;
}
if (!SKHtmlCanvas.elements)
SKHtmlCanvas.elements = new Map<string, HTMLCanvasElement>();
SKHtmlCanvas.elements[elementId] = element;
const view = new SKHtmlCanvas(useGL, element, callback);
htmlCanvas.SKHtmlCanvas = view;
return view;
}
public static deinit(elementId: string) {
if (!elementId)
return;
const element = SKHtmlCanvas.elements[elementId];
SKHtmlCanvas.elements.delete(elementId);
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.deinit();
htmlCanvas.SKHtmlCanvas = undefined;
}
public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) {
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop);
}
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number)
{
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height);
}
public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) {
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable);
}
public static putImageData(element: HTMLCanvasElement, pData: number, width: number, height: number) {
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height);
}
public constructor(useGL: boolean, element: HTMLCanvasElement, callback: DotNet.DotNetObjectReference) {
this.htmlCanvas = element;
this.renderFrameCallback = callback;
if (useGL) {
const ctx = SKHtmlCanvas.createWebGLContext(this.htmlCanvas);
if (!ctx) {
console.error(`Failed to create WebGL context: err ${ctx}`);
return null;
}
// make current
GL.makeContextCurrent(ctx);
// read values
const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING);
this.glInfo = {
context: ctx,
fboId: fbo ? fbo.id : 0,
stencil: GLctx.getParameter(GLctx.STENCIL_BITS),
sample: 0, // TODO: GLctx.getParameter(GLctx.SAMPLES)
depth: GLctx.getParameter(GLctx.DEPTH_BITS),
};
}
}
public deinit() {
this.setEnableRenderLoop(false);
}
public setCanvasSize(width: number, height: number)
{
this.newWidth = width;
this.newHeight = height;
if(this.htmlCanvas.width != this.newWidth)
{
this.htmlCanvas.width = this.newWidth;
}
if(this.htmlCanvas.height != this.newHeight)
{
this.htmlCanvas.height = this.newHeight;
}
if (this.glInfo) {
// make current
GL.makeContextCurrent(this.glInfo.context);
}
}
public requestAnimationFrame(renderLoop?: boolean) {
// optionally update the render loop
if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop)
this.setEnableRenderLoop(renderLoop);
// skip because we have a render loop
if (this.renderLoopRequest !== 0)
return;
// add the draw to the next frame
this.renderLoopRequest = window.requestAnimationFrame(() => {
if (this.glInfo) {
// make current
GL.makeContextCurrent(this.glInfo.context);
}
if(this.htmlCanvas.width != this.newWidth)
{
this.htmlCanvas.width = this.newWidth;
}
if(this.htmlCanvas.height != this.newHeight)
{
this.htmlCanvas.height = this.newHeight;
}
this.renderFrameCallback.invokeMethod('Invoke');
this.renderLoopRequest = 0;
// we may want to draw the next frame
if (this.renderLoopEnabled)
this.requestAnimationFrame();
});
}
public setEnableRenderLoop(enable: boolean) {
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}...`);
this.requestAnimationFrame();
} else if (this.renderLoopRequest !== 0) {
window.cancelAnimationFrame(this.renderLoopRequest);
this.renderLoopRequest = 0;
}
}
public putImageData(pData: number, width: number, height: number): boolean {
if (this.glInfo || !pData || width <= 0 || width <= 0)
return false;
var ctx = this.htmlCanvas.getContext('2d');
if (!ctx) {
console.error(`Failed to obtain 2D canvas context.`);
return false;
}
// make sure the canvas is scaled correctly for the drawing
this.htmlCanvas.width = width;
this.htmlCanvas.height = height;
// set the canvas to be the bytes
var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4);
var imageData = new ImageData(buffer, width, height);
ctx.putImageData(imageData, 0, 0);
return true;
}
static createWebGLContext(htmlCanvas: HTMLCanvasElement): WebGLRenderingContext | WebGL2RenderingContext {
const contextAttributes = {
alpha: 1,
depth: 1,
stencil: 8,
antialias: 0,
premultipliedAlpha: 1,
preserveDrawingBuffer: 0,
preferLowPowerToHighPerformance: 0,
failIfMajorPerformanceCaveat: 0,
majorVersion: 2,
minorVersion: 0,
enableExtensionsByDefault: 1,
explicitSwapControl: 0,
renderViaOffscreenBackBuffer: 1,
};
let ctx: WebGLRenderingContext = GL.createContext(htmlCanvas, contextAttributes);
if (!ctx && contextAttributes.majorVersion > 1) {
console.warn('Falling back to WebGL 1.0');
contextAttributes.majorVersion = 1;
contextAttributes.minorVersion = 0;
ctx = GL.createContext(htmlCanvas, contextAttributes);
}
return ctx;
}
}

68
src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.ts

@ -1,68 +0,0 @@

type SizeWatcherElement = {
SizeWatcher: SizeWatcherInstance;
} & HTMLElement
type SizeWatcherInstance = {
callback: DotNet.DotNetObjectReference;
}
export class SizeWatcher {
static observer: ResizeObserver;
static elements: Map<string, HTMLElement>;
public static observe(element: HTMLElement, elementId: string, callback: DotNet.DotNetObjectReference) {
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
};
SizeWatcher.elements[elementId] = element;
SizeWatcher.observer.observe(element);
SizeWatcher.invoke(element);
}
public static unobserve(elementId: string) {
if (!elementId || !SizeWatcher.observer)
return;
//console.info('Removing size watcher observation...');
const element = SizeWatcher.elements[elementId];
SizeWatcher.elements.delete(elementId);
SizeWatcher.observer.unobserve(element);
}
static init() {
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) {
SizeWatcher.invoke(entry.target);
}
});
}
static invoke(element: Element) {
const watcherElement = element as SizeWatcherElement;
const instance = watcherElement.SizeWatcher;
if (!instance || !instance.callback)
return;
return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight);
}
}

7
src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/extras.d.ts

@ -1,7 +0,0 @@
declare namespace DotNet {
interface DotNetObjectReference extends DotNet.DotNetObject {
_id: number;
dispose();
}
}

326
src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/emscripten/index.d.ts

@ -1,326 +0,0 @@
// Type definitions for Emscripten 1.39.16
// Project: https://emscripten.org
// Definitions by: Kensuke Matsuzaki <https://github.com/zakki>
// Periklis Tsirakidis <https://github.com/periklis>
// Bumsik Kim <https://github.com/kbumsik>
// Louis DeScioli <https://github.com/lourd>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.2
/** Other WebAssembly declarations, for compatibility with older versions of Typescript */
declare namespace WebAssembly {
interface Module {}
}
declare namespace Emscripten {
interface FileSystemType {}
type EnvironmentType = 'WEB' | 'NODE' | 'SHELL' | 'WORKER';
type JSType = 'number' | 'string' | 'array' | 'boolean';
type TypeCompatibleWithC = number | string | any[] | boolean;
type CIntType = 'i8' | 'i16' | 'i32' | 'i64';
type CFloatType = 'float' | 'double';
type CPointerType = 'i8*' | 'i16*' | 'i32*' | 'i64*' | 'float*' | 'double*' | '*';
type CType = CIntType | CFloatType | CPointerType;
type WebAssemblyImports = Array<{
name: string;
kind: string;
}>;
type WebAssemblyExports = Array<{
module: string;
name: string;
kind: string;
}>;
interface CCallOpts {
async?: boolean | undefined;
}
}
interface EmscriptenModule {
print(str: string): void;
printErr(str: string): void;
arguments: string[];
environment: Emscripten.EnvironmentType;
preInit: Array<{ (): void }>;
preRun: Array<{ (): void }>;
postRun: Array<{ (): void }>;
onAbort: { (what: any): void };
onRuntimeInitialized: { (): void };
preinitializedWebGLContext: WebGLRenderingContext;
noInitialRun: boolean;
noExitRuntime: boolean;
logReadFiles: boolean;
filePackagePrefixURL: string;
wasmBinary: ArrayBuffer;
destroy(object: object): void;
getPreloadedPackage(remotePackageName: string, remotePackageSize: number): ArrayBuffer;
instantiateWasm(
imports: Emscripten.WebAssemblyImports,
successCallback: (module: WebAssembly.Module) => void,
): Emscripten.WebAssemblyExports;
locateFile(url: string, scriptDirectory: string): string;
onCustomMessage(event: MessageEvent): void;
// USE_TYPED_ARRAYS == 1
HEAP: Int32Array;
IHEAP: Int32Array;
FHEAP: Float64Array;
// USE_TYPED_ARRAYS == 2
HEAP8: Int8Array;
HEAP16: Int16Array;
HEAP32: Int32Array;
HEAPU8: Uint8Array;
HEAPU16: Uint16Array;
HEAPU32: Uint32Array;
HEAPF32: Float32Array;
HEAPF64: Float64Array;
TOTAL_STACK: number;
TOTAL_MEMORY: number;
FAST_MEMORY: number;
addOnPreRun(cb: () => any): void;
addOnInit(cb: () => any): void;
addOnPreMain(cb: () => any): void;
addOnExit(cb: () => any): void;
addOnPostRun(cb: () => any): void;
preloadedImages: any;
preloadedAudios: any;
_malloc(size: number): number;
_free(ptr: number): void;
}
/**
* A factory function is generated when setting the `MODULARIZE` build option
* to `1` in your Emscripten build. It return a Promise that resolves to an
* initialized, ready-to-call `EmscriptenModule` instance.
*
* By default, the factory function will be named `Module`. It's recommended to
* use the `EXPORT_ES6` option, in which the factory function will be the
* default export. If used without `EXPORT_ES6`, the factory function will be a
* global variable. You can rename the variable using the `EXPORT_NAME` build
* option. It's left to you to declare any global variables as needed in your
* application's types.
* @param moduleOverrides Default properties for the initialized module.
*/
type EmscriptenModuleFactory<T extends EmscriptenModule = EmscriptenModule> = (
moduleOverrides?: Partial<T>,
) => Promise<T>;
declare namespace FS {
interface Lookup {
path: string;
node: FSNode;
}
interface FSStream {}
interface FSNode {}
interface ErrnoError {}
let ignorePermissions: boolean;
let trackingDelegate: any;
let tracking: any;
let genericErrors: any;
//
// paths
//
function lookupPath(path: string, opts: any): Lookup;
function getPath(node: FSNode): string;
//
// nodes
//
function isFile(mode: number): boolean;
function isDir(mode: number): boolean;
function isLink(mode: number): boolean;
function isChrdev(mode: number): boolean;
function isBlkdev(mode: number): boolean;
function isFIFO(mode: number): boolean;
function isSocket(mode: number): boolean;
//
// devices
//
function major(dev: number): number;
function minor(dev: number): number;
function makedev(ma: number, mi: number): number;
function registerDevice(dev: number, ops: any): void;
//
// core
//
function syncfs(populate: boolean, callback: (e: any) => any): void;
function syncfs(callback: (e: any) => any, populate?: boolean): void;
function mount(type: Emscripten.FileSystemType, opts: any, mountpoint: string): any;
function unmount(mountpoint: string): void;
function mkdir(path: string, mode?: number): any;
function mkdev(path: string, mode?: number, dev?: number): any;
function symlink(oldpath: string, newpath: string): any;
function rename(old_path: string, new_path: string): void;
function rmdir(path: string): void;
function readdir(path: string): any;
function unlink(path: string): void;
function readlink(path: string): string;
function stat(path: string, dontFollow?: boolean): any;
function lstat(path: string): any;
function chmod(path: string, mode: number, dontFollow?: boolean): void;
function lchmod(path: string, mode: number): void;
function fchmod(fd: number, mode: number): void;
function chown(path: string, uid: number, gid: number, dontFollow?: boolean): void;
function lchown(path: string, uid: number, gid: number): void;
function fchown(fd: number, uid: number, gid: number): void;
function truncate(path: string, len: number): void;
function ftruncate(fd: number, len: number): void;
function utime(path: string, atime: number, mtime: number): void;
function open(path: string, flags: string, mode?: number, fd_start?: number, fd_end?: number): FSStream;
function close(stream: FSStream): void;
function llseek(stream: FSStream, offset: number, whence: number): any;
function read(stream: FSStream, buffer: ArrayBufferView, offset: number, length: number, position?: number): number;
function write(
stream: FSStream,
buffer: ArrayBufferView,
offset: number,
length: number,
position?: number,
canOwn?: boolean,
): number;
function allocate(stream: FSStream, offset: number, length: number): void;
function mmap(
stream: FSStream,
buffer: ArrayBufferView,
offset: number,
length: number,
position: number,
prot: number,
flags: number,
): any;
function ioctl(stream: FSStream, cmd: any, arg: any): any;
function readFile(path: string, opts: { encoding: 'binary'; flags?: string | undefined }): Uint8Array;
function readFile(path: string, opts: { encoding: 'utf8'; flags?: string | undefined }): string;
function readFile(path: string, opts?: { flags?: string | undefined }): Uint8Array;
function writeFile(path: string, data: string | ArrayBufferView, opts?: { flags?: string | undefined }): void;
//
// module-level FS code
//
function cwd(): string;
function chdir(path: string): void;
function init(
input: null | (() => number | null),
output: null | ((c: number) => any),
error: null | ((c: number) => any),
): void;
function createLazyFile(
parent: string | FSNode,
name: string,
url: string,
canRead: boolean,
canWrite: boolean,
): FSNode;
function createPreloadedFile(
parent: string | FSNode,
name: string,
url: string,
canRead: boolean,
canWrite: boolean,
onload?: () => void,
onerror?: () => void,
dontCreateFile?: boolean,
canOwn?: boolean,
): void;
function createDataFile(
parent: string | FSNode,
name: string,
data: ArrayBufferView,
canRead: boolean,
canWrite: boolean,
canOwn: boolean,
): FSNode;
}
declare var MEMFS: Emscripten.FileSystemType;
declare var NODEFS: Emscripten.FileSystemType;
declare var IDBFS: Emscripten.FileSystemType;
// Below runtime function/variable declarations are exportable by
// -s EXTRA_EXPORTED_RUNTIME_METHODS. You can extend or merge
// EmscriptenModule interface to add runtime functions.
//
// For example, by using -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']"
// You can access ccall() via Module["ccall"]. In this case, you should
// extend EmscriptenModule to pass the compiler check like the following:
//
// interface YourOwnEmscriptenModule extends EmscriptenModule {
// ccall: typeof ccall;
// }
//
// See: https://emscripten.org/docs/getting_started/FAQ.html#why-do-i-get-typeerror-module-something-is-not-a-function
declare function ccall(
ident: string,
returnType: Emscripten.JSType | null,
argTypes: Emscripten.JSType[],
args: Emscripten.TypeCompatibleWithC[],
opts?: Emscripten.CCallOpts,
): any;
declare function cwrap(
ident: string,
returnType: Emscripten.JSType | null,
argTypes: Emscripten.JSType[],
opts?: Emscripten.CCallOpts,
): (...args: any[]) => any;
declare function setValue(ptr: number, value: any, type: Emscripten.CType, noSafe?: boolean): void;
declare function getValue(ptr: number, type: Emscripten.CType, noSafe?: boolean): number;
declare function allocate(
slab: number[] | ArrayBufferView | number,
types: Emscripten.CType | Emscripten.CType[],
allocator: number,
ptr?: number,
): number;
declare function stackAlloc(size: number): number;
declare function stackSave(): number;
declare function stackRestore(ptr: number): void;
declare function UTF8ToString(ptr: number, maxBytesToRead?: number): string;
declare function stringToUTF8(str: string, outPtr: number, maxBytesToRead?: number): void;
declare function lengthBytesUTF8(str: string): number;
declare function allocateUTF8(str: string): number;
declare function allocateUTF8OnStack(str: string): number;
declare function UTF16ToString(ptr: number): string;
declare function stringToUTF16(str: string, outPtr: number, maxBytesToRead?: number): void;
declare function lengthBytesUTF16(str: string): number;
declare function UTF32ToString(ptr: number): string;
declare function stringToUTF32(str: string, outPtr: number, maxBytesToRead?: number): void;
declare function lengthBytesUTF32(str: string): number;
declare function intArrayFromString(stringy: string, dontAddNull?: boolean, length?: number): number[];
declare function intArrayToString(array: number[]): string;
declare function writeStringToMemory(str: string, buffer: number, dontAddNull: boolean): void;
declare function writeArrayToMemory(array: number[], buffer: number): void;
declare function writeAsciiToMemory(str: string, buffer: number, dontAddNull: boolean): void;
declare function addRunDependency(id: any): void;
declare function removeRunDependency(id: any): void;
declare function addFunction(func: (...args: any[]) => any, signature?: string): number;
declare function removeFunction(funcPtr: number): void;
declare var ALLOC_NORMAL: number;
declare var ALLOC_STACK: number;
declare var ALLOC_STATIC: number;
declare var ALLOC_DYNAMIC: number;
declare var ALLOC_NONE: number;

14
src/Web/Avalonia.Web.Blazor/tsconfig.json

@ -1,14 +0,0 @@
{
"compilerOptions": {
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "ES2020",
"module": "ES2020",
"outDir": "wwwroot"
},
"exclude": [
"node_modules"
]
}

1
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/Common.ts

@ -0,0 +1 @@


40
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/DpiWatcher.ts

@ -0,0 +1,40 @@
export class DpiWatcher {
static lastDpi: number;
static timerId: number;
static callback?: DotNet.DotNetObject;
public static getDpi() {
return window.devicePixelRatio;
}
public static start(callback: DotNet.DotNetObject): number {
//console.info(`Starting DPI watcher with callback ${callback._id}...`);
DpiWatcher.lastDpi = window.devicePixelRatio;
DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000);
DpiWatcher.callback = callback;
return DpiWatcher.lastDpi;
}
public static stop() {
//console.info(`Stopping DPI watcher with callback ${DpiWatcher.callback._id}...`);
window.clearInterval(DpiWatcher.timerId);
DpiWatcher.callback = undefined;
}
static update() {
if (!DpiWatcher.callback)
return;
const currentDpi = window.devicePixelRatio;
const lastDpi = DpiWatcher.lastDpi;
DpiWatcher.lastDpi = currentDpi;
if (Math.abs(lastDpi - currentDpi) > 0.001) {
DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi);
}
}
}

79
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/IndexedDbWrapper.ts

@ -0,0 +1,79 @@
class InnerDbConnection {
constructor(private database: IDBDatabase) { }
private openStore(store: string, mode: IDBTransactionMode): IDBObjectStore {
const tx = this.database.transaction(store, mode);
return tx.objectStore(store);
}
public put(store: string, obj: any, key?: IDBValidKey): Promise<IDBValidKey> {
const os = this.openStore(store, "readwrite");
return new Promise((resolve, reject) => {
const response = os.put(obj, key);
response.onsuccess = () => {
resolve(response.result);
};
response.onerror = () => {
reject(response.error);
};
});
}
public get(store: string, key: IDBValidKey): any {
const os = this.openStore(store, "readonly");
return new Promise((resolve, reject) => {
const response = os.get(key);
response.onsuccess = () => {
resolve(response.result);
};
response.onerror = () => {
reject(response.error);
};
});
}
public delete(store: string, key: IDBValidKey): Promise<void> {
const os = this.openStore(store, "readwrite");
return new Promise((resolve, reject) => {
const response = os.delete(key);
response.onsuccess = () => {
resolve();
};
response.onerror = () => {
reject(response.error);
};
});
}
public close() {
this.database.close();
}
}
export class IndexedDbWrapper {
constructor(private databaseName: string, private objectStores: [string]) {
}
public connect(): Promise<InnerDbConnection> {
const conn = window.indexedDB.open(this.databaseName, 1);
conn.onupgradeneeded = event => {
const db = (<IDBRequest<IDBDatabase>>event.target).result;
this.objectStores.forEach(store => {
db.createObjectStore(store);
});
};
return new Promise((resolve, reject) => {
conn.onsuccess = event => {
resolve(new InnerDbConnection((<IDBRequest<IDBDatabase>>event.target).result));
};
conn.onerror = event => {
reject((<IDBRequest<IDBDatabase>>event.target).error);
};
});
}
}

22
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/InputHelper.ts

@ -0,0 +1,22 @@
export class InputHelper {
public static clear(inputElement: HTMLInputElement) {
inputElement.value = "";
}
public static focus(inputElement: HTMLInputElement) {
inputElement.focus();
inputElement.setSelectionRange(0, 0);
}
public static setCursor(inputElement: HTMLInputElement, kind: string) {
inputElement.style.cursor = kind;
}
public static hide(inputElement: HTMLInputElement) {
inputElement.style.display = 'none';
}
public static show(inputElement: HTMLInputElement) {
inputElement.style.display = 'block';
}
}

35
src/Web/Avalonia.Web.Blazor/Interop/Typescript/NativeControlHost.ts → src/Web/Avalonia.Web.Blazor/webapp/modules/Common/NativeControlHost.ts

@ -14,10 +14,9 @@
}
}
class NativeControlHostTopLevelAttachment
{
_child: HTMLElement;
_host: HTMLElement;
class NativeControlHostTopLevelAttachment {
_child?: HTMLElement;
_host?: HTMLElement;
InitializeWithChildHandle(child: HTMLElement) {
this._child = child;
@ -25,32 +24,38 @@ class NativeControlHostTopLevelAttachment
}
AttachTo(host: HTMLElement): void {
if (this._host) {
if (this._host && this._child) {
this._host.removeChild(this._child);
}
this._host = host;
if (this._host) {
if (this._host && this._child) {
this._host.appendChild(this._child);
}
}
ShowInBounds(x: number, y: number, width: number, height: number): void {
this._child.style.top = y + "px";
this._child.style.left = x + "px";
this._child.style.width = width + "px";
this._child.style.height = height + "px";
this._child.style.display = "block";
if (this._child) {
this._child.style.top = y + "px";
this._child.style.left = x + "px";
this._child.style.width = width + "px";
this._child.style.height = height + "px";
this._child.style.display = "block";
}
}
HideWithSize(width: number, height: number): void {
this._child.style.width = width + "px";
this._child.style.height = height + "px";
this._child.style.display = "none";
if (this._child) {
this._child.style.width = width + "px";
this._child.style.height = height + "px";
this._child.style.display = "none";
}
}
ReleaseChild(): void {
this._child = null;
if (this._child) {
this._child = undefined;
}
}
}

255
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SKHtmlCanvas.ts

@ -0,0 +1,255 @@
// aliases for emscripten
declare let GL: any;
declare let GLctx: WebGLRenderingContext;
declare let Module: EmscriptenModule;
// container for gl info
type SKGLViewInfo = {
context: WebGLRenderingContext | WebGL2RenderingContext | undefined;
fboId: number;
stencil: number;
sample: number;
depth: number;
}
// alias for a potential skia html canvas
type SKHtmlCanvasElement = {
SKHtmlCanvas: SKHtmlCanvas | undefined
} & HTMLCanvasElement
export class SKHtmlCanvas {
static elements: Map<string, HTMLCanvasElement>;
htmlCanvas: HTMLCanvasElement;
glInfo?: SKGLViewInfo;
renderFrameCallback: DotNet.DotNetObject;
renderLoopEnabled: boolean = false;
renderLoopRequest: number = 0;
newWidth?: number;
newHeight?: number;
public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKGLViewInfo | null {
var view = SKHtmlCanvas.init(true, element, elementId, callback);
if (!view || !view.glInfo)
return null;
return view.glInfo;
}
public static initRaster(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): boolean {
var view = SKHtmlCanvas.init(false, element, elementId, callback);
if (!view)
return false;
return true;
}
static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKHtmlCanvas | null {
var htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas) {
console.error(`No canvas element was provided.`);
return null;
}
if (!SKHtmlCanvas.elements)
SKHtmlCanvas.elements = new Map<string, HTMLCanvasElement>();
SKHtmlCanvas.elements.set(elementId, element);
const view = new SKHtmlCanvas(useGL, element, callback);
htmlCanvas.SKHtmlCanvas = view;
return view;
}
public static deinit(elementId: string) {
if (!elementId)
return;
const element = SKHtmlCanvas.elements.get(elementId);
SKHtmlCanvas.elements.delete(elementId);
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.deinit();
htmlCanvas.SKHtmlCanvas = undefined;
}
public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) {
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop);
}
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number) {
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height);
}
public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) {
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable);
}
public static putImageData(element: HTMLCanvasElement, pData: number, width: number, height: number) {
const htmlCanvas = element as SKHtmlCanvasElement;
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
return;
htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height);
}
public constructor(useGL: boolean, element: HTMLCanvasElement, callback: DotNet.DotNetObject) {
this.htmlCanvas = element;
this.renderFrameCallback = callback;
if (useGL) {
const ctx = SKHtmlCanvas.createWebGLContext(this.htmlCanvas);
if (!ctx) {
console.error(`Failed to create WebGL context: err ${ctx}`);
return;
}
// make current
GL.makeContextCurrent(ctx);
// read values
const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING);
this.glInfo = {
context: ctx,
fboId: fbo ? fbo.id : 0,
stencil: GLctx.getParameter(GLctx.STENCIL_BITS),
sample: 0, // TODO: GLctx.getParameter(GLctx.SAMPLES)
depth: GLctx.getParameter(GLctx.DEPTH_BITS),
};
}
}
public deinit() {
this.setEnableRenderLoop(false);
}
public setCanvasSize(width: number, height: number) {
this.newWidth = width;
this.newHeight = height;
if (this.htmlCanvas.width != this.newWidth) {
this.htmlCanvas.width = this.newWidth;
}
if (this.htmlCanvas.height != this.newHeight) {
this.htmlCanvas.height = this.newHeight;
}
if (this.glInfo) {
// make current
GL.makeContextCurrent(this.glInfo.context);
}
}
public requestAnimationFrame(renderLoop?: boolean) {
// optionally update the render loop
if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop)
this.setEnableRenderLoop(renderLoop);
// skip because we have a render loop
if (this.renderLoopRequest !== 0)
return;
// add the draw to the next frame
this.renderLoopRequest = window.requestAnimationFrame(() => {
if (this.glInfo) {
// make current
GL.makeContextCurrent(this.glInfo.context);
}
if (this.htmlCanvas.width != this.newWidth) {
this.htmlCanvas.width = this.newWidth || 0;
}
if (this.htmlCanvas.height != this.newHeight) {
this.htmlCanvas.height = this.newHeight || 0;
}
this.renderFrameCallback.invokeMethod('Invoke');
this.renderLoopRequest = 0;
// we may want to draw the next frame
if (this.renderLoopEnabled)
this.requestAnimationFrame();
});
}
public setEnableRenderLoop(enable: boolean) {
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}...`);
this.requestAnimationFrame();
} else if (this.renderLoopRequest !== 0) {
window.cancelAnimationFrame(this.renderLoopRequest);
this.renderLoopRequest = 0;
}
}
public putImageData(pData: number, width: number, height: number): boolean {
if (this.glInfo || !pData || width <= 0 || width <= 0)
return false;
var ctx = this.htmlCanvas.getContext('2d');
if (!ctx) {
console.error(`Failed to obtain 2D canvas context.`);
return false;
}
// make sure the canvas is scaled correctly for the drawing
this.htmlCanvas.width = width;
this.htmlCanvas.height = height;
// set the canvas to be the bytes
var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4);
var imageData = new ImageData(buffer, width, height);
ctx.putImageData(imageData, 0, 0);
return true;
}
static createWebGLContext(htmlCanvas: HTMLCanvasElement): WebGLRenderingContext | WebGL2RenderingContext {
const contextAttributes = {
alpha: 1,
depth: 1,
stencil: 8,
antialias: 0,
premultipliedAlpha: 1,
preserveDrawingBuffer: 0,
preferLowPowerToHighPerformance: 0,
failIfMajorPerformanceCaveat: 0,
majorVersion: 2,
minorVersion: 0,
enableExtensionsByDefault: 1,
explicitSwapControl: 0,
renderViaOffscreenBackBuffer: 1,
};
let ctx: WebGLRenderingContext = GL.createContext(htmlCanvas, contextAttributes);
if (!ctx && contextAttributes.majorVersion > 1) {
console.warn('Falling back to WebGL 1.0');
contextAttributes.majorVersion = 1;
contextAttributes.minorVersion = 0;
ctx = GL.createContext(htmlCanvas, contextAttributes);
}
return ctx;
}
}

67
src/Web/Avalonia.Web.Blazor/webapp/modules/Common/SizeWatcher.ts

@ -0,0 +1,67 @@
type SizeWatcherElement = {
SizeWatcher: SizeWatcherInstance;
} & HTMLElement
type SizeWatcherInstance = {
callback: DotNet.DotNetObject;
}
export class SizeWatcher {
static observer: ResizeObserver;
static elements: Map<string, HTMLElement>;
public static observe(element: HTMLElement, elementId: string, callback: DotNet.DotNetObject) {
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
};
SizeWatcher.elements.set(elementId, element);
SizeWatcher.observer.observe(element);
SizeWatcher.invoke(element);
}
public static unobserve(elementId: string) {
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);
}
static init() {
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) {
SizeWatcher.invoke(entry.target);
}
});
}
static invoke(element: Element) {
const watcherElement = element as SizeWatcherElement;
const instance = watcherElement.SizeWatcher;
if (!instance || !instance.callback)
return;
return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight);
}
}

164
src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts → src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts

@ -1,132 +1,14 @@
// As we don't have proper package managing for Avalonia.Web project, declare types manually
declare global {
interface FileSystemWritableFileStream {
write(position: number, data: BufferSource | Blob | string): Promise<void>;
truncate(size: number): Promise<void>;
close(): Promise<void>;
}
type PermissionsMode = "read" | "readwrite";
interface FileSystemFileHandle {
name: string,
getFile(): Promise<File>;
createWritable(options?: { keepExistingData?: boolean }): Promise<FileSystemWritableFileStream>;
queryPermission(options?: { mode: PermissionsMode }): Promise<"granted" | "denied" | "prompt">;
requestPermission(options?: { mode: PermissionsMode }): Promise<"granted" | "denied" | "prompt">;
import { IndexedDbWrapper } from "./IndexedDbWrapper";
entries(): AsyncIterableIterator<[string, FileSystemFileHandle]>;
}
type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos";
type StartInDirectory = WellKnownDirectory | FileSystemFileHandle;
interface FilePickerAcceptType {
description: string,
// mime -> ext[] array
accept: { [mime: string]: string | string[] }
}
interface FilePickerOptions {
types?: FilePickerAcceptType[],
excludeAcceptAllOption: boolean,
id?: string,
declare global {
type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos";
type StartInDirectory = WellKnownDirectory | FileSystemFileHandle;
interface OpenFilePickerOptions {
startIn?: StartInDirectory
}
interface OpenFilePickerOptions extends FilePickerOptions {
multiple: boolean
}
interface SaveFilePickerOptions extends FilePickerOptions {
suggestedName?: string
}
interface DirectoryPickerOptions {
id?: string,
interface SaveFilePickerOptions {
startIn?: StartInDirectory
}
interface Window {
showOpenFilePicker: (options: OpenFilePickerOptions) => Promise<FileSystemFileHandle[]>;
showSaveFilePicker: (options: SaveFilePickerOptions) => Promise<FileSystemFileHandle>;
showDirectoryPicker: (options: DirectoryPickerOptions) => Promise<FileSystemFileHandle>;
}
}
// TODO move to another file and use import
class IndexedDbWrapper {
constructor(private databaseName: string, private objectStores: [ string ]) {
}
public connect(): Promise<InnerDbConnection> {
const conn = window.indexedDB.open(this.databaseName, 1);
conn.onupgradeneeded = event => {
const db = (<IDBRequest<IDBDatabase>>event.target).result;
this.objectStores.forEach(store => {
db.createObjectStore(store);
});
}
return new Promise((resolve, reject) => {
conn.onsuccess = event => {
resolve(new InnerDbConnection((<IDBRequest<IDBDatabase>>event.target).result));
}
conn.onerror = event => {
reject((<IDBRequest<IDBDatabase>>event.target).error);
}
});
}
}
class InnerDbConnection {
constructor(private database: IDBDatabase) { }
private openStore(store: string, mode: IDBTransactionMode): IDBObjectStore {
const tx = this.database.transaction(store, mode);
return tx.objectStore(store);
}
public put(store: string, obj: any, key?: IDBValidKey): Promise<IDBValidKey> {
const os = this.openStore(store, "readwrite");
return new Promise((resolve, reject) => {
const response = os.put(obj, key);
response.onsuccess = () => {
resolve(response.result);
};
response.onerror = () => {
reject(response.error);
};
});
}
public get(store: string, key: IDBValidKey): any {
const os = this.openStore(store, "readonly");
return new Promise((resolve, reject) => {
const response = os.get(key);
response.onsuccess = () => {
resolve(response.result);
};
response.onerror = () => {
reject(response.error);
};
});
}
public delete(store: string, key: IDBValidKey): Promise<void> {
const os = this.openStore(store, "readwrite");
return new Promise((resolve, reject) => {
const response = os.delete(key);
response.onsuccess = () => {
resolve();
};
response.onerror = () => {
reject(response.error);
};
});
}
public close() {
this.database.close();
}
}
const fileBookmarksStore: string = "fileBookmarks";
@ -135,7 +17,7 @@ const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [
])
class StorageItem {
constructor(public handle: FileSystemFileHandle, private bookmarkId?: string) { }
constructor(private handle: FileSystemHandle, private bookmarkId?: string) { }
public getName(): string {
return this.handle.name
@ -146,21 +28,35 @@ class StorageItem {
}
public async openRead(): Promise<Blob> {
if (!(this.handle instanceof FileSystemFileHandle)) {
throw new Error("StorageItem is not a file");
}
await this.verityPermissions('read');
return await this.handle.getFile();
const file = await this.handle.getFile();
return file;
}
public async openWrite(): Promise<FileSystemWritableFileStream> {
if (!(this.handle instanceof FileSystemFileHandle)) {
throw new Error("StorageItem is not a file");
}
await this.verityPermissions('readwrite');
return await this.handle.createWritable({ keepExistingData: true });
}
public async getProperties(): Promise<{ Size: number, LastModified: number, Type: string }> {
const file = this.handle.getFile && await this.handle.getFile();
return file && {
public async getProperties(): Promise<{ Size: number, LastModified: number, Type: string } | null> {
const file = this.handle instanceof FileSystemFileHandle
&& await this.handle.getFile();
if (!file) {
return null;
}
return {
Size: file.size,
LastModified: file.lastModified,
Type: file.type
@ -168,6 +64,10 @@ class StorageItem {
}
public async getItems(): Promise<StorageItems> {
if (this.handle.kind !== "directory"){
return new StorageItems([]);
}
const items: StorageItem[] = [];
for await (const [key, value] of this.handle.entries()) {
items.push(new StorageItem(value));
@ -175,7 +75,7 @@ class StorageItem {
return new StorageItems(items);
}
private async verityPermissions(mode: PermissionsMode): Promise<void | never> {
private async verityPermissions(mode: FileSystemPermissionMode): Promise<void | never> {
if (await this.handle.queryPermission({ mode }) === 'granted') {
return;
}
@ -200,7 +100,7 @@ class StorageItem {
connection.close();
}
}
public async deleteBookmark(): Promise<void> {
if (!this.bookmarkId) {
return;

14
src/Web/Avalonia.Web.Blazor/webapp/package.json

@ -0,0 +1,14 @@
{
"name": "avalonia.web",
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean"
},
"devDependencies": {
"@types/emscripten": "^1.39.6",
"@types/wicg-file-system-access": "^2020.9.5",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
}
}

18
src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES6",
"strict": true,
"sourceMap": true,
"outDir": "../wwwroot",
"removeComments": true,
"noEmitOnError": true,
"lib": [
"dom",
"ES6",
"esnext.asynciterable"
]
},
"exclude": [
"node_modules"
]
}

0
src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/index.d.ts → src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts

0
src/Web/Avalonia.Web.Blazor/webapp/webpack.config.js

Loading…
Cancel
Save