CodeMirror & PresetSelect fix (#413)

* yarn upgrade

* update eslint no-multiple-empty-lines rule

* update Next.js & React

Resolves #358.

* use CodeMirror in scratch editor

* use codemirror on new page

* move extensions out of CodeMirror component

* grow scratch codemirror

* fix state bug

* initial theming

* fix scratch page SSR

* fix credits page rendering

* max width for AsyncButton error popup

* word wrap

* bye monaco

* cleanup

* style scrollbars everywhere

* tab stuff

* updates

* more

* update codemirror theme

* new react, backend deps, preset on scratch, bugfixes

* improve monospace areas

* store preset name on scratch

* stylelint

* mypy

* mypy 2

* monospace

* fix monospace

* add padding to editor

* fix saving

* fix selection colours

* Highlight assembly line on source selected line change (#416)

Co-authored-by: Alex Bates <hi@imalex.xyz>

* mypy & black

* font stuff

* remove courier new

* make diff style consistent with editor

* fix codemirror exception when editing eof

* attempt to reduce layout thrashing

* /new: store presetName in localStorage

* use react-window for diff

* link diff column scroll position

* hide double scrollbar

* increase overscan

* pointer-events:none on diff header

* fix diff header thing

* make diff columns non-draggable

* dont shrink tabs

* add draggable bar between diff columns

* remove log

* fix 2-col mode

* fix diff align

* DiffCell support cell=undefined

* drag better

* style .cm-search

* eth style changes

* reduce rerenders when dragging diff bar

Co-authored-by: Alex Bates <hi@imalex.xyz>
Co-authored-by: hatal175 <hatal175@users.noreply.github.com>
Co-authored-by: Alex Bates <alex@nanaian.town>
This commit is contained in:
Ethan Roseman
2022-03-31 09:22:39 -04:00
committed by GitHub
parent 8ddf59b570
commit 8ea10ca286
61 changed files with 4770 additions and 5638 deletions

View File

@@ -1,5 +0,0 @@
# 8/8/2021
* Improved performance by only sending context to the backend if it was changed from the current version
* Added scratch forking

220
README.md
View File

@@ -7,230 +7,12 @@
A collaborative decompilation and reverse engineering website, built with Next.js and Django.
## Directory structure
```
frontend/
public/ ; Static files
src/ ; React/Typescript sourcecode
backend/
compilers/ ; Compiler binaries and configuration
coreapp/ ; API Django app
migrations/ ; Database migrations (generated by Django)
decompme/ ; Main Django app
.env ; Default configuration
.env.local ; Local configuration overrides (not checked-in)
```
## Setup
See [DOCKER.md](DOCKER.md) for instructions on how to run the project in a Docker container.
Dependencies:
- Python >=3.9
- Node.js
- [Yarn](https://yarnpkg.com/getting-started/install)
---
- Create a file to hold environment variables:
```shell
touch .env.local
```
### Frontend
```shell
cd frontend
```
- Install dependencies
```shell
yarn
```
- Start the development webserver
```shell
yarn dev
```
- Access the site via [http://localhost:8080](http://localhost:8080)
### Backend
```shell
cd backend
```
* [Install poetry](https://python-poetry.org/docs/master/#installing-with-the-official-installer) and python dependencies
```shell
poetry install
```
- Install compilers
```shell
poetry run python compilers/download.py
```
- Set up the database
```shell
poetry run python manage.py migrate
```
- Start the API server
```shell
poetry run python manage.py runserver
```
---
The following setup sections are optional.
### Wine setup (for local development, running Windows compilers)
- Create a wineprefix dir
```shell
WINEPREFIX=$HOME/.wine WINEARCH=win32 wineboot --init
```
- Add the WINEPREFIX setting to your .local.env file in the root of the repo
```shell
echo "WINEPREFIX=$HOME/.wine" >> .local.env
```
### GitHub authentication
- [Register a new OAuth application](https://github.com/settings/applications/new)
- "Homepage URL" should be the URL you access the frontend on (e.g. `http://localhost:8080`)
- "Authorization callback URL" should be the same as the homepage URL, but with `/login` appended
- Edit `.env.local`:
- Set `GITHUB_CLIENT_ID` to the application client ID
- Set `GITHUB_CLIENT_SECRET` to the application client secret (do **not** share this)
### Running inside an nginx proxy
Running decomp.me using nginx as a proxy better emulates the production environment and can avoid cookie-related issues.
- Install nginx
- Create an nginx site configuration (typically `/etc/nginx/sites-available/decomp.local`)
```nginx
server {
listen 80;
listen [::]:80;
client_max_body_size 5M;
server_name decomp.local www.decomp.local;
location / {
try_files $uri @proxy_frontend;
}
location /api {
try_files $uri @proxy_api;
}
location /admin {
try_files $uri @proxy_api;
}
location /static {
try_files $uri @proxy_api;
}
location @proxy_api {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Url-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://127.0.0.1:8000;
}
location @proxy_frontend {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Url-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://127.0.0.1:8080;
}
}
```
- Enable the site
```shell
ln -s /etc/nginx/sites-available/decomp.local /etc/nginx/sites-enabled/decomp.local
```
- Add the following lines to `/etc/hosts`:
```
127.0.0.1 decomp.local
127.0.0.1 www.decomp.local
```
- Edit `.env.local`:
- Set `API_BASE=/api`
- Set `ALLOWED_HOSTS=decomp.local`
- If you set up GitHub authentication, change the application URLs to `http://decomp.local` and `http://decomp.local/login`
- Restart nginx, the frontend, and the backend
- Access the site via [http://decomp.local](http://decomp.local)
### Sandbox jail
There is support for running subprocesses within [`nsjail`](https://github.com/google/nsjail).
This is controlled by the `SANDBOX` settings, and is disabled by default in the development `.env` but is enabled inside the `backend` Docker container.
To enable it locally outside of the Docker container:
- Build or install `nsjail` locally. Example instructions for Ubuntu:
- `apt-get install autoconf bison flex gcc g++ git libprotobuf-dev libnl-route-3-dev libtool make pkg-config protobuf-compiler`
- `git clone --recursive --branch=3.0 https://github.com/google/nsjail`
- `cd nsjail && make`
- Enable `unprivileged_userns_clone`
- Temporary: `sudo sysctl -w kernel.unprivileged_userns_clone=1`
- Permanent: `echo 'kernel.unprivileged_userns_clone=1' | sudo tee -a /etc/sysctl.d/00-local-userns.conf && sudo service procps restart`
- Edit `.env.local`:
- Set `USE_SANDBOX_JAIL=on`
- Set `SANDBOX_NSJAIL_BIN_PATH` to the absolute path of the `nsjail` binary built above
## Contributing
Contributions are very much welcome! You may want to [join our Discord server](https://discord.gg/sutqNShRRs).
### Storybook
To learn more, see [CONTRIBUTING.md](docs/CONTRIBUTING.md)
Use `yarn storybook` to run a Storybook instance on [http://localhost:6006](http://localhost:6006). This is useful for testing UI components in isolation.
### Linting
- Check frontend
```shell
cd frontend
yarn lint
```
- Autofix frontend
```shell
cd frontend
yarn lint --fix
```
- Check backend
```shell
cd backend
mypy
```
### Updating the database
If you modify any database models (`models.py`), you'll need to run the following to update the database:
```shell
poetry run python manage.py makemigrations
poetry run python manage.py migrate
```
## License
decomp.me uses the MIT license. All dependencies may contain their own licenses, which decomp.me respects.

View File

@@ -102,6 +102,7 @@ def from_id(compiler_id: str) -> Compiler:
return _compilers[compiler_id]
@cache
def available_compilers() -> List[Compiler]:
return sorted(
_compilers.values(),
@@ -109,6 +110,7 @@ def available_compilers() -> List[Compiler]:
)
@cache
def available_platforms() -> List[Platform]:
return sorted(
set(compiler.platform for compiler in available_compilers()),
@@ -121,6 +123,13 @@ def available_presets(platform: Platform) -> List[Preset]:
return [p for p in _presets if p.compiler.platform == platform]
def preset_from_name(name: str) -> Optional[Preset]:
for p in _presets:
if p.name == name:
return p
return None
DUMMY = DummyCompiler(id="dummy", platform=platforms.DUMMY, cc="")
# GBA

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2022-03-30 11:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("coreapp", "0018_rename_compilers"),
]
operations = [
migrations.AddField(
model_name="scratch",
name="preset",
field=models.CharField(blank=True, max_length=100, null=True),
),
]

View File

@@ -53,6 +53,7 @@ class Scratch(models.Model):
compiler_flags = models.TextField(
max_length=1000, default="", blank=True
) # TODO: reference a CompilerConfig
preset = models.CharField(max_length=100, blank=True, null=True)
target_assembly = models.ForeignKey(Assembly, on_delete=models.CASCADE)
source_code = models.TextField(blank=True)
context = models.TextField(blank=True)

View File

@@ -110,6 +110,7 @@ class ScratchCreateSerializer(serializers.Serializer[None]):
compiler = serializers.CharField(allow_blank=True, required=True)
platform = serializers.CharField(allow_blank=True, required=False)
compiler_flags = serializers.CharField(allow_blank=True, required=False)
preset = serializers.CharField(allow_blank=True, required=False)
source_code = serializers.CharField(allow_blank=True, required=False)
target_asm = serializers.CharField(allow_blank=True)
context = serializers.CharField(allow_blank=True) # type: ignore

View File

@@ -34,6 +34,8 @@ from ..serializers import (
ScratchSerializer,
TerseScratchSerializer,
)
from ..platforms import Platform
logger = logging.getLogger(__name__)
@@ -168,11 +170,10 @@ def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
create_ser.is_valid(raise_exception=True)
data = create_ser.validated_data
platform: Optional[Platform] = None
given_platform = data.get("platform")
if given_platform:
platform = platforms.from_id(given_platform)
else:
platform = None
compiler = compilers.from_id(data["compiler"])
project = data.get("project")
@@ -212,6 +213,10 @@ def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
compiler_flags = data.get("compiler_flags", "")
compiler_flags = CompilerWrapper.filter_compiler_flags(compiler_flags)
preset = data.get("preset", "")
if preset and not compilers.preset_from_name(preset):
raise serializers.ValidationError("Unknown preset:" + preset)
name = data.get("name", diff_label) or "Untitled"
if allow_project and (project or rom_address):
@@ -241,6 +246,7 @@ def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
"name": name,
"compiler": compiler.id,
"compiler_flags": compiler_flags,
"preset": preset,
"context": context,
"diff_label": diff_label,
"source_code": source_code,

331
backend/poetry.lock generated
View File

@@ -36,7 +36,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
[[package]]
name = "black"
version = "22.1.0"
version = "22.3.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
@@ -47,7 +47,7 @@ click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = ">=1.1.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
@@ -88,11 +88,11 @@ unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.0.4"
version = "8.1.0"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
@@ -109,7 +109,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
name = "coreapi"
version = "2.3.3"
description = "Python client library for Core API."
category = "main"
category = "dev"
optional = false
python-versions = "*"
@@ -123,7 +123,7 @@ uritemplate = "*"
name = "coreschema"
version = "0.0.4"
description = "Core Schema."
category = "main"
category = "dev"
optional = false
python-versions = "*"
@@ -132,7 +132,7 @@ jinja2 = "*"
[[package]]
name = "cryptography"
version = "36.0.1"
version = "36.0.2"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
@@ -230,7 +230,7 @@ Django = ">=2.2"
name = "django-stubs"
version = "1.9.0"
description = "Mypy stubs for Django"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
@@ -245,9 +245,9 @@ typing-extensions = "*"
[[package]]
name = "django-stubs-ext"
version = "0.3.1"
version = "0.4.0"
description = "Monkey-patching and extensions for django-stubs"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
@@ -271,7 +271,7 @@ pytz = "*"
name = "djangorestframework-stubs"
version = "1.4.0"
description = "PEP-484 stubs for django-rest-framework"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
@@ -305,17 +305,17 @@ python-versions = ">=3.5"
name = "itypes"
version = "1.2.0"
description = "Simple immutable types for python."
category = "main"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "jinja2"
version = "3.0.3"
version = "3.1.1"
description = "A very fast and expressive template engine."
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.dependencies]
MarkupSafe = ">=2.0"
@@ -352,9 +352,9 @@ dev = ["black (==21.10b0)", "coverage (>=4.5.4)", "fixit (==0.1.1)", "flake8 (>=
[[package]]
name = "markupsafe"
version = "2.1.0"
version = "2.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
category = "dev"
optional = false
python-versions = ">=3.7"
@@ -486,7 +486,7 @@ python-versions = "*"
[[package]]
name = "pytz"
version = "2021.3"
version = "2022.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
@@ -543,7 +543,7 @@ python-versions = ">=3.5"
[[package]]
name = "stdlibs"
version = "2022.2.2"
version = "2022.3.16"
description = "List of packages in the stdlib"
category = "dev"
optional = false
@@ -561,7 +561,7 @@ python-versions = "*"
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "main"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
@@ -575,7 +575,7 @@ python-versions = ">=3.7"
[[package]]
name = "tqdm"
version = "4.63.0"
version = "4.63.1"
description = "Fast, Extensible Progress Meter"
category = "main"
optional = false
@@ -602,25 +602,25 @@ pathspec = ">=0.8.1"
[[package]]
name = "types-pytz"
version = "2021.3.5"
version = "2021.3.6"
description = "Typing stubs for pytz"
category = "main"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "types-pyyaml"
version = "6.0.4"
version = "6.0.5"
description = "Typing stubs for PyYAML"
category = "main"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "types-requests"
version = "2.27.11"
version = "2.27.15"
description = "Typing stubs for requests"
category = "main"
category = "dev"
optional = false
python-versions = "*"
@@ -629,9 +629,9 @@ types-urllib3 = "<1.27"
[[package]]
name = "types-urllib3"
version = "1.26.10"
version = "1.26.11"
description = "Typing stubs for urllib3"
category = "main"
category = "dev"
optional = false
python-versions = "*"
@@ -657,7 +657,7 @@ typing-extensions = ">=3.7.4"
[[package]]
name = "tzdata"
version = "2021.5"
version = "2022.1"
description = "Provider of IANA time zone data"
category = "main"
optional = false
@@ -667,20 +667,20 @@ python-versions = ">=2"
name = "uritemplate"
version = "4.1.1"
description = "Implementation of RFC 6570 URI Templates"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "urllib3"
version = "1.26.8"
version = "1.26.9"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
@@ -703,7 +703,7 @@ trailrunner = ">=1.0"
[[package]]
name = "watchdog"
version = "2.1.6"
version = "2.1.7"
description = "Filesystem events monitoring"
category = "main"
optional = false
@@ -723,7 +723,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "11914ccd6e68c044ea3ca98b2b83eb1278f67e5162f7913f8a0affc8d86a5b7a"
content-hash = "abb7ad3c74a9c8d3add60d978689e979d215a5129ed30e7afc59224250cfcf1d"
[metadata.files]
ansiwrap = [
@@ -739,29 +739,29 @@ attrs = [
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
black = [
{file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
{file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
{file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
{file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
{file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
{file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
{file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
{file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
{file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
{file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
{file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
{file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
{file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
{file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
{file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
{file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
{file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
{file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
{file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
{file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
{file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
{file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
{file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
{file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
{file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"},
{file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"},
{file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"},
{file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"},
{file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"},
{file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"},
{file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"},
{file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"},
{file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"},
{file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"},
{file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"},
{file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"},
{file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"},
{file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"},
{file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"},
{file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"},
{file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"},
{file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"},
{file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"},
{file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"},
{file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"},
{file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
@@ -824,8 +824,8 @@ charset-normalizer = [
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
click = [
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
{file = "click-8.1.0-py3-none-any.whl", hash = "sha256:19a4baa64da924c5e0cd889aba8e947f280309f1a2ce0947a3e3a7bcb7cc72d6"},
{file = "click-8.1.0.tar.gz", hash = "sha256:977c213473c7665d3aa092b41ff12063227751c41d7b17165013e10069cc5cd2"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
@@ -840,26 +840,26 @@ coreschema = [
{file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"},
]
cryptography = [
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"},
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"},
{file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"},
{file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"},
{file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"},
{file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"},
{file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"},
{file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"},
{file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"},
{file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"},
{file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"},
{file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"},
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"},
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815"},
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf"},
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf"},
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86"},
{file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2"},
{file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb"},
{file = "cryptography-36.0.2-cp36-abi3-win32.whl", hash = "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"},
{file = "cryptography-36.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29"},
{file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7"},
{file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e"},
{file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0"},
{file = "cryptography-36.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b"},
{file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77"},
{file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85"},
{file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51"},
{file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"},
{file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"},
]
cxxfilt = [
{file = "cxxfilt-0.3.0-py2.py3-none-any.whl", hash = "sha256:774e85a8d0157775ed43276d89397d924b104135762d86b3a95f81f203094e07"},
@@ -890,8 +890,8 @@ django-stubs = [
{file = "django_stubs-1.9.0-py3-none-any.whl", hash = "sha256:59c9f81af64d214b1954eaf90f037778c8d2b9c2de946a3cda177fefcf588fbd"},
]
django-stubs-ext = [
{file = "django-stubs-ext-0.3.1.tar.gz", hash = "sha256:783c198d7e39a41be0b90fd843fa2770243a642922af679be4b19e03b82c8c28"},
{file = "django_stubs_ext-0.3.1-py3-none-any.whl", hash = "sha256:a51a3e9e844d4e1cacaaedbb33bf3def78a3956eed5d9575a640bd97ccd99cec"},
{file = "django-stubs-ext-0.4.0.tar.gz", hash = "sha256:3104c4748c34bd741c310a3e6af90dffba46e41bccbe243896e38a708262876b"},
{file = "django_stubs_ext-0.4.0-py3-none-any.whl", hash = "sha256:901fc77b6338ea29fa381300ff598dd57d461a4882b756404e2aa7724f76fd7d"},
]
djangorestframework = [
{file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"},
@@ -914,8 +914,8 @@ itypes = [
{file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"},
]
jinja2 = [
{file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
{file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
{file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"},
{file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"},
]
jwt = [
{file = "jwt-1.3.1-py3-none-any.whl", hash = "sha256:61c9170f92e736b530655e75374681d4fcca9cfa8763ab42be57353b2b203494"},
@@ -952,46 +952,46 @@ libcst = [
{file = "libcst-0.4.1.tar.gz", hash = "sha256:961ab38c0ef318c384a287f1e4f877bb61ce93945f352b14b5dbbe7a317882b1"},
]
markupsafe = [
{file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3028252424c72b2602a323f70fbf50aa80a5d3aa616ea6add4ba21ae9cc9da4c"},
{file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:290b02bab3c9e216da57c1d11d2ba73a9f73a614bbdcc027d299a60cdfabb11a"},
{file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e104c0c2b4cd765b4e83909cde7ec61a1e313f8a75775897db321450e928cce"},
{file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24c3be29abb6b34052fd26fc7a8e0a49b1ee9d282e3665e8ad09a0a68faee5b3"},
{file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204730fd5fe2fe3b1e9ccadb2bd18ba8712b111dcabce185af0b3b5285a7c989"},
{file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d3b64c65328cb4cd252c94f83e66e3d7acf8891e60ebf588d7b493a55a1dbf26"},
{file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96de1932237abe0a13ba68b63e94113678c379dca45afa040a17b6e1ad7ed076"},
{file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75bb36f134883fdbe13d8e63b8675f5f12b80bb6627f7714c7d6c5becf22719f"},
{file = "MarkupSafe-2.1.0-cp310-cp310-win32.whl", hash = "sha256:4056f752015dfa9828dce3140dbadd543b555afb3252507348c493def166d454"},
{file = "MarkupSafe-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:d4e702eea4a2903441f2735799d217f4ac1b55f7d8ad96ab7d4e25417cb0827c"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f0eddfcabd6936558ec020130f932d479930581171368fd728efcfb6ef0dd357"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ddea4c352a488b5e1069069f2f501006b1a4362cb906bee9a193ef1245a7a61"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09c86c9643cceb1d87ca08cdc30160d1b7ab49a8a21564868921959bd16441b8"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a0abef2ca47b33fb615b491ce31b055ef2430de52c5b3fb19a4042dbc5cadb"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:736895a020e31b428b3382a7887bfea96102c529530299f426bf2e636aacec9e"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:679cbb78914ab212c49c67ba2c7396dc599a8479de51b9a87b174700abd9ea49"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:84ad5e29bf8bab3ad70fd707d3c05524862bddc54dc040982b0dbcff36481de7"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-win32.whl", hash = "sha256:8da5924cb1f9064589767b0f3fc39d03e3d0fb5aa29e0cb21d43106519bd624a"},
{file = "MarkupSafe-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:454ffc1cbb75227d15667c09f164a0099159da0c1f3d2636aa648f12675491ad"},
{file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:142119fb14a1ef6d758912b25c4e803c3ff66920635c44078666fe7cc3f8f759"},
{file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2a5a856019d2833c56a3dcac1b80fe795c95f401818ea963594b345929dffa7"},
{file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1fb9b2eec3c9714dd936860850300b51dbaa37404209c8d4cb66547884b7ed"},
{file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62c0285e91414f5c8f621a17b69fc0088394ccdaa961ef469e833dbff64bd5ea"},
{file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc3150f85e2dbcf99e65238c842d1cfe69d3e7649b19864c1cc043213d9cd730"},
{file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f02cf7221d5cd915d7fa58ab64f7ee6dd0f6cddbb48683debf5d04ae9b1c2cc1"},
{file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5653619b3eb5cbd35bfba3c12d575db2a74d15e0e1c08bf1db788069d410ce8"},
{file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d2f5d97fcbd004c03df8d8fe2b973fe2b14e7bfeb2cfa012eaa8759ce9a762f"},
{file = "MarkupSafe-2.1.0-cp38-cp38-win32.whl", hash = "sha256:3cace1837bc84e63b3fd2dfce37f08f8c18aeb81ef5cf6bb9b51f625cb4e6cd8"},
{file = "MarkupSafe-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:fabbe18087c3d33c5824cb145ffca52eccd053061df1d79d4b66dafa5ad2a5ea"},
{file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:023af8c54fe63530545f70dd2a2a7eed18d07a9a77b94e8bf1e2ff7f252db9a3"},
{file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d66624f04de4af8bbf1c7f21cc06649c1c69a7f84109179add573ce35e46d448"},
{file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c532d5ab79be0199fa2658e24a02fce8542df196e60665dd322409a03db6a52c"},
{file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ec74fada3841b8c5f4c4f197bea916025cb9aa3fe5abf7d52b655d042f956"},
{file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c653fde75a6e5eb814d2a0a89378f83d1d3f502ab710904ee585c38888816c"},
{file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:961eb86e5be7d0973789f30ebcf6caab60b844203f4396ece27310295a6082c7"},
{file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:598b65d74615c021423bd45c2bc5e9b59539c875a9bdb7e5f2a6b92dfcfc268d"},
{file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:599941da468f2cf22bf90a84f6e2a65524e87be2fce844f96f2dd9a6c9d1e635"},
{file = "MarkupSafe-2.1.0-cp39-cp39-win32.whl", hash = "sha256:e6f7f3f41faffaea6596da86ecc2389672fa949bd035251eab26dc6697451d05"},
{file = "MarkupSafe-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:b8811d48078d1cf2a6863dafb896e68406c5f513048451cd2ded0473133473c7"},
{file = "MarkupSafe-2.1.0.tar.gz", hash = "sha256:80beaf63ddfbc64a0452b841d8036ca0611e049650e20afcb882f5d3c266d65f"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
moreorless = [
{file = "moreorless-0.4.0-py2.py3-none-any.whl", hash = "sha256:17f1fbef60fd21c84ee085a929fe3acefcaddca30df5dd09c024e9939a9e6a00"},
@@ -1117,8 +1117,8 @@ python-levenshtein = [
{file = "python-Levenshtein-0.12.2.tar.gz", hash = "sha256:dc2395fbd148a1ab31090dd113c366695934b9e85fe5a4b2a032745efd0346f6"},
]
pytz = [
{file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
{file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
]
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
@@ -1168,8 +1168,8 @@ sqlparse = [
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
]
stdlibs = [
{file = "stdlibs-2022.2.2-py3-none-any.whl", hash = "sha256:ab08f11e6e523a8b7b0bcba490bf2de2452e1b2f5c1299f070727ebf5f1f7914"},
{file = "stdlibs-2022.2.2.tar.gz", hash = "sha256:dc03d51e0a29fb727ce89a22b71424fe8ab7856f186c9d1281457443ed8facc0"},
{file = "stdlibs-2022.3.16-py3-none-any.whl", hash = "sha256:7e8c69fe8a6cb53be3d9b3987bd1db3bbc417e00bc294c2ec95cfbf7759ab5c2"},
{file = "stdlibs-2022.3.16.tar.gz", hash = "sha256:975709d4bff578a0668921f301741bdf147e8376e0aee2e46e8b2c99a26ba949"},
]
textwrap3 = [
{file = "textwrap3-0.9.2-py2.py3-none-any.whl", hash = "sha256:bf5f4c40faf2a9ff00a9e0791fed5da7415481054cef45bb4a3cfb1f69044ae0"},
@@ -1184,28 +1184,28 @@ tomli = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
tqdm = [
{file = "tqdm-4.63.0-py2.py3-none-any.whl", hash = "sha256:e643e071046f17139dea55b880dc9b33822ce21613b4a4f5ea57f202833dbc29"},
{file = "tqdm-4.63.0.tar.gz", hash = "sha256:1d9835ede8e394bb8c9dcbffbca02d717217113adc679236873eeaac5bc0b3cd"},
{file = "tqdm-4.63.1-py2.py3-none-any.whl", hash = "sha256:6461b009d6792008d0000e1b0c7ca50195ec78c0e808a3a6b668a56a3236c3a5"},
{file = "tqdm-4.63.1.tar.gz", hash = "sha256:4230a49119a416c88cc47d0d2d32d5d90f1a282d5e497d49801950704e49863d"},
]
trailrunner = [
{file = "trailrunner-1.1.3-py3-none-any.whl", hash = "sha256:7eea60167384329012a5b5464b4e97da68e8984fa52286094c08dbe9fce8901d"},
{file = "trailrunner-1.1.3.tar.gz", hash = "sha256:ceb22d0c6e6439a72d4d94585aae2599107f6f9cf28c102cb7643a958f8b9bb7"},
]
types-pytz = [
{file = "types-pytz-2021.3.5.tar.gz", hash = "sha256:fef8de238ee95135952229a2a23bfb87bd63d5a6c8598106a46cfcf48f069ea8"},
{file = "types_pytz-2021.3.5-py3-none-any.whl", hash = "sha256:8831f689379ac9e2a62668157381379ed74b3702980e08e71f8673c179c4e3c7"},
{file = "types-pytz-2021.3.6.tar.gz", hash = "sha256:74547fd90d8d8ab4f1eedf3a344a7d186d97486973895f81221a712e1e2cd993"},
{file = "types_pytz-2021.3.6-py3-none-any.whl", hash = "sha256:6805c72d51118923c5bf98633c39593d5b464d2ab49a803440e2d7ab6b8920df"},
]
types-pyyaml = [
{file = "types-PyYAML-6.0.4.tar.gz", hash = "sha256:6252f62d785e730e454dfa0c9f0fb99d8dae254c5c3c686903cf878ea27c04b7"},
{file = "types_PyYAML-6.0.4-py3-none-any.whl", hash = "sha256:693b01c713464a6851f36ff41077f8adbc6e355eda929addfb4a97208aea9b4b"},
{file = "types-PyYAML-6.0.5.tar.gz", hash = "sha256:464e050914f3d1d83a8c038e1cf46da5cb96b7cd02eaa096bcaa03675edd8a2e"},
{file = "types_PyYAML-6.0.5-py3-none-any.whl", hash = "sha256:2fd21310870addfd51db621ad9f3b373f33ee3cbb81681d70ef578760bd22d35"},
]
types-requests = [
{file = "types-requests-2.27.11.tar.gz", hash = "sha256:6a7ed24b21780af4a5b5e24c310b2cd885fb612df5fd95584d03d87e5f2a195a"},
{file = "types_requests-2.27.11-py3-none-any.whl", hash = "sha256:506279bad570c7b4b19ac1f22e50146538befbe0c133b2cea66a9b04a533a859"},
{file = "types-requests-2.27.15.tar.gz", hash = "sha256:2d371183c535208d2cc8fe7473d9b49c344c7077eb70302eb708638fb86086a8"},
{file = "types_requests-2.27.15-py3-none-any.whl", hash = "sha256:77d09182a68e447e9e8b0ffc21abf54618b96f07689dffbb6a41cf0356542969"},
]
types-urllib3 = [
{file = "types-urllib3-1.26.10.tar.gz", hash = "sha256:a26898f530e6c3f43f25b907f2b884486868ffd56a9faa94cbf9b3eb6e165d6a"},
{file = "types_urllib3-1.26.10-py3-none-any.whl", hash = "sha256:d755278d5ecd7a7a6479a190e54230f241f1a99c19b81518b756b19dc69e518c"},
{file = "types-urllib3-1.26.11.tar.gz", hash = "sha256:24d64e441168851eb05f1d022de18ae31558f5649c8f1117e384c2e85e31315b"},
{file = "types_urllib3-1.26.11-py3-none-any.whl", hash = "sha256:bd0abc01e9fb963e4fddd561a56d21cc371b988d1245662195c90379077139cd"},
]
typing-extensions = [
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
@@ -1217,45 +1217,46 @@ typing-inspect = [
{file = "typing_inspect-0.7.1.tar.gz", hash = "sha256:047d4097d9b17f46531bf6f014356111a1b6fb821a24fe7ac909853ca2a782aa"},
]
tzdata = [
{file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"},
{file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"},
{file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"},
{file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"},
]
uritemplate = [
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
]
urllib3 = [
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
]
usort = [
{file = "usort-1.0.2-py3-none-any.whl", hash = "sha256:0e7ee0702902d4d54fdd35cbc81f5590df2573db29e72aeb6eddaa9e9d01cef9"},
{file = "usort-1.0.2.tar.gz", hash = "sha256:f0dbdfcf18b117323dff3a03df804957ba3b755c1069d2cf98bee133592bd369"},
]
watchdog = [
{file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"},
{file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"},
{file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"},
{file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"},
{file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"},
{file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"},
{file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"},
{file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"},
{file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"},
{file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"},
{file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"},
{file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"},
{file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"},
{file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"},
{file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"},
{file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"},
{file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:177bae28ca723bc00846466016d34f8c1d6a621383b6caca86745918d55c7383"},
{file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d1cf7dfd747dec519486a98ef16097e6c480934ef115b16f18adb341df747a4"},
{file = "watchdog-2.1.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f14ce6adea2af1bba495acdde0e510aecaeb13b33f7bd2f6324e551b26688ca"},
{file = "watchdog-2.1.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4d0e98ac2e8dd803a56f4e10438b33a2d40390a72750cff4939b4b274e7906fa"},
{file = "watchdog-2.1.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:81982c7884aac75017a6ecc72f1a4fedbae04181a8665a34afce9539fc1b3fab"},
{file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0b4a1fe6201c6e5a1926f5767b8664b45f0fcb429b62564a41f490ff1ce1dc7a"},
{file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6e6ae29b72977f2e1ee3d0b760d7ee47896cb53e831cbeede3e64485e5633cc8"},
{file = "watchdog-2.1.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b9777664848160449e5b4260e0b7bc1ae0f6f4992a8b285db4ec1ef119ffa0e2"},
{file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:19b36d436578eb437e029c6b838e732ed08054956366f6dd11875434a62d2b99"},
{file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b61acffaf5cd5d664af555c0850f9747cc5f2baf71e54bbac164c58398d6ca7b"},
{file = "watchdog-2.1.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e877c70245424b06c41ac258023ea4bd0c8e4ff15d7c1368f17cd0ae6e351dd"},
{file = "watchdog-2.1.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d802d65262a560278cf1a65ef7cae4e2bc7ecfe19e5451349e4c67e23c9dc420"},
{file = "watchdog-2.1.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b3750ee5399e6e9c69eae8b125092b871ee9e2fcbd657a92747aea28f9056a5c"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:ed6d9aad09a2a948572224663ab00f8975fae242aa540509737bb4507133fa2d"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_armv7l.whl", hash = "sha256:b26e13e8008dcaea6a909e91d39b629a39635d1a8a7239dd35327c74f4388601"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_i686.whl", hash = "sha256:0908bb50f6f7de54d5d31ec3da1654cb7287c6b87bce371954561e6de379d690"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64.whl", hash = "sha256:bdcbf75580bf4b960fb659bbccd00123d83119619195f42d721e002c1621602f"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:81a5861d0158a7e55fe149335fb2bbfa6f48cbcbd149b52dbe2cd9a544034bbd"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_s390x.whl", hash = "sha256:03b43d583df0f18782a0431b6e9e9965c5b3f7cf8ec36a00b930def67942c385"},
{file = "watchdog-2.1.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ae934e34c11aa8296c18f70bf66ed60e9870fcdb4cc19129a04ca83ab23e7055"},
{file = "watchdog-2.1.7-py3-none-win32.whl", hash = "sha256:49639865e3db4be032a96695c98ac09eed39bbb43fe876bb217da8f8101689a6"},
{file = "watchdog-2.1.7-py3-none-win_amd64.whl", hash = "sha256:340b875aecf4b0e6672076a6f05cfce6686935559bb6d34cebedee04126a9566"},
{file = "watchdog-2.1.7-py3-none-win_ia64.whl", hash = "sha256:351e09b6d9374d5bcb947e6ac47a608ec25b9d70583e9db00b2fcdb97b00b572"},
{file = "watchdog-2.1.7.tar.gz", hash = "sha256:3fd47815353be9c44eebc94cc28fe26b2b0c5bd889dafc4a5a7cbdf924143480"},
]
wrapt = [
{file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"},

View File

@@ -14,16 +14,12 @@ cxxfilt = "^0.3.0"
django-cors-headers = "^3.11.0"
django-environ = "^0.8.1"
django-filter = "^21.1"
django-stubs = "^1.9.0"
django-stubs-ext = "^0.3.1"
djangorestframework = "^3.13.1"
djangorestframework-stubs = "^1.4.0"
mypy = "^0.931"
psycopg2-binary = "^2.9.3"
pycparser = "^2.21"
python-Levenshtein = "^0.12.2"
responses = "^0.18.0"
types-requests = "^2.27.10"
watchdog = "^2.1.6"
jwt = "^1.3.1"
PyGithub = "^1.55"
@@ -33,6 +29,10 @@ tqdm = "^4.62.3"
[tool.poetry.dev-dependencies]
black = "^22.1.0"
usort = "^1.0.1"
django-stubs-ext = "^0.4.0"
django-stubs = "1.9.0"
djangorestframework-stubs = "^1.4.0"
types-requests = "^2.27.15"
[build-system]
requires = ["poetry-core>=1.0.0"]

119
docs/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,119 @@
# Contributing
## Directory structure
```
frontend/
public/ ; Static files
src/ ; React/Typescript sourcecode
backend/
compilers/ ; Compiler binaries and configuration
coreapp/ ; API Django app
migrations/ ; Database migrations (generated by Django)
decompme/ ; Main Django app
.env ; Default configuration
.env.local ; Local configuration overrides (not checked-in)
```
## Setup
See [DOCKER.md](DOCKER.md) for instructions on how to run the project in a Docker container. Otherwise, continue reading this guide.
Dependencies:
- Python >=3.9
- Node.js
- [Yarn](https://yarnpkg.com/getting-started/install)
- [Poetry](https://python-poetry.org/docs/master/#installing-with-the-official-installer)
---
Create a file to hold environment variables:
```shell
touch .env.local
```
### Backend
```shell
cd backend
```
* Install Python dependencies with [poetry](https://python-poetry.org/docs/master/#installing-with-the-official-installer)
```shell
poetry install
```
- Install compilers
```shell
poetry run python compilers/download.py
```
- Set up the database
```shell
poetry run python manage.py migrate
```
- Start the API server
```shell
poetry run python manage.py runserver
```
---
### Frontend
```shell
cd frontend
```
- Install dependencies
```shell
yarn
```
- Start the development webserver
```shell
yarn dev
```
- Access the site via [http://localhost:8080](http://localhost:8080)
### Optional steps
- [Configure wine for Windows compiler on Linux](WINE.md)
- [Set up GitHub authentication](GITHUB.md)
- [Install nsjail to run the compiler sandbox](SANDBOX.md)
- [Configure an nginx reverse proxy](NGINX.md)
## Notes
### Updating the database
If you modify any database models (`models.py`), you'll need to run the following to update the database:
```shell
poetry run python manage.py makemigrations
poetry run python manage.py migrate
```
## Linting
- Check frontend
```shell
cd frontend
yarn lint
```
- Autofix frontend
```shell
cd frontend
yarn lint --fix
```
- Check backend
```shell
cd backend
mypy
```
### Storybook
Use `yarn storybook` to run a Storybook instance on [http://localhost:6006](http://localhost:6006). This is useful for testing UI components in isolation.

9
docs/GITHUB.md Normal file
View File

@@ -0,0 +1,9 @@
### GitHub authentication
- [Register a new OAuth application](https://github.com/settings/applications/new)
- "Homepage URL" should be the URL you access the frontend on (e.g. `http://localhost:8080`)
- "Authorization callback URL" should be the same as the homepage URL, but with `/login` appended
- Edit `.env.local`:
- Set `GITHUB_CLIENT_ID` to the application client ID
- Set `GITHUB_CLIENT_SECRET` to the application client secret (do **not** share this)

76
docs/NGINX.md Normal file
View File

@@ -0,0 +1,76 @@
### Running inside an nginx proxy
Running decomp.me using nginx as a proxy better emulates the production environment and can avoid cookie-related issues.
- Install nginx
- Create an nginx site configuration (typically `/etc/nginx/sites-available/decomp.local`)
```nginx
server {
listen 80;
listen [::]:80;
client_max_body_size 5M;
server_name decomp.local www.decomp.local;
location / {
try_files $uri @proxy_frontend;
}
location /api {
try_files $uri @proxy_api;
}
location /admin {
try_files $uri @proxy_api;
}
location /static {
try_files $uri @proxy_api;
}
location @proxy_api {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Url-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://127.0.0.1:8000;
}
location @proxy_frontend {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Url-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://127.0.0.1:8080;
}
location /_next/webpack-hmr {
proxy_pass http://127.0.0.1:8080/_next/webpack-hmr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
- Enable the site
```shell
ln -s /etc/nginx/sites-available/decomp.local /etc/nginx/sites-enabled/decomp.local
```
- Add the following lines to `/etc/hosts`:
```
127.0.0.1 decomp.local
127.0.0.1 www.decomp.local
```
- Edit `.env.local`:
- Set `API_BASE=/api`
- Set `ALLOWED_HOSTS=decomp.local`
- If you set up GitHub authentication, change the application URLs to `http://decomp.local` and `http://decomp.local/login`
- Restart nginx, the frontend, and the backend
- Access the site via [http://decomp.local](http://decomp.local)

19
docs/SANDBOX.md Normal file
View File

@@ -0,0 +1,19 @@
### Sandbox jail
There is support for running subprocesses within [`nsjail`](https://github.com/google/nsjail).
This is controlled by the `SANDBOX` settings, and is disabled by default in the development `.env` but is enabled inside the `backend` Docker container.
To enable it locally outside of the Docker container:
- Build or install `nsjail` locally. Example instructions for Ubuntu:
- `apt-get install autoconf bison flex gcc g++ git libprotobuf-dev libnl-route-3-dev libtool make pkg-config protobuf-compiler`
- `git clone --recursive --branch=3.0 https://github.com/google/nsjail`
- `cd nsjail && make`
- Enable `unprivileged_userns_clone`
- Temporary: `sudo sysctl -w kernel.unprivileged_userns_clone=1`
- Permanent: `echo 'kernel.unprivileged_userns_clone=1' | sudo tee -a /etc/sysctl.d/00-local-userns.conf && sudo service procps restart`
- Edit `.env.local`:
- Set `USE_SANDBOX_JAIL=on`
- Set `SANDBOX_NSJAIL_BIN_PATH` to the absolute path of the `nsjail` binary built above

10
docs/WINE.md Normal file
View File

@@ -0,0 +1,10 @@
### Wine setup (for local development, running Windows compilers)
- Create a wineprefix dir
```shell
WINEPREFIX=$HOME/.wine WINEARCH=win32 wineboot --init
```
- Add the WINEPREFIX setting to your .local.env file in the root of the repo
```shell
echo "WINEPREFIX=$HOME/.wine" >> .local.env
```

View File

@@ -22,7 +22,7 @@
"no-else-return": "off",
"no-trailing-spaces": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": "error",
"no-multiple-empty-lines": ["error", { "max": 1, "maxBOF": 0, "maxEOF": 0 }],
"comma-dangle": "off",
"@typescript-eslint/comma-dangle": ["error", "always-multiline"],
"comma-spacing": "off",

View File

@@ -1,7 +1,6 @@
const path = require("path")
const { config } = require("dotenv")
const { execSync } = require("child_process")
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin")
for (const envFile of [".env.local", ".env"]) {
config({ path: `../${envFile}` })
@@ -54,11 +53,6 @@ module.exports = {
include: path.resolve(__dirname, '../src'),
})
config.plugins.push(new MonacoWebpackPlugin({
languages: [],
filename: "[name].worker.[contenthash].js",
}))
return config
},
}

View File

@@ -1,5 +1,4 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited

View File

@@ -1,7 +1,6 @@
const { execSync } = require("child_process")
const { config } = require("dotenv")
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin")
for (const envFile of [".env.local", ".env"]) {
config({ path: `../${envFile}` })
@@ -72,11 +71,6 @@ module.exports = withPlausibleProxy({
use: ["@svgr/webpack"],
})
config.plugins.push(new MonacoWebpackPlugin({
languages: [],
filename: "[name].worker.[contenthash].js",
}))
return config
},
images: {
@@ -87,4 +81,6 @@ module.exports = withPlausibleProxy({
runtimeCaching,
disable: process.env.NODE_ENV === "development",
},
swcMinify: true,
experimental: {},
}))))

View File

@@ -13,6 +13,8 @@
},
"dependencies": {
"@badrap/bar-of-progress": "^0.1.2",
"@codemirror/basic-setup": "^0.19.1",
"@codemirror/lang-cpp": "^0.19.1",
"@primer/octicons-react": "^16.3.1",
"@react-hook/resize-observer": "^1.2.2",
"ansi-to-react": "^6.1.6",
@@ -21,19 +23,20 @@
"downshift": "^6.1.7",
"framer-motion": "^4.1.17",
"is-mobile": "^3.0.0",
"monaco-editor": "^0.29.1",
"next": "^11.1.2",
"next": "12",
"next-plausible": "^3.1.4",
"next-pwa": "^5.3.1",
"next-translate": "^1.3.5",
"react": "^18.0.0-alpha-fd5e01c2e-20210913",
"react": "^18.0.0",
"react-contenteditable": "^3.3.6",
"react-dom": "^18.0.0-alpha-fd5e01c2e-20210913",
"react-dom": "^18.0.0",
"react-hot-toast": "^2.1.0",
"react-laag": "^2.0.3",
"react-modal": "^3.14.4",
"react-simple-resizer": "^2.1.0",
"react-timeago": "^6.2.1",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.6",
"sass": "^1.42.1",
"swr": "^1.2.1",
"use-debounce": "^7.0.0",
@@ -41,7 +44,7 @@
"use-persisted-state": "^0.3.3"
},
"devDependencies": {
"@babel/core": "^7.15.8",
"@babel/core": "^7.17.8",
"@next/eslint-plugin-next": "^11.1.2",
"@storybook/addon-actions": "^6.3.10",
"@storybook/addon-essentials": "^6.3.10",
@@ -53,15 +56,17 @@
"@storybook/preset-scss": "^1.0.3",
"@storybook/react": "^6.3.10",
"@svgr/webpack": "^5.5.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0-alpha.1",
"@testing-library/user-event": "^13.4.1",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^13.0.0-alpha.6",
"@testing-library/user-event": "^13.5.0",
"@types/react-modal": "^3.13.1",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"autoprefixer": "^10.3.1",
"babel-jest": "^27.2.5",
"babel-loader": "^8.2.2",
"babel-jest": "^27.5.1",
"babel-loader": "^8.2.4",
"css-loader": "^6.4.0",
"cssnano": "^5.0.7",
"dotenv": "^10.0.0",
@@ -71,11 +76,10 @@
"eslint-plugin-css-modules": "^2.11.0",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^4.12.4",
"eslint-plugin-testing-library": "^5.1.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.2.5",
"jest": "^27.5.1",
"jest-transform-stub": "^2.0.0",
"monaco-editor-webpack-plugin": "^5.0.0",
"next-remove-imports": "^1.0.6",
"postcss": "^8.3.6",
"postcss-loader": "^6.1.1",

View File

@@ -4,13 +4,20 @@
color: var(--a800);
padding: 1rem;
border-radius: 0.4rem;
min-width: 6rem;
max-width: 40rem;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
pre {
max-width: 100%;
white-space: pre-wrap;
}
}
.asyncBtn {

View File

@@ -36,7 +36,7 @@
scrollbar-width: thin;
user-select: text;
font-family: monospace;
font-size: 12px;
font-family: var(--monospace);
font-size: 0.8rem;
white-space: pre;
}

View File

@@ -30,9 +30,10 @@ export type Props = {
compilation: api.Compilation
isCompiling?: boolean
isCompilationOld?: boolean
selectedSourceLine: number | null
}
export default function CompilationPanel({ compilation, isCompiling, isCompilationOld }: Props) {
export default function CompilationPanel({ compilation, isCompiling, isCompilationOld, selectedSourceLine }: Props) {
const [diff, setDiff] = useState<api.DiffOutput | null>(null)
const problemState = getProblemState(compilation)
@@ -47,6 +48,7 @@ export default function CompilationPanel({ compilation, isCompiling, isCompilati
diff={diff}
isCompiling={isCompiling}
isCurrentOutdated={isCompilationOld || problemState == ProblemState.ERRORS}
selectedSourceLine={selectedSourceLine}
/>
</resizer.Section>
<resizer.Bar

View File

@@ -1,54 +1,44 @@
.diff {
height: 100%;
position: relative;
overflow: auto;
scrollbar-color: #fff3 transparent;
scrollbar-width: thin;
display: flex;
flex-direction: column;
}
.column {
height: max-content;
overflow: initial !important; /* overrides resizer.Section inline style */
}
.outdated {
filter: grayscale(25%) brightness(70%);
.bodyContainer {
flex-grow: 1;
}
.bar {
cursor: col-resize;
background: var(--a50);
position: sticky !important; /* overrides resizer.Bar inline style */
top: 0;
}
.row {
padding: 0 4px;
height: 1.25em;
z-index: 99;
}
.header {
display: flex;
display: inline-flex;
align-items: center;
gap: 4px;
height: 3em;
padding: 0 1em;
position: sticky;
top: 0;
font-size: 0.8rem;
font-weight: 500;
color: var(--g1200);
border-bottom: 1px dashed var(--a50);
backdrop-filter: blur(3px);
user-select: none;
cursor: default;
}
// Columns
.headers,
.row {
display: grid;
width: 100%;
grid-template-columns: var(--diff-left-width) auto var(--diff-right-width);
overflow-x: hidden;
}
@supports not (backdrop-filter: blur(3px)) {
@@ -59,22 +49,52 @@
.body {
flex: 1;
overflow: auto;
padding: 8px 0;
overflow: overlay !important; // note: webkit only, see #275
user-select: text;
font-family: monospace;
font-size: 12px;
font-family: var(--monospace);
font-size: 0.75rem;
white-space: pre;
list-style: none;
scrollbar-color: #fff3 transparent;
scrollbar-width: thin;
}
.lineNumber {
.row {
height: 16px;
line-height: 16px;
align-items: stretch;
> .cell {
overflow: hidden;
color: #b3c1d3;
padding: 0 1em;
&.highlight {
background-color: rgba(255, 255, 255, 0.05);
.lineNumber {
color: #c6c6c6;
}
}
}
}
.cell span.lineNumber {
display: inline-block;
color: var(--a300);
color: #676e95;
min-width: 3ch;
min-width: 24px;
padding: 0 3px 0 5px;
text-align: right;
user-select: none;
}
.immediate { color: #6d6dff; }

View File

@@ -1,14 +1,22 @@
/* eslint css-modules/no-unused-class: off */
import { ReactNode } from "react"
import { createContext, CSSProperties, forwardRef, HTMLAttributes, useContext, useEffect, useState } from "react"
import classNames from "classnames"
import * as resizer from "react-simple-resizer"
import AutoSizer from "react-virtualized-auto-sizer"
import { FixedSizeList } from "react-window"
import * as api from "../../lib/api"
import { useSize } from "../../lib/hooks"
import Loading from "../loading.svg"
import styles from "./Diff.module.scss"
import DragBar from "./DragBar"
const PADDING_TOP = 0
const PADDING_BOTTOM = 0
const SelectedSourceLineContext = createContext<number | null>(null)
function FormatDiffText({ texts }: { texts: api.DiffText[] }) {
return <> {
@@ -24,57 +32,135 @@ function FormatDiffText({ texts }: { texts: api.DiffText[] }) {
} </>
}
function DiffColumn({ diff, prop, header, className }: {
diff: api.DiffOutput | null
prop: keyof api.DiffRow & keyof api.DiffHeader
header: ReactNode
function DiffCell({ cell, className }: {
cell: api.DiffCell | undefined
className?: string
}) {
return <resizer.Section className={classNames(styles.column, className)} minSize={100}>
<div className={classNames(styles.row, styles.header)}>
{header}
</div>
<div className={styles.body}>
{diff?.rows?.map?.((row, i) => (
<div key={i} className={styles.row}>
{typeof row[prop]?.src_line != "undefined" && <span className={styles.lineNumber}>{row[prop].src_line}</span>}
{row[prop] && <FormatDiffText texts={row[prop].text} />}
</div>
))}
</div>
</resizer.Section>
const selectedSourceLine = useContext(SelectedSourceLineContext)
const hasLineNo = typeof cell?.src_line != "undefined"
if (!cell)
return <div className={classNames(styles.cell, className)} />
return <div
className={classNames(className, {
[styles.cell]: true,
[styles.highlight]: hasLineNo && cell.src_line == selectedSourceLine,
})}
>
{hasLineNo && <span className={styles.lineNumber}>{cell.src_line}</span>}
<FormatDiffText texts={cell.text} />
</div>
}
function DiffRow({ data, index, style }: {
data: api.DiffRow[]
index: number
style: CSSProperties
}) {
const row = data[index]
return <li
className={styles.row}
style={{
...style,
top: `${parseFloat(style.top.toString()) + PADDING_TOP}px`,
}}
>
<DiffCell cell={row.base} />
<DiffCell cell={row.current} />
<DiffCell cell={row.previous} />
</li>
}
// https://github.com/bvaughn/react-window#can-i-add-padding-to-the-top-and-bottom-of-a-list
const innerElementType = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListElement>>(({ style, ...rest }, ref) => {
return <ul
ref={ref}
style={{
...style,
height: `${parseFloat(style.height.toString()) + PADDING_TOP + PADDING_BOTTOM}px`,
}}
{...rest}
/>
})
innerElementType.displayName = "innerElementType"
function DiffBody({ diff }: { diff: api.DiffOutput }) {
return <div className={styles.bodyContainer}>
{diff?.rows && <AutoSizer>
{({ height, width }) => (
<FixedSizeList
className={styles.body}
itemCount={diff.rows.length}
itemData={diff.rows}
itemSize={16}
overscanCount={40}
width={width}
height={height}
innerElementType={innerElementType}
>
{DiffRow}
</FixedSizeList>
)}
</AutoSizer>}
</div>
}
export type Props = {
diff: api.DiffOutput
isCompiling: boolean
isCurrentOutdated: boolean
selectedSourceLine: number | null
}
export default function Diff({ diff, isCompiling, isCurrentOutdated }: Props) {
return <resizer.Container className={styles.diff}>
<DiffColumn diff={diff} prop="base" header="Target" />
<resizer.Bar
size={1}
className={styles.bar}
expandInteractiveArea={{ left: 2, right: 2 }}
/>
<DiffColumn
diff={diff}
prop="current"
header={<>
export default function Diff({ diff, isCompiling, isCurrentOutdated, selectedSourceLine }: Props) {
const container = useSize<HTMLDivElement>()
const [barPos, setBarPos] = useState(NaN)
const [prevBarPos, setPrevBarPos] = useState(NaN)
const hasPreviousColumn = !!diff?.rows?.[0]?.previous
const columnMinWidth = 100
const clampedBarPos = Math.max(columnMinWidth, Math.min(container.width - columnMinWidth - (hasPreviousColumn ? columnMinWidth : 0), barPos))
const clampedPrevBarPos = hasPreviousColumn ? Math.max(clampedBarPos + columnMinWidth, Math.min(container.width - columnMinWidth, prevBarPos)) : container.width
useEffect(() => {
// Initially distribute the bar positions across the container
if (isNaN(barPos) && container.width) {
const numSections = hasPreviousColumn ? 3 : 2
setBarPos(container.width / numSections)
setPrevBarPos(container.width / numSections * 2)
}
}, [barPos, container.width, hasPreviousColumn])
return <div
ref={container.ref}
className={styles.diff}
style={{
"--diff-left-width": `${clampedBarPos}px`,
"--diff-right-width": `${container.width - clampedPrevBarPos}px`,
"--diff-current-filter": isCurrentOutdated ? "grayscale(25%) brightness(70%)" : "",
} as CSSProperties}
>
<DragBar pos={clampedBarPos} onChange={setBarPos} />
{hasPreviousColumn && <DragBar pos={clampedPrevBarPos} onChange={setPrevBarPos} />}
<div className={styles.headers}>
<div className={styles.header}>
Target
</div>
<div className={styles.header}>
Current
{isCompiling && <Loading width={20} height={20} />}
</>}
className={classNames({ [styles.outdated]: isCurrentOutdated })}
/>
{diff?.header?.previous && <>
<resizer.Bar
size={1}
className={styles.bar}
expandInteractiveArea={{ left: 2, right: 2 }}
/>
<DiffColumn diff={diff} prop="previous" header="Saved" />
</>}
</resizer.Container>
</div>
{hasPreviousColumn && <div className={styles.header}>
Previous
</div>}
</div>
<SelectedSourceLineContext.Provider value={selectedSourceLine}>
<DiffBody diff={diff} />
</SelectedSourceLineContext.Provider>
</div>
}

View File

@@ -0,0 +1,24 @@
.vertical {
width: 4px;
height: 100%;
position: absolute;
top: 0;
bottom: 0;
z-index: 99;
cursor: col-resize;
&::after {
content: "";
display: block;
width: 1px;
height: 100%;
background: var(--a50);
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
}
}

View File

@@ -0,0 +1,41 @@
import { useEffect, useRef, useState } from "react"
import styles from "./DragBar.module.scss"
export interface Props {
pos: number
onChange: (pos: number) => void
}
export default function DragBar({ pos, onChange }: Props) {
const [isActive, setIsActive] = useState(false)
const ref = useRef<HTMLDivElement>()
useEffect(() => {
const onMouseMove = (evt: MouseEvent) => {
if (isActive) {
const parent = ref.current.parentElement
onChange(evt.clientX - parent.getBoundingClientRect().x)
}
}
const onMouseUp = () => {
setIsActive(false)
}
document.addEventListener("mousemove", onMouseMove)
document.addEventListener("mouseup", onMouseUp)
return () => {
document.removeEventListener("mousemove", onMouseMove)
document.removeEventListener("mouseup", onMouseUp)
}
})
return <div
ref={ref}
className={styles.vertical}
style={{ left: `${pos}px` }}
onMouseDown={() => setIsActive(true)}
/>
}

View File

@@ -0,0 +1,140 @@
import { MutableRefObject, useEffect, useRef } from "react"
import { Extension, EditorState } from "@codemirror/state"
import { EditorView } from "@codemirror/view"
import { useDebouncedCallback } from "use-debounce"
import { materialPalenight } from "../../lib/themes/dark"
export interface Props {
value: string
onChange?: (value: string) => void
onHoveredLineChange?: (value: number | null) => void
onSelectedLineChange?: (value: number) => void
className?: string
viewRef?: MutableRefObject<EditorView | null>
extensions: Extension // const
}
export default function CodeMirror({
value,
onChange,
onHoveredLineChange,
onSelectedLineChange,
className,
viewRef: viewRefProp,
extensions,
}: Props) {
const el = useRef<HTMLDivElement>()
const valueRef = useRef(value)
valueRef.current = value
const onChangeRef = useRef(onChange)
onChangeRef.current = onChange
const viewRef = useRef<EditorView>()
const extensionsRef = useRef(extensions)
extensionsRef.current = extensions
const selectedLineRef = useRef<number>()
const hoveredLineRef = useRef<number>()
const onHoveredLineChangeRef = useRef(onHoveredLineChange)
onHoveredLineChangeRef.current = onHoveredLineChange
const onSelectedLineChangeRef = useRef(onSelectedLineChange)
onSelectedLineChangeRef.current = onSelectedLineChange
// Initial view creation
useEffect(() => {
viewRef.current = new EditorView({
state: EditorState.create({
doc: valueRef.current,
extensions: [
EditorState.transactionExtender.of(({ newDoc, newSelection }) => {
// value / onChange
const newValue = newDoc.toString()
if (newValue !== valueRef.current) {
onChangeRef.current?.(newValue)
}
// selectedSourceLine
const line = newDoc.lineAt(newSelection.main.from).number
if (hoveredLineRef.current !== line) {
hoveredLineRef.current = line
requestAnimationFrame(() => {
onSelectedLineChangeRef.current?.(line)
})
}
return null
}),
extensionsRef.current,
materialPalenight,
],
}),
parent: el.current,
})
if (viewRefProp)
viewRefProp.current = viewRef.current
return () => {
viewRef.current.destroy()
viewRef.current = null
if (viewRefProp)
viewRefProp.current = null
}
}, [viewRefProp])
// Replace doc when `value` prop changes
useEffect(() => {
const view = viewRef.current
if (view) {
const prevValue = view.state.doc.toString()
if (prevValue != value) {
view.dispatch(
view.state.update({
changes: {
from: 0,
to: prevValue.length,
insert: value,
},
})
)
}
}
}, [value])
const debouncedOnMouseMove = useDebouncedCallback(
event => {
if (!onHoveredLineChangeRef.current)
return
const view = viewRef.current
let newLine: number | null = null
if (view) {
const line = view.state.doc.lineAt(view.posAtCoords({ x: event.clientX, y: event.clientY })).number
if (line) {
newLine = line
}
}
if (selectedLineRef.current != newLine) {
selectedLineRef.current = newLine
onHoveredLineChangeRef.current?.(newLine)
}
},
100,
{ leading: true, trailing: true },
)
return <div
ref={el}
onMouseMove={debouncedOnMouseMove}
className={className}
style={{ fontSize: "0.8em" }} />
}

View File

@@ -1,28 +0,0 @@
.editor {
flex: 1;
resize: none;
border: 0;
outline: none !important;
font-size: 12px;
font-family: monospace;
line-height: 1.5;
user-select: initial;
width: 100%;
}
.loadingContainer {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
svg {
width: 24px;
height: 24px;
}
}

View File

@@ -1,65 +0,0 @@
import { Suspense } from "react"
import dynamic from "next/dynamic"
import classNames from "classnames"
import mobile from "is-mobile"
import LoadingSpinner from "../loading.svg"
import styles from "./Editor.module.scss"
import type { Props as MonacoEditorProps } from "./MonacoEditor"
import getTheme from "./monacoTheme"
const isMobile = mobile()
const isSSR = typeof window === "undefined"
interface Props extends MonacoEditorProps {
bubbleSuspense?: boolean
useLoadingSpinner?: boolean
}
const MonacoEditor = (isSSR || isMobile) ? null : dynamic(() => import("./MonacoEditor"))
// Wrapper component that asyncronously loads MonacoEditor on desktop,
// falling back to a simple textarea on mobile
export default function Editor(props: Props) {
const monacoTheme = getTheme()
const style = {
color: monacoTheme.colors["editor.foreground"],
backgroundColor: monacoTheme.colors["editor.background"],
padding: (props.padding ?? (props.showMargin ? 20 : 0)) + "px",
}
const textarea = <textarea
className={classNames(styles.editor, props.className)}
spellCheck={false}
value={props.value}
readOnly={!props.onChange}
onChange={event => {
const value = event.target.value
if (props.onChange)
props.onChange(value)
}}
style={style}
/>
if (MonacoEditor) {
const loading = props.useLoadingSpinner ? <div
className={classNames(styles.loadingContainer, props.className)}
style={style}
>
<LoadingSpinner />
</div> : textarea
if (props.bubbleSuspense) {
return <MonacoEditor {...props} />
} else {
return <Suspense fallback={loading}>
<MonacoEditor {...props} />
</Suspense>
}
} else {
return textarea
}
}

View File

@@ -1,34 +0,0 @@
.container {
flex-grow: 1;
/* fix border-radius overflow */
overflow: hidden;
& :global(.monaco-editor),
& :global(.monaco-editor-background),
& :global(.margin),
& :global(.inputarea.ime-input) {
background-color: transparent !important;
}
/* weird box at top-left */
& :global(.monaco-hover) {
display: none;
}
& :global(.mac .monaco-mouse-cursor-text) {
cursor: text !important;
}
& :global(.view-lines) {
user-select: initial;
}
}
.readonly {
cursor: text;
& :global(.cursor) {
display: none !important;
}
}

View File

@@ -1,94 +0,0 @@
import { useState } from "react"
import { ComponentStory, ComponentMeta } from "@storybook/react"
import MonacoEditor from "./MonacoEditor"
export default {
title: "MonacoEditor",
component: MonacoEditor,
} as ComponentMeta<typeof MonacoEditor>
const Template: ComponentStory<typeof MonacoEditor> = args => {
const [value, setValue] = useState(args.value)
return <div style={{ display: "flex", width: "95vw", height: "95vh" }}>
<MonacoEditor {...args} value={value} onChange={setValue} />
</div>
}
export const C: ComponentStory<typeof MonacoEditor> = Template.bind({})
C.args = {
language: "c",
value:
`s32 collision_heap_free(void* data) {
if (gGameStatusPtr->isBattle) {
return _heap_free(&D_803DA800, data);
} else {
return _heap_free(&D_80268000, data);
}
}
`,
lineNumbers: true,
showMargin: true,
}
export const Mips: ComponentStory<typeof MonacoEditor> = Template.bind({})
Mips.args = {
language: "mips",
value:
`.set noat # allow manual use of $at
.set noreorder # don't insert nops after branches
glabel sins
/* 3F9F0 800645F0 3084FFFF */ andi $a0, $a0, 0xffff
/* 3F9F4 800645F4 00042102 */ srl $a0, $a0, 4
/* 3F9F8 800645F8 30820400 */ andi $v0, $a0, 0x400
/* 3F9FC 800645FC 10400004 */ beqz $v0, .L80064610
/* 3FA00 80064600 00802821 */ addu $a1, $a0, $zero
/* 3FA04 80064604 00041027 */ nor $v0, $zero, $a0
/* 3FA08 80064608 08019185 */ j .L80064614
/* 3FA0C 8006460C 304203FF */ andi $v0, $v0, 0x3ff
.L80064610:
/* 3FA10 80064610 308203FF */ andi $v0, $a0, 0x3ff
.L80064614:
/* 3FA14 80064614 00021040 */ sll $v0, $v0, 1
/* 3FA18 80064618 3C038009 */ lui $v1, %hi(sintable)
/* 3FA1C 8006461C 00621821 */ addu $v1, $v1, $v0
/* 3FA20 80064620 94633DE0 */ lhu $v1, %lo(sintable)($v1)
/* 3FA24 80064624 30A20800 */ andi $v0, $a1, 0x800
/* 3FA28 80064628 14400003 */ bnez $v0, .L80064638
/* 3FA2C 8006462C 00031023 */ negu $v0, $v1
/* 3FA30 80064630 0801918F */ j .L8006463C
/* 3FA34 80064634 00031400 */ sll $v0, $v1, 0x10
.L80064638:
/* 3FA38 80064638 00021400 */ sll $v0, $v0, 0x10
.L8006463C:
/* 3FA3C 8006463C 03E00008 */ jr $ra
/* 3FA40 80064640 00021403 */ sra $v0, $v0, 0x10
/* 3FA44 80064644 00000000 */ nop
/* 3FA48 80064648 00000000 */ nop
/* 3FA4C 8006464C 00000000 */ nop
`,
}
export const OverScrollTest: ComponentStory<typeof MonacoEditor> = () => {
const args = Mips.args
const [value, setValue] = useState(args.value)
return <div style={{ height: "150vh" }}>
Page should begin scrolling after the editor hits the bottom
<div style={{ display: "flex", width: "95vw", height: "400px" }}>
<MonacoEditor {...args} value={value} onChange={setValue} language="mips" />
</div>
</div>
}
export const Readonly: ComponentStory<typeof MonacoEditor> = () => {
const args = Mips.args
return <div style={{ display: "flex", width: "95vw", height: "95vh" }}>
<MonacoEditor {...args} value={args.value} language="mips" />
</div>
}

View File

@@ -1,177 +0,0 @@
import { useEffect, useState, useRef, MutableRefObject } from "react"
import classNames from "classnames"
import { editor, languages } from "monaco-editor"
import * as c from "./language/c"
import * as mips from "./language/mips"
import styles from "./MonacoEditor.module.scss"
import monacoTheme from "./monacoTheme"
import "monaco-editor/min/vs/editor/editor.main.css"
if (typeof window === "undefined") {
throw new Error("Editor component does not work with SSR, use next/dynamic with { ssr: false }")
}
languages.register({ id: "decompme_c" })
languages.setLanguageConfiguration("decompme_c", c.conf)
languages.setMonarchTokensProvider("decompme_c", c.language)
languages.register({ id: "decompme_mips" })
languages.setLanguageConfiguration("decompme_mips", mips.conf)
languages.setMonarchTokensProvider("decompme_mips", mips.language)
function convertLanguage(language: string) {
if (language === "c")
return "decompme_c"
else if (language === "mips")
return "decompme_mips"
else
return "plaintext"
}
export type EditorInstance = editor.IStandaloneCodeEditor;
export type Props = {
className?: string
// This is a controlled component
value: string
onChange?: (value: string) => void
instanceRef?: MutableRefObject<editor.IStandaloneCodeEditor>
// Options
language: "c" | "mips"
lineNumbers?: boolean
showMargin?: boolean
padding?: number // css
}
export default function Editor({ value, onChange, className, showMargin, padding, language, lineNumbers, instanceRef }: Props) {
const isReadOnly = typeof onChange === "undefined"
const containerRef = useRef<HTMLDivElement>(null)
const [editorInstance, setEditorInstance] = useState<editor.IStandaloneCodeEditor | null>(null)
// Effect to set up the editor. This is run once when the component is mounted.
useEffect(() => {
editor.defineTheme("custom", monacoTheme())
const editorInstance = editor.create(containerRef.current, {
language: convertLanguage(language),
value,
theme: "custom",
autoDetectHighContrast: false,
minimap: {
enabled: false,
},
lineNumbers: lineNumbers ? "on" : "off",
renderLineHighlightOnlyWhenFocus: true,
scrollBeyondLastLine: false,
scrollbar: {
alwaysConsumeMouseWheel: false,
},
contextmenu: true,
//fontLigatures: true,
//fontFamily: "Jetbrains Mono",
readOnly: isReadOnly,
domReadOnly: isReadOnly,
occurrencesHighlight: !isReadOnly,
renderLineHighlight: isReadOnly ? "none" : "all",
padding: {
top: padding ?? (showMargin ? 20 : 0), // to match gutter
bottom: padding ?? 0,
},
glyphMargin: !!showMargin,
folding: !!showMargin,
lineDecorationsWidth: padding ?? (showMargin ? 10 : 0),
lineNumbersMinChars: showMargin ? 2 : 0,
automaticLayout: true,
})
setEditorInstance(editorInstance)
if (instanceRef)
instanceRef.current = editorInstance
const model = editorInstance.getModel()
if (model) {
model.onDidChangeContent(() => {
if (onChange)
onChange(model.getValue())
})
} else {
console.error("monaco editor has no model")
}
return () => editorInstance.dispose()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
// Update value.
useEffect(() => {
const model = editorInstance?.getModel()
// Only update the model value if it is different; otherwise, the
// model state will be reset every time the user types!
if (model && model.getValue() !== value) {
console.warn("editor value reset")
model.setValue(value)
}
}, [editorInstance, value])
// Update language.
useEffect(() => {
const model = editorInstance?.getModel()
if (model) {
editor.setModelLanguage(model, convertLanguage(language))
}
}, [editorInstance, language])
useEffect(() => {
editorInstance?.updateOptions({ lineNumbers: lineNumbers ? "on" : "off" })
}, [editorInstance, lineNumbers])
useEffect(() => {
editorInstance?.updateOptions({
glyphMargin: !!showMargin,
folding: !!showMargin,
lineDecorationsWidth: padding ?? (showMargin ? 10 : 0),
lineNumbersMinChars: showMargin ? 2 : 0,
})
}, [editorInstance, padding, showMargin])
return <div
ref={containerRef}
className={classNames(
styles.container,
className,
{
[styles.readonly]: isReadOnly,
},
)}
onKeyDownCapture={e => {
if (isReadOnly) {
// disable changing lines with arrow keys
if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "ArrowLeft" || e.key === "ArrowRight") {
e.stopPropagation()
}
// disable command palette
if (e.key === "F1") {
e.preventDefault()
e.stopPropagation()
}
} else {
// Command Palette
if ((e.ctrlKey || e.metaKey) && e.key === "p") {
e.preventDefault()
e.stopPropagation()
if (e.shiftKey)
editorInstance?.trigger("", "editor.action.quickCommand", "")
//console.log(editor.getSupportedActions())
}
}
}}
/>
}

View File

@@ -1,3 +0,0 @@
import Editor from "./Editor"
export default Editor

View File

@@ -1,449 +0,0 @@
import type { languages } from "monaco-editor"
export const conf: languages.LanguageConfiguration = {
comments: {
lineComment: "//",
blockComment: ["/*", "*/"],
},
brackets: [
["{", "}"],
["[", "]"],
["(", ")"],
],
autoClosingPairs: [
{ open: "[", close: "]" },
{ open: "{", close: "}" },
{ open: "(", close: ")" },
{ open: "'", close: "'", notIn: ["string", "comment"] },
{ open: "\"", close: "\"", notIn: ["string"] },
],
surroundingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: "\"", close: "\"" },
{ open: "'", close: "'" },
],
folding: {
markers: {
start: new RegExp("^\\s*#pragma\\s+region\\b"),
end: new RegExp("^\\s*#pragma\\s+endregion\\b"),
},
},
}
export const language: languages.IMonarchLanguage = {
defaultToken: "",
tokenPostfix: ".c",
brackets: [
{ token: "delimiter.curly", open: "{", close: "}" },
{ token: "delimiter.parenthesis", open: "(", close: ")" },
{ token: "delimiter.square", open: "[", close: "]" },
{ token: "delimiter.angle", open: "<", close: ">" },
],
keywords: [
"abstract",
"amp",
"array",
"auto",
"break",
"case",
"catch",
"class",
"const",
"constexpr",
"const_cast",
"continue",
"cpu",
"decltype",
"default",
"delegate",
"delete",
"do",
"double",
"dynamic_cast",
"each",
"else",
"enum",
"event",
"explicit",
"export",
"extern",
"false",
"final",
"finally",
"for",
"friend",
"gcnew",
"generic",
"goto",
"if",
"in",
"initonly",
"inline",
"interface",
"interior_ptr",
"internal",
"literal",
"long",
"mutable",
"namespace",
"new",
"noexcept",
"nullptr",
"__nullptr",
"operator",
"override",
"partial",
"pascal",
"pin_ptr",
"private",
"property",
"protected",
"public",
"ref",
"register",
"reinterpret_cast",
"restrict",
"return",
"safe_cast",
"sealed",
"short",
"signed",
"sizeof",
"static",
"static_assert",
"static_cast",
"struct",
"switch",
"template",
"this",
"thread_local",
"throw",
"tile_static",
"true",
"try",
"typedef",
"typeid",
"typename",
"union",
"using",
"virtual",
"volatile",
"wchar_t",
"where",
"while",
"_asm", // reserved word with one underscores
"_based",
"_cdecl",
"_declspec",
"_fastcall",
"_if_exists",
"_if_not_exists",
"_inline",
"_multiple_inheritance",
"_pascal",
"_single_inheritance",
"_stdcall",
"_virtual_inheritance",
"_w64",
"__abstract", // reserved word with two underscores
"__alignof",
"__asm",
"__assume",
"__based",
"__box",
"__builtin_alignof",
"__cdecl",
"__clrcall",
"__declspec",
"__delegate",
"__event",
"__except",
"__fastcall",
"__finally",
"__forceinline",
"__gc",
"__hook",
"__identifier",
"__if_exists",
"__if_not_exists",
"__inline",
"__int128",
"__int16",
"__int32",
"__int64",
"__int8",
"__interface",
"__leave",
"__m128",
"__m128d",
"__m128i",
"__m256",
"__m256d",
"__m256i",
"__m64",
"__multiple_inheritance",
"__newslot",
"__nogc",
"__noop",
"__nounwind",
"__novtordisp",
"__pascal",
"__pin",
"__pragma",
"__property",
"__ptr32",
"__ptr64",
"__raise",
"__restrict",
"__resume",
"__sealed",
"__single_inheritance",
"__stdcall",
"__super",
"__thiscall",
"__try",
"__try_cast",
"__typeof",
"__unaligned",
"__unhook",
"__uuidof",
"__value",
"__virtual_inheritance",
"__w64",
"__wchar_t",
],
types: [
"void",
"s64",
"s32",
"s16",
"s8",
"u64",
"u32",
"u16",
"u8",
"f64",
"f32",
"bool",
"char",
"int",
"float",
"long",
"unsigned",
],
operators: [
"=",
"!",
"~",
"?",
":",
"&&",
"||",
"++",
"--",
"+",
"-",
"*",
"/",
"&",
"|",
"^",
"%",
"<<",
">>",
">>>",
"+=",
"-=",
"*=",
"/=",
"&=",
"|=",
"^=",
"%=",
"<<=",
">>=",
">>>=",
"...",
],
comparisonOperators: [
">",
"<",
"==",
"<=",
">=",
"!=",
],
// we include these common regular expressions
symbols: /[=><!~?:&|+\-*/^%]+/,
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
integersuffix: /(ll|LL|u|U|l|L)?(ll|LL|u|U|l|L)?/,
floatsuffix: /[fFlL]?/,
encoding: /u|u8|U|L/,
// The main tokenizer for our languages
tokenizer: {
root: [
// Macro
[/[A-Z][A-Z0-9_]*(?=\b)/, { token: "macro" }],
// PascalCase for types
[/[A-Z]\w*/, { token: "storage.type" }],
// function call
[/[a-zA-Z_]\w*(?=\()/, { token: "function" }],
// identifiers and keywords
[
/[a-zA-Z_]\w*/,
{
cases: {
"@keywords": { token: "keyword.$0" },
"@types": { token: "storage.type.$0" },
"@default": "identifier",
},
},
],
// The preprocessor checks must be before whitespace as they check /^\s*#/ which
// otherwise fails to match later after other whitespace has been removed.
// Inclusion
[/^\s*#\s*include/, { token: "keyword.directive.include", next: "@include" }],
// Preprocessor directive
[/^\s*#\s*\w+/, "keyword.directive"],
// whitespace
{ include: "@whitespace" },
// [[ attributes ]].
[/\[\s*\[/, { token: "annotation", next: "@annotation" }],
// delimiters and operators
[/[{}()[\]]/, "@brackets"],
[/[<>](?!@symbols)/, "@brackets"],
[
/@symbols/,
{
cases: {
"@comparisonOperators": { token: "operator.comparison" },
"@operators": { token: "operator" },
"@default": { token: "delimiter" },
},
},
],
// numbers
[/\d*\d+[eE]([-+]?\d+)?(@floatsuffix)/, "number.float"],
[/\d*\.\d+([eE][-+]?\d+)?(@floatsuffix)/, "number.float"],
[/0[xX][0-9a-fA-F']*[0-9a-fA-F](@integersuffix)/, "number.hex"],
[/0[0-7']*[0-7](@integersuffix)/, "number.octal"],
[/0[bB][0-1']*[0-1](@integersuffix)/, "number.binary"],
[/\d[\d']*\d(@integersuffix)/, "number"],
[/\d(@integersuffix)/, "number"],
// delimiter: after number because of .\d floats
[/[;,.]/, "delimiter"],
// strings
[/"([^"\\]|\\.)*$/, "string.invalid"], // non-teminated string
[/"/, "string", "@string"],
// characters
[/'[^\\']'/, "string"],
[/(')(@escapes)(')/, ["string", "string.escape", "string"]],
[/'/, "string.invalid"],
],
whitespace: [
[/[ \t\r\n]+/, ""],
[/\/\*\*(?!\/)/, "comment.doc", "@doccomment"],
[/\/\*/, "comment", "@comment"],
[/\/\/.*\\$/, "comment", "@linecomment"],
[/\/\/.*$/, "comment"],
],
comment: [
[/[^/*]+/, "comment"],
[/\*\//, "comment", "@pop"],
[/[/*]/, "comment"],
],
//For use with continuous line comments
linecomment: [
[/.*[^\\]$/, "comment", "@pop"],
[/[^]+/, "comment"],
],
//Identical copy of comment above, except for the addition of .doc
doccomment: [
[/[^/*]+/, "comment.doc"],
[/\*\//, "comment.doc", "@pop"],
[/[/*]/, "comment.doc"],
],
string: [
[/[^\\"]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, "string", "@pop"],
],
raw: [
[
/(.*)(\))(?:([^ ()\\\t"]*))(")/,
{
cases: {
"$3==$S2": [
"string.raw",
"string.raw.end",
"string.raw.end",
{ token: "string.raw.end", next: "@pop" },
],
"@default": ["string.raw", "string.raw", "string.raw", "string.raw"],
},
},
],
[/.*/, "string.raw"],
],
annotation: [
{ include: "@whitespace" },
[/using|alignas/, "keyword"],
[/[a-zA-Z0-9_]+/, "annotation"],
[/[,:]/, "delimiter"],
[/[()]/, "@brackets"],
[/\]\s*\]/, { token: "annotation", next: "@pop" }],
],
include: [
[
/(\s*)(<)([^<>]*)(>)/,
[
"",
"keyword.directive.include.begin",
"string.include.identifier",
{ token: "keyword.directive.include.end", next: "@pop" },
] as languages.IMonarchLanguageAction,
],
[
/(\s*)(")([^"]*)(")/,
[
"",
"keyword.directive.include.begin",
"string.include.identifier",
{ token: "keyword.directive.include.end", next: "@pop" },
] as languages.IMonarchLanguageAction,
],
],
},
}

View File

@@ -1,167 +0,0 @@
import type { languages } from "monaco-editor"
export const conf: languages.LanguageConfiguration = {
comments: {
lineComment: "#",
blockComment: ["/*", "*/"],
},
brackets: [
["(", ")"],
],
autoClosingPairs: [
{ open: "(", close: ")" },
{ open: "\"", close: "\"", notIn: ["string"] },
],
surroundingPairs: [
{ open: "(", close: ")" },
{ open: "\"", close: "\"" },
],
}
export const language: languages.IMonarchLanguage = {
defaultToken: "",
tokenPostfix: ".mips",
brackets: [
{ token: "delimiter.parenthesis", open: "(", close: ")" },
],
keywords: [
"glabel",
],
instructions: [
"lb", "lbu", "ld", "ldl", "ldr", "lh", "lhu", "ll", "lld", "lw", "lwl", "lwr", "lwu", "sb", "sc", "scd", "sd", "sdl", "sdr", "sh", "sw", "swl", "swr", "sync", "add", "addi", "addiu", "addu", "and", "andi", "dadd", "daddi", "daddiu", "daddu", "ddiv", "ddivu", "div", "divu", "dmult", "dmultu", "dsll", "dsll32", "dsllv", "dsra", "dsra32", "dsrav", "dsrl", "dsrl32", "dsrlv", "dsub", "dsubu", "lui", "mfhi", "mflo", "mthi", "mtlo", "mult", "multu", "nor", "or", "ori", "sll", "sllv", "slt", "slti", "sltiu", "sltu", "sra", "srav", "srl", "srlv", "sub", "subu", "xor", "xori", "beq", "beql", "bgez", "bgezal", "bgezall", "bgezl", "bgtz", "bgtzl", "blez", "blezl", "bltz", "bltzal", "bltzall", "bltzl", "bne", "bnel", "j", "jal", "jalr", "jr", "break", "syscall", "teq", "teqi", "tge", "tgei", "tgeiu", "tgeu", "tlt", "tlti", "tltiu", "tltu", "tne", "tnei", "cache", "dmfc0", "dmtc0", "eret", "mfc0", "mtc0", "tlbp", "tlbr", "tlbwi", "tlbwr", "bc1f", "bc1fl", "bc1t", "bc1tl", "cfc1", "ctc1", "dmfc1", "dmtc1", "ldc1", "lwc1", "mfc1", "mtc1", "sdc1", "swc1",
"beqz", "bnez", "negu", "nop",
],
registers: [
"$zero", "$t0", "$s0", "$t8", "$at", "$t1", "$s1", "$t9", "$v0", "$t2", "$s2", "$k0", "$v1", "$t3", "$s3", "$k1", "$a0", "$t4", "$s4", "$gp", "$a1", "$t5", "$s5", "$sp", "$a2", "$t6", "$s6", "$s8", "$a3", "$t7", "$s7", "$ra",
],
// we include these common regular expressions
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
integersuffix: /(ll|LL|u|U|l|L)?(ll|LL|u|U|l|L)?/,
encoding: /u|u8|U|L/,
// The main tokenizer for our languages
tokenizer: {
root: [
// whitespace
{ include: "@whitespace" },
// identifiers and keywords
[
/[a-zA-Z_]\w*/,
{
cases: {
"jal": { token: "function" },
"@instructions": { token: "support.function.$0" },
"@keywords": { token: "keyword.$0" },
"@default": "identifier",
},
},
],
[
/\$\w+/,
{
cases: {
"@registers": { token: "entity.name.register.$0" },
"@default": "identifier",
},
},
],
[/%(hi|lo)/, "macro"],
[/\.\w+/, { token: "keyword.directive" }],
// delimiters and operators
[/[()]/, "@brackets"],
// numbers
[/\d*\d+[eE]([-+]?\d+)?/, "number.float"],
[/\d*\.\d+([eE][-+]?\d+)?/, "number.float"],
[/0[xX][0-9a-fA-F']*[0-9a-fA-F](@integersuffix)/, "number.hex"],
[/0[0-7']*[0-7](@integersuffix)/, "number.octal"],
[/0[bB][0-1']*[0-1](@integersuffix)/, "number.binary"],
[/\d[\d']*\d(@integersuffix)/, "number"],
[/\d(@integersuffix)/, "number"],
// delimiter: after number because of .\d floats
[/[;,.]/, "delimiter"],
// strings
[/"([^"\\]|\\.)*$/, "string.invalid"], // non-teminated string
[/"/, "string", "@string"],
// characters
[/'[^\\']'/, "string"],
[/(')(@escapes)(')/, ["string", "string.escape", "string"]],
[/'/, "string.invalid"],
],
whitespace: [
[/[ \t\r\n]+/, ""],
[/\/\*/, "comment", "@comment"],
[/#.*\\$/, "comment", "@linecomment"],
[/#.*$/, "comment"],
],
comment: [
[/[^/*]+/, "comment"],
[/\*\//, "comment", "@pop"],
[/[/*]/, "comment"],
],
//For use with continuous line comments
linecomment: [
[/.*[^#]$/, "comment", "@pop"],
[/[^]+/, "comment"],
],
string: [
[/[^\\"]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, "string", "@pop"],
],
raw: [
[
/(.*)(\))(?:([^ ()\\\t"]*))(")/,
{
cases: {
"$3==$S2": [
"string.raw",
"string.raw.end",
"string.raw.end",
{ token: "string.raw.end", next: "@pop" },
],
"@default": ["string.raw", "string.raw", "string.raw", "string.raw"],
},
},
],
[/.*/, "string.raw"],
],
include: [
[
/(\s*)(<)([^<>]*)(>)/,
[
"",
"keyword.directive.include.begin",
"string.include.identifier",
{ token: "keyword.directive.include.end", next: "@pop" },
] as languages.IMonarchLanguageAction,
],
[
/(\s*)(")([^"]*)(")/,
[
"",
"keyword.directive.include.begin",
"string.include.identifier",
{ token: "keyword.directive.include.end", next: "@pop" },
] as languages.IMonarchLanguageAction,
],
],
},
}

View File

@@ -1,38 +0,0 @@
import type { editor } from "monaco-editor"
export default function getTheme(): editor.IStandaloneThemeData {
const style = typeof window !== "undefined" ? window.getComputedStyle(document.body) : null
return {
"base": "vs-dark",
"inherit": false,
"rules": [
{ "token": "", "foreground": "8599b3" },
{ "token": "identifier", "foreground": "8599B3" },
{ "token": "delimiter", "foreground": "656f7e" },
{ "token": "operator", "foreground": "7F83FF" },
{ "token": "operator.comparison", "foreground": "FF4ABA" },
{ "token": "comment", "foreground": "465173" },
{ "token": "string", "foreground": "0AFAFA" },
{ "token": "number", "foreground": "0AFAFA" },
{ "token": "function", "foreground": "FF4A98" },
{ "token": "constant.language", "foreground": "0AFAFA" },
{ "token": "constant.character, constant.other", "foreground": "0AFAFA" },
{ "token": "keyword", "foreground": "7F83FF" },
{ "token": "entity.name", "foreground": "FF4A98" },
{ "token": "entity.other.attribute-name", "foreground": "FF4A98" },
{ "token": "support.function", "foreground": "45B8FF" },
{ "token": "storage", "foreground": "45B8FF" },
{ "token": "macro", "foreground": "3bff6c" },
],
"colors": {
"editor.foreground": "#8599b3",
"editor.background": style?.getPropertyValue?.("--g200") || "#111415",
"editor.selectionBackground": "#ffffff22",
"editor.lineHighlightBackground": "#ccccff07",
"editorCursor.foreground": "#c9cbfc",
"editorWhitespace.foreground": "#c9cbfc11",
"editorLineNumber.foreground":"#ccccff60",
},
}
}

View File

@@ -32,6 +32,6 @@ export default class ErrorBoundary extends React.Component<Props, State> {
return <div className={classNames(styles.error, this.props.className)} />
}
return this.props.children
return this.props.children || null
}
}

View File

@@ -2,10 +2,7 @@ import ReactModal from "react-modal"
import styles from "./Modal.module.scss"
export type Props = {
}
export default function Modal(props: ReactModal.Props & Props) {
export default function Modal(props: ReactModal.Props) {
return <ReactModal
className={styles.dialog}

View File

@@ -3,7 +3,6 @@ import classNames from "classnames"
import PlatformIcon from "./PlatformIcon"
import styles from "./PlatformSelect.module.scss"
export type Props = {
platforms: {
[key: string]: {

View File

@@ -1,6 +1,6 @@
import React from "react"
import { act, render } from "@testing-library/react"
import { render } from "@testing-library/react"
import * as api from "../../lib/api"
@@ -26,17 +26,16 @@ const scratchJson: api.Scratch = {
"parent": null,
"project": null,
"project_function": null,
"preset": "",
}
test("renders without causing a state change", async () => {
const onChange = jest.fn()
act(() => {
render(<Scratch
scratch={scratchJson}
onChange={onChange}
/>)
})
render(<Scratch
scratch={scratchJson}
onChange={onChange}
/>)
expect(onChange).toHaveBeenCalledTimes(0)
})

View File

@@ -31,18 +31,21 @@ export default function Scratch({
const [leftTab, setLeftTab] = useState("source")
const [rightTab, setRightTab] = useState("diff")
const [selectedSourceLine, setSelectedSourceLine] = useState<number | null>()
const setCompilerOpts = setCompilerOptsFunction({ scratch, setScratch })
const leftTabs = useLeftTabs({
scratch,
setScratch,
setSelectedSourceLine,
})
const rightTabs = useRightTabs({
compilation,
isCompiling,
isCompilationOld,
selectedSourceLine,
})
return <div ref={container.ref} className={styles.container}>

View File

@@ -36,6 +36,12 @@
.editor {
flex-grow: 1;
overflow: hidden;
:global(.cm-editor) {
height: 100%;
}
}
.loading {

View File

@@ -1,8 +1,11 @@
import { useEffect, useState } from "react"
import { basicSetup } from "@codemirror/basic-setup"
import { cpp } from "@codemirror/lang-cpp"
import * as api from "../../lib/api"
import Button from "../Button"
import Editor from "../Editor"
import CodeMirror from "../Editor/CodeMirror"
import Loading from "../loading.svg"
import Modal from "../Modal"
@@ -16,7 +19,7 @@ export type Props = {
}
export default function ScratchDecompileModal({ open, onClose, scratch, setSourceCode }: Props) {
const [decompiledCode, setDecompiledCode] = useState(null)
const [decompiledCode, setDecompiledCode] = useState<string | null>(null)
useEffect(() => {
if (open) {
@@ -43,14 +46,12 @@ export default function ScratchDecompileModal({ open, onClose, scratch, setSourc
<p>This is generally useful when you've edited the function signature or symbols pertaining to the function.
This new decompilation should reflect your changes. </p>
{decompiledCode ? <>
<Editor
{(typeof decompiledCode == "string") ? <>
<CodeMirror
className={styles.editor}
language="c"
value={decompiledCode}
onChange={c => setDecompiledCode(c)}
lineNumbers
showMargin
extensions={[basicSetup, cpp()]}
/>
<p>Would you like to reset this scratch's source code to this newly decompiled iteration?</p>
</> : <Loading className={styles.loading} />}

View File

@@ -5,11 +5,30 @@
.editor {
height: 100%;
overflow: hidden;
:global(.cm-editor) {
height: 100%;
}
:global(.cm-gutters),
:global(.cm-content) {
margin: 1em;
}
:global(.cm-gutters) {
margin-right: 0;
}
:global(.cm-content) {
margin-left: 0;
}
}
.diffTab {
display: flex;
overflow: auto;
overflow: hidden;
}
.about {

View File

@@ -1,16 +1,39 @@
import { useRef } from "react"
import { basicSetup, EditorView } from "@codemirror/basic-setup"
import { indentMore, indentLess } from "@codemirror/commands"
import { cpp } from "@codemirror/lang-cpp"
import { indentUnit } from "@codemirror/language"
import { StateCommand } from "@codemirror/state"
import { keymap } from "@codemirror/view"
import { Compilation, Scratch, useUserIsYou } from "../../lib/api"
import CompilerOpts from "../compiler/CompilerOpts"
import CompilationPanel from "../Diff/CompilationPanel"
import Editor from "../Editor"
import { EditorInstance } from "../Editor/MonacoEditor"
import CodeMirror from "../Editor/CodeMirror"
import ScoreBadge from "../ScoreBadge"
import { Tab } from "../Tabs"
import AboutScratch from "./AboutScratch"
import styles from "./renderTabs.module.scss"
const indent: StateCommand = ({ state, dispatch }) => {
if (state.selection.ranges.some(r => !r.empty)) return indentMore({ state, dispatch })
dispatch(state.update(state.replaceSelection(" "), { scrollIntoView: true, userEvent: "input" }))
return true
}
const CODEMIRROR_EXTENSIONS = [
basicSetup,
cpp(),
keymap.of([{
key: "Tab",
run: indent,
shift: indentLess,
}]),
indentUnit.of(" "),
]
type ScratchTab = LeftScratchTab | RightScratchTab
function renderTabs(tabs: {[i: number]: JSX.Element}, filter?: Array<ScratchTab>) {
@@ -36,12 +59,13 @@ export enum RightScratchTab {
* @param {Array<LeftScratchTab>} [filter=undefined] The tabs that you want to filter out
* @returns Left tabs of scratch
*/
export function useLeftTabs({ scratch, setScratch }: {
export function useLeftTabs({ scratch, setScratch, setSelectedSourceLine }: {
scratch: Scratch
setScratch: (s: Partial<Scratch>) => void
setSelectedSourceLine: (s: number | null) => void
}, filter?: Array<LeftScratchTab>): React.ReactElement<typeof Tab>[] {
const sourceEditor = useRef<EditorInstance>()
const contextEditor = useRef<EditorInstance>()
const sourceEditor = useRef<EditorView>()
const contextEditor = useRef<EditorView>()
const userIsYou = useUserIsYou()
return renderTabs({
@@ -50,19 +74,17 @@ export function useLeftTabs({ scratch, setScratch }: {
key="source"
tabKey="source"
label="Source code"
onSelect={() => sourceEditor.current && sourceEditor.current.focus()}
onSelect={() => sourceEditor.current?.focus?.()}
>
<Editor
instanceRef={sourceEditor}
<CodeMirror
viewRef={sourceEditor}
className={styles.editor}
language="c"
value={scratch.source_code}
onChange={value => {
setScratch({ source_code: value })
}}
lineNumbers
showMargin
bubbleSuspense
onSelectedLineChange={setSelectedSourceLine}
extensions={CODEMIRROR_EXTENSIONS}
/>
</Tab>
),
@@ -80,19 +102,16 @@ export function useLeftTabs({ scratch, setScratch }: {
tabKey="context"
label="Context"
className={styles.context}
onSelect={() => contextEditor.current && contextEditor.current.focus()}
onSelect={() => contextEditor.current?.focus?.()}
>
<Editor
instanceRef={contextEditor}
<CodeMirror
viewRef={contextEditor}
className={styles.editor}
language="c"
value={scratch.context}
onChange={value => {
setScratch({ context: value })
}}
lineNumbers
showMargin
bubbleSuspense
extensions={CODEMIRROR_EXTENSIONS}
/>
</Tab>
),
@@ -115,10 +134,11 @@ export function useLeftTabs({ scratch, setScratch }: {
* @param {Array<RightScratchTab>} [filter=undefined] The tabs that you want to filter out
* @returns Right tabs of scratch
*/
export function useRightTabs({ compilation, isCompiling, isCompilationOld }: {
export function useRightTabs({ compilation, isCompiling, isCompilationOld, selectedSourceLine }: {
compilation?: Compilation
isCompiling: boolean
isCompilationOld: boolean
selectedSourceLine: number | null
}, filter?: Array<RightScratchTab>): React.ReactElement<typeof Tab>[] {
return renderTabs({
[RightScratchTab.DIFF]: (
@@ -137,6 +157,7 @@ export function useRightTabs({ compilation, isCompiling, isCompilationOld }: {
compilation={compilation}
isCompiling={isCompiling}
isCompilationOld={isCompilationOld}
selectedSourceLine={selectedSourceLine}
/>}
</Tab>
),

View File

@@ -16,7 +16,6 @@ import ScratchIcon from "./ScratchIcon"
import styles from "./ScratchList.module.scss"
import UserLink from "./user/UserLink"
export interface Props {
url?: string
className?: string

View File

@@ -32,6 +32,7 @@
.tabButton {
position: relative;
flex-shrink: 0;
display: inline-flex;
gap: 0.5em;

View File

@@ -63,7 +63,7 @@
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
font-family: monospace;
font-family: var(--monospace);
font-size: 0.8rem;
padding: 0.6em 1em;

View File

@@ -63,6 +63,7 @@ export function FlagOption({ flag, description }: { flag: string, description?:
export type CompilerOptsT = {
compiler: string
compiler_flags: string
preset: string
}
export type Props = {
@@ -80,6 +81,7 @@ export default function CompilerOpts({ platform, value, onChange, isPopup }: Pro
onChange({
compiler,
compiler_flags: opts,
preset: "",
})
}
@@ -87,6 +89,7 @@ export default function CompilerOpts({ platform, value, onChange, isPopup }: Pro
onChange({
compiler,
compiler_flags: opts,
preset: "",
})
}
@@ -94,6 +97,7 @@ export default function CompilerOpts({ platform, value, onChange, isPopup }: Pro
onChange({
compiler: preset.compiler,
compiler_flags: preset.flags,
preset: preset.name,
})
}
@@ -116,7 +120,7 @@ export default function CompilerOpts({ platform, value, onChange, isPopup }: Pro
<PlatformIcon platform={platform} size={32} />
<div className={styles.preset}>
Preset
<PresetSelect platform={platform} flags={opts} setPreset={setPreset} />
<PresetSelect platform={platform} presetName={value.preset} setPreset={setPreset} />
</div>
</div>
<div className={styles.container} data-is-popup={isPopup}>

View File

@@ -1,33 +1,43 @@
import * as api from "../../lib/api"
import Select from "../Select"
import Select from "../Select2"
export default function PresetSelect({ className, platform, flags, setPreset, serverPresets }: {
function presetsToOptions(presets: api.CompilerPreset[], addCustom: boolean): { [key: string]: string } {
const options = {}
if (addCustom) {
options["Custom"] = "Custom"
}
for (const preset of presets) {
options[preset.name] = preset.name
}
return options
}
export default function PresetSelect({ className, platform, presetName, setPreset, serverPresets }: {
className?: string
platform: string
flags: string
presetName: string // "" for custom
setPreset: (preset: api.CompilerPreset) => void
serverPresets?: api.CompilerPreset[]
}) {
if (!serverPresets)
serverPresets = api.usePlatforms()[platform].presets
const selectedPreset = serverPresets.find(p => p.flags === flags)
const selectedPreset = serverPresets.find(p => p.name === presetName)
return <Select className={className} onChange={e => {
if ((e.target as HTMLSelectElement).value === "custom") {
return
}
if (!selectedPreset && presetName !== "")
console.warn(`Scratch.preset == '${presetName}' but no preset with that name was found.`)
const preset = serverPresets.find(p => p.name === (e.target as HTMLSelectElement).value)
setPreset(preset)
}}>
{!selectedPreset && <option value="custom" selected>Custom</option>}
{serverPresets.map(preset =>
<option key={preset.name} value={preset.name} selected={preset === selectedPreset}>
{preset.name}
</option>
)}
</Select>
return <Select
className={className}
options={presetsToOptions(serverPresets, !selectedPreset)}
value={selectedPreset?.name || "Custom"}
onChange={name => {
const preset = serverPresets.find(p => p.name === name)
if (preset)
setPreset(preset)
}}
/>
}

View File

@@ -4,6 +4,8 @@
position: relative;
display: inline-block;
img {
border-radius: 999px;
overflow: hidden;

View File

@@ -12,8 +12,8 @@ export type Props = {
}
export default function UserAvatar({ user, className }: Props) {
return <div className={classNames(styles.avatar, className)}>
return <span className={classNames(styles.avatar, className)}>
{!api.isAnonUser(user) && user.avatar_url && <Image src={user.avatar_url} alt="" layout="fill" />}
{user.is_online && <div className={styles.online} title="Online" />}
</div>
</span>
}

View File

@@ -188,6 +188,7 @@ export interface Scratch extends TerseScratch {
slug: string // avoid using, use `url` instead
description: string
compiler_flags: string
preset: string
source_code: string
context: string
diff_label: string
@@ -352,6 +353,7 @@ export function useSaveScratch(localScratch: Scratch): () => Promise<Scratch> {
context: undefinedIfUnchanged(savedScratch, localScratch, "context"),
compiler: undefinedIfUnchanged(savedScratch, localScratch, "compiler"),
compiler_flags: undefinedIfUnchanged(savedScratch, localScratch, "compiler_flags"),
preset: undefinedIfUnchanged(savedScratch, localScratch, "preset"),
name: undefinedIfUnchanged(savedScratch, localScratch, "name"),
description: undefinedIfUnchanged(savedScratch, localScratch, "description"),
})

View File

@@ -0,0 +1,130 @@
import { HighlightStyle, tags } from "@codemirror/highlight"
import { Extension } from "@codemirror/state"
import { EditorView } from "@codemirror/view"
const ivory = "#abb2bf",
darkBackground = "#21252b",
highlightBackground = "rgba(255, 255, 255, 0.05)",
background = "#121415",
tooltipBackground = "#353a42",
selection = "rgba(255, 255, 255, 0.1)",
cursor = "#ffffff"
// TODO move to css
export const materialPalenightTheme = EditorView.theme(
{
"&": {
color: "#ffffff",
backgroundColor: background,
},
".cm-content": {
caretColor: cursor,
},
"&.cm-focused .cm-cursor": {
borderLeftColor: cursor,
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
{ backgroundColor: selection },
".cm-panels": { backgroundColor: darkBackground, color: "#ffffff" },
".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-activeLine": { backgroundColor: highlightBackground },
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
backgroundColor: "#bad0f847",
outline: "1px solid #515a6b",
},
".cm-gutters": {
background: background,
color: "#676e95",
border: "none",
},
".cm-activeLineGutter": {
color: "#c6c6c6",
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]": {
backgroundColor: highlightBackground,
color: ivory,
},
},
},
{ dark: true }
)
// https://github.com/codemirror/highlight/blob/main/src/highlight.ts#L549
export const highlightStyle = HighlightStyle.define([
{ tag: tags.link, class: "cmt-link" },
{ tag: tags.heading, class: "cmt-heading" },
{ tag: tags.emphasis, class: "cmt-emphasis" },
{ tag: tags.strong, class: "cmt-strong" },
{ tag: tags.keyword, class: "cmt-keyword" },
{ tag: tags.atom, class: "cmt-atom" },
{ tag: tags.bool, class: "cmt-bool" },
{ tag: tags.url, class: "cmt-url" },
{ tag: tags.labelName, class: "cmt-labelName" },
{ tag: tags.inserted, class: "cmt-inserted" },
{ tag: tags.deleted, class: "cmt-deleted" },
{ tag: tags.literal, class: "cmt-literal" },
{ tag: tags.string, class: "cmt-string" },
{ tag: tags.number, class: "cmt-number" },
{ tag: [tags.regexp, tags.escape, tags.special(tags.string)], class: "cmt-string2" },
{ tag: tags.variableName, class: "cmt-variableName" },
{ tag: tags.local(tags.variableName), class: "cmt-variableName cmt-local" },
{ tag: tags.definition(tags.variableName), class: "cmt-variableName cmt-definition" },
{ tag: tags.special(tags.variableName), class: "cmt-variableName2" },
{ tag: tags.definition(tags.propertyName), class: "cmt-propertyName cmt-definition" },
{ tag: tags.typeName, class: "cmt-typeName" },
{ tag: tags.namespace, class: "cmt-namespace" },
{ tag: tags.className, class: "cmt-className" },
{ tag: tags.macroName, class: "cmt-macroName" },
{ tag: tags.propertyName, class: "cmt-propertyName" },
{ tag: tags.operator, class: "cmt-operator" },
{ tag: tags.comment, class: "cmt-comment" },
{ tag: tags.meta, class: "cmt-meta" },
{ tag: tags.invalid, class: "cmt-invalid" },
{ tag: tags.punctuation, class: "cmt-punctuation" },
])
/// Extension to enable the Material Palenight theme (both the editor theme and
/// the highlight style).
export const materialPalenight: Extension = [
materialPalenightTheme,
highlightStyle,
]

View File

@@ -2,10 +2,111 @@
:root {
--link: #3db8e9;
--monospace: "Menlo", "Monaco", monospace;
}
.themePlum {
@include theme.theme(#951fd9, #292f33);
.cm-content {
color: #8599b3;
cursor: text;
}
.cm-gutters,
.cm-content {
font-size: 0.8rem;
font-family: var(--monospace);
line-height: 1.5;
}
.cmt-variableName {
color: #ff4a98;
}
.cmt-punctuation {
color: #656f7e;
}
.cmt-operator {
color: #7f83ff;
}
.cmt-comment {
color: #465173;
}
.cmt-string,
.cmt-number,
.cmt-bool {
color: #0afafa;
}
.cmt-typeName {
color: #45b8ff;
}
.cmt-keyword {
color: #7f83ff;
}
.cmt-meta {
color: #3bff6c;
}
}
.cm-panel {
font-size: 1rem;
// from Button.module.scss
.cm-button {
border-radius: 4px;
padding: 0.4em 0.8em;
font-size: 0.8rem;
font-weight: 500;
user-select: none;
appearance: none;
display: inline-flex;
gap: 0.5em;
align-items: center;
background: transparent;
color: var(--a800);
border: 1px solid var(--g500);
&:not(:disabled) {
cursor: pointer;
&:hover {
color: var(--a900);
border-color: var(--g800);
}
}
}
.cm-textfield {
padding: 0.4em 0.6em;
color: var(--g1200);
background: var(--g200);
font: 0.8rem var(--monospace);
border: 1px solid var(--g500);
border-radius: 4px;
outline: none !important;
&::-webkit-input-placeholder {
color: var(--g700);
}
}
input[type=checkbox] {
vertical-align: middle;
}
}
html {
@@ -71,3 +172,8 @@ main {
.routerProgressBar {
z-index: 999;
}
div {
scrollbar-color: #fff3 transparent;
scrollbar-width: thin;
}

View File

@@ -63,7 +63,7 @@
color: var(--g1200);
background: var(--g200);
font: 0.8em monospace;
font: 0.8rem var(--monospace);
border: 1px solid var(--g500);
border-radius: 4px;
@@ -88,6 +88,12 @@
border: 1px solid var(--g500);
border-radius: 4px;
overflow: hidden;
:global(.cm-editor) {
height: 100%;
}
}
.compilerContainer {

View File

@@ -5,13 +5,15 @@ import { GetStaticProps } from "next"
import Link from "next/link"
import { useRouter } from "next/router"
import { basicSetup } from "@codemirror/basic-setup"
import { cpp } from "@codemirror/lang-cpp"
import { usePlausible } from "next-plausible"
import useTranslation from "next-translate/useTranslation"
import AsyncButton from "../components/AsyncButton"
import { useCompilersForPlatform } from "../components/compiler/compilers"
import PresetSelect from "../components/compiler/PresetSelect"
import Editor from "../components/Editor"
import CodeMirror from "../components/Editor/CodeMirror"
import Footer from "../components/Footer"
import Nav from "../components/Nav"
import PageTitle from "../components/PageTitle"
@@ -74,6 +76,7 @@ export default function NewScratch({ serverCompilers }: {
const [platform, setPlatform] = useState("")
const [compilerId, setCompiler] = useState<string>()
const [compilerFlags, setCompilerFlags] = useState<string>("")
const [presetName, setPresetName] = useState<string>("")
const router = useRouter()
const plausible = usePlausible()
@@ -84,11 +87,10 @@ export default function NewScratch({ serverCompilers }: {
}, [asm])
const [label, setLabel] = useState<string>("")
const [lineNumbers, setLineNumbers] = useState(false)
const setPreset = (preset: api.CompilerPreset) => {
setCompiler(preset.compiler)
setCompilerFlags(preset.flags)
setPresetName(preset.name)
}
// Load fields from localStorage
@@ -100,6 +102,7 @@ export default function NewScratch({ serverCompilers }: {
setPlatform(localStorage["new_scratch_platform"] ?? "")
setCompiler(localStorage["new_scratch_compiler"] ?? undefined)
setCompilerFlags(localStorage["new_scratch_compilerFlags"] ?? "")
setPresetName(localStorage["new_scratch_presetName"] ?? "")
} catch (error) {
console.warn("bad localStorage", error)
}
@@ -113,7 +116,8 @@ export default function NewScratch({ serverCompilers }: {
localStorage["new_scratch_platform"] = platform
localStorage["new_scratch_compiler"] = compilerId
localStorage["new_scratch_compilerFlags"] = compilerFlags
}, [label, asm, context, platform, compilerId, compilerFlags])
localStorage["new_scratch_presetName"] = presetName
}, [label, asm, context, platform, compilerId, compilerFlags, presetName])
const platformCompilers = useCompilersForPlatform(platform, serverCompilers.compilers)
const compiler = platformCompilers[compilerId]
@@ -150,6 +154,7 @@ export default function NewScratch({ serverCompilers }: {
platform,
compiler: compilerId,
compiler_flags: compilerFlags,
preset: presetName,
diff_label: label || defaultLabel || "",
})
@@ -162,7 +167,6 @@ export default function NewScratch({ serverCompilers }: {
await router.push(scratch.html_url)
} catch (error) {
setLineNumbers(true) // line numbers are likely relevant to the error
console.error(error)
throw error
}
@@ -222,7 +226,7 @@ export default function NewScratch({ serverCompilers }: {
<PresetSelect
className={styles.compilerChoiceSelect}
platform={platform}
flags={compilerFlags}
presetName={presetName}
setPreset={setPreset}
serverPresets={platform && serverCompilers.platforms[platform].presets}
/>
@@ -243,32 +247,29 @@ export default function NewScratch({ serverCompilers }: {
placeholder={defaultLabel}
onChange={e => setLabel((e.target as HTMLInputElement).value)}
className={styles.textInput}
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
/>
</div>
<div className={styles.editorContainer}>
<p className={styles.label}>Target assembly <small>(required)</small></p>
<Editor
<CodeMirror
className={styles.editor}
language="mips"
value={asm}
onChange={setAsm}
padding={10}
showMargin={lineNumbers}
lineNumbers={lineNumbers}
extensions={basicSetup}
/>
</div>
<div className={styles.editorContainer}>
<p className={styles.label}>
Context <small>(any typedefs, structs, and declarations you would like to include go here; typically generated with m2ctx.py)</small>
</p>
<Editor
<CodeMirror
className={styles.editor}
language="c"
value={context}
onChange={setContext}
padding={10}
showMargin={lineNumbers}
lineNumbers={lineNumbers}
extensions={[basicSetup, cpp()]}
/>
</div>

View File

@@ -1,8 +1,9 @@
import { Suspense, useState } from "react"
import { Suspense, useState, useEffect } from "react"
import { GetServerSideProps } from "next"
import useSWR from "swr"
import { useDebouncedCallback } from "use-debounce"
import LoadingSpinner from "../../components/loading.svg"
import PageTitle from "../../components/PageTitle"
@@ -60,7 +61,8 @@ export const getServerSideProps: GetServerSideProps = async context => {
}
export default function ScratchPage({ initialScratch, initialCompilation }: { initialScratch: api.Scratch, initialCompilation?: api.Compilation }) {
const [scratch, setScratch] = useState(initialScratch)
const [scratch, setScratchImmediate] = useState(initialScratch)
const setScratch = useDebouncedCallback(setScratchImmediate, 100, { leading: true, trailing: true }) // reduce layout thrashing
useWarnBeforeScratchUnload(scratch)
@@ -81,6 +83,21 @@ export default function ScratchPage({ initialScratch, initialCompilation }: { in
setScratch(scratch => ({ ...scratch, owner: cached.owner }))
}
// Scratch uses suspense but SSR does not support it so we just render a loading state
// in server-side rendering mode.
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
}, [])
if (!isMounted) {
return <>
<ScratchPageTitle scratch={scratch} compilation={initialCompilation} />
<main className={styles.container}>
<LoadingSpinner className={styles.loading} />
</main>
</>
}
return <>
<ScratchPageTitle scratch={scratch} compilation={initialCompilation} />
<main className={styles.container}>

View File

@@ -15,7 +15,8 @@
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true
"isolatedModules": true,
"incremental": true
},
"include": [
"next-env.d.ts",

File diff suppressed because it is too large Load Diff