Initial commit

Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com>
Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com>
Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com>
Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com>
Co-Authored-By: Johan  <4934465+joolsus@users.noreply.github.com>
Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com>
Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
Michael Cartner
2024-01-26 11:52:55 +01:00
commit b9c60b07dc
2789 changed files with 868795 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
# Noodl Platform
Cross platform implementation of platform specific features.
## Getting Started
When the app is starting we have to set the desired providers.
### Electron
```ts
// Setup the platform before anything else is loading
// This is a problem since we are calling the platform when importing
import "@noodl/platform-electron";
// Then import the platform etc via:
import { filesystem, platform } from "@noodl/platform";
```
### Node
```
$ npm install @noodl/platform @noodl/platform-node
```
```ts
// Setup the platform before anything else is loading
// This is a problem since we are calling the platform when importing
import "@noodl/platform-node";
// Then import the platform etc via:
import { filesystem, platform } from "@noodl/platform";
```
## Features
### Platform
```ts
import { platform } from "@noodl/platform";
platform.getBuildNumber().then((version) => {});
```
### File System
```ts
import { filesystem } from "@noodl/platform";
filesystem.readJson("path/to/file.json").then((content) => {
console.log(content.value);
});
```
### Storage (Config Storage)
> This API still needs some love to bring a better universal config system to Noodl.
```ts
import { JSONStorage } from "@noodl/platform";
JSONStorage.get("my-key").then((content) => {
// content = json file
});
JSONStorage.set("my-key", { key: "value" }).then(() => {
// done
});
JSONStorage.remove("my-key").then(() => {
// done
});
```

View File

@@ -0,0 +1,8 @@
{
"name": "@noodl/platform",
"version": "2.7.0",
"main": "src/index.ts",
"description": "Cross platform implementation of platform specific features.",
"author": "Noodl <info@noodl.net>",
"homepage": "https://noodl.net"
}

View File

@@ -0,0 +1,53 @@
export type FileBlob = Buffer | string;
export interface FileInfo {
fullPath: string;
name: string;
isDirectory: boolean;
}
export interface FileStat {
size: number;
}
export type OpenDialogOptions = {
allowCreateDirectory?: boolean;
};
/**
* File System that is designed to be cross platform.
*/
export interface IFileSystem {
resolve(...paths: string[]): string;
join(...paths: string[]): string;
exists(path: string): boolean;
dirname(path: string): string;
basename(path: string): string;
file(path: string): FileStat;
writeFile(path: string, blob: FileBlob): Promise<void>;
writeFileOverride(path: string, blob: FileBlob): Promise<void>;
readFile(path: string): Promise<string>;
readBinaryFile(path: string): Promise<Buffer>;
removeFile(path: string): Promise<void>;
renameFile(oldPath: string, newPath: string): Promise<void>;
copyFile(from: string, to: string): Promise<void>;
copyFolder(from: string, to: string): Promise<void>;
readJson<T = any>(path: string): Promise<T>;
writeJson(path: string, obj: any): Promise<void>;
isDirectoryEmpty(path: string): Promise<boolean>;
listDirectory(path: string): Promise<FileInfo[]>;
/** List all the files in this folder recursively */
listDirectoryFiles(path: string): Promise<FileInfo[]>;
makeDirectory(path: string): Promise<void>;
removeDirRecursive(path: string): void;
openDialog(args: OpenDialogOptions): Promise<string>;
unzipUrl(url: string, to: string): Promise<void>;
makeUniquePath(path: string): string;
}

View File

@@ -0,0 +1 @@
export * from "./common";

View File

@@ -0,0 +1,27 @@
import { IFileSystem } from './filesystem';
import { IPlatform, PlatformWeb } from './platform';
import { IStorage } from './storage';
import { StorageWeb } from './storage/storage-web';
export * from './filesystem';
export * from './platform';
export * from './storage';
export * from './utils';
let platform: IPlatform = new PlatformWeb('0.0.0', undefined, '0');
let filesystem: IFileSystem;
let JSONStorage: IStorage = new StorageWeb();
export { platform, filesystem, JSONStorage };
export function setPlatform(value: IPlatform): void {
platform = value;
}
export function setFileSystem(value: IFileSystem): void {
filesystem = value;
}
export function setStorage(value: IStorage): void {
JSONStorage = value;
}

View File

@@ -0,0 +1,76 @@
export enum PlatformOS {
Web = "web",
Windows = "windows",
MacOS = "macOS",
Linux = "linux",
Unknown = "unknown"
}
export interface IPlatform {
get name(): string;
get os(): PlatformOS;
/**
* @example '1'
*/
getBuildNumber(): string | undefined;
/**
* @example '2.6.3-1'
*/
getFullVersion(): string;
/**
* @example '2.6.3'
*/
getVersion(): string;
/**
* @example '2.6.3' or '2.6.3-AI'
*/
getVersionWithTag(): string;
/**
* @example Windows: 'C:/Users/Eric/AppData/Roaming/Noodl'
* @example OSX: '/Users/eric/Library/Preferences/Noodl'
*/
getUserDataPath(): string;
/**
* @example Windows: 'C:/Users/Eric/OneDrive/Dokument'
*/
getDocumentsPath(): string;
/**
* @example Windows: 'C:/Users/Eric/AppData/Local/Temp/'
* @example OSX: '/var/folders/8w/29mdvxz11f13l68p4xg_m_vc0000gn/T/'
*/
getTempPath(): string;
/**
* @example Windows: 'C:/GitHub/noodl-editor/'
* @example OSX: '/Users/eric/Documents/GitHub/noodl-editor/'
*/
getAppPath(): string;
/**
* Open the given external protocol URL in the desktop's default manner.
* (For example, mailto: URLs in the user's default mail agent).
*
* @param url
*/
openExternal(url: string): Promise<void>;
/**
* Write the specified text string to the system clipboard.
*
* @param value
*/
copyToClipboard(value: string): Promise<void>;
}
// OSX and Windows add trailing slashes to the temp folder, Linux doesn't
export function addTrailingSlash(path: string): string {
return path[path.length - 1] !== "/" ? path + "/" : path;
}

View File

@@ -0,0 +1,3 @@
export * from "./common";
export * from "./support";
export * from "./platform-web";

View File

@@ -0,0 +1,53 @@
import { IPlatform } from '@noodl/platform';
import { PlatformOS } from './common';
export class PlatformWeb implements IPlatform {
get name(): string {
return 'Web';
}
get os(): PlatformOS {
return PlatformOS.Web;
}
constructor(
private readonly _version: string,
private readonly _versionTag: string,
private readonly _buildNumber: string
) {}
getBuildNumber(): string | undefined {
return this._buildNumber;
}
getFullVersion(): string {
return this._version + '-' + this._buildNumber;
}
getVersion(): string {
return this._version;
}
getVersionWithTag(): string {
return this._versionTag ? `${this._version}-${this._versionTag}` : this._version;
}
getUserDataPath(): string {
return '/user';
}
getDocumentsPath(): string {
return '/documents';
}
getTempPath(): string {
return '/tmp';
}
getAppPath(): string {
return '/app';
}
async openExternal(url: string): Promise<void> {
window.open(url, '_blank').focus();
}
async copyToClipboard(value: string): Promise<void> {
await navigator.clipboard.writeText(value);
}
}

View File

@@ -0,0 +1,43 @@
/**
* https://stackoverflow.com/a/61725416/3211243
*/
export function isElectron(): boolean {
// Renderer process
// @ts-ignore window
if (
typeof window !== "undefined" &&
typeof window.process === "object" &&
// @ts-ignore
window.process.type === "renderer"
) {
return true;
}
// Main process
if (
typeof process !== "undefined" &&
typeof process.versions === "object" &&
!!process.versions.electron
) {
return true;
}
// Detect the user agent when the `nodeIntegration` option is set to true
// @ts-ignore navigator
if (
typeof navigator === "object" &&
typeof navigator.userAgent === "string" &&
navigator.userAgent.indexOf("Electron") >= 0
) {
return true;
}
return false;
}
export function isNode(): boolean {
return (
typeof process !== "undefined" &&
process.release.name.search(/node|io.js/) !== -1
);
}

View File

@@ -0,0 +1,5 @@
export interface IStorage {
get(key: string): Promise<any>;
set(key: string, data: { [key: string]: any }): Promise<void>;
remove(key: string): Promise<void>;
}

View File

@@ -0,0 +1,2 @@
export * from "./common";
export * from "./storage-web";

View File

@@ -0,0 +1,13 @@
import { IStorage } from "./common";
export class StorageWeb implements IStorage {
get(key: string): Promise<any> {
throw new Error("Method not implemented.");
}
set(key: string, data: { [key: string]: any }): Promise<void> {
throw new Error("Method not implemented.");
}
remove(key: string): Promise<void> {
throw new Error("Method not implemented.");
}
}

View File

@@ -0,0 +1,28 @@
export namespace ConvertUtils {
export function bytesToKilobytes(bytes: number): number {
const kilobytes = bytes / 1024;
return kilobytes;
}
export function bytesToMegabytes(bytes: number): number {
const megabytes = bytes / (1024 * 1024);
return megabytes;
}
export function bytesToGigabytes(bytes: number): number {
const gigabytes = bytes / (1024 * 1024 * 1024);
return gigabytes;
}
export function bytesToMostSuitableSize(bytes: number): string {
if (bytes < 1024) {
return bytes + ' bytes';
} else if (bytes < 1024 * 1024) {
return bytesToKilobytes(bytes).toFixed(2) + ' KB';
} else if (bytes < 1024 * 1024 * 1024) {
return bytesToMegabytes(bytes).toFixed(2) + ' MB';
} else {
return bytesToGigabytes(bytes).toFixed(2) + ' GB';
}
}
}

View File

@@ -0,0 +1,3 @@
export * from './convert';
export * from './promise';
export * from './random';

View File

@@ -0,0 +1,30 @@
// TODO(typescript): Remove when we upgrade to Typescript 4.5
type Awaited<T> = T extends null | undefined
? T // special case for `null | undefined` when not in `--strictNullChecks` mode
: T extends object & { then(onfulfilled: infer F): any } // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
? F extends (value: infer V, ...args: any) => any // if the argument to `then` is callable, extracts the first argument
? Awaited<V> // recursively unwrap the value
: never // the argument to `then` was not callable
: T; // non-object or non-thenable
type PromiseHash = Record<string, Promise<unknown>>;
type AwaitedPromiseHash<T extends PromiseHash> = {
[P in keyof T]: Awaited<T[P]>;
};
export namespace PromiseUtils {
export async function allObjects<T extends PromiseHash>(object: T): Promise<AwaitedPromiseHash<T>> {
return Object.fromEntries(
await Promise.all(
Object.entries(object).map(async ([key, promise]) => {
return [key, await promise];
})
)
);
}
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}

View File

@@ -0,0 +1,9 @@
export namespace RandomUtils {
export function range(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function within(max: number): number {
return Math.floor(Math.random() * max);
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"exclude": []
}