diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9d8562d..5327201 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -47,9 +47,6 @@ jobs: with: path: "main" - - name: Copy favicon folder - run: cp -r main/favicon ./dist/ - - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: diff --git a/README.md b/README.md index 2098947..08d99f5 100644 --- a/README.md +++ b/README.md @@ -19,52 +19,53 @@ --- -Topos is a web based live coding environment. Topos is capable of many things: +Topos is a web based live coding environment designed to be installation-free, independant and fun. Topos is loosely based on the [Monome Teletype](https://monome.org/docs/teletype/). The application follows the same operating principle, but adapts it to the rich multimedia context offered by web browsers. Topos is capable of many things: -- it is a music sequencer made for improvisation and composition alike -- it is a synthesizer capable of additive, substractive, FM and wavetable - synthesis, backed up by a [powerful web based audio engine](https://www.npmjs.com/package/superdough) -- it can also generate video thanks to [Hydra](https://hydra.ojack.xyz/) and - custom oscilloscopes, frequency visualizers and image sequencing capabilities -- it can be used to sequence other MIDI devices (and soon.. OSC!) +- it is a generative/algorithmic music sequencer made for **improvisation** and **composition** alike +- it is a synthesizer capable of _additive_, _substractive_, _FM_ and _wavetable + synthesis_, backed up by a [powerful web based audio engine](https://www.npmjs.com/package/superdough) +- it can also generate video thanks to [Hydra](https://hydra.ojack.xyz/), + oscilloscopes, frequency visualizers and image/canvas sequencing capabilities +- it can be used to sequence other MIDI and OSC devices (the latter using a **NodeJS** script) - it is made to be used without the need of installing anything, always ready at [https://topos.live](https://topos.live) -- Topos is also an emulation and personal extension of the [Monome Teletype](https://monome.org/docs/teletype/) --- -![Screenshot](https://github.com/Bubobubobubobubo/Topos/blob/main/img/topos_gif.gif) +![Screenshot](https://github.com/Bubobubobubobubo/Topos/blob/main/src/assets/topos_gif.gif) ## Disclaimer -**Topos** is still a young project developed by two hobbyists :) Contributions are welcome! We wish to be as inclusive and welcoming as possible to your ideas and suggestions! The software is working quite well and we are continuously striving to improve it. +**Topos** is still a young and experimental project developed by two hobbyists :) Contributions are welcome! We wish to be as inclusive and welcoming as possible to your ideas and suggestions! The software is working quite well and we are continuously striving to improve it. Note that most features are rather experimental and that we don't really have any classical training in web development. -## Installation (for devs and contributors) +## Local Installation (for devs and contributors) To run the application, you will need to install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/). Then, clone the repository and run: - `yarn install` - `yarn run dev` -To build the application for production, you will need to install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/). Then, clone the repository and run: +You are good to go. The application will update itself automatically with every change to the codebase. To test the production version of the applicationn, you will need to install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/). Then, clone the repository and run: - `yarn run build` - `yarn run start` -Always run a build before committing to check for compiler errors. The automatic deployment on the `main` branch will not accept compiler errors! +If the build passes, you can be sure that it will also pass our **CI** pipeline that deploys the application to [https://topos.live](https://topos.live). Always run a build before committing to check for compiler errors. The automatic deployment on the `main` branch will not accept compiler errors! -To build a standalone browser application using [Tauri](https://tauri.app/), you will need to have [Node.js](https://nodejs.org/en/), [Yarn](https://yarnpkg.com/en/) and [Rust](https://www.rust-lang.org/) installed. Then, clone the repository and run: +## Tauri version + +Topos can also be compiled as a standalone application using [Tauri](https://tauri.app/). You will need [Node.js](https://nodejs.org/en/), [Yarn](https://yarnpkg.com/en/) and [Rust](https://www.rust-lang.org/) to be installed on your computer. Then, clone the repository and run: - `yarn tauri build` - `yarn tauri dev` -The `tauri` version is only here to quickstart future developments but nothing has been done yet. +The `tauri` version has never been fleshed out. It's a template for later developments if Topos ever wants to escape from the web :) ## Docker -### Run the application +To run the **Docker** version, run the following command: -`docker run -p 8001:80 yassinsiouda/topos:latest` +`docker run -p 8001:80 bubobubobubo/topos:latest` ### Build and run the prod image @@ -72,8 +73,7 @@ The `tauri` version is only here to quickstart future developments but nothing ### Build and run the dev image -**First installation** -First you need to map node_modules to your local machine for your ide intellisense to work properly +First you need to map `node_modules` to your local machine for your IDE IntelliSense to work properly : ```bash docker compose --profile dev up -d @@ -81,8 +81,21 @@ docker cp topos-dev:/app/node_modules . docker compose --profile dev down ``` -**Then** +then run the following command: ```bash docker compose --profile dev up ``` + +Note that a Docker version of Topos is automatically generated everytime a commit is done on the `main` branch. + +## Credits + +- Felix Roos for the [SuperDough](https://www.npmjs.com/package/superdough) audio engine. +- Frank Force for the [ZzFX](https://github.com/KilledByAPixel/ZzFX) synthesizer. +- Kristoffer Ekstrand for the [AKWF](https://www.adventurekid.se/akrt/waveforms/adventure-kid-waveforms/) waveforms. +- Ryan Kirkbride for some of the audio samples in the [Dough-Fox](https://github.com/Bubobubobubobubo/Dough-Fox) sample pack, taken from [here](https://github.com/Qirky/FoxDot/tree/master/FoxDot/snd). +- Adel Faure for the [JGS](https://adelfaure.net/https://adelfaure.net/) font. +- Raphaël Bastide for the [Steps Mono](https://github.com/raphaelbastide/steps-mono/) font. + +Many thanks to all the contributors and folks who tried the software already :) \ No newline at end of file diff --git a/favicon/site.webmanifest b/favicon/site.webmanifest deleted file mode 100644 index fa99de7..0000000 --- a/favicon/site.webmanifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "", - "short_name": "", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/img/screnshot.png b/img/screnshot.png deleted file mode 100644 index 3a21862..0000000 Binary files a/img/screnshot.png and /dev/null differ diff --git a/index.html b/index.html index 83b5b39..bba9383 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,14 @@ - - + Topos + - - - + + + + diff --git a/manifest.webmanifest b/manifest.webmanifest deleted file mode 100644 index 8fb49fd..0000000 --- a/manifest.webmanifest +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Topos", - "short_name": "Topos", - "description": "Live coding environment", - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone", - "scope": "/", - "start_url": "/", - "icons": [ - { - "src": "./favicon/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any maskable" - }, - { - "src": "./favicon/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "any maskable" - } - ] -} diff --git a/package.json b/package.json index 520f9f0..045dced 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "typescript": "^5.2.2", "vite": "^4.4.5", "vite-plugin-compression": "^0.5.1", - "vite-plugin-pwa": "^0.16.7" + "vite-plugin-pwa": "^0.17.4" }, "dependencies": { "@codemirror/lang-javascript": "^6.1.9", diff --git a/favicon/android-chrome-192x192.png b/public/favicon/android-chrome-192x192.png similarity index 100% rename from favicon/android-chrome-192x192.png rename to public/favicon/android-chrome-192x192.png diff --git a/favicon/android-chrome-512x512.png b/public/favicon/android-chrome-512x512.png similarity index 100% rename from favicon/android-chrome-512x512.png rename to public/favicon/android-chrome-512x512.png diff --git a/favicon/apple-touch-icon.png b/public/favicon/apple-touch-icon.png similarity index 100% rename from favicon/apple-touch-icon.png rename to public/favicon/apple-touch-icon.png diff --git a/favicon/browserconfig.xml b/public/favicon/browserconfig.xml similarity index 100% rename from favicon/browserconfig.xml rename to public/favicon/browserconfig.xml diff --git a/favicon/favicon-16x16.png b/public/favicon/favicon-16x16.png similarity index 100% rename from favicon/favicon-16x16.png rename to public/favicon/favicon-16x16.png diff --git a/favicon/favicon-32x32.png b/public/favicon/favicon-32x32.png similarity index 100% rename from favicon/favicon-32x32.png rename to public/favicon/favicon-32x32.png diff --git a/favicon/favicon.ico b/public/favicon/favicon.ico similarity index 100% rename from favicon/favicon.ico rename to public/favicon/favicon.ico diff --git a/public/favicon/favicon.svg b/public/favicon/favicon.svg new file mode 100644 index 0000000..b152ce8 --- /dev/null +++ b/public/favicon/favicon.svg @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/favicon/mstile-150x150.png b/public/favicon/mstile-150x150.png similarity index 100% rename from favicon/mstile-150x150.png rename to public/favicon/mstile-150x150.png diff --git a/favicon/safari-pinned-tab.svg b/public/favicon/safari-pinned-tab.svg similarity index 100% rename from favicon/safari-pinned-tab.svg rename to public/favicon/safari-pinned-tab.svg diff --git a/public/favicon/screenshot_miniature.png b/public/favicon/screenshot_miniature.png new file mode 100644 index 0000000..1f6bc9e Binary files /dev/null and b/public/favicon/screenshot_miniature.png differ diff --git a/public/favicon/topos_code.png b/public/favicon/topos_code.png new file mode 100644 index 0000000..1f6bc9e Binary files /dev/null and b/public/favicon/topos_code.png differ diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..e0633b8 --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,37 @@ +{ + "name": "Topos", + "short_name": "Topos", + "icons": [ + { + "src": "favicon/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "favicon/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "display": "standalone", + "start_url": "/", + "scope": "/", + "theme_color": "#ffffff", + "background_color": "#ffffff", + "description": "Topos is a web based live coding platform", + "screenshots": [ + { + "src": "favicon/screenshot_miniature.png", + "sizes": "640x320", + "type": "image/gif", + "form_factor": "wide", + "label": "Topos application" + }, + { + "src": "favicon/topos_code.png", + "sizes": "1280x768", + "type": "image/gif", + "label": "Topos code" + } + ] +} diff --git a/src/API.ts b/src/API.ts index f5ec3dd..856e697 100644 --- a/src/API.ts +++ b/src/API.ts @@ -121,7 +121,7 @@ export class UserAPI { public currentSeed: string | undefined = undefined; public localSeeds = new Map(); public patternCache = new LRUCache({ max: 10000, ttl: 10000 * 60 * 5 }); - public invalidPatterns: {[key: string]: boolean} = {}; + public invalidPatterns: { [key: string]: boolean } = {}; public cueTimes: { [key: string]: number } = {}; private errorTimeoutID: number = 0; private printTimeoutID: number = 0; @@ -757,15 +757,15 @@ export class UserAPI { this.patternCache.delete(id); }; - maybeToNumber = (something: any): number|any => { + maybeToNumber = (something: any): number | any => { // If something is BigInt - if(typeof something === "bigint") { + if (typeof something === "bigint") { return Number(something); } else { return something; } } - + cache = (key: string, value: any) => { /** * Gets or sets a value in the cache. @@ -774,40 +774,40 @@ export class UserAPI { * @param value - The value to set * @returns The value of the key */ - if(value !== undefined) { - if(isGenerator(value)) { - if(this.patternCache.has(key)) { - const cachedValue = (this.patternCache.get(key) as Generator).next().value - if(cachedValue!==0 && !cachedValue) { - const generator = value as unknown as Generator - this.patternCache.set(key, generator); - return this.maybeToNumber(generator.next().value); - } - return this.maybeToNumber(cachedValue); - } else { + if (value !== undefined) { + if (isGenerator(value)) { + if (this.patternCache.has(key)) { + const cachedValue = (this.patternCache.get(key) as Generator).next().value + if (cachedValue !== 0 && !cachedValue) { const generator = value as unknown as Generator this.patternCache.set(key, generator); return this.maybeToNumber(generator.next().value); } - } else if(isGeneratorFunction(value)) { - if(this.patternCache.has(key)) { - const cachedValue = (this.patternCache.get(key) as Generator).next().value; - if(cachedValue || cachedValue===0 || cachedValue===0n) { - return this.maybeToNumber(cachedValue); - } else { - const generator = value(); - this.patternCache.set(key, generator); - return this.maybeToNumber(generator.next().value); - } + return this.maybeToNumber(cachedValue); + } else { + const generator = value as unknown as Generator + this.patternCache.set(key, generator); + return this.maybeToNumber(generator.next().value); + } + } else if (isGeneratorFunction(value)) { + if (this.patternCache.has(key)) { + const cachedValue = (this.patternCache.get(key) as Generator).next().value; + if (cachedValue || cachedValue === 0 || cachedValue === 0n) { + return this.maybeToNumber(cachedValue); } else { const generator = value(); this.patternCache.set(key, generator); return this.maybeToNumber(generator.next().value); } } else { - this.patternCache.set(key, value); - return this.maybeToNumber(value); + const generator = value(); + this.patternCache.set(key, generator); + return this.maybeToNumber(generator.next().value); } + } else { + this.patternCache.set(key, value); + return this.maybeToNumber(value); + } } else { return this.maybeToNumber(this.patternCache.get(key)); } @@ -833,27 +833,27 @@ export class UserAPI { if (this.app.api.patternCache.has(key)) { player = this.app.api.patternCache.get(key) as Player; - if (typeof input === "string" && - player.input !== input && - player.atTheBeginning()) { - replace = true; + if (typeof input === "string" && + player.input !== input && + player.atTheBeginning()) { + replace = true; } } if ((typeof input !== "string" || validSyntax) && (!player || replace)) { const newPlayer = new Player(input, options, this.app, zid); - if(newPlayer.isValid()) { + if (newPlayer.isValid()) { player = newPlayer this.patternCache.set(key, player); - } else if(typeof input === "string") { + } else if (typeof input === "string") { this.invalidPatterns[input] = true; } } - if(player) { + if (player) { - if(player.atTheBeginning()) { - if(typeof input === "string" && !validSyntax) this.app.api.log(`Invalid syntax: ${input}`); + if (player.atTheBeginning()) { + if (typeof input === "string" && !validSyntax) this.app.api.log(`Invalid syntax: ${input}`); } if (player.ziffers.generator && player.ziffers.generatorDone) { @@ -920,7 +920,7 @@ export class UserAPI { * * @returns True if the code is being evaluated for the first time */ - const firstTime = this.app.api.onceEvaluator; + const firstTime = this.app.api.onceEvaluator; this.app.api.onceEvaluator = false; return firstTime; @@ -1395,7 +1395,7 @@ export class UserAPI { /** * Returns the number of pulses in a given bar */ - return (this.tempo()*this.ppqn()*this.nominator())/60; + return (this.tempo() * this.ppqn() * this.nominator()) / 60; } // ============================================================= @@ -1444,7 +1444,7 @@ export class UserAPI { const results: boolean[] = nArray.map( (value) => (this.app.clock.pulses_since_origin - Math.floor(nudge * this.ppqn())) % - Math.floor(value * this.ppqn()) === + Math.floor(value * this.ppqn()) === 0, ); return results.some((value) => value === true); @@ -1464,7 +1464,7 @@ export class UserAPI { const results: boolean[] = nArray.map( (value) => (this.app.clock.pulses_since_origin - nudgeInPulses) % - Math.floor(value * barLength) === + Math.floor(value * barLength) === 0, ); return results.some((value) => value === true); @@ -2071,7 +2071,7 @@ export class UserAPI { // ============================================================= register = (name: string, operation: EventOperation): void => { - AbstractEvent.prototype[name] = function ( + AbstractEvent.prototype[name] = function( this: AbstractEvent, ...args: any[] ) { @@ -2259,7 +2259,7 @@ export class UserAPI { * Returns the current pulse location in the current bar. * @returns The current pulse location in the current bar */ - return ((this.epulse() / this.pulsesForBar())*this.w())%this.w() + return ((this.epulse() / this.pulsesForBar()) * this.w()) % this.w() } public clear = (): boolean => { @@ -2268,9 +2268,9 @@ export class UserAPI { * @param timeout - The timeout in seconds */ const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; - const ctx = canvas.getContext("2d")!; - ctx.clearRect(0, 0, canvas.width, canvas.height); - return true; + const ctx = canvas.getContext("2d")!; + ctx.clearRect(0, 0, canvas.width, canvas.height); + return true; } public w = (): number => { @@ -2307,21 +2307,21 @@ export class UserAPI { return this.w() / 2; } - public background = (color: string|number, ...gb:number[]): boolean => { + public background = (color: string | number, ...gb: number[]): boolean => { /** * Set background color of the canvas. * @param color - The color to set. String or 3 numbers representing RGB values. */ const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; - if(typeof color === "number") color = `rgb(${color},${gb[0]},${gb[1]})`; + if (typeof color === "number") color = `rgb(${color},${gb[0]},${gb[1]})`; ctx.fillStyle = color; ctx.fillRect(0, 0, canvas.width, canvas.height); return true; } bg = this.background; - public linearGradient = (x1: number, y1: number, x2: number, y2: number, ...stops: (number|string)[]) => { + public linearGradient = (x1: number, y1: number, x2: number, y2: number, ...stops: (number | string)[]) => { /** * Set linear gradient on the canvas. * @param x1 - The x-coordinate of the start point @@ -2334,15 +2334,15 @@ export class UserAPI { const ctx = canvas.getContext("2d")!; const gradient = ctx.createLinearGradient(x1, y1, x2, y2); // Parse pairs of values from stops - for(let i=0; i { + public radialGradient = (x1: number, y1: number, r1: number, x2: number, y2: number, r2: number, ...stops: (number | string)[]) => { /** * Set radial gradient on the canvas. * @param x1 - The x-coordinate of the start circle @@ -2356,15 +2356,15 @@ export class UserAPI { const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; const gradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2); - for(let i=0; i { + public conicGradient = (x: number, y: number, angle: number, ...stops: (number | string)[]) => { /** * Set conic gradient on the canvas. * @param x - The x-coordinate of the center of the gradient @@ -2375,9 +2375,9 @@ export class UserAPI { const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; const gradient = ctx.createConicGradient(x, y, angle); - for(let i=0; i { - if(typeof curves === "object") { + if (typeof curves === "object") { fillStyle = curves.fillStyle || "white"; x = curves.x || this.wc(); y = curves.y || this.hc(); curve = curves.curve || 1.5; - radius = curves.radius || this.hc()/2; + radius = curves.radius || this.hc() / 2; curves = curves.curves || 6; } const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; - + // Draw the shape using quadratic Bézier curves ctx.beginPath(); ctx.fillStyle = fillStyle; - + if (curves === 0) { // Draw a circle if curves = 0 ctx.arc(x, y, radius, 0, 2 * Math.PI); @@ -2429,7 +2429,7 @@ export class UserAPI { ctx.fill(); } else if (curves === 1) { // Draw a single curve (ellipse) if curves = 1 - ctx.ellipse(x, y, radius*0.8, (radius* curve)*0.7, 0, 0, 2 * Math.PI); + ctx.ellipse(x, y, radius * 0.8, (radius * curve) * 0.7, 0, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); } else if (curves === 2) { @@ -2438,20 +2438,20 @@ export class UserAPI { // First curve ctx.quadraticCurveTo(x + radius * curve, y, x, y + radius); - + // Second symmetric curve ctx.quadraticCurveTo(x - radius * curve, y, x, y - radius); ctx.closePath(); ctx.fill(); - } else { + } else { // Draw the curved shape with the specified number of curves ctx.moveTo(x, y - radius); let points = []; for (let i = 0; i < curves; i++) { const startAngle = (i / curves) * 2 * Math.PI; const endAngle = startAngle + (2 * Math.PI) / curves; - + const controlX = x + radius * curve * Math.cos(startAngle + Math.PI / curves); const controlY = y + radius * curve * Math.sin(startAngle + Math.PI / curves); points.push([x + radius * Math.cos(startAngle), y + radius * Math.sin(startAngle)]); @@ -2466,28 +2466,28 @@ export class UserAPI { ctx.fillStyle = secondary; // Form the shape from points with straight lines and fill it ctx.moveTo(points[0][0], points[0][1]); - for(let point of points) ctx.lineTo(point[0], point[1]); + for (let point of points) ctx.lineTo(point[0], point[1]); // Close and fill - + ctx.closePath(); ctx.fill(); } return true; }; - + public equilateral = ( - radius: number|ShapeObject = this.hc()/3, + radius: number | ShapeObject = this.hc() / 3, fillStyle: string = "white", rotation: number = 0, x: number = this.wc(), y: number = this.hc(), ): boolean => { - if(typeof radius === "object") { + if (typeof radius === "object") { fillStyle = radius.fillStyle || "white"; x = radius.x || this.wc(); y = radius.y || this.hc(); rotation = radius.rotation || 0; - radius = radius.radius || this.hc()/3; + radius = radius.radius || this.hc() / 3; } const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; @@ -2506,20 +2506,20 @@ export class UserAPI { } public triangular = ( - width: number|ShapeObject = this.hc()/3, - height: number = this.hc()/3, + width: number | ShapeObject = this.hc() / 3, + height: number = this.hc() / 3, fillStyle: string = "white", rotation: number = 0, x: number = this.wc(), y: number = this.hc(), ): boolean => { - if(typeof width === "object") { + if (typeof width === "object") { fillStyle = width.fillStyle || "white"; x = width.x || this.wc(); y = width.y || this.hc(); rotation = width.rotation || 0; - height = width.height || this.hc()/3; - width = width.width || this.hc()/3; + height = width.height || this.hc() / 3; + width = width.width || this.hc() / 3; } const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; @@ -2539,16 +2539,16 @@ export class UserAPI { pointy = this.triangular; public ball = ( - radius: number|ShapeObject = this.hc()/3, + radius: number | ShapeObject = this.hc() / 3, fillStyle: string = "white", x: number = this.wc(), y: number = this.hc(), ): boolean => { - if(typeof radius === "object") { + if (typeof radius === "object") { fillStyle = radius.fillStyle || "white"; x = radius.x || this.wc(); y = radius.y || this.hc(); - radius = radius.radius || this.hc()/3; + radius = radius.radius || this.hc() / 3; } const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; @@ -2585,26 +2585,26 @@ export class UserAPI { stroke = slices.stroke || "black"; slices = slices.slices || 3; } - + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; ctx.save(); ctx.translate(x, y); ctx.rotate((rotation * Math.PI) / 180); - - if(slices<2) { + + if (slices < 2) { ctx.beginPath(); ctx.arc(0, 0, radius, 0, 2 * Math.PI); ctx.closePath(); - ctx.fillStyle = slices<1 ? secondary : fillStyle; + ctx.fillStyle = slices < 1 ? secondary : fillStyle; ctx.fill(); - + ctx.beginPath(); ctx.arc(0, 0, hole, 0, 2 * Math.PI); ctx.closePath(); ctx.fillStyle = secondary; ctx.fill(); - + ctx.restore(); return true; } @@ -2615,17 +2615,17 @@ export class UserAPI { for (let i = 0; i < totalSlices; i++) { const startAngle = i * sliceAngle; const endAngle = (i + 1) * sliceAngle; - + // Calculate the position of the outer arc const outerStartX = hole * Math.cos(startAngle); const outerStartY = hole * Math.sin(startAngle); - + ctx.beginPath(); ctx.moveTo(outerStartX, outerStartY); ctx.arc(0, 0, radius, startAngle, endAngle); ctx.arc(0, 0, hole, endAngle, startAngle, true); ctx.closePath(); - + // Fill and stroke the slices with the specified fill style if (i < slices - eaten) { // Regular slices are white @@ -2639,14 +2639,14 @@ export class UserAPI { ctx.strokeStyle = stroke; ctx.stroke(); } - + ctx.restore(); return true; }; public pie = ( - slices: number|ShapeObject = 3, + slices: number | ShapeObject = 3, eaten: number = 0, radius: number = this.hc() / 3, fillStyle: string = "white", @@ -2667,18 +2667,18 @@ export class UserAPI { eaten = slices.eaten || 0; slices = slices.slices || 3; } - + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; ctx.save(); ctx.translate(x, y); ctx.rotate((rotation * Math.PI) / 180); - - if(slices<2) { + + if (slices < 2) { ctx.beginPath(); ctx.arc(0, 0, radius, 0, 2 * Math.PI); ctx.closePath(); - ctx.fillStyle = slices<1 ? secondary : fillStyle; + ctx.fillStyle = slices < 1 ? secondary : fillStyle; ctx.fill(); ctx.restore(); return true; @@ -2695,7 +2695,7 @@ export class UserAPI { ctx.arc(0, 0, radius, startAngle, endAngle); ctx.lineTo(0, 0); // Connect to center ctx.closePath(); - + // Fill and stroke the slices with the specified fill style if (i < slices - eaten) { // Regular slices are white @@ -2709,67 +2709,67 @@ export class UserAPI { ctx.strokeStyle = stroke; ctx.stroke(); } - + ctx.restore(); return true; }; - + public star = ( - points: number|ShapeObject = 5, - radius: number = this.hc()/3, + points: number | ShapeObject = 5, + radius: number = this.hc() / 3, fillStyle: string = "white", rotation: number = 0, - outerRadius: number = radius/100, + outerRadius: number = radius / 100, x: number = this.wc(), y: number = this.hc(), ): boolean => { - if(typeof points === "object") { - radius = points.radius || this.hc()/3; + if (typeof points === "object") { + radius = points.radius || this.hc() / 3; fillStyle = points.fillStyle || "white"; x = points.x || this.wc(); y = points.y || this.hc(); rotation = points.rotation || 0; - outerRadius = points.outerRadius || radius/100; + outerRadius = points.outerRadius || radius / 100; points = points.points || 5; } - const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; - if(points<1) return this.ball(radius, fillStyle, x, y); - if(points==1) return this.equilateral(radius, fillStyle, 0, x, y); - const ctx = canvas.getContext("2d")!; - ctx.save(); - ctx.translate(x, y); - ctx.rotate((rotation * Math.PI) / 180); - ctx.beginPath(); - ctx.moveTo(0, -radius); - for (let i = 0; i < points; i++) { - ctx.rotate(Math.PI / points); - ctx.lineTo(0, -(radius * outerRadius)); - ctx.rotate(Math.PI / points); - ctx.lineTo(0, -radius); - } - ctx.closePath(); - ctx.fillStyle = fillStyle; - ctx.fill(); - ctx.restore(); - return true; + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; + if (points < 1) return this.ball(radius, fillStyle, x, y); + if (points == 1) return this.equilateral(radius, fillStyle, 0, x, y); + const ctx = canvas.getContext("2d")!; + ctx.save(); + ctx.translate(x, y); + ctx.rotate((rotation * Math.PI) / 180); + ctx.beginPath(); + ctx.moveTo(0, -radius); + for (let i = 0; i < points; i++) { + ctx.rotate(Math.PI / points); + ctx.lineTo(0, -(radius * outerRadius)); + ctx.rotate(Math.PI / points); + ctx.lineTo(0, -radius); + } + ctx.closePath(); + ctx.fillStyle = fillStyle; + ctx.fill(); + ctx.restore(); + return true; }; public stroke = ( - width: number|ShapeObject = 1, + width: number | ShapeObject = 1, strokeStyle: string = "white", rotation: number = 0, - x1: number = this.wc()-this.wc()/10, + x1: number = this.wc() - this.wc() / 10, y1: number = this.hc(), - x2: number = this.wc()+this.wc()/5, + x2: number = this.wc() + this.wc() / 5, y2: number = this.hc(), ): boolean => { - if(typeof width === "object") { + if (typeof width === "object") { strokeStyle = width.strokeStyle || "white"; - x1 = width.x1 || this.wc()-this.wc()/10; + x1 = width.x1 || this.wc() - this.wc() / 10; y1 = width.y1 || this.hc(); - x2 = width.x2 || this.wc()+this.wc()/5; + x2 = width.x2 || this.wc() + this.wc() / 5; y2 = width.y2 || this.hc(); rotation = width.rotation || 0; width = width.width || 1; @@ -2781,7 +2781,7 @@ export class UserAPI { ctx.rotate((rotation * Math.PI) / 180); ctx.beginPath(); ctx.moveTo(0, 0); - ctx.lineTo(x2-x1, y2-y1); + ctx.lineTo(x2 - x1, y2 - y1); ctx.lineWidth = width; ctx.strokeStyle = strokeStyle; ctx.stroke(); @@ -2790,20 +2790,20 @@ export class UserAPI { }; public box = ( - width: number|ShapeObject = this.wc()/4, - height: number = this.wc()/4, + width: number | ShapeObject = this.wc() / 4, + height: number = this.wc() / 4, fillStyle: string = "white", rotation: number = 0, - x: number = this.wc()-this.wc()/8, - y: number = this.hc()-this.hc()/8, + x: number = this.wc() - this.wc() / 8, + y: number = this.hc() - this.hc() / 8, ): boolean => { - if(typeof width === "object") { + if (typeof width === "object") { fillStyle = width.fillStyle || "white"; - x = width.x || this.wc()-this.wc()/4; - y = width.y || this.hc()-this.hc()/2; + x = width.x || this.wc() - this.wc() / 4; + y = width.y || this.hc() - this.hc() / 2; rotation = width.rotation || 0; - height = width.height || this.wc()/4; - width = width.width || this.wc()/4; + height = width.height || this.wc() / 4; + width = width.width || this.wc() / 4; } const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; @@ -2817,27 +2817,27 @@ export class UserAPI { } public smiley = ( - happiness: number|ShapeObject = 0, - radius: number = this.hc()/3, + happiness: number | ShapeObject = 0, + radius: number = this.hc() / 3, eyeSize: number = 3.0, fillStyle: string = "yellow", rotation: number = 0, x: number = this.wc(), y: number = this.hc(), ): boolean => { - if(typeof happiness === "object") { + if (typeof happiness === "object") { fillStyle = happiness.fillStyle || "yellow"; x = happiness.x || this.wc(); y = happiness.y || this.hc(); rotation = happiness.rotation || 0; eyeSize = happiness.eyeSize || 3.0; - radius = happiness.radius || this.hc()/3; + radius = happiness.radius || this.hc() / 3; happiness = happiness.happiness || 0; } const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; const ctx = canvas.getContext("2d")!; // Map the rotation value to an angle within the range of -PI to PI - const rotationAngle = rotation/100 * Math.PI; + const rotationAngle = rotation / 100 * Math.PI; ctx.save(); ctx.translate(x, y); ctx.rotate(rotationAngle); @@ -2871,16 +2871,16 @@ export class UserAPI { const mouthY = radius / 2; const mouthLength = radius * 0.9; const smileFactor = 0.25; // Adjust for the smile curvature - + let controlPointX = 0; let controlPointY = 0; - + if (happiness >= 0) { controlPointY = mouthY + happiness * smileFactor * radius / 2; } else { controlPointY = mouthY + happiness * smileFactor * radius / 2; } - + ctx.beginPath(); ctx.moveTo(-mouthLength / 2, mouthY); ctx.quadraticCurveTo(controlPointX, controlPointY, mouthLength / 2, mouthY); @@ -2892,7 +2892,7 @@ export class UserAPI { } drawText = ( - text: string|ShapeObject, + text: string | ShapeObject, fontSize: number = 24, rotation: number = 0, font: string = "Arial", @@ -2901,7 +2901,7 @@ export class UserAPI { fillStyle: string = "white", filter: string = "none", ): boolean => { - if(typeof text === "object") { + if (typeof text === "object") { fillStyle = text.fillStyle || "white"; x = text.x || this.wc(); y = text.y || this.hc(); @@ -2925,16 +2925,16 @@ export class UserAPI { } image = ( - url: string|ShapeObject, - width: number = this.wc()/2, - height: number = this.hc()/2, + url: string | ShapeObject, + width: number = this.wc() / 2, + height: number = this.hc() / 2, rotation: number = 0, x: number = this.wc(), y: number = this.hc(), filter: string = "none", ): boolean => { - if(typeof url === "object") { - if(!url.url) return true; + if (typeof url === "object") { + if (!url.url) return true; x = url.x || this.wc(); y = url.y || this.hc(); rotation = url.rotation || 0; @@ -2951,12 +2951,12 @@ export class UserAPI { ctx.filter = filter; const image = new Image(); image.src = url; - ctx.drawImage(image, -width/2, -height/2, width, height); + ctx.drawImage(image, -width / 2, -height / 2, width, height); ctx.restore(); return true; } - randomChar = (length: number= 1, min: number = 0, max: number = 65536): string => { + randomChar = (length: number = 1, min: number = 0, max: number = 65536): string => { return Array.from( { length }, () => String.fromCodePoint(Math.floor(Math.random() * (max - min) + min)) @@ -2967,7 +2967,7 @@ export class UserAPI { const codePoint = Math.floor(Math.random() * (max - min) + min); return String.fromCodePoint(codePoint); }; - + emoji = (n: number = 1): string => { return this.randomChar(n, 0x1f600, 0x1f64f); }; @@ -3068,7 +3068,7 @@ export class UserAPI { this.app.clock.time_signature = [numerator, denominator]; }; - public cue = (functionName: string|Function): void => { + public cue = (functionName: string | Function): void => { functionName = typeof functionName === "function" ? functionName.name : functionName; this.cueTimes[functionName] = this.app.clock.pulses_since_origin; }; @@ -3086,7 +3086,6 @@ export class UserAPI { let theme_names = this.getThemes(); let selected_theme = theme_names[Math.floor(Math.random() * theme_names.length)]; this.app.readTheme(selected_theme); - this.app.api.log(selected_theme); } public nextTheme = (): void => { diff --git a/src/assets/android-chrome-192x192.png b/src/assets/android-chrome-192x192.png new file mode 100644 index 0000000..6425053 Binary files /dev/null and b/src/assets/android-chrome-192x192.png differ diff --git a/src/assets/android-chrome-512x512.png b/src/assets/android-chrome-512x512.png new file mode 100644 index 0000000..4254207 Binary files /dev/null and b/src/assets/android-chrome-512x512.png differ diff --git a/src/assets/apple-touch-icon.png b/src/assets/apple-touch-icon.png new file mode 100644 index 0000000..979536b Binary files /dev/null and b/src/assets/apple-touch-icon.png differ diff --git a/src/assets/favicon-16x16.png b/src/assets/favicon-16x16.png new file mode 100644 index 0000000..b79c6b8 Binary files /dev/null and b/src/assets/favicon-16x16.png differ diff --git a/src/assets/favicon-32x32.png b/src/assets/favicon-32x32.png new file mode 100644 index 0000000..2f344cd Binary files /dev/null and b/src/assets/favicon-32x32.png differ diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico new file mode 100644 index 0000000..19cdd93 Binary files /dev/null and b/src/assets/favicon.ico differ diff --git a/src/assets/favicon.svg b/src/assets/favicon.svg new file mode 100644 index 0000000..b152ce8 --- /dev/null +++ b/src/assets/favicon.svg @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/src/assets/mstile-150x150.png b/src/assets/mstile-150x150.png new file mode 100644 index 0000000..bef2f21 Binary files /dev/null and b/src/assets/mstile-150x150.png differ diff --git a/src/assets/safari-pinned-tab.svg b/src/assets/safari-pinned-tab.svg new file mode 100644 index 0000000..c46073d --- /dev/null +++ b/src/assets/safari-pinned-tab.svg @@ -0,0 +1,15 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/topos_code.png b/src/assets/topos_code.png new file mode 100644 index 0000000..95ba295 Binary files /dev/null and b/src/assets/topos_code.png differ diff --git a/topos_frog.png b/src/assets/topos_frog.png similarity index 100% rename from topos_frog.png rename to src/assets/topos_frog.png diff --git a/src/assets/topos_gif.gif b/src/assets/topos_gif.gif new file mode 100644 index 0000000..ad26885 Binary files /dev/null and b/src/assets/topos_gif.gif differ diff --git a/src/main.ts b/src/main.ts index e0581f8..8d165d6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -31,13 +31,12 @@ import { makeStringExtensions } from "./extensions/StringExtensions"; import { installInterfaceLogic } from "./InterfaceLogic"; import { installWindowBehaviors } from "./WindowBehavior"; import { makeNumberExtensions } from "./extensions/NumberExtensions"; -// @ts-ignore -import { registerSW } from "virtual:pwa-register"; import colors from "./colors.json"; +// @ts-ignore +const images = import.meta.glob("./assets/*") + + -if ("serviceWorker" in navigator) { - registerSW(); -} export class Editor { // Universes and settings diff --git a/vite.config.js b/vite.config.js index 22d1e67..f53522b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,15 +4,16 @@ import viteCompression from "vite-plugin-compression"; const vitePWAconfiguration = { devOptions: { - enabled: true, + enabled: false, suppressWarnings: true, }, - workbox: { sourcemap: false, cleanupOutdatedCaches: false, + maximumFileSizeToCacheInBytes: 10000000, globPatterns: [ "**/*.{js,js.gz,css,html,gif,png,json,woff,woff2,json,ogg,wav,mp3,ico,png,svg}", + "favicon/*.{js,js.gz,css,html,gif,png,json,woff,woff2,json,ogg,wav,mp3,ico,png,svg}", ], runtimeCaching: [ { @@ -35,14 +36,9 @@ const vitePWAconfiguration = { }, ], }, - includeAssets: [ - "favicon/favicon.icon", - "favicon/apple-touch-icon.png", - "mask-icon.svg", - ], - manifest: "manifest.webmanifest", + manifest: false, registerType: "autoUpdate", - injectRegister: "auto", + injectRegister: "script-defer", }; export default defineConfig(({ command, mode, ssrBuild }) => { @@ -54,6 +50,13 @@ export default defineConfig(({ command, mode, ssrBuild }) => { port: 8000, strictPort: true, }, + build: { + outDir: "dist", + emptyOutDir: true, + cssCodeSplit: true, + cssMinify: true, + minify: true, + } }; } else { return { diff --git a/yarn.lock b/yarn.lock index d32d3cc..7c847aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2203,7 +2203,7 @@ fast-glob@^3.2.12: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.3.1: +fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -3774,13 +3774,13 @@ vite-plugin-markdown@^2.1.0: htmlparser2 "^6.0.0" markdown-it "^12.0.0" -vite-plugin-pwa@^0.16.7: - version "0.16.7" - resolved "https://registry.yarnpkg.com/vite-plugin-pwa/-/vite-plugin-pwa-0.16.7.tgz#3dcacc342766ff3598472ac7d5e0782d14e2853e" - integrity sha512-4WMA5unuKlHs+koNoykeuCfTcqEGbiTRr8sVYUQMhc6tWxZpSRnv9Ojk4LKmqVhoPGHfBVCdGaMo8t9Qidkc1Q== +vite-plugin-pwa@^0.17.4: + version "0.17.4" + resolved "https://registry.yarnpkg.com/vite-plugin-pwa/-/vite-plugin-pwa-0.17.4.tgz#be3b3714d4148681bc73e8e0b1e6ce1a71fa79f2" + integrity sha512-j9iiyinFOYyof4Zk3Q+DtmYyDVBDAi6PuMGNGq6uGI0pw7E+LNm9e+nQ2ep9obMP/kjdWwzilqUrlfVRj9OobA== dependencies: debug "^4.3.4" - fast-glob "^3.3.1" + fast-glob "^3.3.2" pretty-bytes "^6.1.1" workbox-build "^7.0.0" workbox-window "^7.0.0"