Let Prettier format the code

This commit is contained in:
Hannes Güdelhöfer 2025-01-02 15:40:14 +01:00
parent c7866794c9
commit 3765caa811
No known key found for this signature in database
GPG key ID: D5D24ACBD421D3B3
90 changed files with 1907 additions and 1621 deletions

View file

@ -9,201 +9,201 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### New Features
* Support for Renardo (Foxdot fork)
* web: Sandbox web languages using iframes
* web: Toggle line numbers with <kbd>Shift</kbd>-<kbd>Ctrl</kbd>-<kbd>W</kbd>
- Support for Renardo (Foxdot fork)
- web: Sandbox web languages using iframes
- web: Toggle line numbers with <kbd>Shift</kbd>-<kbd>Ctrl</kbd>-<kbd>W</kbd>
and linewrap with <kbd>Shift</kbd>-<kbd>Ctrl</kbd>-<kbd>W</kbd>.
* web(strudel): Code highlighting
- web(strudel): Code highlighting
### Bugfixes
* repl: Use `sardine` instead of `fishery` when running Sardine REPL
* web(hydra): Use correct canvas size by default + pixelated image rendering
* web(strudel): Misc fixse
* Lots of name clashing between hydra, strudel and mercury-web were solved with
- repl: Use `sardine` instead of `fishery` when running Sardine REPL
- web(hydra): Use correct canvas size by default + pixelated image rendering
- web(strudel): Misc fixse
- Lots of name clashing between hydra, strudel and mercury-web were solved with
language sandboxes.
## [1.1.1] - 2024-01-29
### Bugfixes
* web(strudel): Use REPL evaluate method, this fixes the "`setcps` is not defined" error.
- web(strudel): Use REPL evaluate method, this fixes the "`setcps` is not defined" error.
## [1.1.0] - 2024-01-28
### Bugfixes
* web(hydra): Fixed issues with P5 not loading properly
* web(hydra): `await` now works properly
* web: Fixed issue with `flok-web` binary script running in development mode
- web(hydra): Fixed issues with P5 not loading properly
- web(hydra): `await` now works properly
- web: Fixed issue with `flok-web` binary script running in development mode
### New Features
* web: Share URL command for sharing session URL along with current layout and
- web: Share URL command for sharing session URL along with current layout and
code.
* web: `hideErrors` query parameter option for hiding errors of web targets
* web: `code` and `c0`..`c7` hash parameters to set code for each editor
- web: `hideErrors` query parameter option for hiding errors of web targets
- web: `code` and `c0`..`c7` hash parameters to set code for each editor
### Changed
* web(hydra): Updated P5 to 1.9.0
* web(mercury): Updated Mercury to 0.14.0
* web(strudel): Updated Strudel to 1.0.0
* web: Updated Codemirror and related packages
* web: Updated Yjs and related packages
* web: Updated Vite 5
* web: Moved `username` and `targets` query parameters as hash parameters
- web(hydra): Updated P5 to 1.9.0
- web(mercury): Updated Mercury to 0.14.0
- web(strudel): Updated Strudel to 1.0.0
- web: Updated Codemirror and related packages
- web: Updated Yjs and related packages
- web: Updated Vite 5
- web: Moved `username` and `targets` query parameters as hash parameters
## [1.0.2] - 2024-01-13
### Changed
* repl: Fix error when trying to use flok-repl without `-T`/`--types` argument (default value)
- repl: Fix error when trying to use flok-repl without `-T`/`--types` argument (default value)
## [1.0.1] - 2024-01-13
### Changed
* web: Fixed and optimized flok-web package, by removing lots of runtime deps
- web: Fixed and optimized flok-web package, by removing lots of runtime deps
that were used on build time only.
* web: Removed pinned flok-repl version from REPL instructions.
- web: Removed pinned flok-repl version from REPL instructions.
## [1.0.0] - 2024-01-12
Complete rewrite of Flok, with a new architecture and new features. See
Complete rewrite of Flok, with a new architecture and new features. See
[README.md](../README.md) for more information.
* Started new web project, based on Vite, Tailwind, Radix UI and Shadcn CSS frameworks.
* Upgraded CodeMirror to version 6
* Upgraded Yjs to latest version
* New modular architecture, with separate plugins related to codemirror
- Started new web project, based on Vite, Tailwind, Radix UI and Shadcn CSS frameworks.
- Upgraded CodeMirror to version 6
- Upgraded Yjs to latest version
- New modular architecture, with separate plugins related to codemirror
(following their new architecture as well)
* Document layout is now part of the session state, and can be changed
- Document layout is now part of the session state, and can be changed
dynamically, instead of being based on the query parameter `?layout`.
* Improved REPL message handling and UI
* Command palette for executing commands and configuring the editor
* Added support for Strudel and Mercury as web targets
- Improved REPL message handling and UI
- Command palette for executing commands and configuring the editor
- Added support for Strudel and Mercury as web targets
## [0.4.12] - 2024-01-06
### Added
* web: Toggle comment with Ctrl-/ or Cmd-/
* web(sardine): Free all sound for Sardine
- web: Toggle comment with Ctrl-/ or Cmd-/
- web(sardine): Free all sound for Sardine
### Changed
* repl(sardine): Changed interpreter name (fishery -> sardine)
* web: Bugfixes related to the server script (flok-web)
* web: Fixed @types/react wrong version
- repl(sardine): Changed interpreter name (fishery -> sardine)
- web: Bugfixes related to the server script (flok-web)
- web: Fixed @types/react wrong version
## [0.4.8] - 2022-05-26
### Added
* web: Add key binding Ctrl+Shift+H to hide or show editors
- web: Add key binding Ctrl+Shift+H to hide or show editors
### Changed
* web: Replace yjsdemo WebSocket Yjs server for an embedded WS server
* web: Use a random english name + first 8 chars of uuid as default session name
* web: Upgrade p5 to version 1.4.1
* web: Upgrade hydra-synth to version 1.3.16
* web: Upgrade Next to version 12
* web: Restore package bundle
- web: Replace yjsdemo WebSocket Yjs server for an embedded WS server
- web: Use a random english name + first 8 chars of uuid as default session name
- web: Upgrade p5 to version 1.4.1
- web: Upgrade hydra-synth to version 1.3.16
- web: Upgrade Next to version 12
- web: Restore package bundle
## [0.4.6] - 2021-10-31
### Added
* web: Print possible network IPs when starting web server locally
* web: New --static-dir option for setting a custom static files directory when
- web: Print possible network IPs when starting web server locally
- web: New --static-dir option for setting a custom static files directory when
starting web server.
### Changed
* web: Upgrade hydra-synth to 1.3.8
* web: Upgrade next to 11
* repl: Remove "tidal> " from stdout on tidal target; other minor improvements
- web: Upgrade hydra-synth to 1.3.8
- web: Upgrade next to 11
- repl: Remove "tidal> " from stdout on tidal target; other minor improvements
## [0.4.5] - 2021-01-05
### Added
* web: Read-only mode by adding a `readonly=1` query parameter to a session URL
* repl: In `foxdot` REPL, call `load_startup_file()` after loading Foxdot package.
- web: Read-only mode by adding a `readonly=1` query parameter to a session URL
- repl: In `foxdot` REPL, call `load_startup_file()` after loading Foxdot package.
### Changed
* web: Upgrade hydra-synth to 1.3.6
- web: Upgrade hydra-synth to 1.3.6
## [0.4.4] - 2020-12-23
### Added
* web: Follow remote cursors when moving (jump to line), but only if editor is
- web: Follow remote cursors when moving (jump to line), but only if editor is
blurred.
* web: Make remote caret visible to current user too.
* web: Properly support block evaluation with parens for
- web: Make remote caret visible to current user too.
- web: Properly support block evaluation with parens for
`sclang`/`remote_sclang` targets.
### Changed
* web: Skip 'hydra' as a flok-repl example when joining a session
* repl: Bugfix when using custom REPL command
* repl: On sclang targets, add semicolons after closing parens
- web: Skip 'hydra' as a flok-repl example when joining a session
- repl: Bugfix when using custom REPL command
- repl: On sclang targets, add semicolons after closing parens
## [0.4.3] - 2020-12-04
### Added
* repl, web: Support for Mercury
* web: When joining session, show flok-repl example based on first target in
- repl, web: Support for Mercury
- web: When joining session, show flok-repl example based on first target in
layout.
* web: New shortcut for evaluating web-target (e.g. Hydra) code only on local
- web: New shortcut for evaluating web-target (e.g. Hydra) code only on local
client (Ctrl-Alt-Enter for block execution, Shift-Alt-Enter for line
execution).
### Changed
* web: Upgrade to Hydra 1.3.4
- web: Upgrade to Hydra 1.3.4
## [0.4.2] - 2020-11-12
### Changed
* web: Better styles for selected text and remote caret
* repl: Fix path to embedded BootTidal.hs when using fallback
- web: Better styles for selected text and remote caret
- repl: Fix path to embedded BootTidal.hs when using fallback
## [0.4.1] - 2020-10-11
### Added
* web: Copy button for copying flok-repl example when joining session
* repl: Add package metadata and data dir, which includes a Tidal bootScript
- web: Copy button for copying flok-repl example when joining session
- repl: Add package metadata and data dir, which includes a Tidal bootScript
file.
### Changed
* web: Disable form when creating session, while session page loads
* repl: If ghc-pkg fails to get tidal data dir, fallback to embedded bootScript
- web: Disable form when creating session, while session page loads
- repl: If ghc-pkg fails to get tidal data dir, fallback to embedded bootScript
file.
## [0.4.0] - 2020-10-06
### Added
* repl: Add `--nickname/-N` to send REPL messages (out/err) to named user instead
- repl: Add `--nickname/-N` to send REPL messages (out/err) to named user instead
of all connected users.
* repl: Add `--notify-to-all` to force send messages to all users (old behaviour).
* repl: Add extra option `ghci` for `tidal` REPL type, for customizing ghci
- repl: Add `--notify-to-all` to force send messages to all users (old behaviour).
- repl: Add extra option `ghci` for `tidal` REPL type, for customizing ghci
binary path.
* web: Show current Flok version when joining session.
* web: Include -N option on flok-repl example when joining session.
- web: Show current Flok version when joining session.
- web: Include -N option on flok-repl example when joining session.
### Changed
* Now, by default, `flok-repl` *will not* send messages to all users by default.
- Now, by default, `flok-repl` _will not_ send messages to all users by default.
You need to enable the `--notify-to-all` option if you want the old
behaviour. If `--nickname/-N` and `--notify-to-all` are not present,
flok-repl won't send any messages, only print them on standard output/error.
@ -212,11 +212,11 @@ Complete rewrite of Flok, with a new architecture and new features. See
### Added
* repl: Add new `--config` option for customizing parameters from a JSON file
* repl: Load config file from `FLOK_CONFIG` environment file, if defined. Also,
load *.env files* automatically.
- repl: Add new `--config` option for customizing parameters from a JSON file
- repl: Load config file from `FLOK_CONFIG` environment file, if defined. Also,
load _.env files_ automatically.
### Changed
* web: Bugfix: host prop was undefined on first page load, failing to connect
- web: Bugfix: host prop was undefined on first page load, failing to connect
afterwards.

View file

@ -14,21 +14,21 @@ orientation.
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities

219
README.md
View file

@ -4,9 +4,9 @@ Web-based P2P collaborative editor for live coding music and graphics
## Features
* Similar to Etherpad, but focused on code evaluation for livecoding.
* Multiple separate slots for different languages and tools.
* REPL plugins: allows user to locally evaluate code from interpreters (like
- Similar to Etherpad, but focused on code evaluation for livecoding.
- Multiple separate slots for different languages and tools.
- REPL plugins: allows user to locally evaluate code from interpreters (like
Haskell, Ruby, Python, etc.):
- [TidalCycles](https://tidalcycles.org/)
- [SuperCollider](https://supercollider.github.io/) (sclang)
@ -14,66 +14,66 @@ Web-based P2P collaborative editor for live coding music and graphics
- [Renardo](https://renardo.org/)
- [Mercury](#mercury)
- [Sardine](https://sardine.raphaelforment.fr)
- [SonicPi](https://sonic-pi.net/) (*not implemented yet*, see [#29](https://github.com/munshkr/flok/issues/29))
- [SonicPi](https://sonic-pi.net/) (_not implemented yet_, see [#29](https://github.com/munshkr/flok/issues/29))
- ... any interpreter with a REPL (Python, Ruby, etc.)
* Web Plugins, for languages embedded in editor:
- Web Plugins, for languages embedded in editor:
- [Hydra](https://github.com/ojack/hydra)
- [p5.js](https://p5js.org/)
- [Strudel](https://strudel.cc/)
- [Mercury Web](https://www.timohoogland.com/mercury-livecoding/)
- [VEDA.js](https://github.com/fand/vedajs) (*not implemented yet*, see [#82](https://github.com/munshkr/flok/pull/82))
- [VEDA.js](https://github.com/fand/vedajs) (_not implemented yet_, see [#82](https://github.com/munshkr/flok/pull/82))
## Usage
### Public server
**WARNING - Please Read**: Using a public server can be dangerous as *anyone*
can execute code on your computer via Flok, so *please* make sure you only
**WARNING - Please Read**: Using a public server can be dangerous as _anyone_
can execute code on your computer via Flok, so _please_ make sure you only
share your session URL to trusted users and friends when you use a public
server. I will not be held responsible for any damaged caused by Flok. You
server. I will not be held responsible for any damaged caused by Flok. You
have been warned.
This is a list of known public servers:
* [flok.cc](https://flok.cc)
- [flok.cc](https://flok.cc)
#### Create a session
When you enter a Flok server, you will be shown an empty session with a single
slot, with a _target_ selected (usually `hydra`). You can either change the
slot, with a _target_ selected (usually `hydra`). You can either change the
target by clicking on the target selector at the top-left corner of the slot, or
add more slots by clicking on the *Command button* (at the top-right corner of the
screen), and then clicking on *Add Pane*, or *Configure*.
add more slots by clicking on the _Command button_ (at the top-right corner of the
screen), and then clicking on _Add Pane_, or _Configure_.
A *target* is the language or tool that Flok will communicate to create sound or
A _target_ is the language or tool that Flok will communicate to create sound or
images within the web page, or through `flok-repl`.
If you clicked on *Configure*, enter the name of the targets, separated with
commas. You can use a target multiple times and Flok will create that many
If you clicked on _Configure_, enter the name of the targets, separated with
commas. You can use a target multiple times and Flok will create that many
number of slots to write code. Currently the maximum number of slots is 8.
Examples:
* `tidal, foxdot, hydra`: 3 slots, with tidal, foxdot and hydra respectively.
* `sclang, sclang, sclang, hydra, hydra`: 5 slots total, the first 3 with
- `tidal, foxdot, hydra`: 3 slots, with tidal, foxdot and hydra respectively.
- `sclang, sclang, sclang, hydra, hydra`: 5 slots total, the first 3 with
`sclang` and the last 2 with `hydra`.
* `mercury, hydra`: 2 slots total, one with Mercury and one with Hydra.
- `mercury, hydra`: 2 slots total, one with Mercury and one with Hydra.
You will also be asked to enter a nickname. This is the name that will be shown
to other users under your cursor, when you write code. You can change it any
time by clicking on the *Change Username* inside the *Command* menu.
You will also be asked to enter a nickname. This is the name that will be shown
to other users under your cursor, when you write code. You can change it any
time by clicking on the _Change Username_ inside the _Command_ menu.
Now, just copy the URL and share it with your friends! They will be able to
Now, just copy the URL and share it with your friends! They will be able to
join the session and write code with you :-)
If you are using any target that requires a REPL, you will need to start it
separately. See the *Connect REPLs to Flok* section below.
separately. See the _Connect REPLs to Flok_ section below.
#### Connect REPLs to Flok
The last step is to start `flok-repl`, to connect Flok with your REPLs.
Just click on the *REPLs* button at the top-right corner of the screen, and
Just click on the _REPLs_ button at the top-right corner of the screen, and
copy the command shown there. It will look something like this:
```sh
@ -84,7 +84,7 @@ npx flok-repl@latest -H wss://next.flok.cc \
```
This command will automatically try to download and install `flok-repl` and
start it, connecting it to your session. If you have multiple different targets
start it, connecting it to your session. If you have multiple different targets
with REPLs, the command will start one process for each target from the same
command.
@ -109,16 +109,16 @@ npm install -g flok-web@latest flok-repl@latest
This will download and install the latest Flok web version and start a server.
Your local server will be available on
[http://localhost:3000](http://localhost:3000) from your computer. To share the
URL with your friends, change `localhost` with your local LAN IP. `flok-web`
[http://localhost:3000](http://localhost:3000) from your computer. To share the
URL with your friends, change `localhost` with your local LAN IP. `flok-web`
will try to guess your local IP in your LAN, and show it on the console, but it
might not always work.
#### Secure mode (https)
In some cases, it's needed to run Flok in secure mode, using https. This is
In some cases, it's needed to run Flok in secure mode, using https. This is
needed for some browsers, like Chrome, to allow access to the microphone and
camera (which might be needed for some targets, like Hydra). You can easily
camera (which might be needed for some targets, like Hydra). You can easily
run Flok in secure mode by passing the `--secure` parameter:
```sh
@ -128,12 +128,12 @@ npx flok-web@latest --secure
#### Note about remote users (not LAN)
Sharing your local server to other users in the Internet is a bit more
complicated, and it depends on your router and network configuration. You will
complicated, and it depends on your router and network configuration. You will
need to configure your router to forward the port 3000 to your computer, and
then share your public IP with your friends. You can find your public IP by
visiting [https://whatismyipaddress.com/](https://whatismyipaddress.com/). Also
then share your public IP with your friends. You can find your public IP by
visiting [https://whatismyipaddress.com/](https://whatismyipaddress.com/). Also
make sure to check your firewall settings, to allow incoming connections to
port 3000. It's possible that some of your remote friends won't be able to
port 3000. It's possible that some of your remote friends won't be able to
connect to your local server, because of their own network configuration.
### Supported REPL targets
@ -149,12 +149,12 @@ object, like this:
##### Extra options
* `bootScript`: Path to a custom initialization script.
- `bootScript`: Path to a custom initialization script.
* `useStack`: Uses `stack exec -- ghci` instead of plain `ghci`. Use this if
- `useStack`: Uses `stack exec -- ghci` instead of plain `ghci`. Use this if
you installed Tidal using Stack.
* `ghci`: Use a specific Ghci command instead of plain `ghci`.
- `ghci`: Use a specific Ghci command instead of plain `ghci`.
This overrides `useStack` option, if used too.
#### Sardine
@ -165,7 +165,7 @@ case if you followed a regular install.
##### Extra options
* `python`: Path to your custom `sardine` Python REPL. Use this if you need
- `python`: Path to your custom `sardine` Python REPL. Use this if you need
to target a specific install of Sardine (Python version, different path, etc).
#### FoxDot
@ -174,7 +174,7 @@ Use `flok-repl` with the `-t foxdot` parameter.
##### Extra options
* `python`: Path to Python binary. Use this if you need to use a custom Python
- `python`: Path to Python binary. Use this if you need to use a custom Python
version.
#### Renardo
@ -185,7 +185,7 @@ Use `flok-repl` with the `-t renardo` parameter.
##### Extra options
* `python`: Path to Python binary. Use this if you need to use a custom Python
- `python`: Path to Python binary. Use this if you need to use a custom Python
version.
#### SuperCollider
@ -193,39 +193,38 @@ Use `flok-repl` with the `-t renardo` parameter.
In the case of SuperCollider, there are two types of REPLs: `sclang` and
`remote_sclang`. The first one tries to run a `sclang` process and interact
with it, while the second one uses
[FlokQuark](https://github.com/munshkr/FlokQuark) to communicate with SC. Read
[FlokQuark](https://github.com/munshkr/FlokQuark) to communicate with SC. Read
[more](https://github.com/munshkr/FlokQuark/blob/master/README.md) for
installing and using it.
##### `sclang` vs. `remote_sclang`
* As of today `sclang` does not currently work on Windows, you will have to use
`remote_sclang`.
- As of today `sclang` does not currently work on Windows, you will have to use
`remote_sclang`.
* `remote_sclang` needs SC IDE to be running, and you need FlokQuark installed
- `remote_sclang` needs SC IDE to be running, and you need FlokQuark installed
and running there. Be sure to start your flok-repl with both `-t remote_sclang`
and `-n sclang` flags.
* If you use `remote_sclang`, you won't see Post messages from Flok, because
FlokQuark does not currently capture Post messages and errors. It is
- If you use `remote_sclang`, you won't see Post messages from Flok, because
FlokQuark does not currently capture Post messages and errors. It is
recommended to deattach the Post window and have it visible while using Flok.
* `sclang` can't use any GUI object (like Scopes, Proxy mixers, etc.). You will
- `sclang` can't use any GUI object (like Scopes, Proxy mixers, etc.). You will
need to use `remote_sclang` + SC IDE for this.
#### Hydra
[Hydra](https://hydra.ojack.xyz/) is a video synth and coding environment, inspired in
analog video synthesis, that runs directly in the browser and is already included in
the web App. You don't need to install anything as it runs on the browser. Just use
the web App. You don't need to install anything as it runs on the browser. Just use
the `hydra` target to execute Hydra code.
You can also use [p5.js](https://p5js.org/) within a `hydra` target, like you would in
the official Hydra editor.
##### `P()` function
The `P()` function allows you to use strudel mini-patterns in Hydra.
It uses the same timing information as Strudel itself, so it will be synchronized with the audio.
@ -256,26 +255,28 @@ fft(index: number,
```
Parameters:
- `index: number` : The index of the bucket to return the value from.
- `buckets: number`: The number of buckets to combine the underlying FFT data
too. Defaults to 8.
- `options?: { min?: number; max?: number, scale?: number }`:
- `min?: number`: Minimum clamp value of the underlying data. Defaults to
-150.
- `max?: number`: Maximum clamp value of the underlying data. Defaults to 0.
- `scale?: number`: Scale of the output. Defaults to 1 (so the output is
from 0 to 1)
- `analyzerId?: string`: Which Strudel analyser to listen to. Defaults to
`flok-master`, which is also automatically added to all strudel patterns.
Can be used to route different patterns to different parts of the hydra
visualiser
- `min?: number`: Minimum clamp value of the underlying data. Defaults to
-150.
- `max?: number`: Maximum clamp value of the underlying data. Defaults to 0.
- `scale?: number`: Scale of the output. Defaults to 1 (so the output is
from 0 to 1)
- `analyzerId?: string`: Which Strudel analyser to listen to. Defaults to
`flok-master`, which is also automatically added to all strudel patterns.
Can be used to route different patterns to different parts of the hydra
visualiser
Example:
```js
solid(() => fft(0,1), 0)
.mask(shape(5,.05))
.rotate(() => 50 * fft(0, 40)) // we need to supply a function
// for the parameter, for it to update automaticaly.
solid(() => fft(0, 1), 0)
.mask(shape(5, 0.05))
.rotate(() => 50 * fft(0, 40)); // we need to supply a function
// for the parameter, for it to update automaticaly.
```
**Caveat**: Because of how we setup the analyze node on Strudel, every Strudel pane
@ -320,79 +321,79 @@ npm start
### Packages overview
This repository is a monorepo, with multiple modular packages. Each package
has its own README with more information. Here is a brief overview of the
This repository is a monorepo, with multiple modular packages. Each package
has its own README with more information. Here is a brief overview of the
packages:
#### App packages
* [`flok-web`](packages/web): Web Server for Flok
* [`flok-repl`](packages/repl): REPL Client for Flok
* [`flok-server`](packages/server): Flok server, handles sessions and
- [`flok-web`](packages/web): Web Server for Flok
- [`flok-repl`](packages/repl): REPL Client for Flok
- [`flok-server`](packages/server): Flok server, handles sessions and
communication between clients.
#### Lib packages
* [`@flok-editor/pubsub`](packages/pubsub): Pub/Sub client-server, used for
- [`@flok-editor/pubsub`](packages/pubsub): Pub/Sub client-server, used for
remote code execution and message passing on Flok
* [`@flok-editor/session`](packages/session): Flok session package
* [`@flok-editor/server-middleware`](packages/server-middleware): Server
- [`@flok-editor/session`](packages/session): Flok session package
- [`@flok-editor/server-middleware`](packages/server-middleware): Server
middleware for Flok, handles WebSocket connections and WebRTC signaling
* [`@flok-editor/cm-eval`](packages/cm-eval): CodeMirror 6 extension for code
- [`@flok-editor/cm-eval`](packages/cm-eval): CodeMirror 6 extension for code
evaluation
* [`@flok-editor/lang-tidal`](packages/lang-tidal): TidalCycles language support
- [`@flok-editor/lang-tidal`](packages/lang-tidal): TidalCycles language support
for CodeMirror 6
#### Examples
* [`example-vanilla-js`](packages/example-vanilla-js): Example of a Flok-based
- [`example-vanilla-js`](packages/example-vanilla-js): Example of a Flok-based
collaborative editor written in pure JS and Vite
### Design constraints (v1.0)
* Include a simplified vanilla JS example
* Use [CodeMirror 6](https://codemirror.net/)
* Best code editor library for the Web
* Latest version (v6) comes with better extensibility and accesability
* Use [Yjs](https://yjs.dev/) for collaborative editor
* Battle-tested and updated
* Now supports CodeMirror 6:
[y-codemirror.next](https://github.com/yjs/y-codemirror.next)
* More modular and extensible, similar to CodeMirror extensions, e.g.:
* Line/block-based evaluation: `@flok-editor/cm-eval`
* TidalCycles pattern and RMS decorators: `@flok-editor/cm-tidalcycles-decorators`
* TidalCycles autocompletion: `@flok-editor/cm-tidalcycles-autocompletion`
* Hydra synth autocompletion: `@flok-editor/cm-hydra-autocompletion`
* Better UI for customizing editor and session configuration
* Menu, toast, dialogs
* *nice to have* Import external JS libraries dynamically, instead of bundling
- Include a simplified vanilla JS example
- Use [CodeMirror 6](https://codemirror.net/)
- Best code editor library for the Web
- Latest version (v6) comes with better extensibility and accesability
- Use [Yjs](https://yjs.dev/) for collaborative editor
- Battle-tested and updated
- Now supports CodeMirror 6:
[y-codemirror.next](https://github.com/yjs/y-codemirror.next)
- More modular and extensible, similar to CodeMirror extensions, e.g.:
- Line/block-based evaluation: `@flok-editor/cm-eval`
- TidalCycles pattern and RMS decorators: `@flok-editor/cm-tidalcycles-decorators`
- TidalCycles autocompletion: `@flok-editor/cm-tidalcycles-autocompletion`
- Hydra synth autocompletion: `@flok-editor/cm-hydra-autocompletion`
- Better UI for customizing editor and session configuration
- Menu, toast, dialogs
- _nice to have_ Import external JS libraries dynamically, instead of bundling
them with Flok
* Similar to JS playgrounds, like [codesandbox.io](https://codesandbox.io/)
* User can have their own set of libraries to be loaded automatically or
easily on new sketches
* Connect to local filesystem for files and libraries
- Similar to JS playgrounds, like [codesandbox.io](https://codesandbox.io/)
- User can have their own set of libraries to be loaded automatically or
easily on new sketches
- Connect to local filesystem for files and libraries
### Hash parameters
* `username` (string): Default user name. Eg: `#username=arbor`
* `targets` (list of strings): If session is empty, configure it with the
- `username` (string): Default user name. Eg: `#username=arbor`
- `targets` (list of strings): If session is empty, configure it with the
specified targets by default. Eg: `#targets=hydra,strudel`
* `c0`, `c1`, ..., `c7` (string): Default code to load on each document/pane
- `c0`, `c1`, ..., `c7` (string): Default code to load on each document/pane
(if available). **Code must be encoded in Base64**. Eg:
`#c0=bm9pc2UoKS5vdXQoKQ%253D%253D` (decodes to `noise().out()`).
* `code` (string): An alias of `c0` (see above)
- `code` (string): An alias of `c0` (see above)
### Query parameters
* `readOnly` (boolean): Disable editing. If true, it won't ask for a user name
- `readOnly` (boolean): Disable editing. If true, it won't ask for a user name
when loading.
* `bgOpacity` (number): Background opacity. Valid range: [0, 1]
* `noWebEval` (list of strings): Disable evaluation of the specified web
- `bgOpacity` (number): Background opacity. Valid range: [0, 1]
- `noWebEval` (list of strings): Disable evaluation of the specified web
targets. Useful for embedding Flok in a website, where the website already has
its own evaluation mechanism. This still sends messages to parent window.
Options: `*`, `[webTarget]`. Eg: `?noWebEval=hydra` disables only Hydra.
`?noWebEval=*` disables all web targets.
* `hideErrors` (boolean): Do not show errors for web targets (hydra, strudel, etc)
- `hideErrors` (boolean): Do not show errors for web targets (hydra, strudel, etc)
### Window messages
@ -402,7 +403,7 @@ the code.
#### Events
* `change`: When the session changes. This usually happens at the beginning,
- `change`: When the session changes. This usually happens at the beginning,
when the session is empty, and when the user changes the targets.
```json
@ -423,9 +424,9 @@ the code.
}
```
* `eval`: On evaluation. This happens when the user presses the "Run" button or
- `eval`: On evaluation. This happens when the user presses the "Run" button or
when the user presses one of the shortcuts for evaluating (e.g. `Ctrl+Enter`)
on the editor. Only the content of the document that was evaluated is sent.
on the editor. Only the content of the document that was evaluated is sent.
```json
{
@ -438,10 +439,9 @@ the code.
## Acknowledgments
* [Etherpad](https://github.com/ether/etherpad-lite)
* [Troop](https://github.com/Qirky/Troop)
* [TidalBridge](https://gitlab.com/colectivo-de-livecoders/tidal-bridge)
- [Etherpad](https://github.com/ether/etherpad-lite)
- [Troop](https://github.com/Qirky/Troop)
- [TidalBridge](https://gitlab.com/colectivo-de-livecoders/tidal-bridge)
## Contributing
@ -450,7 +450,6 @@ page](https://github.com/munshkr/flok). This project is intended to be a safe,
welcoming space for collaboration, and contributors are expected to adhere to
the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
## License
This project is licensed under GPL 3+. Refer to [LICENSE.txt](LICENSE.txt)

View file

@ -53,7 +53,7 @@ export function getBlock(state: EditorState): EvalBlock {
export const evaluateBlockOrSelection = (
view: EditorView,
doc: Document,
web: boolean = false
web: boolean = false,
) => {
const { state } = view;
const selection = getSelection(state);
@ -71,7 +71,7 @@ export const evaluateBlockOrSelection = (
export const evaluateLine = (
view: EditorView,
doc: Document,
web: boolean = false
web: boolean = false,
) => {
const { state } = view;
const { text, from, to } = getLine(state);
@ -82,7 +82,7 @@ export const evaluateLine = (
export const evaluateDocument = (
view: EditorView,
doc: Document,
web: boolean = false
web: boolean = false,
) => {
const { state } = view;
const { from } = state.doc.line(1);
@ -106,7 +106,7 @@ export function evalKeymap(
documentEvalKeys?: string[];
defaultMode?: "block" | "document";
web?: boolean;
} = {}
} = {},
) {
return keymap.of([
...defaultEvalKeys.map((key) => ({

View file

@ -18,7 +18,7 @@ export const flash = (
view: EditorView,
from: number | null,
to: number | null,
timeout: number = 150
timeout: number = 150,
) => {
if (from === null || to === null) return;
view.dispatch({ effects: setFlash.of([from, to]) });

View file

@ -21,5 +21,5 @@ export const remoteEvalFlash = (document: Document) =>
destroy() {
document.session.off("eval", this._handleEval);
}
}
},
);

View file

@ -1,37 +1,35 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@flok-editor/codemirror example</title>
</head>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@flok-editor/codemirror example</title>
</head>
<body>
<div class="slots">
<div class="slot" id="slot1">
<select class="target">
<option value="tidal">tidal</option>
<option value="hydra">hydra</option>
<option value="foxdot">foxdot</option>
<option value="renardo">renardo</option>
</select>
<div class="editor"></div>
</div>
<body>
<div class="slots">
<div class="slot" id="slot1">
<select class="target">
<option value="tidal">tidal</option>
<option value="hydra">hydra</option>
<option value="foxdot">foxdot</option>
<option value="renardo">renardo</option>
</select>
<div class="editor"></div>
<div class="slot" id="slot2">
<select class="target">
<option value="tidal">tidal</option>
<option value="hydra">hydra</option>
<option value="foxdot">foxdot</option>
<option value="renardo">renardo</option>
</select>
<div class="editor"></div>
</div>
</div>
<div class="slot" id="slot2">
<select class="target">
<option value="tidal">tidal</option>
<option value="hydra">hydra</option>
<option value="foxdot">foxdot</option>
<option value="renardo">renardo</option>
</select>
<div class="editor"></div>
</div>
</div>
<script type="module" src="/main.js"></script>
</body>
<script type="module" src="/main.js"></script>
</body>
</html>

View file

@ -13,7 +13,7 @@ const flokBasicSetup = (doc) => {
const text = doc.getText();
const undoManager = new UndoManager(text);
// if target is hydra, use "web" mode to evaluate on browser only, not REPLs
const web = doc.target === "hydra"
const web = doc.target === "hydra";
return [
flashField(),
@ -23,7 +23,7 @@ const flokBasicSetup = (doc) => {
];
};
const createEditor = doc => {
const createEditor = (doc) => {
const state = EditorState.create({
doc: doc.content,
extensions: [
@ -46,10 +46,10 @@ const createEditor = doc => {
targetEl.addEventListener("change", (e) => {
doc.target = e.target.value;
})
});
doc.session.on(`change-target:${doc.id}`, () => {
targetEl.value = doc.target;
})
});
return [state, view];
};
@ -64,7 +64,7 @@ const handleEvalHydra = (msg) => {
};
const session = new Session("default", { port: 3000 });
window.session = session
window.session = session;
session.on("change", (...args) => console.log("change", ...args));
session.on("message", handleMessage);
@ -76,11 +76,11 @@ session.on("sync", () => {
session.setActiveDocuments([
{ id: "slot1", target: "tidal" },
{ id: "slot2", target: "hydra" },
])
]);
}
// Create editors for each document
session.getDocuments().map(doc => createEditor(doc))
})
session.getDocuments().map((doc) => createEditor(doc));
});
session.initialize();

View file

@ -4,23 +4,23 @@ This is an example repository containing a minimal
[CodeMirror](https://codemirror.net/6/) language support package. The idea is to
clone it, rename it, and edit it to create support for a new language.
Things you'll need to do (see the [language support example](https://codemirror.net/6/examples/lang-package/)
Things you'll need to do (see the [language support example](https://codemirror.net/6/examples/lang-package/)
for a more detailed tutorial):
* `git grep EXAMPLE` and replace all instances with your language name.
- `git grep EXAMPLE` and replace all instances with your language name.
* Rewrite the grammar in `src/syntax.grammar` to cover your language. See the
[Lezer system guide](https://lezer.codemirror.net/docs/guide/#writing-a-grammar)
for information on this file format.
- Rewrite the grammar in `src/syntax.grammar` to cover your language. See the
[Lezer system guide](https://lezer.codemirror.net/docs/guide/#writing-a-grammar)
for information on this file format.
* Adjust the metadata in `src/index.ts` to work with your new grammar.
- Adjust the metadata in `src/index.ts` to work with your new grammar.
* Adjust the grammar tests in `test/cases.txt`.
- Adjust the grammar tests in `test/cases.txt`.
* Build (`npm run prepare`) and test (`npm test`).
- Build (`npm run prepare`) and test (`npm test`).
* Rewrite this readme file.
- Rewrite this readme file.
* Optionally add a license.
- Optionally add a license.
* Publish. Put your package on npm under a name like `codemirror-lang-EXAMPLE`.
- Publish. Put your package on npm under a name like `codemirror-lang-EXAMPLE`.

View file

@ -1,12 +1,12 @@
import typescript from "rollup-plugin-ts"
import { lezer } from "@lezer/generator/rollup"
import typescript from "rollup-plugin-ts";
import { lezer } from "@lezer/generator/rollup";
export default {
input: "src/index.ts",
external: id => id != "tslib" && !/^(\.?\/|\w:)/.test(id),
external: (id) => id != "tslib" && !/^(\.?\/|\w:)/.test(id),
output: [
{ file: "dist/index.cjs", format: "cjs" },
{ dir: "./dist", format: "es" }
{ dir: "./dist", format: "es" },
],
plugins: [lezer(), typescript()]
}
plugins: [lezer(), typescript()],
};

View file

@ -1,10 +1,10 @@
import { haskell } from "@codemirror/legacy-modes/mode/haskell";
export const tidalLanguage = {
...haskell,
name: "tidal",
languageData: {
...haskell.languageData,
closeBrackets: {brackets: ["(", "[", "{", "<", '"'], before: ')]}>"'}
}
}
...haskell,
name: "tidal",
languageData: {
...haskell.languageData,
closeBrackets: { brackets: ["(", "[", "{", "<", '"'], before: ')]}>"' },
},
};

View file

@ -5,13 +5,13 @@ message passing in Flok.
## Features
* Basic Publish/Subscribe functionality: `subscribe`, `unsubscribe`, `publish`,
- Basic Publish/Subscribe functionality: `subscribe`, `unsubscribe`, `publish`,
`unsubscribeAll`.
* Payload is JSON serialized, topics are any string.
* Allows subscribing or unsubscribing without being connected by storing state
- Payload is JSON serialized, topics are any string.
- Allows subscribing or unsubscribing without being connected by storing state
on the client.
* Automatically reconnects if connection is lost, recovering client's state.
* Ping/pong mechanism for detecting and closing broken connections both on
- Automatically reconnects if connection is lost, recovering client's state.
- Ping/pong mechanism for detecting and closing broken connections both on
client and server.
## Usage
@ -19,21 +19,21 @@ message passing in Flok.
### Server
```js
import { WebSocketServer } from "ws"
import { PubSubServer } from "@flok-editor/pubsub"
import { WebSocketServer } from "ws";
import { PubSubServer } from "@flok-editor/pubsub";
// To use the server, you need to create a `WebSocketServer` and pass it to
// `PubSubServer`.
const wss = new WebSocketServer({ port: 4000 });
const server = new PubSubServer({ wss });
console.log(`PubSub server listening on`, wss.address())
console.log(`PubSub server listening on`, wss.address());
```
### Client
```js
import { PubSubClient } from "@flok-editor/pubsub"
import { PubSubClient } from "@flok-editor/pubsub";
const client = new PubSubClient({ url: "ws://localhost:4000" });
@ -48,31 +48,31 @@ let internalId;
client.on("open", () => {
// Publish a message (any JSON serializable object) to a topic
internalId = setInterval(() => {
client.publish("a", { salutation: "hello!" })
client.publish("a", { salutation: "hello!" });
}, 2000);
})
});
// Add an error event handler to ignore connection errors
client.on("error", () => { });
client.on("error", () => {});
client.on("close", () => clearInterval(internalId))
client.on("close", () => clearInterval(internalId));
setTimeout(() => {
// Unsubscribe from a topic
client.unsubscribe("b");
// ... or from all topics with a single call
client.unsubscribeAll();
}, 5000)
}, 5000);
// you can add a listener for all message events
client.on("message", (topic, data) => {
console.log("message", topic, data)
})
console.log("message", topic, data);
});
// ...or only from a specific topic
client.on("message:a", (data) => {
console.log("message from topic 'a'", data)
})
console.log("message from topic 'a'", data);
});
// Finally, you can subscribe and listen to messages in a single call
client.subscribe("c", (data) => {

View file

@ -1,4 +1,4 @@
import { PubSubClient } from "../dist/index.js"
import { PubSubClient } from "../dist/index.js";
const client = new PubSubClient({ url: "ws://localhost:4000" });
@ -13,28 +13,28 @@ let internalId;
client.on("open", () => {
// Publish a message (any JSON serializable object) to a topic
internalId = setInterval(() => {
client.publish("a", { salutation: "hello!" })
client.publish("a", { salutation: "hello!" });
}, 2000);
})
});
// Add an error event handler to ignore connection errors
client.on("error", () => { });
client.on("error", () => {});
client.on("close", () => clearInterval(internalId))
client.on("close", () => clearInterval(internalId));
setTimeout(() => {
// Unsubscribe from a topic
client.unsubscribe("b");
// ... or from all topics with a single call
client.unsubscribeAll();
}, 5000)
}, 5000);
// you can add a listener for all message events
client.on("message", (topic, data) => {
console.log("message", topic, data)
})
console.log("message", topic, data);
});
// ...or only from a specific topic
client.on("message:a", (data) => {
console.log("message from topic 'a'", data)
})
console.log("message from topic 'a'", data);
});

View file

@ -1,7 +1,7 @@
import { WebSocketServer } from "ws"
import { PubSubServer } from "../dist/server.js"
import { WebSocketServer } from "ws";
import { PubSubServer } from "../dist/server.js";
const wss = new WebSocketServer({ port: 4000 });
const server = new PubSubServer({ wss });
console.log(`PubSub server listening on`, wss.address())
console.log(`PubSub server listening on`, wss.address());

View file

@ -179,7 +179,7 @@ export class PubSubClient {
protected _send(
type: ClientMessageType,
payload?: any,
cb?: (err?: Error) => void
cb?: (err?: Error) => void,
) {
const data = JSON.stringify({ type, payload });
this._ws.send(data, (err?: Error) => {

View file

@ -21,15 +21,16 @@ const readConfig = (path) => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const packageInfo = readConfig(path.resolve(__dirname, "../package.json"));
const knownTypes = ["command", ...Object.keys(replClasses).filter(
(repl) => repl !== "default"
)];
const knownTypes = [
"command",
...Object.keys(replClasses).filter((repl) => repl !== "default"),
];
const program = new Command();
program.version(packageInfo.version);
program
.option("-t, --types <types...>", "Type/s of REPL", ["command"])
.option("-H, --hub <url>", "Server (or \"hub\") address", "ws://localhost:3000")
.option("-H, --hub <url>", 'Server (or "hub") address', "ws://localhost:3000")
.option("-s, --session-name <name>", "Session name", "default")
.option("-n, --target-name <name>", "Use the specified target name")
.option("-T, --tags <tags...>", "Tags for REPL messages")
@ -46,25 +47,12 @@ const configPath = opts.config || process.env.FLOK_CONFIG;
const config = configPath ? readConfig(configPath) : {};
// Override config with command line options
const options = [
"types",
"hub",
"sessionName",
"targetName",
"tags",
"path",
];
options.forEach(opt => {
const options = ["types", "hub", "sessionName", "targetName", "tags", "path"];
options.forEach((opt) => {
config[opt] = config[opt] || opts[opt];
});
const {
hub,
sessionName,
targetName,
tags,
path: pubSubPath,
} = config;
const { hub, sessionName, targetName, tags, path: pubSubPath } = config;
// Remove duplicates
const types = [...new Set(config.types)];
@ -79,20 +67,27 @@ if (opts.listTypes) {
process.exit(0);
}
const useDefaultREPL = types.some(type => type === "command");
const useDefaultREPL = types.some((type) => type === "command");
// If using default REPL and no command was specified, throw error
if (useDefaultREPL && !cmd) {
console.error("You specified a 'command' type, but forgot to specify a REPL command (e.g.: flok-repl -- cat)");
console.error(
"You specified a 'command' type, but forgot to specify a REPL command (e.g.: flok-repl -- cat)",
);
program.outputHelp();
process.exit(1);
}
// Check if all types are known
if (!useDefaultREPL) {
const unknownTypes = [...new Set(types.filter(type => !knownTypes.includes(type)))];
const unknownTypes = [
...new Set(types.filter((type) => !knownTypes.includes(type))),
];
if (unknownTypes.length > 0) {
console.error(`Unknown types: ${unknownTypes.join(', ')}. Must be one of:`, knownTypes);
console.error(
`Unknown types: ${unknownTypes.join(", ")}. Must be one of:`,
knownTypes,
);
process.exit(1);
}
}
@ -114,9 +109,10 @@ console.log("Hub address:", hub);
console.log("Session name:", sessionName);
if (targetName) console.log("Target name:", targetName);
console.log("Types:", types);
if (Object.keys(extraOptions).length > 0) console.log("Extra options:", extraOptions);
if (Object.keys(extraOptions).length > 0)
console.log("Extra options:", extraOptions);
types.forEach(type => {
types.forEach((type) => {
const useDefaultREPL = type === "command";
// Set target based on name or type
@ -164,4 +160,4 @@ types.forEach(type => {
replClient.emitter.on("close", ({ code }) => {
process.exit(code);
});
})
});

View file

@ -37,7 +37,7 @@ function createREPLFor(repl: string, ctx: CommandREPLContext) {
function readPackageMetadata() {
const packageJsonPath = path.resolve(
__dirname,
path.join("..", "package.json")
path.join("..", "package.json"),
);
const rawBody = fs.readFileSync(packageJsonPath);
const body = JSON.parse(rawBody.toString());

View file

@ -62,7 +62,7 @@ abstract class BaseREPL {
({ message }: { message: Message }) => {
const { body } = message;
this.write(body);
}
},
);
}

View file

@ -47,7 +47,7 @@ class TidalREPL extends CommandREPL {
debug(`Failed to get tidal data dir`);
debug(
`You will need to specify the location of your TidalCycles bootloading script.\n` +
`Read more: https://github.com/munshkr/flok/wiki/Failed-to-get-tidal-data-dir`
`Read more: https://github.com/munshkr/flok/wiki/Failed-to-get-tidal-data-dir`,
);
throw err;
}

View file

@ -57,13 +57,13 @@ export default (conn: any, topics: Map<string, Set<any>>) => {
const topic = map.setIfUndefined(
topics,
topicName,
() => new Set()
() => new Set(),
);
topic.add(conn);
// add topic to conn
subscribedTopics.add(topicName);
}
}
},
);
break;
case "unsubscribe":
@ -73,7 +73,7 @@ export default (conn: any, topics: Map<string, Set<any>>) => {
if (subs) {
subs.delete(conn);
}
}
},
);
break;
case "publish":

View file

@ -30,7 +30,7 @@ const setPersistence = (
persistence_: {
bindState: (arg0: string, arg1: WSSharedDoc) => void;
writeState: (arg0: string, arg1: WSSharedDoc) => Promise<any>;
} | null
} | null,
) => {
persistence = persistence_;
};
@ -75,7 +75,7 @@ export class WSSharedDoc extends Doc {
updated,
removed,
}: { added: number[]; updated: number[]; removed: number[] },
conn: any
conn: any,
) => {
const changedClients = added.concat(updated, removed);
if (conn !== null) {
@ -95,7 +95,7 @@ export class WSSharedDoc extends Doc {
encoding.writeVarUint(encoder, messageAwareness);
encoding.writeVarUint8Array(
encoder,
awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients)
awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients),
);
const buff = encoding.toUint8Array(encoder);
this.conns.forEach((_, c) => {
@ -123,7 +123,7 @@ const messageListener = (conn: any, doc: WSSharedDoc, message: Uint8Array) => {
awarenessProtocol.applyAwarenessUpdate(
doc.awareness,
decoding.readVarUint8Array(decoder),
conn
conn,
);
break;
}
@ -137,7 +137,7 @@ const closeConn = (doc: WSSharedDoc, conn: any) => {
awarenessProtocol.removeAwarenessStates(
doc.awareness,
Array.from(controlledIds),
null
null,
);
if (doc.conns.size === 0 && persistence !== null) {
// if persisted, we store state and destroy ydocument
@ -162,7 +162,7 @@ const send = (doc: WSSharedDoc, conn: any, m: Uint8Array) => {
m,
/** @param {any} err */ (err) => {
err != null && closeConn(doc, conn);
}
},
);
} catch (e) {
closeConn(doc, conn);
@ -174,7 +174,7 @@ const pingTimeout = 30000;
export const setupWSConnection = (
conn: WebSocket,
req: http.IncomingMessage,
{ docName = req.url.slice(1).split("?")[0], gc = true }: any = {}
{ docName = req.url.slice(1).split("?")[0], gc = true }: any = {},
) => {
conn.binaryType = "arraybuffer";
// get doc, create if it does not exist yet
@ -190,7 +190,7 @@ export const setupWSConnection = (
doc.conns.set(conn, new Set());
// listen and reply to events
conn.on("message", (message: ArrayBuffer) =>
messageListener(conn, doc, new Uint8Array(message))
messageListener(conn, doc, new Uint8Array(message)),
);
conn.on("close", () => {
closeConn(doc, conn);
@ -228,8 +228,8 @@ export const setupWSConnection = (
encoder,
awarenessProtocol.encodeAwarenessUpdate(
doc.awareness,
Array.from(awarenessStates.keys())
)
Array.from(awarenessStates.keys()),
),
);
send(doc, conn, encoding.toUint8Array(encoder));
}

View file

@ -143,7 +143,7 @@ export class Session {
// Remove duplicates on items (duplicate ids) by creating an object/map
const newTargets = Object.fromEntries(
items.map(({ id, target }) => [id, target])
items.map(({ id, target }) => [id, target]),
);
// Calculate ids to delete and ids to create
@ -151,7 +151,7 @@ export class Session {
const oldIds = Array.from(targets.keys());
const toDelete = oldIds.filter((id) => !newIds.includes(id));
const toAddOrUpdate = newIds.filter(
(id) => !oldIds.includes(id) || oldTargets[id] !== newTargets[id]
(id) => !oldIds.includes(id) || oldTargets[id] !== newTargets[id],
);
debug("toDelete", toDelete);
debug("toAddOrUpdate", toAddOrUpdate);
@ -164,7 +164,7 @@ export class Session {
getDocuments(): Document[] {
return Array.from(this._yTargets().keys()).map(
(id) => new Document(this, id)
(id) => new Document(this, id),
);
}
@ -203,7 +203,7 @@ export class Session {
target: string,
body: string,
context: EvalContext,
mode: EvalMode = "default"
mode: EvalMode = "default",
) {
const msg: EvalMessage = {
docId,
@ -223,7 +223,7 @@ export class Session {
if (mode !== "webLocal") {
this._pubSubClient.publish(
`session:${this.name}:target:${target}:eval`,
msg
msg,
);
}
}
@ -247,7 +247,7 @@ export class Session {
destroy() {
this.removeAllListeners();
["error", "open", "close"].forEach((e) =>
this._pubSubClient.removeAllListeners(e)
this._pubSubClient.removeAllListeners(e),
);
this._synced = false;
this._initialized = false;
@ -314,7 +314,7 @@ export class Session {
`${this.wsUrl}/doc`,
this.name,
this.yDoc,
{ awareness: this.awareness }
{ awareness: this.awareness },
);
this._wsProvider.on("synced", () => {
if (!this._synced) {
@ -381,9 +381,9 @@ export class Session {
// Notify to flok-repls
this._pubSubClient.publish(
`session:${this.name}:target:${target}:in`,
message
message,
);
}
},
);
this._pubSubClient.subscribe(
@ -392,7 +392,7 @@ export class Session {
debug(`session:${this.name}:target:${target}:out`, args);
this._emitter.emit(`message`, args);
this._emitter.emit(`message:${target}`, args);
}
},
);
// Subscribes to messages directed to ourselves
@ -401,11 +401,11 @@ export class Session {
(args) => {
debug(
`session:${this.name}:target:${target}:user:${this.user}:out`,
args
args,
);
this._emitter.emit(`message`, args);
this._emitter.emit(`message:${target}`, args);
}
},
);
}
@ -438,7 +438,7 @@ export class Session {
const newValue = ymap.get(key);
debug(
`Document "${key}" was added or updated. New target: "${newValue}". ` +
`Previous target: "${change.oldValue}".`
`Previous target: "${change.oldValue}".`,
);
debug(`Subscribe to ${newValue}`);
this._subscribeToTarget(newValue);

View file

@ -43,5 +43,5 @@ startServer({
secure: opts.secure,
sslCert: opts.sslCert,
sslKey: opts.sslKey,
staticDir: opts.staticDir
})
staticDir: opts.staticDir,
});

View file

@ -1,17 +1,20 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/flok.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Web-based P2P collaborative editor for live coding sounds and images"
/>
<title>Flok</title>
</head>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/flok.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Web-based P2P collaborative editor for live coding sounds and images" />
<title>Flok</title>
</head>
<body class="min-h-screen font-sans antialiased overscroll-none overflow-hidden text-slate-50">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
<body
class="min-h-screen font-sans antialiased overscroll-none overflow-hidden text-slate-50"
>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};

View file

@ -2,9 +2,9 @@
// do some pre-build tasks like copying files, etc. that are required for the
// build process.
import { existsSync, mkdirSync, readdirSync, copyFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { resolve, dirname } from 'path';
import { existsSync, mkdirSync, readdirSync, copyFileSync } from "fs";
import { fileURLToPath } from "url";
import { resolve, dirname } from "path";
// Strudel
// * mkdir -p public/assets
@ -12,15 +12,20 @@ import { resolve, dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const publicAssetsDir = resolve(__dirname, '../public/assets');
const strudelAssetsDir = resolve(__dirname, '../../../node_modules/@strudel/core/dist/assets');
const publicAssetsDir = resolve(__dirname, "../public/assets");
const strudelAssetsDir = resolve(
__dirname,
"../../../node_modules/@strudel/core/dist/assets",
);
if (!existsSync(publicAssetsDir)) {
mkdirSync(publicAssetsDir, { recursive: true });
}
const files = readdirSync(strudelAssetsDir).filter(file => file.startsWith('clockworker--'));
files.forEach(file => {
const files = readdirSync(strudelAssetsDir).filter((file) =>
file.startsWith("clockworker--"),
);
files.forEach((file) => {
const src = resolve(strudelAssetsDir, file);
const dest = resolve(publicAssetsDir, file);
copyFileSync(src, dest);

View file

@ -17,9 +17,7 @@ const __dirname = path.dirname(__filename);
function info(msg) {
const timestamp = new Date().toLocaleString("en-US").split(",")[1].trim();
console.log(
`${pc.dim(timestamp)} ${pc.bold(pc.cyan("[flok-web]"))} ${pc.green(
msg,
)}`,
`${pc.dim(timestamp)} ${pc.bold(pc.cyan("[flok-web]"))} ${pc.green(msg)}`,
);
}
@ -30,13 +28,15 @@ export async function startServer({ onReady, staticDir, ...opts }) {
app.use(compression());
if (staticDir) {
info(`Serving extra static files at ${pc.gray(staticDir)}`)
app.use(express.static(staticDir))
info(`Serving extra static files at ${pc.gray(staticDir)}`);
app.use(express.static(staticDir));
}
let viteServer;
if (opts.secure) {
info(`Using SSL certificate at ${pc.gray(opts.sslCert)} (key at ${pc.gray(opts.sslKey)})`)
info(
`Using SSL certificate at ${pc.gray(opts.sslCert)} (key at ${pc.gray(opts.sslKey)})`,
);
const key = fs.readFileSync(opts.sslKey);
const cert = fs.readFileSync(opts.sslCert);
viteServer = https.createServer({ key, cert }, app);
@ -56,20 +56,25 @@ export async function startServer({ onReady, staticDir, ...opts }) {
const server = withFlokServer(viteServer);
server.listen(opts.port, onReady || (() => {
const netResults = getPossibleIpAddresses();
const schema = opts.secure ? "https" : "http";
info(`Listening on ${schema}://localhost:${opts.port}`);
if (netResults.length > 1) {
info("If on LAN, try sharing with your friends one of these URLs:");
Object.entries(netResults).map(([k, v]) => {
info(`\t${k}: ${schema}://${v}:${opts.port}`);
});
} else {
info(
`If on LAN, try sharing with your friends ${schema}://${Object.values(netResults)[0]}:${opts.port}`);
}
}))
server.listen(
opts.port,
onReady ||
(() => {
const netResults = getPossibleIpAddresses();
const schema = opts.secure ? "https" : "http";
info(`Listening on ${schema}://localhost:${opts.port}`);
if (netResults.length > 1) {
info("If on LAN, try sharing with your friends one of these URLs:");
Object.entries(netResults).map(([k, v]) => {
info(`\t${k}: ${schema}://${v}:${opts.port}`);
});
} else {
info(
`If on LAN, try sharing with your friends ${schema}://${Object.values(netResults)[0]}:${opts.port}`,
);
}
}),
);
} catch (err) {
console.error(err);
process.exit(1);

View file

@ -1,20 +1,21 @@
@font-face {
font-family: 'BigBlue TerminalPlus';
src: url('BigBlue_TerminalPlus.woff2') format('woff2'),
url('BigBlue_TerminalPlus.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 80%;
font-family: "BigBlue TerminalPlus";
src:
url("BigBlue_TerminalPlus.woff2") format("woff2"),
url("BigBlue_TerminalPlus.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 80%;
}
@font-face {
font-family: 'BigBlue Terminal 437TT';
src: url('BigBlue_Terminal_437TT.woff2') format('woff2'),
url('BigBlue_Terminal_437TT.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 80%;
font-family: "BigBlue Terminal 437TT";
src:
url("BigBlue_Terminal_437TT.woff2") format("woff2"),
url("BigBlue_Terminal_437TT.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 80%;
}

View file

@ -1,10 +1,10 @@
@font-face {
font-family: 'Fira Code VF';
src: url('FiraCode-VF.woff2') format('woff2-variations'),
url('woff/FiraCode-VF.woff') format('woff-variations');
font-family: "Fira Code VF";
src:
url("FiraCode-VF.woff2") format("woff2-variations"),
url("woff/FiraCode-VF.woff") format("woff-variations");
/* font-weight requires a range: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide#Using_a_variable_font_font-face_changes */
font-weight: 300 700;
font-style: normal;
size-adjust: 87%;
}
}

View file

@ -1,140 +1,153 @@
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-Thin.woff2') format('woff2'),
url('IBMPlexMono-Thin.woff') format('woff');
font-weight: 100;
font-style: normal;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-Thin.woff2") format("woff2"),
url("IBMPlexMono-Thin.woff") format("woff");
font-weight: 100;
font-style: normal;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-Regular.woff2') format('woff2'),
url('IBMPlexMono-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-Regular.woff2") format("woff2"),
url("IBMPlexMono-Regular.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-Bold.woff2') format('woff2'),
url('IBMPlexMono-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-Bold.woff2") format("woff2"),
url("IBMPlexMono-Bold.woff") format("woff");
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-LightItalic.woff2') format('woff2'),
url('IBMPlexMono-LightItalic.woff') format('woff');
font-weight: 300;
font-style: italic;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-LightItalic.woff2") format("woff2"),
url("IBMPlexMono-LightItalic.woff") format("woff");
font-weight: 300;
font-style: italic;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-ExtraLightItalic.woff2') format('woff2'),
url('IBMPlexMono-ExtraLightItalic.woff') format('woff');
font-weight: 200;
font-style: italic;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-ExtraLightItalic.woff2") format("woff2"),
url("IBMPlexMono-ExtraLightItalic.woff") format("woff");
font-weight: 200;
font-style: italic;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-SemiBold.woff2') format('woff2'),
url('IBMPlexMono-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-SemiBold.woff2") format("woff2"),
url("IBMPlexMono-SemiBold.woff") format("woff");
font-weight: 600;
font-style: normal;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-Italic.woff2') format('woff2'),
url('IBMPlexMono-Italic.woff') format('woff');
font-weight: normal;
font-style: italic;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-Italic.woff2") format("woff2"),
url("IBMPlexMono-Italic.woff") format("woff");
font-weight: normal;
font-style: italic;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-ThinItalic.woff2') format('woff2'),
url('IBMPlexMono-ThinItalic.woff') format('woff');
font-weight: 100;
font-style: italic;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-ThinItalic.woff2") format("woff2"),
url("IBMPlexMono-ThinItalic.woff") format("woff");
font-weight: 100;
font-style: italic;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-SemiBoldItalic.woff2') format('woff2'),
url('IBMPlexMono-SemiBoldItalic.woff') format('woff');
font-weight: 600;
font-style: italic;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-SemiBoldItalic.woff2") format("woff2"),
url("IBMPlexMono-SemiBoldItalic.woff") format("woff");
font-weight: 600;
font-style: italic;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-Light.woff2') format('woff2'),
url('IBMPlexMono-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-Light.woff2") format("woff2"),
url("IBMPlexMono-Light.woff") format("woff");
font-weight: 300;
font-style: normal;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-MediumItalic.woff2') format('woff2'),
url('IBMPlexMono-MediumItalic.woff') format('woff');
font-weight: 500;
font-style: italic;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-MediumItalic.woff2") format("woff2"),
url("IBMPlexMono-MediumItalic.woff") format("woff");
font-weight: 500;
font-style: italic;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-Medium.woff2') format('woff2'),
url('IBMPlexMono-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-Medium.woff2") format("woff2"),
url("IBMPlexMono-Medium.woff") format("woff");
font-weight: 500;
font-style: normal;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-BoldItalic.woff2') format('woff2'),
url('IBMPlexMono-BoldItalic.woff') format('woff');
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-BoldItalic.woff2") format("woff2"),
url("IBMPlexMono-BoldItalic.woff") format("woff");
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 90%;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-ExtraLight.woff2') format('woff2'),
url('IBMPlexMono-ExtraLight.woff') format('woff');
font-weight: 200;
font-style: normal;
font-display: swap;
size-adjust: 90%
font-family: "IBM Plex Mono";
src:
url("IBMPlexMono-ExtraLight.woff2") format("woff2"),
url("IBMPlexMono-ExtraLight.woff") format("woff");
font-weight: 200;
font-style: normal;
font-display: swap;
size-adjust: 90%;
}

View file

@ -1,40 +1,43 @@
@font-face {
font-family: 'jgs7';
src: url('jgs7.woff2') format('woff2'),
url('jgs7.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 105%;
font-family: "jgs7";
src:
url("jgs7.woff2") format("woff2"),
url("jgs7.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 105%;
}
@font-face {
font-family: 'jgs';
src: url('jgs.woff2') format('woff2'),
url('jgs.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 105%;
font-family: "jgs";
src:
url("jgs.woff2") format("woff2"),
url("jgs.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 105%;
}
@font-face {
font-family: 'jgs_Font';
src: url('jgs_Font.woff2') format('woff2'),
url('jgs_Font.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 105%;
font-family: "jgs_Font";
src:
url("jgs_Font.woff2") format("woff2"),
url("jgs_Font.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 105%;
}
@font-face {
font-family: 'jgs5';
src: url('jgs5.woff2') format('woff2'),
url('jgs5.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 105%;
font-family: "jgs5";
src:
url("jgs5.woff2") format("woff2"),
url("jgs5.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 105%;
}

View file

@ -1,300 +1,329 @@
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-SemiBoldItalic.woff2') format('woff2'),
url('JetBrainsMonoNL-SemiBoldItalic.woff') format('woff');
font-weight: 600;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-SemiBoldItalic.woff2") format("woff2"),
url("JetBrainsMonoNL-SemiBoldItalic.woff") format("woff");
font-weight: 600;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-ExtraBoldItalic.woff2') format('woff2'),
url('JetBrainsMono-ExtraBoldItalic.woff') format('woff');
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-ExtraBoldItalic.woff2") format("woff2"),
url("JetBrainsMono-ExtraBoldItalic.woff") format("woff");
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-MediumItalic.woff2') format('woff2'),
url('JetBrainsMonoNL-MediumItalic.woff') format('woff');
font-weight: 500;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-MediumItalic.woff2") format("woff2"),
url("JetBrainsMonoNL-MediumItalic.woff") format("woff");
font-weight: 500;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-LightItalic.woff2') format('woff2'),
url('JetBrainsMono-LightItalic.woff') format('woff');
font-weight: 300;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-LightItalic.woff2") format("woff2"),
url("JetBrainsMono-LightItalic.woff") format("woff");
font-weight: 300;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-LightItalic.woff2') format('woff2'),
url('JetBrainsMonoNL-LightItalic.woff') format('woff');
font-weight: 300;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-LightItalic.woff2") format("woff2"),
url("JetBrainsMonoNL-LightItalic.woff") format("woff");
font-weight: 300;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-ExtraBold.woff2') format('woff2'),
url('JetBrainsMono-ExtraBold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-ExtraBold.woff2") format("woff2"),
url("JetBrainsMono-ExtraBold.woff") format("woff");
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-ThinItalic.woff2') format('woff2'),
url('JetBrainsMono-ThinItalic.woff') format('woff');
font-weight: 100;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-ThinItalic.woff2") format("woff2"),
url("JetBrainsMono-ThinItalic.woff") format("woff");
font-weight: 100;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-ExtraBold.woff2') format('woff2'),
url('JetBrainsMonoNL-ExtraBold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-ExtraBold.woff2") format("woff2"),
url("JetBrainsMonoNL-ExtraBold.woff") format("woff");
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-ExtraLight.woff2') format('woff2'),
url('JetBrainsMonoNL-ExtraLight.woff') format('woff');
font-weight: 200;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-ExtraLight.woff2") format("woff2"),
url("JetBrainsMonoNL-ExtraLight.woff") format("woff");
font-weight: 200;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-MediumItalic.woff2') format('woff2'),
url('JetBrainsMono-MediumItalic.woff') format('woff');
font-weight: 500;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-MediumItalic.woff2") format("woff2"),
url("JetBrainsMono-MediumItalic.woff") format("woff");
font-weight: 500;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-Regular.woff2') format('woff2'),
url('JetBrainsMono-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-Regular.woff2") format("woff2"),
url("JetBrainsMono-Regular.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-ExtraBoldItalic.woff2') format('woff2'),
url('JetBrainsMonoNL-ExtraBoldItalic.woff') format('woff');
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-ExtraBoldItalic.woff2") format("woff2"),
url("JetBrainsMonoNL-ExtraBoldItalic.woff") format("woff");
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-Italic.woff2') format('woff2'),
url('JetBrainsMonoNL-Italic.woff') format('woff');
font-weight: normal;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-Italic.woff2") format("woff2"),
url("JetBrainsMonoNL-Italic.woff") format("woff");
font-weight: normal;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-ExtraLightItalic.woff2') format('woff2'),
url('JetBrainsMono-ExtraLightItalic.woff') format('woff');
font-weight: 200;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-ExtraLightItalic.woff2") format("woff2"),
url("JetBrainsMono-ExtraLightItalic.woff") format("woff");
font-weight: 200;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-Medium.woff2') format('woff2'),
url('JetBrainsMono-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-Medium.woff2") format("woff2"),
url("JetBrainsMono-Medium.woff") format("woff");
font-weight: 500;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-Italic.woff2') format('woff2'),
url('JetBrainsMono-Italic.woff') format('woff');
font-weight: normal;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-Italic.woff2") format("woff2"),
url("JetBrainsMono-Italic.woff") format("woff");
font-weight: normal;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-BoldItalic.woff2') format('woff2'),
url('JetBrainsMonoNL-BoldItalic.woff') format('woff');
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-BoldItalic.woff2") format("woff2"),
url("JetBrainsMonoNL-BoldItalic.woff") format("woff");
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-ExtraLight.woff2') format('woff2'),
url('JetBrainsMono-ExtraLight.woff') format('woff');
font-weight: 200;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-ExtraLight.woff2") format("woff2"),
url("JetBrainsMono-ExtraLight.woff") format("woff");
font-weight: 200;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-SemiBoldItalic.woff2') format('woff2'),
url('JetBrainsMono-SemiBoldItalic.woff') format('woff');
font-weight: 600;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-SemiBoldItalic.woff2") format("woff2"),
url("JetBrainsMono-SemiBoldItalic.woff") format("woff");
font-weight: 600;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-Thin.woff2') format('woff2'),
url('JetBrainsMono-Thin.woff') format('woff');
font-weight: 100;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-Thin.woff2") format("woff2"),
url("JetBrainsMono-Thin.woff") format("woff");
font-weight: 100;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-SemiBold.woff2') format('woff2'),
url('JetBrainsMono-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-SemiBold.woff2") format("woff2"),
url("JetBrainsMono-SemiBold.woff") format("woff");
font-weight: 600;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-SemiBold.woff2') format('woff2'),
url('JetBrainsMonoNL-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-SemiBold.woff2") format("woff2"),
url("JetBrainsMonoNL-SemiBold.woff") format("woff");
font-weight: 600;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-Thin.woff2') format('woff2'),
url('JetBrainsMonoNL-Thin.woff') format('woff');
font-weight: 100;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-Thin.woff2") format("woff2"),
url("JetBrainsMonoNL-Thin.woff") format("woff");
font-weight: 100;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-Regular.woff2') format('woff2'),
url('JetBrainsMonoNL-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-Regular.woff2") format("woff2"),
url("JetBrainsMonoNL-Regular.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-ExtraLightItalic.woff2') format('woff2'),
url('JetBrainsMonoNL-ExtraLightItalic.woff') format('woff');
font-weight: 200;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-ExtraLightItalic.woff2") format("woff2"),
url("JetBrainsMonoNL-ExtraLightItalic.woff") format("woff");
font-weight: 200;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-BoldItalic.woff2') format('woff2'),
url('JetBrainsMono-BoldItalic.woff') format('woff');
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-BoldItalic.woff2") format("woff2"),
url("JetBrainsMono-BoldItalic.woff") format("woff");
font-weight: bold;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-ThinItalic.woff2') format('woff2'),
url('JetBrainsMonoNL-ThinItalic.woff') format('woff');
font-weight: 100;
font-style: italic;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-ThinItalic.woff2") format("woff2"),
url("JetBrainsMonoNL-ThinItalic.woff") format("woff");
font-weight: 100;
font-style: italic;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-Light.woff2') format('woff2'),
url('JetBrainsMonoNL-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-Light.woff2") format("woff2"),
url("JetBrainsMonoNL-Light.woff") format("woff");
font-weight: 300;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono NL';
src: url('JetBrainsMonoNL-Bold.woff2') format('woff2'),
url('JetBrainsMonoNL-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono NL";
src:
url("JetBrainsMonoNL-Bold.woff2") format("woff2"),
url("JetBrainsMonoNL-Bold.woff") format("woff");
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('JetBrainsMono-Bold.woff2') format('woff2'),
url('JetBrainsMono-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 85%;
font-family: "JetBrains Mono";
src:
url("JetBrainsMono-Bold.woff2") format("woff2"),
url("JetBrainsMono-Bold.woff") format("woff");
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 85%;
}

View file

@ -1,10 +1,10 @@
@font-face {
font-family: 'Monocraft Nerd Font';
src: url('MonocraftNerdFontComplete-.woff2') format('woff2'),
url('MonocraftNerdFontComplete-.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 80%;
font-family: "Monocraft Nerd Font";
src:
url("MonocraftNerdFontComplete-.woff2") format("woff2"),
url("MonocraftNerdFontComplete-.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 80%;
}

View file

@ -1,10 +1,10 @@
@font-face {
font-family: 'Roboto Mono';
src: url('RobotoMono-Regular.woff2') format('woff2'),
url('RobotoMono-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 90%;
font-family: "Roboto Mono";
src:
url("RobotoMono-Regular.woff2") format("woff2"),
url("RobotoMono-Regular.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 90%;
}

View file

@ -1,20 +1,21 @@
@font-face {
font-family: 'Steps Mono';
src: url('Steps-Mono-Thin.woff2') format('woff2'),
url('Steps-Mono-Thin.woff') format('woff');
font-weight: 100;
font-style: normal;
font-display: swap;
size-adjust: 100%;
font-family: "Steps Mono";
src:
url("Steps-Mono-Thin.woff2") format("woff2"),
url("Steps-Mono-Thin.woff") format("woff");
font-weight: 100;
font-style: normal;
font-display: swap;
size-adjust: 100%;
}
@font-face {
font-family: 'Steps-Mono';
src: url('Steps-Mono-Mono.woff2') format('woff2'),
url('Steps-Mono-Mono.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 95%;
font-family: "Steps-Mono";
src:
url("Steps-Mono-Mono.woff2") format("woff2"),
url("Steps-Mono-Mono.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 95%;
}

View file

@ -1,10 +1,10 @@
@font-face {
font-family: 'Syne Mono';
src: url('SyneMono-Regular.woff2') format('woff2'),
url('SyneMono-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 95%;
font-family: "Syne Mono";
src:
url("SyneMono-Regular.woff2") format("woff2"),
url("SyneMono-Regular.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 95%;
}

View file

@ -1,11 +1,10 @@
@font-face {
font-family: 'Ubuntu Mono';
src: url('UbuntuMono-Bold.woff2') format('woff2'),
url('UbuntuMono-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 105%;
font-family: "Ubuntu Mono";
src:
url("UbuntuMono-Bold.woff2") format("woff2"),
url("UbuntuMono-Bold.woff") format("woff");
font-weight: bold;
font-style: normal;
font-display: swap;
size-adjust: 105%;
}

View file

@ -1,192 +1,320 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, noarchive">
<meta name="format-detection" content="telephone=no">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex, noarchive" />
<meta name="format-detection" content="telephone=no" />
<title>Transfonter demo</title>
<link href="stylesheet.css" rel="stylesheet">
<link href="stylesheet.css" rel="stylesheet" />
<style>
/*
/*
http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* demo styles */
body {
background: #f0f0f0;
color: #000;
}
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* demo styles */
body {
background: #f0f0f0;
color: #000;
}
.page {
background: #fff;
width: 920px;
margin: 0 auto;
padding: 20px 20px 0 20px;
overflow: hidden;
}
.font-container {
overflow-x: auto;
overflow-y: hidden;
margin-bottom: 40px;
line-height: 1.3;
white-space: nowrap;
padding-bottom: 5px;
}
h1 {
position: relative;
background: #444;
font-size: 32px;
color: #fff;
padding: 10px 20px;
margin: 0 -20px 12px -20px;
}
.letters {
font-size: 25px;
margin-bottom: 20px;
}
.s10:before {
content: "10px";
}
.s11:before {
content: "11px";
}
.s12:before {
content: "12px";
}
.s14:before {
content: "14px";
}
.s18:before {
content: "18px";
}
.s24:before {
content: "24px";
}
.s30:before {
content: "30px";
}
.s36:before {
content: "36px";
}
.s48:before {
content: "48px";
}
.s60:before {
content: "60px";
}
.s72:before {
content: "72px";
}
.s10:before,
.s11:before,
.s12:before,
.s14:before,
.s18:before,
.s24:before,
.s30:before,
.s36:before,
.s48:before,
.s60:before,
.s72:before {
font-family: Arial, sans-serif;
font-size: 10px;
font-weight: normal;
font-style: normal;
color: #999;
padding-right: 6px;
}
pre {
display: block;
padding: 9px;
margin: 0 0 12px;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
font-size: 13px;
line-height: 1.428571429;
color: #333;
font-weight: normal;
font-style: normal;
background-color: #f5f5f5;
border: 1px solid #ccc;
overflow-x: auto;
border-radius: 4px;
}
/* responsive */
@media (max-width: 959px) {
.page {
background: #fff;
width: 920px;
margin: 0 auto;
padding: 20px 20px 0 20px;
overflow: hidden;
}
.font-container {
overflow-x: auto;
overflow-y: hidden;
margin-bottom: 40px;
line-height: 1.3;
white-space: nowrap;
padding-bottom: 5px;
}
h1 {
position: relative;
background: #444;
font-size: 32px;
color: #fff;
padding: 10px 20px;
margin: 0 -20px 12px -20px;
}
.letters {
font-size: 25px;
margin-bottom: 20px;
}
.s10:before {
content: '10px';
}
.s11:before {
content: '11px';
}
.s12:before {
content: '12px';
}
.s14:before {
content: '14px';
}
.s18:before {
content: '18px';
}
.s24:before {
content: '24px';
}
.s30:before {
content: '30px';
}
.s36:before {
content: '36px';
}
.s48:before {
content: '48px';
}
.s60:before {
content: '60px';
}
.s72:before {
content: '72px';
}
.s10:before, .s11:before, .s12:before, .s14:before,
.s18:before, .s24:before, .s30:before, .s36:before,
.s48:before, .s60:before, .s72:before {
font-family: Arial, sans-serif;
font-size: 10px;
font-weight: normal;
font-style: normal;
color: #999;
padding-right: 6px;
}
pre {
display: block;
padding: 9px;
margin: 0 0 12px;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
font-size: 13px;
line-height: 1.428571429;
color: #333;
font-weight: normal;
font-style: normal;
background-color: #f5f5f5;
border: 1px solid #ccc;
overflow-x: auto;
border-radius: 4px;
}
/* responsive */
@media (max-width: 959px) {
.page {
width: auto;
margin: 0;
}
width: auto;
margin: 0;
}
}
</style>
</head>
<body>
<div class="page">
<div class="demo">
<h1 style="font-family: 'VT323'; font-weight: normal; font-style: normal;">VT323 Regular</h1>
<pre title="Usage">.your-style {
</head>
<body>
<div class="page">
<div class="demo">
<h1
style="
font-family: &quot;VT323&quot;;
font-weight: normal;
font-style: normal;
"
>
VT323 Regular
</h1>
<pre title="Usage">
.your-style {
font-family: 'VT323';
font-weight: normal;
font-style: normal;
}</pre>
}</pre
>
<pre title="Preload (optional)">
&lt;link rel=&quot;preload&quot; href=&quot;VT323-Regular.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;</pre>
<div class="font-container" style="font-family: 'VT323'; font-weight: normal; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#&lt;&gt;$%&^+-=~
</p>
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
&lt;link rel=&quot;preload&quot; href=&quot;VT323-Regular.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin&gt;</pre
>
<div
class="font-container"
style="
font-family: &quot;VT323&quot;;
font-weight: normal;
font-style: normal;
"
>
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br />
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />
0123456789.:,;()*!?'@#&lt;&gt;$%&^+-=~
</p>
<p class="s10" style="font-size: 10px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s11" style="font-size: 11px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s12" style="font-size: 12px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s14" style="font-size: 14px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s18" style="font-size: 18px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s24" style="font-size: 24px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s30" style="font-size: 30px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s36" style="font-size: 36px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s48" style="font-size: 48px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s60" style="font-size: 60px">
The quick brown fox jumps over the lazy dog.
</p>
<p class="s72" style="font-size: 72px">
The quick brown fox jumps over the lazy dog.
</p>
</div>
</div>
</div>
</div>
</body>
</body>
</html>

View file

@ -1,10 +1,10 @@
@font-face {
font-family: 'VT323';
src: url('VT323-Regular.woff2') format('woff2'),
url('VT323-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 120%;
font-family: "VT323";
src:
url("VT323-Regular.woff2") format("woff2"),
url("VT323-Regular.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
size-adjust: 120%;
}

View file

@ -70,7 +70,7 @@ export function ConfigureDialog({
.split(",")
.map((t) => t.trim())
.filter((t) => knownTargets.includes(t)),
[targetsValue]
[targetsValue],
);
const newOrCurrentTargets = newTargets.length > 0 ? newTargets : targets;

View file

@ -51,7 +51,7 @@ const panicCodes = panicCodesUntyped as { [target: string]: string };
const panicKeymap = (
doc: Document,
keys: string[] = ["Cmd-.", "Ctrl-.", "Alt-."]
keys: string[] = ["Cmd-.", "Ctrl-.", "Alt-."],
) => {
const panicCode = panicCodes[doc.target];
@ -74,7 +74,7 @@ interface FlokSetupOptions {
const flokSetup = (
doc: Document,
{ readOnly = false }: FlokSetupOptions = {}
{ readOnly = false }: FlokSetupOptions = {},
) => {
const text = doc.getText();
const undoManager = new UndoManager(text);

View file

@ -17,7 +17,7 @@ const HydraCanvas = ({
ref={ref}
className={cn(
"absolute top-0 left-0",
fullscreen && "h-full w-full block overflow-hidden"
fullscreen && "h-full w-full block overflow-hidden",
)}
style={{
imageRendering: "pixelated",

View file

@ -51,14 +51,14 @@ export function Mosaic({ className, items }: MosaicProps) {
<div
className={cn(
"flex flex-col items-stretch p-1 h-screen gap-1 overflow-hidden",
className
className,
)}
>
{rows.map((rowItems, i) => (
<div
className={cn(
"flex flex-row gap-1",
halfHeight ? "h-[50vh]" : "h-screen"
halfHeight ? "h-[50vh]" : "h-screen",
)}
key={i}
>

View file

@ -21,7 +21,7 @@ export const Pane = ({
<div
className={cn(
"flex overflow-auto relative",
halfHeight ? "h-[50vh]" : "h-screen"
halfHeight ? "h-[50vh]" : "h-screen",
)}
>
<TargetSelect

View file

@ -24,15 +24,15 @@ export function ReplsInfo({
const replTargets = targets.filter((t) => !webTargets.includes(t));
if (replTargets.length === 0) return null;
const terminalSpec = (OS === "windows" ? 'PowerShell ' : '');
const lineSeparator = (OS === 'windows' ? `\`` : `\\`);
const terminalSpec = OS === "windows" ? "PowerShell " : "";
const lineSeparator = OS === "windows" ? `\`` : `\\`;
const replCommand =
`npx flok-repl@latest -H ${sessionUrl} ${lineSeparator}\n` +
` -s ${sessionName} ${lineSeparator}\n` +
` -t ${replTargets.join(" ")} ${lineSeparator}\n` +
` -T user:${userName}`;
const copyToClipboard = () => {
setCopied(true);
setTimeout(() => setCopied(false), 1000);
@ -44,7 +44,8 @@ export function ReplsInfo({
<p className="text-sm text-slate-500 dark:text-slate-400">
This session has one or more targets that need an external REPL process
to run on your computer. To run code executed on these targets, you will
need to run <code>flok-repl</code> on a {terminalSpec}terminal, like this:
need to run <code>flok-repl</code> on a {terminalSpec}terminal, like
this:
</p>
<div className="mt-4 mb-4 relative">
<pre className="rounded bg-slate-800 mr-3 p-3 whitespace-pre-wrap w-full">

View file

@ -82,7 +82,7 @@ export default function SessionCommandDialog({
onEditorSettingsChange({
...editorSettings,
fontFamily: font,
})
}),
)();
};
@ -92,7 +92,7 @@ export default function SessionCommandDialog({
onEditorSettingsChange({
...editorSettings,
theme,
})
}),
)();
};
@ -170,7 +170,7 @@ export default function SessionCommandDialog({
onEditorSettingsChange({
...editorSettings,
lineNumbers: !lineNumbers,
})
}),
)}
>
<FileDigit className="mr-2 h-4 w-4" />
@ -181,7 +181,7 @@ export default function SessionCommandDialog({
onEditorSettingsChange({
...editorSettings,
wrapText: !wrapText,
})
}),
)}
>
<WrapText className="mr-2 h-4 w-4" />
@ -192,7 +192,7 @@ export default function SessionCommandDialog({
onEditorSettingsChange({
...editorSettings,
vimMode: !vimMode,
})
}),
)}
>
<TextCursorIcon className="mr-2 h-4 w-4" />
@ -203,7 +203,9 @@ export default function SessionCommandDialog({
<CommandSeparator />
<CommandGroup heading="Display">
<CommandList>
<CommandItem onSelect={wrapHandler(props.onEditorChangeDisplaySettings)}>
<CommandItem
onSelect={wrapHandler(props.onEditorChangeDisplaySettings)}
>
<Monitor className="mr-2 h-4 w-4" />
<span>Change display settings</span>
</CommandItem>

View file

@ -142,7 +142,7 @@ export function StatusBar({
<div
className={cn(
"fixed bottom-0 left-0 z-10 h-8 w-screen p-1 pl-2 pr-2 text-xs flex flex-row shadow-lg shadow-black/50",
className
className,
)}
>
{pubSubState && (

View file

@ -29,7 +29,7 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
}
},
);
export interface ButtonProps

View file

@ -16,7 +16,7 @@ const Command = ({
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-lg bg-white dark:bg-slate-800",
className
className,
)}
{...props}
/>
@ -44,13 +44,14 @@ const CommandInput = ({
}: React.ComponentProps<typeof CommandPrimitive.Input>) => (
<div
className="flex items-center border-b border-b-slate-100 px-4 dark:border-b-slate-700"
cmdk-input-wrapper="">
cmdk-input-wrapper=""
>
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-slate-400 disabled:cursor-not-allowed disabled:opacity-50 dark:text-slate-50",
className
className,
)}
{...props}
/>
@ -95,7 +96,7 @@ const CommandGroup = ({
ref={ref}
className={cn(
"overflow-hidden py-3 px-2 text-slate-700 dark:text-slate-400 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:text-slate-900 [&_[cmdk-group-heading]]:dark:text-slate-300",
className
className,
)}
{...props}
/>
@ -125,7 +126,7 @@ const CommandItem = ({
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-md py-1.5 px-2 text-sm font-medium outline-none aria-selected:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:aria-selected:bg-slate-700",
className
className,
)}
{...props}
/>
@ -141,7 +142,7 @@ const CommandShortcut = ({
<span
className={cn(
"ml-auto text-xs tracking-widest text-slate-500",
className
className,
)}
{...props}
/>

View file

@ -28,7 +28,7 @@ const DialogOverlay = ({
<DialogPrimitive.Overlay
className={cn(
"data-[state=closed]:animate-out data-[state=open]:fade-in data-[state=closed]:fade-out fixed inset-0 z-50 bg-black/50 transition-all duration-100",
className
className,
)}
{...props}
ref={ref}
@ -49,9 +49,10 @@ const DialogContent = ({
className={cn(
"animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0 fixed z-50 grid w-full gap-4 rounded-b-lg bg-white p-6 sm:max-w-lg sm:rounded-lg",
"dark:bg-slate-900",
className
className,
)}
{...props}>
{...props}
>
{children}
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<X className="h-4 w-4" />
@ -69,7 +70,7 @@ const DialogHeader = ({
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left overflow-auto",
className
className,
)}
{...props}
/>
@ -83,7 +84,7 @@ const DialogFooter = ({
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
className,
)}
{...props}
/>
@ -100,7 +101,7 @@ const DialogTitle = ({
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className
className,
)}
{...props}
/>

View file

@ -29,9 +29,10 @@ const DropdownMenuSubTrigger = ({
className={cn(
"flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
inset && "pl-8",
className
className,
)}
{...props}>
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
@ -48,7 +49,7 @@ const DropdownMenuSubContent = ({
ref={ref}
className={cn(
"animate-in slide-in-from-left-1 z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white p-1 text-slate-700 shadow-md dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400",
className
className,
)}
{...props}
/>
@ -68,7 +69,7 @@ const DropdownMenuContent = ({
sideOffset={sideOffset}
className={cn(
"animate-in data-[side=right]:slide-in-from-left-2 data-[side=left]:slide-in-from-right-2 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white p-1 text-slate-700 shadow-md dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400",
className
className,
)}
{...props}
/>
@ -89,7 +90,7 @@ const DropdownMenuItem = ({
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
inset && "pl-8",
className
className,
)}
{...props}
/>
@ -107,10 +108,11 @@ const DropdownMenuCheckboxItem = ({
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
className
className,
)}
checked={checked}
{...props}>
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
@ -132,9 +134,10 @@ const DropdownMenuRadioItem = ({
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
className
className,
)}
{...props}>
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
@ -158,7 +161,7 @@ const DropdownMenuLabel = ({
className={cn(
"px-2 py-1.5 text-sm font-semibold text-slate-900 dark:text-slate-300",
inset && "pl-8",
className
className,
)}
{...props}
/>
@ -186,7 +189,7 @@ const DropdownMenuShortcut = ({
<span
className={cn(
"ml-auto text-xs tracking-widest text-slate-500",
className
className,
)}
{...props}
/>

View file

@ -132,7 +132,7 @@ export function FloatingPanel({
// Size and position are relative to window.innerWidth and window.innerHeight
const [size, setSize] = useState<Size>(store.get(sizeSettingId, defaultSize));
const [position, setPosition] = useState<Position>(
store.get(posSettingId, defaultPosition)
store.get(posSettingId, defaultPosition),
);
// Save position and size
@ -151,7 +151,7 @@ export function FloatingPanel({
width: clamp(size.width) * innerWidth,
height: clamp(size.height) * innerHeight,
}),
[size, resizeTriggered]
[size, resizeTriggered],
);
const absPosition = useMemo(
@ -159,14 +159,14 @@ export function FloatingPanel({
x: clamp(position.x) * innerWidth,
y: clamp(position.y) * innerHeight,
}),
[position, resizeTriggered]
[position, resizeTriggered],
);
return (
<Rnd
className={cn(
"overflow-hidden rounded-md pl-1 pr-1 pb-2 border border-gray-800 shadow-lg shadow-black/50 text-slate-50 font-mono text-xs bg-black bg-opacity-70 z-10",
className
className,
)}
size={absSize}
position={absPosition}
@ -195,7 +195,7 @@ export function FloatingPanel({
<div
className={cn(
headerClassName,
"font-bold sticky top-0 cursor-move pb-1"
"font-bold sticky top-0 cursor-move pb-1",
)}
>
<div className="flex flex-row">

View file

@ -12,7 +12,7 @@ const Input = ({ className, ref, ...props }: InputProps) => {
<input
className={cn(
"flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
className,
)}
ref={ref}
{...props}

View file

@ -14,7 +14,7 @@ const Label = ({
ref={ref}
className={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
className,
)}
{...props}
/>

View file

@ -22,7 +22,7 @@ const Menubar = ({
ref={ref}
className={cn(
"flex h-8 items-center space-x-1 border-slate-100 bg-white dark:border-slate-800 dark:bg-slate-900 bg-opacity-50 dark:bg-opacity-50",
className
className,
)}
{...props}
/>
@ -38,7 +38,7 @@ const MenubarTrigger = ({
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-[0.2rem] py-1.5 px-3 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
className
className,
)}
{...props}
/>
@ -59,9 +59,10 @@ const MenubarSubTrigger = ({
className={cn(
"flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
inset && "pl-8",
className
className,
)}
{...props}>
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
@ -77,7 +78,7 @@ const MenubarSubContent = ({
ref={ref}
className={cn(
"animate-in slide-in-from-left-1 z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white p-1 shadow-md dark:border-slate-700 dark:bg-slate-800",
className
className,
)}
{...props}
/>
@ -96,7 +97,7 @@ const MenubarContent = ({
align={align}
className={cn(
"animate-in slide-in-from-top-1 z-50 min-w-[12rem] overflow-hidden rounded-md border border-slate-100 bg-white p-1 text-slate-700 shadow-md dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400",
className
className,
)}
{...props}
/>
@ -117,7 +118,7 @@ const MenubarItem = ({
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1 px-1 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
inset && "pl-8",
className
className,
)}
{...props}
/>
@ -135,10 +136,11 @@ const MenubarCheckboxItem = ({
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
className
className,
)}
checked={checked}
{...props}>
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
@ -159,9 +161,10 @@ const MenubarRadioItem = ({
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
className
className,
)}
{...props}>
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
@ -185,7 +188,7 @@ const MenubarLabel = ({
className={cn(
"px-2 py-1.5 text-sm font-semibold text-slate-900 dark:text-slate-300",
inset && "pl-8",
className
className,
)}
{...props}
/>
@ -214,7 +217,7 @@ const MenubarShortcut = ({
<span
className={cn(
"ml-auto text-xs tracking-widest text-slate-500",
className
className,
)}
{...props}
/>

View file

@ -23,7 +23,7 @@ const PopoverContent = ({
sideOffset={sideOffset}
className={cn(
"animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 data-[side=right]:slide-in-from-left-2 data-[side=left]:slide-in-from-right-2 z-50 w-72 rounded-md border border-slate-100 bg-white p-4 shadow-md outline-none dark:border-slate-800 dark:bg-slate-800",
className
className,
)}
{...props}
/>

View file

@ -22,9 +22,10 @@ const SelectTrigger = ({
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
className,
)}
{...props}>
{...props}
>
{children}
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Trigger>
@ -42,9 +43,10 @@ const SelectContent = ({
ref={ref}
className={cn(
"animate-in fade-in-80 relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white text-slate-700 shadow-md dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400",
className
className,
)}
{...props}>
{...props}
>
<SelectPrimitive.Viewport className="p-1">
{children}
</SelectPrimitive.Viewport>
@ -62,7 +64,7 @@ const SelectLabel = ({
ref={ref}
className={cn(
"py-1.5 pr-2 pl-8 text-sm font-semibold text-slate-900 dark:text-slate-300",
className
className,
)}
{...props}
/>
@ -79,9 +81,10 @@ const SelectItem = ({
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
className
className,
)}
{...props}>
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />

View file

@ -15,7 +15,7 @@ const ToastViewport = ({
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:bottom-0 sm:right-0 sm:flex-col md:max-w-[420px]",
className
className,
)}
{...props}
/>
@ -38,7 +38,7 @@ const toastVariants = cva(
defaultVariants: {
variant: "default",
},
}
},
);
const Toast = ({
@ -67,7 +67,7 @@ const ToastAction = ({
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border border-slate-200 bg-transparent px-3 text-sm font-medium transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-red-100 group-[.destructive]:hover:border-slate-50 group-[.destructive]:hover:bg-red-100 group-[.destructive]:hover:text-red-600 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:hover:text-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800",
className
className,
)}
{...props}
/>
@ -83,10 +83,11 @@ const ToastClose = ({
ref={ref}
className={cn(
"absolute top-2 right-2 rounded-md p-1 text-slate-500 opacity-0 transition-opacity hover:text-slate-900 focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:hover:text-slate-50",
className
className,
)}
toast-close=""
{...props}>
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
);

View file

@ -25,7 +25,7 @@ const toggleVariants = cva(
variant: "default",
size: "default",
},
}
},
);
const Toggle = ({

View file

@ -23,7 +23,7 @@ const TooltipContent = ({
sideOffset={sideOffset}
className={cn(
"animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=top]:slide-in-from-bottom-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 z-50 overflow-hidden rounded-md border border-slate-100 bg-white px-3 py-1.5 text-sm text-slate-700 shadow-md dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400",
className
className,
)}
{...props}
/>

View file

@ -9,7 +9,11 @@ export interface WebTargetIframeProps {
displaySettings: DisplaySettings;
}
export const WebTargetIframe = ({ target, session, displaySettings }: WebTargetIframeProps) => {
export const WebTargetIframe = ({
target,
session,
displaySettings,
}: WebTargetIframeProps) => {
const ref = useRef<HTMLIFrameElement | null>(null);
const query = useQuery();

View file

@ -8,7 +8,7 @@ export function useAnimationFrame(callback: (timestamp: number) => void) {
callback(timestamp);
requestId.current = requestAnimationFrame(animate);
},
[callback]
[callback],
);
useEffect(() => {

View file

@ -19,10 +19,10 @@ const toObject = (hash: string) =>
export function useHash(): [
HashRecord,
(newHash: HashRecord | React.SetStateAction<HashRecord>) => void
(newHash: HashRecord | React.SetStateAction<HashRecord>) => void,
] {
const [hash, setHash] = useState<HashRecord>(() =>
toObject(window.location.hash)
toObject(window.location.hash),
);
const hashChangeHandler = useCallback(() => {
@ -41,7 +41,7 @@ export function useHash(): [
if (typeof newHash === "function") newHash = newHash(hash);
window.location.hash = fromObject(newHash);
},
[hash]
[hash],
);
return [hash, updateHash];

View file

@ -12,7 +12,7 @@ export function useShortcut(
keyShortcuts: string[],
handler: (e: KeyboardEvent) => void,
deps: any[] = [],
preventDefault: boolean = true
preventDefault: boolean = true,
) {
keyShortcuts.forEach((keyShortcut) => {
const parts = keyShortcut.toLowerCase().split("-");

View file

@ -10,7 +10,7 @@ import { forEachDocumentContext } from "@/lib/utils";
export function useStrudelCodemirrorExtensions(
session: Session | null,
editorRefs: React.RefObject<ReactCodeMirrorRef>[]
editorRefs: React.RefObject<ReactCodeMirrorRef>[],
) {
useAnimationFrame(
useCallback(() => {
@ -24,8 +24,8 @@ export function useStrudelCodemirrorExtensions(
highlightMiniLocations(view, ctx.phase || 0, ctx.haps || []);
},
session,
editorRefs
editorRefs,
);
}, [session, editorRefs])
}, [session, editorRefs]),
);
}

View file

@ -81,7 +81,7 @@ export const reducer = (state: State, action: Action): State => {
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
};
@ -106,7 +106,7 @@ export const reducer = (state: State, action: Action): State => {
...t,
open: false,
}
: t
: t,
),
};
}

View file

@ -16,7 +16,7 @@ export function useWebTarget<Controller>(
loadIf?: (deps: any[]) => boolean;
onEval?: (instance: Controller, evalMsg: EvalMessage) => void;
onError?: (err: unknown) => void;
}
},
) {
const query = useQuery();
const noWebEval = query.get("noWebEval")?.split(",") || [];

View file

@ -29,8 +29,15 @@
}
.text-stroke {
text-shadow: -1px -1px 0 #000, 0 -1px 0 #000, 1px -1px 0 #000, 1px 0 0 #000,
1px 1px 0 #000, 0 1px 0 #000, -1px 1px 0 #000, -1px 0 0 #000;
text-shadow:
-1px -1px 0 #000,
0 -1px 0 #000,
1px -1px 0 #000,
1px 0 0 #000,
1px 1px 0 #000,
0 1px 0 #000,
-1px 1px 0 #000,
-1px 0 0 #000;
}
:root {

View file

@ -8,9 +8,11 @@ export const defaultDisplaySettings: DisplaySettings = {
canvasPixelSize: 1,
showCanvas: true,
enableFft: true,
}
};
export function sanitizeDisplaySettings(settings: DisplaySettings): DisplaySettings {
export function sanitizeDisplaySettings(
settings: DisplaySettings,
): DisplaySettings {
// Pixel size should be at least 1 to prevent division-by-zero errors
const minPixelSize = 1;
@ -18,13 +20,11 @@ export function sanitizeDisplaySettings(settings: DisplaySettings): DisplaySetti
// canvas; should be low enough
const maxPixelSize = 50;
return {
...settings,
canvasPixelSize: Math.max(
minPixelSize,
Math.min(
maxPixelSize,
Math.round(settings.canvasPixelSize))),
canvasPixelSize: Math.max(
minPixelSize,
Math.min(maxPixelSize, Math.round(settings.canvasPixelSize)),
),
};
}

View file

@ -1,18 +1,18 @@
export const fonts = {
"BigBlue Terminal": "BigBlue TerminalPlus",
"Courier": "Courier",
"Fira Code" : "Fira Code VF",
Courier: "Courier",
"Fira Code": "Fira Code VF",
"IBM Plex Mono": "IBM Plex Mono",
"Inconsolata": "Inconsolata",
"JetBrains": "JetBrains Mono",
"JGS": "jgs_Font",
"Monaco": "Monaco",
"MonoCraft": "Monocraft Nerd Font",
"OpenDyslexic" : "OpenDyslexicMono",
"Roboto Mono" : "Roboto Mono",
Inconsolata: "Inconsolata",
JetBrains: "JetBrains Mono",
JGS: "jgs_Font",
Monaco: "Monaco",
MonoCraft: "Monocraft Nerd Font",
OpenDyslexic: "OpenDyslexicMono",
"Roboto Mono": "Roboto Mono",
"Steps Mono": "Steps Mono",
"Syne Mono" : "Syne Mono",
"Ubuntu Mono" : "Ubuntu Mono",
"VT323" : "VT323",
"Syne Mono": "Syne Mono",
"Ubuntu Mono": "Ubuntu Mono",
VT323: "VT323",
};
export default fonts;

View file

@ -1,6 +1,6 @@
import Hydra from "hydra-synth";
import { isWebglSupported } from "@/lib/webgl-detector.js";
import {DisplaySettings} from "@/lib/display-settings.ts";
import { DisplaySettings } from "@/lib/display-settings.ts";
declare global {
interface Window {
@ -57,7 +57,7 @@ export class HydraWrapper {
// For some reason on Android mobile, Chrome has this object undefined:
if (!window.navigator.mediaDevices) {
this._onWarning(
"navigator.mediaDevices is not defined. You won't be able to use the Webcam or screen capturing."
"navigator.mediaDevices is not defined. You won't be able to use the Webcam or screen capturing.",
);
}
@ -85,62 +85,77 @@ export class HydraWrapper {
window.P = (pattern: any) => {
return () => {
// parse using the strudel mini parser
const reified = window.strudel.mini.minify(pattern)
const reified = window.strudel.mini.minify(pattern);
const now = window.strudel.core.getTime()
const now = window.strudel.core.getTime();
// query the current value
const arc = reified.queryArc(now, now)
const arc = reified.queryArc(now, now);
return arc[0].value;
}
}
};
};
// initialized a streaming canvas with the strudel draw context canvas
// this allows us to use the strudel output
window.useStrudelCanvas = (s: any) => {
const canvas = window.strudel.draw.getDrawContext().canvas
canvas.style.display = "none"
s.init({src: canvas})
}
const clamp = (num: number, min: number, max: number) => Math.min(Math.max(num, min), max);
const canvas = window.strudel.draw.getDrawContext().canvas;
canvas.style.display = "none";
s.init({ src: canvas });
};
const clamp = (num: number, min: number, max: number) =>
Math.min(Math.max(num, min), max);
// Enables Hydra to use Strudel frequency data
// with `.scrollX(() => fft(1,0)` it will influence the x-axis, according to the fft data
// first number is the index of the bucket, second is the number of buckets to aggregate the number too
window.fft = (index: number, buckets: number = 8, options?: { min?: number; max?: number, scale?: number, analyzerId?: string }) => {
const analyzerId = options?.analyzerId ?? "flok-master"
window.fft = (
index: number,
buckets: number = 8,
options?: {
min?: number;
max?: number;
scale?: number;
analyzerId?: string;
},
) => {
const analyzerId = options?.analyzerId ?? "flok-master";
const min = options?.min ?? -150;
const scale = options?.scale ?? 1
const max = options?.max ?? 0
const scale = options?.scale ?? 1;
const max = options?.max ?? 0;
// Strudel is not initialized yet, so we just return a default value
if(window.strudel == undefined) return .5;
if (window.strudel == undefined) return 0.5;
// If display settings are not enabled, we just return a default value
if(!(this._displaySettings.enableFft ?? true)) return .5;
if (!(this._displaySettings.enableFft ?? true)) return 0.5;
// Enable auto-analyze
window.strudel.enableAutoAnalyze = true;
// If the analyzerId is not defined, we just return a default value
if(window.strudel.webaudio.analysers[analyzerId] == undefined) {
return .5
if (window.strudel.webaudio.analysers[analyzerId] == undefined) {
return 0.5;
}
const freq = window.strudel.webaudio.getAnalyzerData("frequency", analyzerId) as Array<number>;
const bucketSize = (freq.length) / buckets
const freq = window.strudel.webaudio.getAnalyzerData(
"frequency",
analyzerId,
) as Array<number>;
const bucketSize = freq.length / buckets;
// inspired from https://github.com/tidalcycles/strudel/blob/a7728e3d81fb7a0a2dff9f2f4bd9e313ddf138cd/packages/webaudio/scope.mjs#L53
const normalized = freq.map((it: number) => {
const norm = clamp((it - min) / (max - min), 0, 1);
return norm * scale;
})
});
return normalized.slice(bucketSize * index, bucketSize * (index + 1))
.reduce((a, b) => a + b, 0) / bucketSize
}
return (
normalized
.slice(bucketSize * index, bucketSize * (index + 1))
.reduce((a, b) => a + b, 0) / bucketSize
);
};
this.initialized = true;
console.log("Hydra initialized");

View file

@ -1 +1 @@
declare module 'mercury-engine';
declare module "mercury-engine";

View file

@ -1,5 +1,5 @@
import { DisplaySettings } from "@/lib/display-settings";
export interface SettingsMessage {
displaySettings?: DisplaySettings
displaySettings?: DisplaySettings;
}

View file

@ -82,7 +82,7 @@ export class StrudelWrapper {
import("@strudel/serial"),
import("@strudel/soundfonts"),
this.webaudio,
controls
controls,
);
try {
await Promise.all([
@ -118,11 +118,11 @@ export class StrudelWrapper {
// queries the stack of strudel patterns for the current time
const allHaps = this._repl.scheduler.pattern.queryArc(
Math.max(lastFrame!, phase - 1 / 10), // make sure query is not larger than 1/10 s
phase
phase,
);
// filter out haps that are not active right now
const currentFrame = allHaps.filter(
(hap: any) => phase >= hap.whole.begin && phase <= hap.endClipped
(hap: any) => phase >= hap.whole.begin && phase <= hap.endClipped,
);
// iterate over each strudel doc
Object.keys(this._docPatterns).forEach((docId: any) => {
@ -134,7 +134,7 @@ export class StrudelWrapper {
},
(err: any) => {
console.error("[strudel] draw error", err);
}
},
);
this._repl = repl({
@ -169,14 +169,15 @@ export class StrudelWrapper {
}
}
async tryEval(msg: EvalMessage) {
if (!this.initialized) await this.initialize();
try {
const {body: code, docId} = msg;
const { body: code, docId } = msg;
// little hack that injects the docId at the end of the code to make it available in afterEval
// also add ann analyser node to all patterns, for fft data in hydra
const pattern = await this._repl.evaluate(`${code}\n${this.enableAutoAnalyze ? this.hapAnalyzeSnippet : ""}\n//${docId}`);
const pattern = await this._repl.evaluate(
`${code}\n${this.enableAutoAnalyze ? this.hapAnalyzeSnippet : ""}\n//${docId}`,
);
if (pattern) {
this._docPatterns[docId] = pattern.docId(docId); // docId is needed for highlighting
const allPatterns = stack(...Object.values(this._docPatterns));

View file

@ -1,31 +1,31 @@
import { createTheme } from '@uiw/codemirror-themes';
import { tags as t } from '@lezer/highlight';
import { createTheme } from "@uiw/codemirror-themes";
import { tags as t } from "@lezer/highlight";
export const ayuDark = createTheme({
theme: 'dark',
theme: "dark",
settings: {
background: 'transparent',
backgroundImage: '',
foreground: '#B3B1AD',
caret: '#FFCC66',
selection: '#253340',
selectionMatch: '#253340',
lineHighlight: 'rgba(37, 52, 64, 0.7)',
gutterBorder: '1px solid #0A0E14',
gutterBackground: '#0A0E14',
gutterForeground: '#4E5561',
background: "transparent",
backgroundImage: "",
foreground: "#B3B1AD",
caret: "#FFCC66",
selection: "#253340",
selectionMatch: "#253340",
lineHighlight: "rgba(37, 52, 64, 0.7)",
gutterBorder: "1px solid #0A0E14",
gutterBackground: "#0A0E14",
gutterForeground: "#4E5561",
},
styles: [
{ tag: t.comment, color: '#4E5561' },
{ tag: t.variableName, color: '#FFCC66' },
{ tag: [t.string, t.special(t.brace)], color: '#BAE67E' },
{ tag: t.number, color: '#D4BFFF' },
{ tag: t.bool, color: '#FF8F40' },
{ tag: t.null, color: '#FF8F40' },
{ tag: t.keyword, color: '#FF8F40' },
{ tag: t.operator, color: '#FF8F40' },
{ tag: t.className, color: '#5CCFE6' },
{ tag: t.definition(t.typeName), color: '#5CCFE6' },
{ tag: t.typeName, color: '#5CCFE6' },
{ tag: t.comment, color: "#4E5561" },
{ tag: t.variableName, color: "#FFCC66" },
{ tag: [t.string, t.special(t.brace)], color: "#BAE67E" },
{ tag: t.number, color: "#D4BFFF" },
{ tag: t.bool, color: "#FF8F40" },
{ tag: t.null, color: "#FF8F40" },
{ tag: t.keyword, color: "#FF8F40" },
{ tag: t.operator, color: "#FF8F40" },
{ tag: t.className, color: "#5CCFE6" },
{ tag: t.definition(t.typeName), color: "#5CCFE6" },
{ tag: t.typeName, color: "#5CCFE6" },
],
});

View file

@ -1,30 +1,35 @@
import { createTheme } from '@uiw/codemirror-themes';
import { tags as t } from '@lezer/highlight';
import { createTheme } from "@uiw/codemirror-themes";
import { tags as t } from "@lezer/highlight";
export const dracula = createTheme({
theme: 'dark',
theme: "dark",
settings: {
background: 'transparent',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6D8A88',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
background: "transparent",
foreground: "#f8f8f2",
caret: "#f8f8f0",
selection: "rgba(255, 255, 255, 0.1)",
selectionMatch: "rgba(255, 255, 255, 0.2)",
gutterBackground: "#282a36",
gutterForeground: "#6D8A88",
gutterBorder: "transparent",
lineHighlight: "rgba(255, 255, 255, 0.1)",
},
styles: [
{ tag: t.comment, color: '#6272a4' },
{ tag: t.string, color: '#f1fa8c' },
{ tag: t.atom, color: '#bd93f9' },
{ tag: t.meta, color: '#f8f8f2' },
{ tag: [t.keyword, t.operator, t.tagName], color: '#ff79c6' },
{ tag: [t.function(t.propertyName), t.propertyName], color: '#66d9ef' },
{ tag: t.comment, color: "#6272a4" },
{ tag: t.string, color: "#f1fa8c" },
{ tag: t.atom, color: "#bd93f9" },
{ tag: t.meta, color: "#f8f8f2" },
{ tag: [t.keyword, t.operator, t.tagName], color: "#ff79c6" },
{ tag: [t.function(t.propertyName), t.propertyName], color: "#66d9ef" },
{
tag: [t.definition(t.variableName), t.function(t.variableName), t.className, t.attributeName],
color: '#50fa7b',
tag: [
t.definition(t.variableName),
t.function(t.variableName),
t.className,
t.attributeName,
],
color: "#50fa7b",
},
{ tag: t.atom, color: '#bd93f9' },
{ tag: t.atom, color: "#bd93f9" },
],
});

View file

@ -1,110 +1,110 @@
import { createTheme } from '@uiw/codemirror-themes';
import { tags as t } from '@lezer/highlight';
import { createTheme } from "@uiw/codemirror-themes";
import { tags as t } from "@lezer/highlight";
export const gruvboxDark = createTheme({
theme: 'dark',
theme: "dark",
settings: {
background: 'transparent',
foreground: '#ebdbb2',
caret: '#ebdbb2',
selection: '#b99d555c',
selectionMatch: '#b99d555c',
lineHighlight: '#baa1602b',
gutterBackground: '#282828',
gutterForeground: '#7c6f64',
background: "transparent",
foreground: "#ebdbb2",
caret: "#ebdbb2",
selection: "#b99d555c",
selectionMatch: "#b99d555c",
lineHighlight: "#baa1602b",
gutterBackground: "#282828",
gutterForeground: "#7c6f64",
},
styles: [
{ tag: t.keyword, color: '#fb4934' },
{ tag: t.keyword, color: "#fb4934" },
{
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: '#8ec07c',
color: "#8ec07c",
},
{ tag: [t.variableName], color: '#83a598' },
{ tag: [t.function(t.variableName)], color: '#b8bb26', fontStyle: 'bold' },
{ tag: [t.labelName], color: '#ebdbb2' },
{ tag: [t.variableName], color: "#83a598" },
{ tag: [t.function(t.variableName)], color: "#b8bb26", fontStyle: "bold" },
{ tag: [t.labelName], color: "#ebdbb2" },
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: '#d3869b',
color: "#d3869b",
},
{ tag: [t.definition(t.name), t.separator], color: '#ebdbb2' },
{ tag: [t.brace], color: '#ebdbb2' },
{ tag: [t.definition(t.name), t.separator], color: "#ebdbb2" },
{ tag: [t.brace], color: "#ebdbb2" },
{
tag: [t.annotation],
color: '#fb4934d',
color: "#fb4934d",
},
{
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: '#d3869b',
color: "#d3869b",
},
{
tag: [t.typeName, t.className],
color: '#fabd2f',
color: "#fabd2f",
},
{
tag: [t.operator, t.operatorKeyword],
color: '#fb4934',
color: "#fb4934",
},
{
tag: [t.tagName],
color: '#8ec07c',
fontStyle: 'bold',
color: "#8ec07c",
fontStyle: "bold",
},
{
tag: [t.squareBracket],
color: '#fe8019',
color: "#fe8019",
},
{
tag: [t.angleBracket],
color: '#83a598',
color: "#83a598",
},
{
tag: [t.attributeName],
color: '#8ec07c',
color: "#8ec07c",
},
{
tag: [t.regexp],
color: '#8ec07c',
color: "#8ec07c",
},
{
tag: [t.quote],
color: '#928374',
color: "#928374",
},
{ tag: [t.string], color: '#ebdbb2' },
{ tag: [t.string], color: "#ebdbb2" },
{
tag: t.link,
color: '#a89984',
textDecoration: 'underline',
textUnderlinePosition: 'under',
color: "#a89984",
textDecoration: "underline",
textUnderlinePosition: "under",
},
{
tag: [t.url, t.escape, t.special(t.string)],
color: '#d3869b',
color: "#d3869b",
},
{ tag: [t.meta], color: '#fabd2f' },
{ tag: [t.comment], color: '#928374', fontStyle: 'italic' },
{ tag: t.strong, fontWeight: 'bold', color: '#fe8019' },
{ tag: t.emphasis, fontStyle: 'italic', color: '#b8bb26' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: '#b8bb26' },
{ tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#b8bb26' },
{ tag: [t.meta], color: "#fabd2f" },
{ tag: [t.comment], color: "#928374", fontStyle: "italic" },
{ tag: t.strong, fontWeight: "bold", color: "#fe8019" },
{ tag: t.emphasis, fontStyle: "italic", color: "#b8bb26" },
{ tag: t.strikethrough, textDecoration: "line-through" },
{ tag: t.heading, fontWeight: "bold", color: "#b8bb26" },
{ tag: [t.heading1, t.heading2], fontWeight: "bold", color: "#b8bb26" },
{
tag: [t.heading3, t.heading4],
fontWeight: 'bold',
color: '#fabd2f',
fontWeight: "bold",
color: "#fabd2f",
},
{
tag: [t.heading5, t.heading6],
color: '#fabd2f',
color: "#fabd2f",
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d3869b' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: "#d3869b" },
{
tag: [t.processingInstruction, t.inserted],
color: '#83a598',
color: "#83a598",
},
{
tag: [t.contentSeparator],
color: '#fb4934',
color: "#fb4934",
},
{ tag: t.invalid, color: '#fe8019', borderBottom: `1px dotted #fb4934d` },
{ tag: t.invalid, color: "#fe8019", borderBottom: `1px dotted #fb4934d` },
],
});

View file

@ -23,23 +23,26 @@ interface Theme {
ext: Extension;
}
const noBackground = { "settings" : { "background" : "none" }};
const noBackground = { settings: { background: "none" } };
export const themes: { [key: string]: Theme } = {
ayuDark: { name: "Ayu Dark", ext: ayuDark },
andromeda: { name: "Andromeda", ext: andromedaInit(noBackground) },
bespin : { name: "Bespin", ext: bespinInit(noBackground) },
consoleDark : { name: "Console Dark", ext: consoleDarkInit(noBackground) },
andromeda: { name: "Andromeda", ext: andromedaInit(noBackground) },
bespin: { name: "Bespin", ext: bespinInit(noBackground) },
consoleDark: { name: "Console Dark", ext: consoleDarkInit(noBackground) },
dracula: { name: "Dracula", ext: dracula },
githubDark : { name: "Github Dark", ext: githubDarkInit(noBackground) },
githubDark: { name: "Github Dark", ext: githubDarkInit(noBackground) },
gruvboxDark: { name: "Gruvbox Dark", ext: gruvboxDark },
monokai : { name: "Monokai", ext: monokaiInit(noBackground) },
monokai: { name: "Monokai", ext: monokaiInit(noBackground) },
monokaiDimmed: { name: "Monokai Dimmed", ext: monokai },
nord: { name: "Nord", ext: nord },
oneDark: { name: "One Dark", ext: oneDark },
solarizedDark : { name: "Solarized Dark", ext: solarizedDarkInit(noBackground) },
solarizedDark: {
name: "Solarized Dark",
ext: solarizedDarkInit(noBackground),
},
tokyoNight: { name: "Tokyo Night", ext: tokyoNight },
xcodeDark : { name: "XCode Dark", ext: xcodeDarkInit(noBackground) },
xcodeDark: { name: "XCode Dark", ext: xcodeDarkInit(noBackground) },
};
export default themes;

View file

@ -1,34 +1,34 @@
import { createTheme } from '@uiw/codemirror-themes';
import { tags as t } from '@lezer/highlight';
import { createTheme } from "@uiw/codemirror-themes";
import { tags as t } from "@lezer/highlight";
const monokaiColors = {
background: 'transparent',
foreground: '#c5c8c6',
selection: '#4747a1',
selectionMatch: '#4747a1',
cursor: '#c07020',
dropdownBackground: '#525252',
activeLine: '#30303078',
matchingBracket: '#303030',
keyword: '#676867',
storage: '#676867',
variable: '#c7444a',
parameter: '#6089B4',
function: '#9872A2',
string: '#D08442',
constant: '#8080FF',
type: '#9B0000',
class: '#CE6700',
number: '#6089B4',
comment: '#9A9B99',
heading: '#D0B344',
invalid: '#FF0B00',
regexp: '#D08442',
tag: '#6089B4',
background: "transparent",
foreground: "#c5c8c6",
selection: "#4747a1",
selectionMatch: "#4747a1",
cursor: "#c07020",
dropdownBackground: "#525252",
activeLine: "#30303078",
matchingBracket: "#303030",
keyword: "#676867",
storage: "#676867",
variable: "#c7444a",
parameter: "#6089B4",
function: "#9872A2",
string: "#D08442",
constant: "#8080FF",
type: "#9B0000",
class: "#CE6700",
number: "#6089B4",
comment: "#9A9B99",
heading: "#D0B344",
invalid: "#FF0B00",
regexp: "#D08442",
tag: "#6089B4",
};
export const monokai = createTheme({
theme: 'dark',
theme: "dark",
settings: {
background: monokaiColors.background,
foreground: monokaiColors.foreground,
@ -41,25 +41,47 @@ export const monokai = createTheme({
},
styles: [
{ tag: t.keyword, color: monokaiColors.keyword },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: monokaiColors.variable },
{
tag: [t.name, t.deleted, t.character, t.macroName],
color: monokaiColors.variable,
},
{ tag: [t.propertyName], color: monokaiColors.function },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: monokaiColors.string },
{ tag: [t.function(t.variableName), t.labelName], color: monokaiColors.function },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: monokaiColors.constant },
{
tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)],
color: monokaiColors.string,
},
{
tag: [t.function(t.variableName), t.labelName],
color: monokaiColors.function,
},
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: monokaiColors.constant,
},
{ tag: [t.definition(t.name), t.separator], color: monokaiColors.variable },
{ tag: [t.className], color: monokaiColors.class },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: monokaiColors.number },
{ tag: [t.typeName], color: monokaiColors.type, fontStyle: monokaiColors.type },
{
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: monokaiColors.number,
},
{
tag: [t.typeName],
color: monokaiColors.type,
fontStyle: monokaiColors.type,
},
{ tag: [t.operator, t.operatorKeyword], color: monokaiColors.keyword },
{ tag: [t.url, t.escape, t.regexp, t.link], color: monokaiColors.regexp },
{ tag: [t.meta, t.comment], color: monokaiColors.comment },
{ tag: t.tagName, color: monokaiColors.tag },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: monokaiColors.heading },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: monokaiColors.variable },
{ tag: t.strong, fontWeight: "bold" },
{ tag: t.emphasis, fontStyle: "italic" },
{ tag: t.link, textDecoration: "underline" },
{ tag: t.heading, fontWeight: "bold", color: monokaiColors.heading },
{
tag: [t.atom, t.bool, t.special(t.variableName)],
color: monokaiColors.variable,
},
{ tag: t.invalid, color: monokaiColors.invalid },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.strikethrough, textDecoration: "line-through" },
],
});

View file

@ -1,116 +1,116 @@
import { tags as t } from '@lezer/highlight';
import { createTheme, CreateThemeOptions } from '@uiw/codemirror-themes';
import { tags as t } from "@lezer/highlight";
import { createTheme, CreateThemeOptions } from "@uiw/codemirror-themes";
export const defaultSettingsNord: CreateThemeOptions['settings'] = {
background: 'transparent',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#00000073',
selectionMatch: '#00000073',
gutterBackground: '#2e3440',
gutterForeground: '#4c566a',
gutterActiveForeground: '#d8dee9',
lineHighlight: '#4c566a29',
export const defaultSettingsNord: CreateThemeOptions["settings"] = {
background: "transparent",
foreground: "#FFFFFF",
caret: "#FFFFFF",
selection: "#00000073",
selectionMatch: "#00000073",
gutterBackground: "#2e3440",
gutterForeground: "#4c566a",
gutterActiveForeground: "#d8dee9",
lineHighlight: "#4c566a29",
};
export const nord = createTheme({
theme: 'dark',
settings: {
...defaultSettingsNord,
theme: "dark",
settings: {
...defaultSettingsNord,
},
styles: [
{ tag: t.keyword, color: "#5e81ac" },
{
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: "#88c0d0",
},
styles: [
{ tag: t.keyword, color: '#5e81ac' },
{
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: '#88c0d0',
},
{ tag: [t.variableName], color: '#8fbcbb' },
{ tag: [t.function(t.variableName)], color: '#8fbcbb' },
{ tag: [t.labelName], color: '#81a1c1' },
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: '#5e81ac',
},
{ tag: [t.definition(t.name), t.separator], color: '#a3be8c' },
{ tag: [t.brace], color: '#8fbcbb' },
{
tag: [t.annotation],
color: '#d30102',
},
{
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: '#b48ead',
},
{
tag: [t.typeName, t.className],
color: '#ebcb8b',
},
{
tag: [t.operator, t.operatorKeyword],
color: '#a3be8c',
},
{
tag: [t.tagName],
color: '#b48ead',
},
{
tag: [t.squareBracket],
color: '#bf616a',
},
{
tag: [t.angleBracket],
color: '#d08770',
},
{
tag: [t.attributeName],
color: '#ebcb8b',
},
{
tag: [t.regexp],
color: '#5e81ac',
},
{
tag: [t.quote],
color: '#b48ead',
},
{ tag: [t.string], color: '#a3be8c' },
{
tag: t.link,
color: '#a3be8c',
textDecoration: 'underline',
textUnderlinePosition: 'under',
},
{
tag: [t.url, t.escape, t.special(t.string)],
color: '#8fbcbb',
},
{ tag: [t.meta], color: '#88c0d0' },
{ tag: [t.monospace], color: '#d8dee9', fontStyle: 'italic' },
{ tag: [t.comment], color: '#4c566a', fontStyle: 'italic' },
{ tag: t.strong, fontWeight: 'bold', color: '#5e81ac' },
{ tag: t.emphasis, fontStyle: 'italic', color: '#5e81ac' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: '#5e81ac' },
{ tag: t.special(t.heading1), fontWeight: 'bold', color: '#5e81ac' },
{ tag: t.heading1, fontWeight: 'bold', color: '#5e81ac' },
{
tag: [t.heading2, t.heading3, t.heading4],
fontWeight: 'bold',
color: '#5e81ac',
},
{
tag: [t.heading5, t.heading6],
color: '#5e81ac',
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d08770' },
{
tag: [t.processingInstruction, t.inserted],
color: '#8fbcbb',
},
{
tag: [t.contentSeparator],
color: '#ebcb8b',
},
{ tag: t.invalid, color: '#434c5e', borderBottom: `1px dotted #d30102` },
],
});
{ tag: [t.variableName], color: "#8fbcbb" },
{ tag: [t.function(t.variableName)], color: "#8fbcbb" },
{ tag: [t.labelName], color: "#81a1c1" },
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: "#5e81ac",
},
{ tag: [t.definition(t.name), t.separator], color: "#a3be8c" },
{ tag: [t.brace], color: "#8fbcbb" },
{
tag: [t.annotation],
color: "#d30102",
},
{
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: "#b48ead",
},
{
tag: [t.typeName, t.className],
color: "#ebcb8b",
},
{
tag: [t.operator, t.operatorKeyword],
color: "#a3be8c",
},
{
tag: [t.tagName],
color: "#b48ead",
},
{
tag: [t.squareBracket],
color: "#bf616a",
},
{
tag: [t.angleBracket],
color: "#d08770",
},
{
tag: [t.attributeName],
color: "#ebcb8b",
},
{
tag: [t.regexp],
color: "#5e81ac",
},
{
tag: [t.quote],
color: "#b48ead",
},
{ tag: [t.string], color: "#a3be8c" },
{
tag: t.link,
color: "#a3be8c",
textDecoration: "underline",
textUnderlinePosition: "under",
},
{
tag: [t.url, t.escape, t.special(t.string)],
color: "#8fbcbb",
},
{ tag: [t.meta], color: "#88c0d0" },
{ tag: [t.monospace], color: "#d8dee9", fontStyle: "italic" },
{ tag: [t.comment], color: "#4c566a", fontStyle: "italic" },
{ tag: t.strong, fontWeight: "bold", color: "#5e81ac" },
{ tag: t.emphasis, fontStyle: "italic", color: "#5e81ac" },
{ tag: t.strikethrough, textDecoration: "line-through" },
{ tag: t.heading, fontWeight: "bold", color: "#5e81ac" },
{ tag: t.special(t.heading1), fontWeight: "bold", color: "#5e81ac" },
{ tag: t.heading1, fontWeight: "bold", color: "#5e81ac" },
{
tag: [t.heading2, t.heading3, t.heading4],
fontWeight: "bold",
color: "#5e81ac",
},
{
tag: [t.heading5, t.heading6],
color: "#5e81ac",
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: "#d08770" },
{
tag: [t.processingInstruction, t.inserted],
color: "#8fbcbb",
},
{
tag: [t.contentSeparator],
color: "#ebcb8b",
},
{ tag: t.invalid, color: "#434c5e", borderBottom: `1px dotted #d30102` },
],
});

View file

@ -1,7 +1,7 @@
import {EditorView} from "@codemirror/view"
import {Extension} from "@codemirror/state"
import {HighlightStyle, syntaxHighlighting} from "@codemirror/language"
import {tags as t} from "@lezer/highlight"
import { EditorView } from "@codemirror/view";
import { Extension } from "@codemirror/state";
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { tags as t } from "@lezer/highlight";
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
@ -20,7 +20,7 @@ const chalky = "#e5c07b",
background = "#282c34",
tooltipBackground = "#353a42",
selection = "#3E4451",
cursor = "#528bff"
cursor = "#528bff";
/// The colors used in the theme, as CSS color strings.
export const color = {
@ -39,117 +39,132 @@ export const color = {
background,
tooltipBackground,
selection,
cursor
}
cursor,
};
/// The editor theme styles for One Dark.
export const oneDarkTheme = EditorView.theme({
"&": {
color: ivory,
// backgroundColor: "background"
backgroundColor: "none"
},
export const oneDarkTheme = EditorView.theme(
{
"&": {
color: ivory,
// backgroundColor: "background"
backgroundColor: "none",
},
".cm-content": {
caretColor: cursor
},
".cm-content": {
caretColor: cursor,
},
".cm-cursor, .cm-dropCursor": {borderLeftColor: cursor},
"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {backgroundColor: selection},
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
{ backgroundColor: selection },
".cm-panels": {backgroundColor: darkBackground, color: ivory},
".cm-panels.cm-panels-top": {borderBottom: "2px solid black"},
".cm-panels.cm-panels-bottom": {borderTop: "2px solid black"},
".cm-panels": { backgroundColor: darkBackground, color: ivory },
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
".cm-searchMatch": {
backgroundColor: "#72a1ff59",
outline: "1px solid #457dff"
},
".cm-searchMatch.cm-searchMatch-selected": {
backgroundColor: "#6199ff2f"
},
".cm-searchMatch": {
backgroundColor: "#72a1ff59",
outline: "1px solid #457dff",
},
".cm-searchMatch.cm-searchMatch-selected": {
backgroundColor: "#6199ff2f",
},
".cm-activeLine": {backgroundColor: "#6699ff0b"},
".cm-selectionMatch": {backgroundColor: "#aafe661a"},
".cm-activeLine": { backgroundColor: "#6699ff0b" },
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
backgroundColor: "#bad0f847"
},
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
backgroundColor: "#bad0f847",
},
".cm-gutters": {
backgroundColor: background,
color: stone,
border: "none"
},
".cm-gutters": {
backgroundColor: background,
color: stone,
border: "none",
},
".cm-activeLineGutter": {
backgroundColor: highlightBackground
},
".cm-foldPlaceholder": {
backgroundColor: "transparent",
border: "none",
color: "#ddd"
},
".cm-tooltip": {
border: "none",
backgroundColor: tooltipBackground
},
".cm-tooltip .cm-tooltip-arrow:before": {
borderTopColor: "transparent",
borderBottomColor: "transparent"
},
".cm-tooltip .cm-tooltip-arrow:after": {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
".cm-activeLineGutter": {
backgroundColor: highlightBackground,
color: ivory
}
}
}, {dark: true})
},
".cm-foldPlaceholder": {
backgroundColor: "transparent",
border: "none",
color: "#ddd",
},
".cm-tooltip": {
border: "none",
backgroundColor: tooltipBackground,
},
".cm-tooltip .cm-tooltip-arrow:before": {
borderTopColor: "transparent",
borderBottomColor: "transparent",
},
".cm-tooltip .cm-tooltip-arrow:after": {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground,
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: highlightBackground,
color: ivory,
},
},
},
{ dark: true },
);
/// The highlighting style for code in the One Dark theme.
export const oneDarkHighlightStyle = HighlightStyle.define([
{tag: t.keyword,
color: violet},
{tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: coral},
{tag: [t.function(t.variableName), t.labelName],
color: malibu},
{tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: whiskey},
{tag: [t.definition(t.name), t.separator],
color: ivory},
{tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: chalky},
{tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
color: cyan},
{tag: [t.meta, t.comment],
color: stone},
{tag: t.strong,
fontWeight: "bold"},
{tag: t.emphasis,
fontStyle: "italic"},
{tag: t.strikethrough,
textDecoration: "line-through"},
{tag: t.link,
color: stone,
textDecoration: "underline"},
{tag: t.heading,
fontWeight: "bold",
color: coral},
{tag: [t.atom, t.bool, t.special(t.variableName)],
color: whiskey },
{tag: [t.processingInstruction, t.string, t.inserted],
color: sage},
{tag: t.invalid,
color: invalid},
])
{ tag: t.keyword, color: violet },
{
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: coral,
},
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
{ tag: [t.definition(t.name), t.separator], color: ivory },
{
tag: [
t.typeName,
t.className,
t.number,
t.changed,
t.annotation,
t.modifier,
t.self,
t.namespace,
],
color: chalky,
},
{
tag: [
t.operator,
t.operatorKeyword,
t.url,
t.escape,
t.regexp,
t.link,
t.special(t.string),
],
color: cyan,
},
{ tag: [t.meta, t.comment], color: stone },
{ tag: t.strong, fontWeight: "bold" },
{ tag: t.emphasis, fontStyle: "italic" },
{ tag: t.strikethrough, textDecoration: "line-through" },
{ tag: t.link, color: stone, textDecoration: "underline" },
{ tag: t.heading, fontWeight: "bold", color: coral },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
{ tag: t.invalid, color: invalid },
]);
/// Extension to enable the One Dark theme (both the editor theme and
/// the highlight style).
export const oneDark: Extension = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)]
export const oneDark: Extension = [
oneDarkTheme,
syntaxHighlighting(oneDarkHighlightStyle),
];

View file

@ -1,43 +1,52 @@
import { tags as t } from '@lezer/highlight';
import { createTheme, CreateThemeOptions } from '@uiw/codemirror-themes';
import { tags as t } from "@lezer/highlight";
import { createTheme, CreateThemeOptions } from "@uiw/codemirror-themes";
export const defaultSettingsTokyoNight: CreateThemeOptions['settings'] = {
background: 'transparent',
foreground: '#787c99',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
gutterBackground: '#1a1b26',
gutterForeground: '#787c99',
gutterBorder: 'transparent',
lineHighlight: '#474b6611',
export const defaultSettingsTokyoNight: CreateThemeOptions["settings"] = {
background: "transparent",
foreground: "#787c99",
caret: "#c0caf5",
selection: "#515c7e40",
selectionMatch: "#16161e",
gutterBackground: "#1a1b26",
gutterForeground: "#787c99",
gutterBorder: "transparent",
lineHighlight: "#474b6611",
};
export const tokyoNight = createTheme({
theme: 'dark',
settings: {
...defaultSettingsTokyoNight,
theme: "dark",
settings: {
...defaultSettingsTokyoNight,
},
styles: [
{ tag: t.keyword, color: "#bb9af7" },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: "#c0caf5" },
{ tag: [t.propertyName], color: "#7aa2f7" },
{
tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)],
color: "#9ece6a",
},
styles: [
{ tag: t.keyword, color: '#bb9af7' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#c0caf5' },
{ tag: [t.propertyName], color: '#7aa2f7' },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#9ece6a' },
{ tag: [t.function(t.variableName), t.labelName], color: '#7aa2f7' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#bb9af7' },
{ tag: [t.definition(t.name), t.separator], color: '#c0caf5' },
{ tag: [t.className], color: '#c0caf5' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#ff9e64' },
{ tag: [t.typeName], color: '#0db9d7' },
{ tag: [t.operator, t.operatorKeyword], color: '#bb9af7' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#b4f9f8' },
{ tag: [t.meta, t.comment], color: '#444b6a' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: '#89ddff' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#c0caf5' },
{ tag: t.invalid, color: '#ff5370' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
{ tag: [t.function(t.variableName), t.labelName], color: "#7aa2f7" },
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: "#bb9af7",
},
{ tag: [t.definition(t.name), t.separator], color: "#c0caf5" },
{ tag: [t.className], color: "#c0caf5" },
{
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: "#ff9e64",
},
{ tag: [t.typeName], color: "#0db9d7" },
{ tag: [t.operator, t.operatorKeyword], color: "#bb9af7" },
{ tag: [t.url, t.escape, t.regexp, t.link], color: "#b4f9f8" },
{ tag: [t.meta, t.comment], color: "#444b6a" },
{ tag: t.strong, fontWeight: "bold" },
{ tag: t.emphasis, fontStyle: "italic" },
{ tag: t.link, textDecoration: "underline" },
{ tag: t.heading, fontWeight: "bold", color: "#89ddff" },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: "#c0caf5" },
{ tag: t.invalid, color: "#ff5370" },
{ tag: t.strikethrough, textDecoration: "line-through" },
],
});

View file

@ -61,7 +61,7 @@ export function base64ToUnicode(base64String: string) {
const utf8Bytes = new Uint8Array(
atob(base64String)
.split("")
.map((char) => char.charCodeAt(0))
.map((char) => char.charCodeAt(0)),
);
const decoder = new TextDecoder("utf-8", { fatal: true });
const decodedText = decoder.decode(utf8Bytes);
@ -80,7 +80,7 @@ export function sendToast(
variant: "warning" | "destructive",
title: string,
message: string,
pre?: boolean
pre?: boolean,
) {
window.parent.postMessage(
{
@ -92,7 +92,7 @@ export function sendToast(
pre,
},
},
"*"
"*",
);
}
@ -107,7 +107,7 @@ export function updateDocumentsContext(docId: string, context: object) {
export function forEachDocumentContext(
callback: (context: any, editor: ReactCodeMirrorRef | null) => void,
session: Session,
editorRefs: React.RefObject<ReactCodeMirrorRef>[]
editorRefs: React.RefObject<ReactCodeMirrorRef>[],
) {
const documentsContext = window.documentsContext || {};
for (const docId in documentsContext) {

View file

@ -28,5 +28,5 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<HelmetProvider>
<RouterProvider router={router} />
</HelmetProvider>
</React.StrictMode>
</React.StrictMode>,
);

View file

@ -18,14 +18,16 @@ export function Component() {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const hasWebGl = useMemo(() => isWebglSupported(), []);
const [instance, setInstance] = useState<HydraWrapper | null>(null);
const [displaySettings, setDisplaySettings] = useState(defaultDisplaySettings);
const [displaySettings, setDisplaySettings] = useState(
defaultDisplaySettings,
);
useEffect(() => {
if (hasWebGl) return;
sendToast(
"warning",
"WebGL not available",
"WebGL is disabled or not supported, so Hydra was not initialized"
"WebGL is disabled or not supported, so Hydra was not initialized",
);
}, [hasWebGl]);
@ -58,7 +60,7 @@ export function Component() {
useCallback(() => {
window.m = window.parent?.mercury?.m;
window.strudel = window.parent?.strudel?.strudel;
}, [])
}, []),
);
useEffect(() => {
@ -71,8 +73,8 @@ export function Component() {
if (!instance) return;
instance.tryEval(msg.body);
},
[instance]
)
[instance],
),
);
useSettings(
@ -83,9 +85,18 @@ export function Component() {
setDisplaySettings(msg.displaySettings);
}
},
[instance]
)
[instance],
),
);
return hasWebGl && canvasRef && <HydraCanvas ref={canvasRef} fullscreen displaySettings={displaySettings} />;
return (
hasWebGl &&
canvasRef && (
<HydraCanvas
ref={canvasRef}
fullscreen
displaySettings={displaySettings}
/>
)
);
}

View file

@ -36,8 +36,8 @@ export function Component() {
if (!instance) return;
instance.tryEval(msg.body);
},
[instance]
)
[instance],
),
);
useEffect(() => {

View file

@ -49,8 +49,8 @@ export function Component() {
if (!instance) return;
instance.tryEval(msg);
},
[instance]
)
[instance],
),
);
return null;

View file

@ -19,7 +19,10 @@ import { useQuery } from "@/hooks/use-query";
import { useShortcut } from "@/hooks/use-shortcut";
import { useStrudelCodemirrorExtensions } from "@/hooks/use-strudel-codemirror-extensions";
import { useToast } from "@/hooks/use-toast";
import { DisplaySettings, defaultDisplaySettings } from "@/lib/display-settings";
import {
DisplaySettings,
defaultDisplaySettings,
} from "@/lib/display-settings";
import {
cn,
code2hash,
@ -95,7 +98,8 @@ export function Component() {
const [welcomeDialogOpen, setWelcomeDialogOpen] = useState(false);
const [shareUrlDialogOpen, setShareUrlDialogOpen] = useState(false);
const [configureDialogOpen, setConfigureDialogOpen] = useState(false);
const [displaySettingsDialogOpen, setDisplaySettingsDialogOpen] = useState(false);
const [displaySettingsDialogOpen, setDisplaySettingsDialogOpen] =
useState(false);
const [documents, setDocuments] = useState<Document[]>([]);
const [hidden, setHidden] = useState<boolean>(false);
@ -113,17 +117,19 @@ export function Component() {
});
// Display settings: Try to restore from local storage or use default settings
const [displaySettings, setDisplaySettings] = useState<DisplaySettings>(() => {
const savedSettings = localStorage.getItem("display-settings");
if (savedSettings) {
try {
return JSON.parse(savedSettings);
} catch (error) {
console.error("Error parsing saved display settings:", error);
const [displaySettings, setDisplaySettings] = useState<DisplaySettings>(
() => {
const savedSettings = localStorage.getItem("display-settings");
if (savedSettings) {
try {
return JSON.parse(savedSettings);
} catch (error) {
console.error("Error parsing saved display settings:", error);
}
}
}
return defaultDisplaySettings;
});
return defaultDisplaySettings;
},
);
// Save editor settings to local storage
useEffect(() => {
@ -140,15 +146,15 @@ export function Component() {
const [messagesCount, setMessagesCount] = useState<number>(0);
const [messages, setMessages] = useState<Message[]>([]);
const [autoShowMessages, setAutoShowMessages] = useState<boolean>(
store.get("messages:autoshow", true)
store.get("messages:autoshow", true),
);
const [hideMessagesOnEval, setHideMessagesOnEval] = useState<boolean>(
store.get("messages:hide-on-eval", true)
store.get("messages:hide-on-eval", true),
);
const [sessionUrl, setSessionUrl] = useState<string>("");
const editorRefs = Array.from({ length: 8 }).map(() =>
useRef<ReactCodeMirrorRef>(null)
useRef<ReactCodeMirrorRef>(null),
);
useStrudelCodemirrorExtensions(session, editorRefs);
@ -162,7 +168,7 @@ export function Component() {
if (hideErrors) return;
_toast(options);
},
[_toast, hideErrors]
[_toast, hideErrors],
);
const postMessageParentWindow = (message: any) => {
@ -198,7 +204,7 @@ export function Component() {
// Otherwise, use default target.
if (newSession.getDocuments().length === 0) {
console.log(
"Session is empty, setting targets and code from hash params"
"Session is empty, setting targets and code from hash params",
);
// If `targets` hash param is present and has valid targets, set them as
// active documents.
@ -302,7 +308,7 @@ export function Component() {
console.log(
`%c${target}` + `%c ${content}`,
"font-weight: bold",
type === "stderr" ? "color: #ff5f6b" : ""
type === "stderr" ? "color: #ff5f6b" : "",
);
}
});
@ -421,17 +427,17 @@ export function Component() {
const getFocusedEditorIndex = (): number => {
const i = editorRefs.findIndex(
(ref) => ref.current && ref.current.view?.hasFocus
(ref) => ref.current && ref.current.view?.hasFocus,
);
return i;
};
// Global shortcuts
useShortcut(["Control-J", "Meta-J"], () =>
setCommandsDialogOpen((open) => !open)
setCommandsDialogOpen((open) => !open),
);
useShortcut(["Control-P", "Meta-P"], () =>
setConfigureDialogOpen((open) => !open)
setConfigureDialogOpen((open) => !open),
);
useShortcut(
["Control-Shift-.", "Meta-Shift-."],
@ -442,7 +448,7 @@ export function Component() {
});
toast({ title: "Panic!", duration: 1000 });
},
[documents]
[documents],
);
Array.from({ length: 8 }).map((_, i) => {
useShortcut([`Control-${i}`], () => focusEditor(i - 1), [...editorRefs]);
@ -455,7 +461,7 @@ export function Component() {
const newIndex = mod(curIndex - 1, documents.length);
focusEditor(newIndex);
},
[documents, ...editorRefs]
[documents, ...editorRefs],
);
useShortcut(
["Control-]"],
@ -465,7 +471,7 @@ export function Component() {
const newIndex = mod(curIndex + 1, documents.length);
focusEditor(newIndex);
},
[documents, ...editorRefs]
[documents, ...editorRefs],
);
useShortcut(["Meta-Shift-H", "Control-Shift-H"], () => {
setHidden((p) => !p);
@ -477,14 +483,14 @@ export function Component() {
const replTargets = useMemo(
() =>
[...new Set(documents.map((doc) => doc.target))].filter(
(t) => !webTargets.includes(t)
(t) => !webTargets.includes(t),
),
[documents]
[documents],
);
const targetsList = useMemo(
() => documents.map((doc) => doc.target),
[documents]
[documents],
);
const OS = navigator.userAgent.indexOf("Windows") != -1 ? "windows" : "unix";
@ -524,7 +530,7 @@ export function Component() {
session.setActiveDocuments(
targets
.filter((t) => t)
.map((target, i) => ({ id: String(i + 1), target }))
.map((target, i) => ({ id: String(i + 1), target })),
);
};
@ -548,9 +554,9 @@ export function Component() {
const activeWebTargets = useMemo(
() =>
webTargets.filter((target) =>
documents.some((doc) => doc.target === target)
documents.some((doc) => doc.target === target),
),
[documents]
[documents],
);
return (
@ -573,9 +579,7 @@ export function Component() {
onLayoutAdd={handleViewLayoutAdd}
onLayoutRemove={handleViewLayoutRemove}
onLayoutConfigure={() => setConfigureDialogOpen(true)}
onEditorChangeDisplaySettings={() =>
setDisplaySettingsDialogOpen(true)
}
onEditorChangeDisplaySettings={() => setDisplaySettingsDialogOpen(true)}
/>
<UsernameDialog
name={username}
@ -624,7 +628,7 @@ export function Component() {
<Mosaic
className={cn(
"transition-opacity",
hidden ? "opacity-0" : "opacity-100"
hidden ? "opacity-0" : "opacity-100",
)}
items={documents.map((doc, i) => (
<Pane
@ -644,13 +648,18 @@ export function Component() {
))}
/>
{activeWebTargets.map((target) => (
<WebTargetIframe key={target} session={session} target={target} displaySettings={displaySettings} />
<WebTargetIframe
key={target}
session={session}
target={target}
displaySettings={displaySettings}
/>
))}
<div
className={cn(
"fixed top-1 right-1 flex m-1",
"transition-opacity",
hidden ? "opacity-0" : "opacity-100"
hidden ? "opacity-0" : "opacity-100",
)}
>
{replTargets.length > 0 && (
@ -662,7 +671,7 @@ export function Component() {
<MessagesPanel
className={cn(
"transition-opacity",
hidden ? "opacity-0" : "opacity-100"
hidden ? "opacity-0" : "opacity-100",
)}
messages={messages}
autoShowMessages={autoShowMessages}
@ -675,7 +684,7 @@ export function Component() {
<StatusBar
className={cn(
"transition-opacity",
hidden ? "opacity-0" : "opacity-100"
hidden ? "opacity-0" : "opacity-100",
)}
pubSubState={pubSubState}
syncState={syncState}

View file

@ -1,12 +1,9 @@
const { fontFamily } = require("tailwindcss/defaultTheme")
const { fontFamily } = require("tailwindcss/defaultTheme");
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
fontFamily: {
@ -30,4 +27,4 @@ module.exports = {
},
},
plugins: [require("tailwindcss-animate")],
}
};

View file

@ -6,11 +6,10 @@ import pc from "picocolors";
let Vite;
try {
Vite = await import("vite");
} catch (err) { }
} catch (err) {}
const _State = {
viteConfig: undefined
viteConfig: undefined,
};
function clearState() {
@ -18,9 +17,10 @@ function clearState() {
}
const Config = {
mode: (process.env.NODE_ENV === "production" || !Vite
? "production"
: "development"),
mode:
process.env.NODE_ENV === "production" || !Vite
? "production"
: "development",
inlineViteConfig: undefined,
viteConfigFile: undefined,
ignorePaths: undefined,
@ -30,7 +30,7 @@ const Config = {
function info(msg) {
const timestamp = new Date().toLocaleString("en-US").split(",")[1].trim();
console.log(
`${pc.dim(timestamp)} ${pc.bold(pc.cyan("[flok-web]"))} ${pc.green(msg)}`
`${pc.dim(timestamp)} ${pc.bold(pc.cyan("[flok-web]"))} ${pc.green(msg)}`,
);
}
@ -62,8 +62,8 @@ async function resolveConfig() {
if (Config.inlineViteConfig) {
info(
`${pc.yellow("Inline config")} detected, ignoring ${pc.yellow(
"Vite config file"
)}`
"Vite config file",
)}`,
);
return {
@ -79,10 +79,10 @@ async function resolveConfig() {
{
configFile: Config.viteConfigFile,
},
"build"
"build",
);
info(
`Using ${pc.yellow("Vite")} to resolve the ${pc.yellow("config file")}`
`Using ${pc.yellow("Vite")} to resolve the ${pc.yellow("config file")}`,
);
return config;
} catch (e) {
@ -90,9 +90,9 @@ async function resolveConfig() {
info(
pc.red(
`Unable to use ${pc.yellow("Vite")}, running in ${pc.yellow(
"viteless"
)} mode`
)
"viteless",
)} mode`,
),
);
}
} catch (e) {
@ -117,9 +117,9 @@ async function resolveConfig() {
info(
pc.red(
`Unable to locate ${pc.yellow(
"vite.config.*s file"
)}, using default options`
)
"vite.config.*s file",
)}, using default options`,
),
);
return getDefaultViteConfig();
@ -146,8 +146,8 @@ async function serveStatic() {
info(`${pc.red(`Static files at ${pc.gray(distPath)} not found!`)}`);
info(
`${pc.yellow(
`Did you forget to run ${pc.bold(pc.green("vite build"))} command?`
)}`
`Did you forget to run ${pc.bold(pc.green("vite build"))} command?`,
)}`,
);
} else {
info(`${pc.green(`Serving static files from ${pc.gray(distPath)}`)}`);
@ -163,7 +163,7 @@ async function injectStaticMiddleware(app, middleware) {
app.use(config.base, middleware);
const stubMiddlewareLayer = app._router.stack.find(
(layer) => layer.handle === stubMiddleware
(layer) => layer.handle === stubMiddleware,
);
if (stubMiddlewareLayer !== undefined) {
@ -182,10 +182,7 @@ function isIgnoredPath(path, req) {
: Config.ignorePaths(path, req);
}
function findClosestIndexToRoot(
reqPath,
root
) {
function findClosestIndexToRoot(reqPath, root) {
const basePath = reqPath.slice(0, reqPath.lastIndexOf("/"));
const dirs = basePath.split("/");
@ -265,7 +262,7 @@ async function startServer(server) {
middlewareMode: true,
hmr: { server },
},
})
}),
);
server.on("close", async () => {
@ -284,11 +281,7 @@ function config(config) {
Config.viteConfigFile = config.viteConfigFile;
}
async function bind(
app,
server,
callback
) {
async function bind(app, server, callback) {
info(`Running in ${pc.yellow(Config.mode)} mode`);
clearState();