├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── package.json ├── packages ├── playground │ ├── index.html │ ├── package.json │ ├── public │ │ ├── Gordita-Bold.woff │ │ ├── Gordita-Medium.woff │ │ ├── Gordita-Regular.woff │ │ ├── _redirects │ │ ├── logo.png │ │ ├── manifest.webmanifest │ │ ├── robots.txt │ │ ├── square_logo.png │ │ └── sw.js │ ├── src │ │ ├── app.tsx │ │ ├── assets │ │ │ └── logo.svg │ │ ├── components │ │ │ ├── header.tsx │ │ │ ├── setupSolid.ts │ │ │ ├── update.tsx │ │ │ └── zoomDropdown.tsx │ │ ├── context.tsx │ │ ├── index.tsx │ │ ├── pages │ │ │ ├── edit.tsx │ │ │ ├── home.tsx │ │ │ └── login.tsx │ │ └── utils │ │ │ ├── date.ts │ │ │ ├── exportFiles.tsx │ │ │ ├── isDarkTheme.ts │ │ │ └── serviceWorker.ts │ ├── tsconfig.json │ ├── unocss.config.ts │ └── vite.config.ts └── solid-repl │ ├── build.ts │ ├── package.json │ ├── repl │ ├── compiler.ts │ ├── formatter.ts │ ├── linter.ts │ └── main.css │ ├── src │ ├── components │ │ ├── editor │ │ │ ├── TypeScriptReact.tmLanguage.json │ │ │ ├── css.tmLanguage.json │ │ │ ├── index.tsx │ │ │ ├── monacoTabs.tsx │ │ │ ├── setupSolid.ts │ │ │ ├── vs_dark_good.json │ │ │ └── vs_light_good.json │ │ ├── error.tsx │ │ ├── gridResizer.tsx │ │ ├── preview.tsx │ │ ├── repl.tsx │ │ └── tabs.tsx │ ├── hooks │ │ └── useZoom.ts │ ├── index.ts │ ├── repl.tsx │ └── types.d.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── unocss.config.ts ├── patches └── monaco-editor.patch ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── prettier.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | pnpm-lock.yaml 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SolidJS Core Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Solid Playground 3 |

4 | 5 | # Solid Template Explorer 6 | 7 | This is the source code of the [solid playground](https://playground.solidjs.com) website. 8 | Through it you can quickly discover what the solid compiler will generate from your JSX templates. 9 | 10 | There are 3 modes available: 11 | 12 | - DOM: The classic SPA generation mechanism 13 | - SSR: The server side generation mechanism 14 | - HYDRATION: The client side generation for hydration 15 | - UNIVERSAL: The client side generation for universal (custom renderer) 16 | 17 | ## Getting up and running 18 | 19 | This project is built using the [pnpm](https://pnpm.js.org/) package manager. 20 | 21 | Once you got it up and running you can follow these steps the have a fully working environement: 22 | 23 | ```bash 24 | # Clone the project 25 | $ git clone https://github.com/solidjs/solid-playground 26 | 27 | # cd into the project and install the dependencies 28 | $ cd solid-playground && pnpm i 29 | 30 | # Start the dev server, the address is available at http://localhost:5173 31 | $ pnpm run dev 32 | 33 | # Build the project 34 | $ pnpm run build 35 | ``` 36 | 37 | ## Credits / Technologies used 38 | 39 | - [solid-js](https://github.com/solidjs/solid/): The view library 40 | - [@babel/standalone](https://babeljs.io/docs/en/babel-standalone): The in-browser compiler. Solid compiler relies on babel 41 | - [monaco](https://microsoft.github.io/monaco-editor/): The in-browser code editor. This is the code editor that powers VS Code 42 | - [Windi CSS](https://windicss.org/): The CSS framework 43 | - [vite](https://vitejs.dev/): The module bundler 44 | - [workbox](https://developers.google.com/web/tools/workbox): The service worker generator 45 | - [pnpm](https://pnpm.js.org/): The package manager 46 | - [lz-string](https://github.com/pieroxy/lz-string): The string compression algorithm used to share REPL 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solid-playground-restructured", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "scripts": { 7 | "start": "cd packages/playground && pnpm start", 8 | "build": "cd packages/playground && pnpm build", 9 | "dev": "cd packages/playground && pnpm dev", 10 | "format": "prettier -w ." 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "workspaces": [ 15 | "./packages/*" 16 | ], 17 | "devDependencies": { 18 | "@changesets/cli": "2.27.8", 19 | "prettier": "^3.3.3", 20 | "prettier-plugin-tailwindcss": "^0.6.6" 21 | }, 22 | "pnpm": { 23 | "patchedDependencies": { 24 | "monaco-editor": "patches/monaco-editor.patch" 25 | } 26 | }, 27 | "packageManager": "pnpm@9.9.0" 28 | } 29 | -------------------------------------------------------------------------------- /packages/playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solid Playground 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solid-playground", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "vite build", 7 | "start": "vite preview", 8 | "dev": "vite" 9 | }, 10 | "dependencies": { 11 | "@solid-primitives/scheduled": "^1.5.0", 12 | "@solidjs/router": "^0.15.3", 13 | "dedent": "^1.5.3", 14 | "onigasm": "^2.2.5", 15 | "solid-dismiss": "^1.8.2", 16 | "solid-heroicons": "^3.2.4", 17 | "solid-js": "1.9.5", 18 | "solid-repl": "workspace:*" 19 | }, 20 | "devDependencies": { 21 | "@amoutonbrady/lz-string": "^0.1.0", 22 | "@babel/core": "^7.26.10", 23 | "@babel/plugin-syntax-jsx": "^7.25.9", 24 | "@babel/preset-typescript": "^7.27.0", 25 | "@babel/types": "^7.27.0", 26 | "@solidjs/router": "^0.15.3", 27 | "@types/babel__standalone": "^7.1.9", 28 | "@types/dedent": "^0.7.2", 29 | "@unocss/preset-wind3": "66.1.0-beta.12", 30 | "assert": "^2.1.0", 31 | "csstype": "^3.1.3", 32 | "jszip": "^3.10.1", 33 | "monaco-editor": "^0.52.2", 34 | "register-service-worker": "^1.7.2", 35 | "typescript": "^5.8.3", 36 | "unocss": "^66.0.0", 37 | "vite": "^6.3.3", 38 | "vite-plugin-solid": "^2.11.6" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/playground/public/Gordita-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs/solid-playground/78f9210f86a1b3638e675f84940c8bef6e6ca57a/packages/playground/public/Gordita-Bold.woff -------------------------------------------------------------------------------- /packages/playground/public/Gordita-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs/solid-playground/78f9210f86a1b3638e675f84940c8bef6e6ca57a/packages/playground/public/Gordita-Medium.woff -------------------------------------------------------------------------------- /packages/playground/public/Gordita-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs/solid-playground/78f9210f86a1b3638e675f84940c8bef6e6ca57a/packages/playground/public/Gordita-Regular.woff -------------------------------------------------------------------------------- /packages/playground/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /packages/playground/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs/solid-playground/78f9210f86a1b3638e675f84940c8bef6e6ca57a/packages/playground/public/logo.png -------------------------------------------------------------------------------- /packages/playground/public/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "dir": "ltr", 3 | "lang": "en", 4 | "name": "Solid REPL", 5 | "short_name": "Solid REPL", 6 | "scope": "/", 7 | "display": "standalone", 8 | "start_url": "/", 9 | "background_color": "transparent", 10 | "theme_color": "transparent", 11 | "description": "Solid REPL", 12 | "orientation": "any", 13 | "prefer_related_applications": false, 14 | "icons": [ 15 | { 16 | "src": "/square_logo.png", 17 | "sizes": "144x144", 18 | "type": "image/png" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/playground/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /packages/playground/public/square_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs/solid-playground/78f9210f86a1b3638e675f84940c8bef6e6ca57a/packages/playground/public/square_logo.png -------------------------------------------------------------------------------- /packages/playground/public/sw.js: -------------------------------------------------------------------------------- 1 | const cacheName = 'my-cache'; 2 | 3 | async function fetchAndCacheIfOk(event, stale) { 4 | try { 5 | const response = await fetch(event.request); 6 | 7 | if (response.ok) { 8 | const responseClone = response.clone(); 9 | const cache = await caches.open(cacheName); 10 | await cache.put(event.request, responseClone); 11 | if (stale) self.postMessage({ type: 'cache' }); 12 | } 13 | 14 | return response; 15 | } catch (e) { 16 | const cache = await caches.open(cacheName); 17 | return await cache.match('/index.html'); 18 | } 19 | } 20 | 21 | async function fetchWithCache(event) { 22 | const cache = await caches.open(cacheName); 23 | const response = await cache.match(event.request); 24 | const result = fetchAndCacheIfOk(event, !!response); 25 | if (!!response) { 26 | return response; 27 | } else { 28 | return result; 29 | } 30 | } 31 | 32 | function handleFetch(event) { 33 | if ( 34 | event.request.headers.get('cache-control') !== 'no-cache' && 35 | event.request.method === 'GET' && 36 | event.request.url.startsWith(self.location.origin) 37 | ) { 38 | event.respondWith(fetchWithCache(event)); 39 | } 40 | } 41 | 42 | self.addEventListener('fetch', handleFetch); 43 | 44 | self.addEventListener('install', () => { 45 | self.skipWaiting(); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/playground/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { Show, JSX, Suspense } from 'solid-js'; 2 | import { Router, Route } from '@solidjs/router'; 3 | import { eventBus, setEventBus } from './utils/serviceWorker'; 4 | import { Update } from './components/update'; 5 | import { useZoom } from 'solid-repl/src/hooks/useZoom'; 6 | import { Edit } from './pages/edit'; 7 | import { Home } from './pages/home'; 8 | import { Login } from './pages/login'; 9 | import { AppContextProvider } from './context'; 10 | 11 | export const App = (): JSX.Element => { 12 | /** 13 | * Those next three lines are useful to display a popup 14 | * if the client code has been updated. This trigger a signal 15 | * via an EventBus initiated in the service worker and 16 | * the couple line above. 17 | */ 18 | const { zoomState, updateZoom } = useZoom(); 19 | document.addEventListener('keydown', (e) => { 20 | if (!zoomState.overrideNative) return; 21 | if (!(e.ctrlKey || e.metaKey)) return; 22 | 23 | if (e.key === '=') { 24 | updateZoom('increase'); 25 | e.preventDefault(); 26 | } else if (e.key == '-') { 27 | updateZoom('decrease'); 28 | e.preventDefault(); 29 | } 30 | }); 31 | 32 | return ( 33 |
34 | ( 36 | 37 | {props.children} 38 | 39 | )} 40 | > 41 | 42 | 43 | 44 | 45 | 46 | 47 | setEventBus(false)} />} /> 48 |
49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /packages/playground/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/playground/src/components/header.tsx: -------------------------------------------------------------------------------- 1 | import Dismiss from 'solid-dismiss'; 2 | import { A } from '@solidjs/router'; 3 | import { Icon } from 'solid-heroicons'; 4 | import { unwrap } from 'solid-js/store'; 5 | import { onCleanup, createSignal, Show, ParentComponent } from 'solid-js'; 6 | import { share, link, arrowDownTray, xCircle, bars_3, moon, sun } from 'solid-heroicons/outline'; 7 | import { exportToZip } from '../utils/exportFiles'; 8 | import { ZoomDropdown } from './zoomDropdown'; 9 | import { API, useAppContext } from '../context'; 10 | 11 | import logo from '../assets/logo.svg?url'; 12 | 13 | export const Header: ParentComponent<{ 14 | compiler?: Worker; 15 | fork?: () => void; 16 | share: () => Promise; 17 | }> = (props) => { 18 | const [copy, setCopy] = createSignal(false); 19 | const context = useAppContext()!; 20 | const [showMenu, setShowMenu] = createSignal(false); 21 | const [showProfile, setShowProfile] = createSignal(false); 22 | let menuBtnEl!: HTMLButtonElement; 23 | let profileBtn!: HTMLButtonElement; 24 | 25 | function shareLink() { 26 | props.share().then((url) => { 27 | navigator.clipboard.writeText(url).then(() => { 28 | setCopy(true); 29 | setTimeout(setCopy, 750, false); 30 | }); 31 | }); 32 | } 33 | 34 | window.addEventListener('resize', closeMobileMenu); 35 | onCleanup(() => { 36 | window.removeEventListener('resize', closeMobileMenu); 37 | }); 38 | 39 | function closeMobileMenu() { 40 | setShowMenu(false); 41 | } 42 | 43 | return ( 44 |
45 | 46 | solid-js logo 47 | 48 | {props.children || ( 49 |

50 | SolidJS Playground 51 |

52 | )} 53 | menuBtnEl} 61 | open={showMenu} 62 | setOpen={setShowMenu} 63 | show 64 | > 65 | 79 | 80 | 81 | 93 | 94 | 95 | 96 | 97 | 111 | 120 | 130 | ), 131 | outline: false, 132 | mini: false, 133 | }} 134 | /> 135 | Github 136 | 137 | 138 | 152 |
153 | 161 | Login 162 | 163 | } 164 | > 165 | 168 | profileBtn} open={showProfile} setOpen={setShowProfile}> 169 |
170 | 171 | {context.user()?.display} 172 | 173 | 179 |
180 |
181 |
182 |
183 |
184 | ); 185 | }; 186 | -------------------------------------------------------------------------------- /packages/playground/src/components/setupSolid.ts: -------------------------------------------------------------------------------- 1 | import { languages } from 'monaco-editor'; 2 | 3 | const solidTypes: Record = import.meta.glob('/node_modules/{solid-js,csstype}/**/*.{d.ts,json}', { 4 | eager: true, 5 | query: '?raw', 6 | import: 'default', 7 | }); 8 | 9 | for (const path in solidTypes) { 10 | languages.typescript.typescriptDefaults.addExtraLib(solidTypes[path], `file://${path}`); 11 | languages.typescript.javascriptDefaults.addExtraLib(solidTypes[path], `file://${path}`); 12 | } 13 | 14 | import repl from 'solid-repl/src/repl'; 15 | export default repl; 16 | -------------------------------------------------------------------------------- /packages/playground/src/components/update.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | import { Portal } from 'solid-js/web'; 3 | 4 | import { Icon } from 'solid-heroicons'; 5 | import { xMark } from 'solid-heroicons/outline'; 6 | 7 | export const Update: Component<{ 8 | onDismiss: (...args: unknown[]) => unknown; 9 | }> = (props) => { 10 | const mount = document.getElementById('update'); 11 | 12 | return ( 13 | 14 |
15 | 18 |

There's a new update available.

19 |

Refresh your browser or click the button below to get the latest update of the REPL.

20 | 26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /packages/playground/src/components/zoomDropdown.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from 'solid-heroicons'; 2 | import { magnifyingGlassPlus } from 'solid-heroicons/outline'; 3 | import Dismiss from 'solid-dismiss'; 4 | import { Component, createSignal, createEffect } from 'solid-js'; 5 | import { useZoom } from 'solid-repl/src/hooks/useZoom'; 6 | 7 | export const ZoomDropdown: Component<{ showMenu: boolean }> = (props) => { 8 | const [open, setOpen] = createSignal(false); 9 | const { zoomState, updateZoom, setZoomState } = useZoom(); 10 | const popupDuration = 1250; 11 | let containerEl!: HTMLDivElement; 12 | let prevZoom = zoomState.zoom; 13 | let timeoutId: number | null = null; 14 | let btnEl!: HTMLButtonElement; 15 | let prevFocusedEl: HTMLElement | null; 16 | let stealFocus = true; 17 | 18 | const onMouseMove = () => { 19 | stealFocus = true; 20 | window.clearTimeout(timeoutId!); 21 | }; 22 | 23 | const onKeyDownContainer = (e: KeyboardEvent) => { 24 | if (!open()) return; 25 | 26 | if (e.key === 'Escape' && !stealFocus) { 27 | if (prevFocusedEl) { 28 | setOpen(false); 29 | prevFocusedEl.focus(); 30 | stealFocus = true; 31 | } 32 | window.clearTimeout(timeoutId!); 33 | } 34 | 35 | if (!['Tab', 'Enter', 'Space'].includes(e.key)) return; 36 | stealFocus = false; 37 | prevFocusedEl = null; 38 | window.clearTimeout(timeoutId!); 39 | }; 40 | 41 | createEffect(() => { 42 | if (prevZoom === zoomState.zoom) return; 43 | prevZoom = zoomState.zoom; 44 | 45 | if (stealFocus) { 46 | prevFocusedEl = document.activeElement as HTMLElement; 47 | btnEl.focus(); 48 | stealFocus = false; 49 | } 50 | 51 | setOpen(true); 52 | 53 | window.clearTimeout(timeoutId!); 54 | 55 | timeoutId = window.setTimeout(() => { 56 | setOpen(false); 57 | 58 | stealFocus = true; 59 | if (prevFocusedEl) { 60 | prevFocusedEl.focus(); 61 | } 62 | }, popupDuration); 63 | }); 64 | 65 | createEffect(() => { 66 | if (!open()) { 67 | if (containerEl) { 68 | containerEl.removeEventListener('mouseenter', onMouseMove); 69 | } 70 | stealFocus = true; 71 | } else { 72 | if (containerEl) { 73 | containerEl.addEventListener('mouseenter', onMouseMove, { once: true }); 74 | } 75 | } 76 | }); 77 | 78 | return ( 79 |
{ 83 | window.clearTimeout(timeoutId!); 84 | }} 85 | ref={containerEl} 86 | tabindex="-1" 87 | > 88 | 101 | 102 |
111 |
112 | 119 |
120 | {zoomState.zoom}% 121 |
122 | 129 | 136 |
137 |
138 | 147 | 156 |
157 |
158 |
159 |
160 | ); 161 | }; 162 | -------------------------------------------------------------------------------- /packages/playground/src/context.tsx: -------------------------------------------------------------------------------- 1 | import { Accessor, createContext, createResource, createSignal, ParentComponent, Resource, useContext } from 'solid-js'; 2 | import type { Tab } from 'solid-repl'; 3 | import { isDarkTheme } from './utils/isDarkTheme'; 4 | 5 | interface AppContextType { 6 | token: string; 7 | user: Resource<{ display: string; avatar: string } | undefined>; 8 | tabs: Accessor; 9 | setTabs: (x: Accessor | undefined) => void; 10 | dark: Accessor; 11 | toggleDark: () => void; 12 | } 13 | 14 | const AppContext = createContext(); 15 | 16 | // export const API = 'http://localhost:8787'; 17 | // export const API = '/api'; 18 | export const API = 'https://api.solidjs.com'; 19 | 20 | export const AppContextProvider: ParentComponent = (props) => { 21 | const [token, setToken] = createSignal(localStorage.getItem('token') || ''); 22 | const [user] = createResource(token, async (token) => { 23 | if (!token) 24 | return { 25 | display: '', 26 | avatar: '', 27 | }; 28 | const result = await fetch(`${API}/profile`, { 29 | headers: { 30 | authorization: `Bearer ${token}`, 31 | }, 32 | }); 33 | const body = await result.json(); 34 | return { 35 | display: body.display, 36 | avatar: body.avatar, 37 | }; 38 | }); 39 | 40 | const [dark, setDark] = createSignal(isDarkTheme()); 41 | document.body.classList.toggle('dark', dark()); 42 | 43 | let [tabsGetter, setTabs] = createSignal>(); 44 | return ( 45 | x); 62 | }, 63 | dark, 64 | toggleDark() { 65 | let x = !dark(); 66 | document.body.classList.toggle('dark', x); 67 | setDark(x); 68 | localStorage.setItem('dark', String(x)); 69 | }, 70 | }} 71 | > 72 | {props.children} 73 | 74 | ); 75 | }; 76 | 77 | export const useAppContext = () => useContext(AppContext); 78 | -------------------------------------------------------------------------------- /packages/playground/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'solid-js/web'; 2 | import { App } from './app'; 3 | import { registerServiceWorker } from './utils/serviceWorker'; 4 | import 'solid-repl/repl/main.css'; 5 | import 'virtual:uno.css'; 6 | 7 | render(() => , document.querySelector('#app')!); 8 | 9 | registerServiceWorker(); 10 | -------------------------------------------------------------------------------- /packages/playground/src/pages/edit.tsx: -------------------------------------------------------------------------------- 1 | import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; 2 | import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'; 3 | import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'; 4 | import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'; 5 | import CompilerWorker from 'solid-repl/repl/compiler?worker'; 6 | import FormatterWorker from 'solid-repl/repl/formatter?worker'; 7 | import LinterWorker from 'solid-repl/repl/linter?worker'; 8 | import onigasm from 'onigasm/lib/onigasm.wasm?url'; 9 | import { batch, createResource, createSignal, lazy, onCleanup, Show, Suspense } from 'solid-js'; 10 | import { useMatch, useNavigate, useParams, useSearchParams } from '@solidjs/router'; 11 | import { API, useAppContext } from '../context'; 12 | import { debounce } from '@solid-primitives/scheduled'; 13 | import { defaultTabs } from 'solid-repl/src'; 14 | import type { Tab } from 'solid-repl'; 15 | import type { APIRepl } from './home'; 16 | import { Header } from '../components/header'; 17 | 18 | const Repl = lazy(() => import('../components/setupSolid')); 19 | 20 | window.MonacoEnvironment = { 21 | getWorker(_moduleId: unknown, label: string) { 22 | switch (label) { 23 | case 'css': 24 | return new cssWorker(); 25 | case 'json': 26 | return new jsonWorker(); 27 | case 'typescript': 28 | case 'javascript': 29 | return new tsWorker(); 30 | default: 31 | return new editorWorker(); 32 | } 33 | }, 34 | onigasm, 35 | }; 36 | 37 | interface InternalTab extends Tab { 38 | _source: string; 39 | _name: string; 40 | } 41 | export const Edit = () => { 42 | const [searchParams] = useSearchParams(); 43 | const scratchpad = useMatch(() => '/scratchpad'); 44 | const compiler = new CompilerWorker(); 45 | const formatter = new FormatterWorker(); 46 | const linter = new LinterWorker(); 47 | 48 | const params = useParams(); 49 | const context = useAppContext()!; 50 | const navigate = useNavigate(); 51 | 52 | let disableFetch: true | undefined; 53 | 54 | let readonly = () => !scratchpad() && context.user()?.display != params.user && !localStorage.getItem(params.repl); 55 | 56 | const mapTabs = (toMap: (Tab | InternalTab)[]): InternalTab[] => 57 | toMap.map((tab) => { 58 | if ('_source' in tab) return tab; 59 | return { 60 | _name: tab.name, 61 | get name() { 62 | return this._name; 63 | }, 64 | set name(name: string) { 65 | this._name = name; 66 | updateRepl(); 67 | }, 68 | _source: tab.source, 69 | get source() { 70 | return this._source; 71 | }, 72 | set source(source: string) { 73 | this._source = source; 74 | updateRepl(); 75 | }, 76 | }; 77 | }); 78 | 79 | const [tabs, trueSetTabs] = createSignal([]); 80 | const setTabs = (tabs: (Tab | InternalTab)[]) => trueSetTabs(mapTabs(tabs)); 81 | context.setTabs(tabs); 82 | onCleanup(() => context.setTabs(undefined)); 83 | 84 | const [current, setCurrent] = createSignal(undefined, { equals: false }); 85 | const [resource, { mutate }] = createResource( 86 | () => ({ repl: params.repl, scratchpad: !!scratchpad() }), 87 | async ({ repl, scratchpad }): Promise => { 88 | if (disableFetch) { 89 | disableFetch = undefined; 90 | if (resource.latest) return resource.latest; 91 | } 92 | 93 | let output: APIRepl; 94 | if (scratchpad) { 95 | const myScratchpad = localStorage.getItem('scratchpad'); 96 | if (!myScratchpad) { 97 | output = { 98 | files: defaultTabs.map((x) => ({ 99 | name: x.name, 100 | content: x.source, 101 | })), 102 | } as APIRepl; 103 | localStorage.setItem('scratchpad', JSON.stringify(output)); 104 | } else { 105 | output = JSON.parse(myScratchpad); 106 | } 107 | } else { 108 | output = await fetch(`${API}/repl/${repl}`, { 109 | headers: { authorization: context.token ? `Bearer ${context.token}` : '' }, 110 | }).then((r) => r.json()); 111 | } 112 | 113 | batch(() => { 114 | setTabs( 115 | output.files.map((x) => { 116 | return { name: x.name, source: x.content }; 117 | }), 118 | ); 119 | setCurrent(output.files[0].name); 120 | }); 121 | 122 | return output; 123 | }, 124 | ); 125 | 126 | const reset = () => { 127 | batch(() => { 128 | setTabs(mapTabs(defaultTabs)); 129 | setCurrent(defaultTabs[0].name); 130 | }); 131 | }; 132 | 133 | const updateRepl = debounce( 134 | () => { 135 | const files = tabs().map((x) => ({ name: x.name, content: x.source })); 136 | 137 | if (readonly()) { 138 | localStorage.setItem('scratchpad', JSON.stringify({ files })); 139 | disableFetch = true; 140 | navigate('/scratchpad'); 141 | return; 142 | } else if (scratchpad()) { 143 | localStorage.setItem('scratchpad', JSON.stringify({ files })); 144 | } 145 | 146 | const repl = resource.latest; 147 | if (!repl) return; 148 | 149 | if ((context.token && context.user()?.display == params.user) || localStorage.getItem(params.repl)) { 150 | fetch(`${API}/repl/${params.repl}`, { 151 | method: 'PUT', 152 | headers: { 153 | 'authorization': context.token ? `Bearer ${context.token}` : '', 154 | 'Content-Type': 'application/json', 155 | }, 156 | body: JSON.stringify({ 157 | ...(localStorage.getItem(params.repl) ? { write_token: localStorage.getItem(params.repl) } : {}), 158 | title: repl.title, 159 | version: repl.version, 160 | public: repl.public, 161 | labels: repl.labels, 162 | files, 163 | }), 164 | }); 165 | } 166 | }, 167 | !!scratchpad() ? 10 : 1000, 168 | ); 169 | 170 | return ( 171 | <> 172 |
{}} 175 | share={async () => { 176 | if (scratchpad()) { 177 | const newRepl = { 178 | title: context.user()?.display ? `${context.user()!.display}'s Scratchpad` : 'Anonymous Scratchpad', 179 | public: true, 180 | labels: [], 181 | version: '1.0', 182 | files: tabs().map((x) => ({ name: x.name, content: x.source })), 183 | }; 184 | const response = await fetch(`${API}/repl`, { 185 | method: 'POST', 186 | headers: { 187 | 'authorization': context.token ? `Bearer ${context.token}` : '', 188 | 'Content-Type': 'application/json', 189 | }, 190 | body: JSON.stringify(newRepl), 191 | }); 192 | if (response.status >= 400) { 193 | throw new Error(response.statusText); 194 | } 195 | const { id, write_token } = await response.json(); 196 | if (write_token) { 197 | localStorage.setItem(id, write_token); 198 | const repls = localStorage.getItem('repls'); 199 | if (repls) { 200 | localStorage.setItem('repls', JSON.stringify([...JSON.parse(repls), id])); 201 | } else { 202 | localStorage.setItem('repls', JSON.stringify([id])); 203 | } 204 | } 205 | mutate(() => ({ 206 | id, 207 | title: newRepl.title, 208 | labels: newRepl.labels, 209 | files: newRepl.files, 210 | version: newRepl.version, 211 | public: newRepl.public, 212 | size: 0, 213 | created_at: '', 214 | })); 215 | const url = `/${context.user()?.display || 'anonymous'}/${id}`; 216 | disableFetch = true; 217 | navigate(url); 218 | return `${window.location.origin}${url}`; 219 | } else { 220 | return location.href; 221 | } 222 | }} 223 | > 224 | {resource()?.title && ( 225 | { 229 | mutate((x) => x && { ...x, title: e.currentTarget.value }); 230 | updateRepl(); 231 | }} 232 | /> 233 | )} 234 |
235 | 243 | 244 | 249 | 250 | } 251 | > 252 | 253 | 266 | 267 | 268 | 269 | ); 270 | }; 271 | -------------------------------------------------------------------------------- /packages/playground/src/pages/home.tsx: -------------------------------------------------------------------------------- 1 | import { A, useLocation, useNavigate, useParams } from '@solidjs/router'; 2 | import { Icon } from 'solid-heroicons'; 3 | import { eye, eyeSlash, plus, xMark } from 'solid-heroicons/outline'; 4 | import { createEffect, createResource, createSignal, For, Show, Suspense } from 'solid-js'; 5 | import { createStore, produce } from 'solid-js/store'; 6 | import { defaultTabs } from 'solid-repl/src'; 7 | import { API, useAppContext } from '../context'; 8 | import { decompressFromURL } from '@amoutonbrady/lz-string'; 9 | import { Header } from '../components/header'; 10 | import { timeAgo } from '../utils/date'; 11 | 12 | function parseHash(hash: string, fallback: T): T { 13 | try { 14 | return JSON.parse(decompressFromURL(hash) || ''); 15 | } catch { 16 | return fallback; 17 | } 18 | } 19 | 20 | interface ReplFile { 21 | name: string; 22 | content: string; 23 | } 24 | export interface APIRepl { 25 | id: string; 26 | title: string; 27 | labels: string[]; 28 | files: ReplFile[]; 29 | version: string; 30 | public: boolean; 31 | size: number; 32 | created_at: string; 33 | updated_at?: string; 34 | } 35 | interface Repls { 36 | total: number; 37 | list: APIRepl[]; 38 | } 39 | 40 | export const Home = () => { 41 | const params = useParams(); 42 | const context = useAppContext()!; 43 | const navigate = useNavigate(); 44 | const location = useLocation(); 45 | 46 | createEffect(() => { 47 | if (location.query.hash) { 48 | navigate(`/anonymous/${location.query.hash}`); 49 | } else if (location.hash) { 50 | const initialTabs = parseHash(location.hash.slice(1), defaultTabs); 51 | localStorage.setItem( 52 | 'scratchpad', 53 | JSON.stringify({ 54 | files: initialTabs.map((x) => ({ 55 | name: x.name, 56 | content: x.source, 57 | })), 58 | }), 59 | ); 60 | navigate(`/scratchpad`); 61 | } else if (!context.token && !params.user) { 62 | navigate(`/scratchpad`); 63 | } 64 | }); 65 | 66 | const [repls, setRepls] = createStore({ total: 0, list: [] }); 67 | const [resourceRepls] = createResource( 68 | () => ({ user: params.user }), 69 | async ({ user }) => { 70 | if (!user && !context.token) return { total: 0, list: [] }; 71 | let output = await fetch(`${API}/repl${user ? `/${user}/list` : '?'}`, { 72 | headers: { 73 | Authorization: `Bearer ${context.token}`, 74 | }, 75 | }).then((r) => r.json()); 76 | setRepls(output); 77 | return output; 78 | }, 79 | ); 80 | const get = (x: T) => { 81 | resourceRepls(); 82 | return x; 83 | }; 84 | 85 | const [open, setOpen] = createSignal(); 86 | 87 | return ( 88 | <> 89 |
{ 91 | const url = new URL(document.location.origin); 92 | url.pathname = params.user || context.user.latest!.display; 93 | return url.toString(); 94 | }} 95 | /> 96 |
97 | {`${params.user}'s`} Repls}> 98 |
99 | 126 |

127 | or{' '} 128 | 129 | open my scratchpad 130 | 131 |

132 |
133 |
134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 149 | 164 | 165 | } 166 | > 167 | 168 | {(repl, i) => ( 169 | { 172 | if (e.target.tagName !== 'A') e.currentTarget.querySelector('a')!.click(); 173 | }} 174 | > 175 | 178 | 181 | 182 | 214 | 215 | 216 | )} 217 | 218 | 219 | 220 |
TitleEditedOptions
150 | 156 | 157 | 162 | 163 |
176 | {repl.title} 177 | 179 | {timeAgo(Date.now() - new Date(repl.updated_at || repl.created_at).getTime())} 180 | 183 | { 187 | e.stopPropagation(); 188 | fetch(`${API}/repl/${repl.id}`, { 189 | method: 'PATCH', 190 | headers: { 191 | 'authorization': `Bearer ${context.token}`, 192 | 'Content-Type': 'application/json', 193 | }, 194 | body: JSON.stringify({ 195 | public: !repl.public, 196 | }), 197 | }); 198 | setRepls( 199 | produce((x) => { 200 | x!.list[i()].public = !repl.public; 201 | }), 202 | ); 203 | }} 204 | /> 205 | { 209 | e.stopPropagation(); 210 | setOpen(repl.id); 211 | }} 212 | /> 213 |
221 |
222 | 223 |
{ 226 | if (e.target !== e.currentTarget) return; 227 | setOpen(undefined); 228 | }} 229 | role="presentation" 230 | > 231 | 262 |
263 |
264 | 265 | ); 266 | }; 267 | -------------------------------------------------------------------------------- /packages/playground/src/pages/login.tsx: -------------------------------------------------------------------------------- 1 | import { Navigate, useSearchParams } from '@solidjs/router'; 2 | import { useAppContext } from '../context'; 3 | 4 | export const Login = () => { 5 | const [params] = useSearchParams(); 6 | const context = useAppContext()!; 7 | context.token = params.token; 8 | return ; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/playground/src/utils/date.ts: -------------------------------------------------------------------------------- 1 | const formatter = new Intl.RelativeTimeFormat('en'); 2 | export const timeAgo = (ms: number): string => { 3 | const sec = Math.round(ms / 1000); 4 | const min = Math.round(sec / 60); 5 | const hr = Math.round(min / 60); 6 | const day = Math.round(hr / 24); 7 | const month = Math.round(day / 30); 8 | const year = Math.round(month / 12); 9 | if (sec < 10) { 10 | return 'just now'; 11 | } else if (sec < 45) { 12 | return formatter.format(-sec, 'second'); 13 | } else if (sec < 90 || min < 45) { 14 | return formatter.format(-min, 'minute'); 15 | } else if (min < 90 || hr < 24) { 16 | return formatter.format(-hr, 'hour'); 17 | } else if (hr < 36 || day < 30) { 18 | return formatter.format(-day, 'day'); 19 | } else if (month < 18) { 20 | return formatter.format(-month, 'month'); 21 | } else { 22 | return formatter.format(-year, 'year'); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /packages/playground/src/utils/exportFiles.tsx: -------------------------------------------------------------------------------- 1 | import pkg from '../../package.json'; 2 | import type { Tab } from 'solid-repl'; 3 | import dedent from 'dedent'; 4 | 5 | const viteConfigFile = dedent` 6 | import { defineConfig } from "vite"; 7 | import solidPlugin from "vite-plugin-solid"; 8 | 9 | export default defineConfig({ 10 | plugins: [solidPlugin()], 11 | build: { 12 | target: "esnext", 13 | polyfillDynamicImport: false, 14 | }, 15 | }); 16 | `; 17 | const tsConfig = JSON.stringify( 18 | { 19 | compilerOptions: { 20 | strict: true, 21 | module: 'ESNext', 22 | target: 'ESNext', 23 | jsx: 'preserve', 24 | esModuleInterop: true, 25 | sourceMap: true, 26 | allowJs: true, 27 | lib: ['es6', 'dom'], 28 | rootDir: 'src', 29 | moduleResolution: 'node', 30 | jsxImportSource: 'solid-js', 31 | types: ['solid-js', 'solid-js/dom'], 32 | }, 33 | }, 34 | null, 35 | 2, 36 | ); 37 | 38 | const indexHTML = (tabs: Tab[]) => dedent` 39 | 40 | 41 | Vite Sandbox 42 | 43 | 44 | 45 | 46 |
47 | 48 | 49 | 50 | 51 | `; 52 | 53 | /** 54 | * This function will calculate the dependencies of the 55 | * package.json by using the imports list provided by the bundler, 56 | * and then generating the package.json itself, for the export 57 | */ 58 | function packageJSON(imports: string[]): string { 59 | const deps = imports.reduce( 60 | (acc, importPath): Record => { 61 | const name = importPath.split('/')[0]; 62 | if (!acc[name]) acc[name] = '*'; 63 | return acc; 64 | }, 65 | {} as Record, 66 | ); 67 | 68 | return JSON.stringify( 69 | { 70 | scripts: { 71 | start: 'vite', 72 | build: 'vite build', 73 | }, 74 | dependencies: deps, 75 | devDependencies: { 76 | 'vite': pkg.devDependencies['vite'], 77 | 'vite-plugin-solid': pkg.devDependencies['vite-plugin-solid'], 78 | }, 79 | }, 80 | null, 81 | 2, 82 | ); 83 | } 84 | 85 | /** 86 | * This function will convert the tabs of the playground 87 | * into a ZIP formatted playground that can then be reimported later on 88 | */ 89 | export async function exportToZip(tabs: Tab[]): Promise { 90 | const { default: JSZip } = await import('jszip'); 91 | const zip = new JSZip(); 92 | 93 | // basic structure 94 | zip.file('index.html', indexHTML(tabs)); 95 | zip.file('vite.config.ts', viteConfigFile); 96 | zip.file('tsconfig.json', tsConfig); 97 | zip.folder('src'); 98 | 99 | for (const tab of tabs) { 100 | if (tab.name == 'import_map.json') { 101 | zip.file('package.json', packageJSON(Object.keys(JSON.parse(tab.source)))); 102 | } else { 103 | zip.file(`src/${tab.name}`, tab.source); 104 | } 105 | } 106 | 107 | const blob = await zip.generateAsync({ type: 'blob' }); 108 | const url = URL.createObjectURL(blob); 109 | 110 | const anchor = () as HTMLElement; 111 | document.body.prepend(anchor); 112 | anchor.click(); 113 | anchor.remove(); 114 | } 115 | -------------------------------------------------------------------------------- /packages/playground/src/utils/isDarkTheme.ts: -------------------------------------------------------------------------------- 1 | export const isDarkTheme = () => { 2 | if (typeof window !== 'undefined') { 3 | if (window.localStorage) { 4 | const isDarkTheme = window.localStorage.getItem('dark'); 5 | if (typeof isDarkTheme === 'string') { 6 | return isDarkTheme === 'true'; 7 | } 8 | } 9 | 10 | const userMedia = window.matchMedia('(prefers-color-scheme: dark)'); 11 | if (userMedia.matches) { 12 | return true; 13 | } 14 | } 15 | 16 | // Default theme is light. 17 | return false; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/playground/src/utils/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | import { register } from 'register-service-worker'; 2 | import { createSignal } from 'solid-js'; 3 | 4 | const [eventBus, setEventBus] = createSignal(); 5 | 6 | function registerServiceWorker(): void { 7 | if ('serviceWorker' in navigator && import.meta.env.PROD) { 8 | window.addEventListener('load', () => { 9 | register('/sw.js', { 10 | updated() { 11 | setEventBus(true); 12 | }, 13 | ready(sw) { 14 | sw.addEventListener('message', (event) => { 15 | if (event.type == 'cache') { 16 | setEventBus(true); 17 | } 18 | }); 19 | }, 20 | }); 21 | }); 22 | } 23 | } 24 | 25 | export { eventBus, setEventBus, registerServiceWorker }; 26 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM", "DOM.Iterable", "WebWorker"], 6 | "allowSyntheticDefaultImports": true, 7 | "isolatedModules": true, 8 | "esModuleInterop": true, 9 | "moduleResolution": "node", 10 | "jsx": "preserve", 11 | "resolveJsonModule": true, 12 | "noEmit": true, 13 | "strict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "skipLibCheck": true, 17 | "jsxImportSource": "solid-js", 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./src/**/*"], 21 | "exclude": ["node_modules/"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/playground/unocss.config.ts: -------------------------------------------------------------------------------- 1 | import { presetWind, theme } from '@unocss/preset-wind3'; 2 | import { transformerDirectives, defineConfig } from 'unocss'; 3 | 4 | export default defineConfig({ 5 | theme: { 6 | colors: { 7 | brand: { 8 | default: '#2c4f7c', 9 | dark: '#335d92', 10 | medium: '#446b9e', 11 | light: '#4f88c6', 12 | }, 13 | solid: { 14 | default: '#2c4f7c', 15 | darkbg: '#222222', 16 | darkLighterBg: '#444444', 17 | darkdefault: '#b8d7ff', 18 | darkgray: '#252525', 19 | gray: '#414042', 20 | mediumgray: '#9d9d9d', 21 | lightgray: '#f3f5f7', 22 | dark: '#07254A', 23 | medium: '#446b9e', 24 | light: '#4f88c6', 25 | accent: '#0cdc73', 26 | secondaccent: '#0dfc85', 27 | }, 28 | other: '#1e1e1e', 29 | }, 30 | fontFamily: { 31 | sans: 'Gordita, ' + theme.fontFamily!.sans, 32 | }, 33 | }, 34 | presets: [presetWind()], 35 | transformers: [transformerDirectives()], 36 | }); 37 | -------------------------------------------------------------------------------- /packages/playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | import UnoCSS from 'unocss/vite'; 4 | 5 | export default defineConfig((env) => ({ 6 | plugins: [UnoCSS(), solidPlugin()], 7 | define: { 8 | 'process.env.BABEL_TYPES_8_BREAKING': 'true', 9 | 'process.env.NODE_DEBUG': 'false', 10 | ...(env.command == 'build' ? {} : { global: 'globalThis' }), 11 | }, 12 | build: { 13 | target: 'esnext', 14 | rollupOptions: { 15 | output: { 16 | manualChunks: {}, 17 | entryFileNames: `assets/[name].js`, 18 | chunkFileNames: `assets/[name].js`, 19 | assetFileNames: `assets/[name].[ext]`, 20 | }, 21 | }, 22 | }, 23 | worker: { 24 | rollupOptions: { 25 | output: { 26 | entryFileNames: `assets/[name].js`, 27 | }, 28 | }, 29 | }, 30 | server: { 31 | proxy: { 32 | '/api': { 33 | target: 'http://localhost:8787', 34 | changeOrigin: true, 35 | rewrite: (path) => path.replace(/^\/api/, ''), 36 | }, 37 | }, 38 | }, 39 | })); 40 | -------------------------------------------------------------------------------- /packages/solid-repl/build.ts: -------------------------------------------------------------------------------- 1 | import { build } from 'esbuild'; 2 | import { readFileSync, writeFileSync, unlinkSync } from 'fs'; 3 | import { copyFileSync } from 'fs-extra'; 4 | 5 | build({ 6 | entryPoints: ['./repl/compiler.ts', './repl/formatter.ts', './repl/linter.ts', './repl/main.css'], 7 | outdir: './dist', 8 | minify: true, 9 | bundle: true, 10 | external: ['/Gordita-Medium.woff', '/Gordita-Regular.woff', '/Gordita-Bold.woff'], 11 | define: { 12 | 'process.env.BABEL_TYPES_8_BREAKING': 'true', 13 | 'process.env.NODE_DEBUG': 'false', 14 | 'preventAssignment': 'true', 15 | }, 16 | }).then(() => { 17 | const unoCSS_build = readFileSync('./uno.css'); 18 | const generated_bundle = readFileSync('./dist/main.css'); 19 | 20 | const output_bundle = Buffer.concat([generated_bundle, unoCSS_build]); 21 | 22 | writeFileSync('./dist/bundle.css', output_bundle); 23 | 24 | unlinkSync('./uno.css'); 25 | unlinkSync('./dist/main.css'); 26 | 27 | copyFileSync('./src/types.d.ts', './dist/types.d.ts'); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/solid-repl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.26.0", 3 | "name": "solid-repl", 4 | "description": "Quickly discover what the solid compiler will generate from your JSX template", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/solidjs/solid-playground.git" 8 | }, 9 | "files": [ 10 | "dist" 11 | ], 12 | "homepage": "https://playground.solidjs.com", 13 | "author": "Alexandre Mouton-Brady", 14 | "module": "dist/repl.jsx", 15 | "types": "src/types.d.ts", 16 | "scripts": { 17 | "build": "tsc -p tsconfig.build.json && unocss \"./src/**\" && jiti build.ts", 18 | "tsc": "tsc" 19 | }, 20 | "peerDependencies": { 21 | "solid-js": ">=1.7.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.26.10", 25 | "@babel/plugin-syntax-jsx": "^7.25.9", 26 | "@babel/preset-typescript": "^7.27.0", 27 | "@babel/standalone": "^7.27.0", 28 | "@babel/types": "^7.27.0", 29 | "@types/babel__standalone": "^7.1.9", 30 | "@types/dedent": "^0.7.2", 31 | "@types/fs-extra": "^11.0.4", 32 | "@unocss/cli": "^66.0.0", 33 | "@unocss/preset-wind3": "^66.0.0", 34 | "@unocss/reset": "^66.0.0", 35 | "babel-preset-solid": "^1.9.5", 36 | "esbuild": "^0.25.3", 37 | "eslint-solid-standalone": "^0.13.2", 38 | "fs-extra": "^11.3.0", 39 | "jiti": "^2.4.2", 40 | "monaco-editor": "^0.52.2", 41 | "prettier": "^3.5.3", 42 | "solid-js": "1.9.5", 43 | "typescript": "^5.8.3", 44 | "unocss": "^66.0.0" 45 | }, 46 | "dependencies": { 47 | "@solid-primitives/media": "^2.3.0", 48 | "@solid-primitives/platform": "^0.2.0", 49 | "@solid-primitives/scheduled": "^1.5.0", 50 | "dedent": "^1.5.3", 51 | "monaco-editor-textmate": "^4.0.0", 52 | "monaco-textmate": "^3.0.1", 53 | "onigasm": "^2.2.5", 54 | "solid-dismiss": "^1.8.2", 55 | "solid-heroicons": "^3.2.4" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/solid-repl/repl/compiler.ts: -------------------------------------------------------------------------------- 1 | import type { Tab } from 'solid-repl'; 2 | 3 | import { transform } from '@babel/standalone'; 4 | // @ts-ignore 5 | import babelPresetSolid from 'babel-preset-solid'; 6 | 7 | import dd from 'dedent'; 8 | 9 | let files: Record = {}; 10 | let cache: Record = {}; 11 | let dataToReturn: Record = {}; 12 | 13 | function uid(str: string) { 14 | return Array.from(str) 15 | .reduce((s, c) => (Math.imul(31, s) + c.charCodeAt(0)) | 0, 0) 16 | .toString(); 17 | } 18 | 19 | function babelTransform(filename: string, code: string) { 20 | let { code: transformedCode } = transform(code, { 21 | plugins: [ 22 | // Babel plugin to get file import names 23 | function importGetter() { 24 | return { 25 | visitor: { 26 | Import(path: any) { 27 | const importee: string = path.parent.arguments[0].value; 28 | cache[importee] = path.parent.arguments[0].value = transformImportee(importee); 29 | }, 30 | ImportDeclaration(path: any) { 31 | const importee: string = path.node.source.value; 32 | cache[importee] = path.node.source.value = transformImportee(importee); 33 | }, 34 | ExportAllDeclaration(path: any) { 35 | const importee: string = path.node.source.value; 36 | cache[importee] = path.node.source.value = transformImportee(importee); 37 | }, 38 | ExportNamedDeclaration(path: any) { 39 | const importee: string = path.node.source?.value; 40 | if (importee) { 41 | cache[importee] = path.node.source.value = transformImportee(importee); 42 | } 43 | }, 44 | }, 45 | }; 46 | }, 47 | ], 48 | presets: [ 49 | [babelPresetSolid, { generate: 'dom', hydratable: false }], 50 | ['typescript', { onlyRemoveTypeImports: true }], 51 | ], 52 | filename: filename + '.tsx', 53 | }); 54 | 55 | return transformedCode!.replace('render(', 'window.dispose = render('); 56 | } 57 | 58 | // Returns new import URL 59 | function transformImportee(fileName: string) { 60 | // There's no point re-visiting a node again, as it's already been processed 61 | if (fileName in cache) { 62 | return cache[fileName]; 63 | } 64 | if (!fileName) { 65 | return ''; 66 | } 67 | 68 | // Base cases 69 | if (fileName.includes('://') || !fileName.startsWith('.')) { 70 | if (fileName.endsWith('.css')) { 71 | const id = uid(fileName); 72 | const js = dd` 73 | (() => { 74 | let link = document.getElementById('${id}'); 75 | if (!link) { 76 | link = document.createElement('link') 77 | link.setAttribute('id', ${id}) 78 | document.head.appendChild(link) 79 | } 80 | link.setAttribute('rel', 'stylesheet') 81 | link.setAttribute('href', '${fileName}') 82 | })() 83 | `; 84 | const url = URL.createObjectURL(new Blob([js], { type: 'application/javascript' })); 85 | return url; 86 | } 87 | if (fileName.includes('://')) return fileName; 88 | else { 89 | dataToReturn[fileName] = `https://esm.sh/${fileName}`; 90 | return fileName; 91 | } 92 | } 93 | if (fileName.endsWith('.css')) { 94 | const contents = files[fileName]; 95 | const id = uid(fileName); 96 | const js = dd` 97 | (() => { 98 | let stylesheet = document.getElementById('${id}'); 99 | if (!stylesheet) { 100 | stylesheet = document.createElement('style') 101 | stylesheet.setAttribute('id', ${id}) 102 | document.head.appendChild(stylesheet) 103 | } 104 | const styles = document.createTextNode(\`${contents}\`) 105 | stylesheet.innerHTML = '' 106 | stylesheet.appendChild(styles) 107 | })() 108 | `; 109 | const url = URL.createObjectURL(new Blob([js], { type: 'application/javascript' })); 110 | return url; 111 | } 112 | 113 | // Parse file and all its children through recursion 114 | cache[fileName] = ''; // Prevent infinite recursion 115 | const js = babelTransform(fileName, files[fileName]); 116 | const url = URL.createObjectURL(new Blob([js], { type: 'application/javascript' })); 117 | return url; 118 | } 119 | 120 | function bundle(entryPoint: string, fileRecord: Record) { 121 | files = fileRecord; 122 | for (let out in dataToReturn) { 123 | const url = dataToReturn[out]; 124 | if (url.startsWith('blob:')) URL.revokeObjectURL(dataToReturn[out]); 125 | } 126 | cache = {}; 127 | dataToReturn = {}; 128 | dataToReturn[entryPoint] = transformImportee(entryPoint); 129 | return dataToReturn; 130 | } 131 | 132 | function compile(tabs: Tab[], event: string) { 133 | const tabsRecord: Record = {}; 134 | for (const tab of tabs) { 135 | tabsRecord[`./${tab.name.replace(/.(tsx|jsx)$/, '')}`] = tab.source; 136 | } 137 | const bundled = bundle('./main', tabsRecord); 138 | return { event, compiled: bundled }; 139 | } 140 | 141 | function babel(tab: Tab, compileOpts: any) { 142 | const { code } = transform(tab.source, { 143 | presets: [ 144 | [babelPresetSolid, compileOpts], 145 | ['typescript', { onlyRemoveTypeImports: true }], 146 | ], 147 | filename: tab.name, 148 | }); 149 | return { event: 'BABEL', compiled: code }; 150 | } 151 | 152 | self.addEventListener('message', ({ data }) => { 153 | const { event, tabs, tab, compileOpts } = data; 154 | 155 | try { 156 | if (event === 'BABEL') { 157 | self.postMessage(babel(tab, compileOpts)); 158 | } else if (event === 'ROLLUP') { 159 | self.postMessage(compile(tabs, event)); 160 | } 161 | } catch (e) { 162 | self.postMessage({ event: 'ERROR', error: e }); 163 | } 164 | }); 165 | 166 | export {}; 167 | -------------------------------------------------------------------------------- /packages/solid-repl/repl/formatter.ts: -------------------------------------------------------------------------------- 1 | import { format as prettierFormat } from 'prettier/standalone'; 2 | import * as prettierPluginBabel from 'prettier/plugins/babel'; 3 | import * as prettierPluginEstree from 'prettier/plugins/estree'; 4 | 5 | function format(code: string) { 6 | return prettierFormat(code, { 7 | parser: 'babel-ts', 8 | plugins: [prettierPluginBabel, prettierPluginEstree], 9 | }); 10 | } 11 | 12 | self.addEventListener('message', async ({ data }) => { 13 | const { event, code } = data; 14 | 15 | switch (event) { 16 | case 'FORMAT': 17 | self.postMessage({ 18 | event: 'FORMAT', 19 | code: await format(code), 20 | }); 21 | break; 22 | } 23 | }); 24 | 25 | export {}; 26 | -------------------------------------------------------------------------------- /packages/solid-repl/repl/linter.ts: -------------------------------------------------------------------------------- 1 | import { verify, verifyAndFix } from 'eslint-solid-standalone'; 2 | import type { Linter } from 'eslint-solid-standalone'; 3 | import type { editor } from 'monaco-editor'; 4 | 5 | export interface LinterWorkerPayload { 6 | event: 'LINT' | 'FIX'; 7 | code: string; 8 | ruleSeverityOverrides?: Record; 9 | } 10 | 11 | const messagesToMarkers = (messages: Array): Array => { 12 | if (messages.some((m) => m.fatal)) return []; // no need for any extra highlights on parse errors 13 | return messages.map((m) => ({ 14 | startLineNumber: m.line, 15 | endLineNumber: m.endLine ?? m.line, 16 | startColumn: m.column, 17 | endColumn: m.endColumn ?? m.column, 18 | message: `${m.message}\neslint(${m.ruleId})`, 19 | severity: m.severity === 2 ? 8 /* error */ : 4 /* warning */, 20 | })); 21 | }; 22 | 23 | self.addEventListener('message', ({ data }: MessageEvent) => { 24 | const { event } = data; 25 | try { 26 | if (event === 'LINT') { 27 | const { code, ruleSeverityOverrides } = data; 28 | self.postMessage({ 29 | event: 'LINT' as const, 30 | markers: messagesToMarkers(verify(code, ruleSeverityOverrides)), 31 | }); 32 | } else if (event === 'FIX') { 33 | const { code, ruleSeverityOverrides } = data; 34 | const fixReport = verifyAndFix(code, ruleSeverityOverrides); 35 | self.postMessage({ 36 | event: 'FIX' as const, 37 | markers: messagesToMarkers(fixReport.messages), 38 | output: fixReport.output, 39 | fixed: fixReport.fixed, 40 | }); 41 | } 42 | } catch (e) { 43 | console.error(e); 44 | self.postMessage({ event: 'ERROR' as const, error: e }); 45 | } 46 | }); 47 | 48 | export {}; 49 | -------------------------------------------------------------------------------- /packages/solid-repl/repl/main.css: -------------------------------------------------------------------------------- 1 | @import url('@unocss/reset/tailwind.css'); 2 | 3 | @font-face { 4 | font-family: 'Gordita'; 5 | src: url('/Gordita-Regular.woff') format('woff'); 6 | font-weight: 400; 7 | font-style: normal; 8 | } 9 | 10 | @font-face { 11 | font-family: 'Gordita'; 12 | src: url('/Gordita-Bold.woff') format('woff'); 13 | font-weight: 700; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: 'Gordita'; 19 | src: url('/Gordita-Medium.woff') format('woff'); 20 | font-weight: 500; 21 | font-style: normal; 22 | } 23 | 24 | div[contenteditable='true']:focus { 25 | outline: none !important; 26 | } 27 | 28 | .dark { 29 | color-scheme: dark; 30 | } 31 | -------------------------------------------------------------------------------- /packages/solid-repl/src/components/editor/index.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createEffect, onMount, onCleanup } from 'solid-js'; 2 | import { Uri, languages, editor as mEditor, KeyMod, KeyCode } from 'monaco-editor'; 3 | import { liftOff } from './setupSolid'; 4 | import { useZoom } from '../../hooks/useZoom'; 5 | import { throttle } from '@solid-primitives/scheduled'; 6 | 7 | const Editor: Component<{ 8 | model: mEditor.ITextModel; 9 | disabled?: true; 10 | isDark?: boolean; 11 | withMinimap?: boolean; 12 | formatter?: Worker; 13 | linter?: Worker; 14 | displayErrors?: boolean; 15 | onDocChange?: (code: string) => void; 16 | onEditorReady?: ( 17 | editor: mEditor.IStandaloneCodeEditor, 18 | monaco: { 19 | Uri: typeof Uri; 20 | editor: typeof mEditor; 21 | }, 22 | ) => void; 23 | }> = (props) => { 24 | let parent!: HTMLDivElement; 25 | let editor: mEditor.IStandaloneCodeEditor; 26 | 27 | const { zoomState } = useZoom(); 28 | 29 | if (props.formatter) { 30 | languages.registerDocumentFormattingEditProvider('typescript', { 31 | async provideDocumentFormattingEdits(model) { 32 | props.formatter!.postMessage({ 33 | event: 'FORMAT', 34 | code: model.getValue(), 35 | pos: editor.getPosition(), 36 | }); 37 | 38 | return new Promise((resolve) => { 39 | props.formatter!.addEventListener( 40 | 'message', 41 | ({ data: { code } }) => { 42 | resolve([ 43 | { 44 | range: model.getFullModelRange(), 45 | text: code, 46 | }, 47 | ]); 48 | }, 49 | { once: true }, 50 | ); 51 | }); 52 | }, 53 | }); 54 | } 55 | if (props.linter) { 56 | const listener = ({ data }: any) => { 57 | if (props.displayErrors) { 58 | const { event } = data; 59 | if (event === 'LINT') { 60 | mEditor.setModelMarkers(props.model, 'eslint', data.markers); 61 | } else if (event === 'FIX') { 62 | mEditor.setModelMarkers(props.model, 'eslint', data.markers); 63 | data.fixed && props.model.setValue(data.output); 64 | } 65 | } 66 | }; 67 | props.linter.addEventListener('message', listener); 68 | onCleanup(() => props.linter?.removeEventListener('message', listener)); 69 | } 70 | 71 | const runLinter = throttle((code: string) => { 72 | if (props.linter && props.displayErrors) { 73 | props.linter.postMessage({ 74 | event: 'LINT', 75 | code, 76 | }); 77 | } 78 | }, 250); 79 | 80 | // Initialize Monaco 81 | onMount(() => { 82 | editor = mEditor.create(parent, { 83 | model: null, 84 | fontFamily: 'Menlo, Monaco, "Courier New", monospace', 85 | automaticLayout: true, 86 | readOnly: props.disabled, 87 | fontSize: zoomState.fontSize, 88 | lineDecorationsWidth: 5, 89 | lineNumbersMinChars: 3, 90 | padding: { top: 15 }, 91 | minimap: { 92 | enabled: props.withMinimap, 93 | }, 94 | }); 95 | 96 | createEffect(() => { 97 | editor.updateOptions({ readOnly: !!props.disabled }); 98 | }); 99 | 100 | if (props.linter) { 101 | editor.addAction({ 102 | id: 'eslint.executeAutofix', 103 | label: 'Fix all auto-fixable problems', 104 | contextMenuGroupId: '1_modification', 105 | contextMenuOrder: 3.5, 106 | run: (ed) => { 107 | const code = ed.getValue(); 108 | if (code) { 109 | props.linter?.postMessage({ 110 | event: 'FIX', 111 | code, 112 | }); 113 | } 114 | }, 115 | }); 116 | } 117 | 118 | editor.addCommand(KeyMod.CtrlCmd | KeyCode.KeyS, () => { 119 | // auto-format 120 | editor.getAction('editor.action.formatDocument')?.run(); 121 | // auto-fix problems 122 | props.displayErrors && editor.getAction('eslint.executeAutofix')?.run(); 123 | editor.focus(); 124 | }); 125 | 126 | editor.onDidChangeModelContent(() => { 127 | const code = editor.getValue(); 128 | props.onDocChange?.(code); 129 | runLinter(code); 130 | }); 131 | }); 132 | onCleanup(() => editor.dispose()); 133 | 134 | createEffect(() => { 135 | editor.setModel(props.model); 136 | liftOff(); 137 | }); 138 | 139 | createEffect(() => { 140 | mEditor.setTheme(props.isDark ? 'vs-dark-plus' : 'vs-light-plus'); 141 | }); 142 | 143 | createEffect(() => { 144 | const fontSize = zoomState.fontSize; 145 | editor.updateOptions({ fontSize }); 146 | }); 147 | 148 | createEffect(() => { 149 | languages.typescript.typescriptDefaults.setDiagnosticsOptions({ 150 | noSemanticValidation: !props.displayErrors, 151 | noSyntaxValidation: !props.displayErrors, 152 | }); 153 | }); 154 | 155 | createEffect(() => { 156 | if (props.displayErrors) { 157 | // run on mount and when displayLintMessages is turned on 158 | runLinter(editor.getValue()); 159 | } else { 160 | // reset eslint markers when displayLintMessages is turned off 161 | mEditor.setModelMarkers(props.model, 'eslint', []); 162 | } 163 | }); 164 | 165 | onMount(() => { 166 | props.onEditorReady?.(editor, { Uri, editor: mEditor }); 167 | }); 168 | 169 | return
; 170 | }; 171 | 172 | export default Editor; 173 | -------------------------------------------------------------------------------- /packages/solid-repl/src/components/editor/monacoTabs.tsx: -------------------------------------------------------------------------------- 1 | import { createMemo, onCleanup } from 'solid-js'; 2 | import type { Tab } from 'solid-repl'; 3 | import { Uri, editor, IDisposable } from 'monaco-editor'; 4 | 5 | export const createMonacoTabs = (folder: string, tabs: () => Tab[]) => { 6 | const currentTabs = createMemo>((prevTabs) => { 7 | const newTabs = new Map(); 8 | for (const tab of tabs()) { 9 | const url = `file:///${folder}/${tab.name}`; 10 | const lookup = prevTabs?.get(url); 11 | if (!lookup) { 12 | const uri = Uri.parse(url); 13 | const model = editor.createModel(tab.source, undefined, uri); 14 | const watcher = model.onDidChangeContent(() => (tab.source = model.getValue())); 15 | newTabs.set(url, { model, watcher }); 16 | } else { 17 | lookup.model.setValue(tab.source); 18 | lookup.watcher.dispose(); 19 | lookup.watcher = lookup.model.onDidChangeContent(() => (tab.source = lookup.model.getValue())); 20 | newTabs.set(url, lookup); 21 | } 22 | } 23 | 24 | if (prevTabs) { 25 | for (const [old, lookup] of prevTabs) { 26 | if (!newTabs.has(old)) lookup.model.dispose(); 27 | } 28 | } 29 | return newTabs; 30 | }); 31 | onCleanup(() => { 32 | for (const lookup of currentTabs().values()) lookup.model.dispose(); 33 | }); 34 | 35 | return currentTabs; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/solid-repl/src/components/editor/setupSolid.ts: -------------------------------------------------------------------------------- 1 | import { languages, editor } from 'monaco-editor'; 2 | import vsDark from './vs_dark_good.json'; 3 | import vsLight from './vs_light_good.json'; 4 | import { loadWASM } from 'onigasm'; 5 | import { Registry } from 'monaco-textmate'; 6 | import { wireTmGrammars } from 'monaco-editor-textmate'; 7 | import typescriptReactTM from './TypeScriptReact.tmLanguage.json'; 8 | import cssTM from './css.tmLanguage.json'; 9 | 10 | const compilerOptions: languages.typescript.CompilerOptions = { 11 | strict: true, 12 | target: languages.typescript.ScriptTarget.ESNext, 13 | module: languages.typescript.ModuleKind.ESNext, 14 | moduleResolution: languages.typescript.ModuleResolutionKind.NodeJs, 15 | jsx: languages.typescript.JsxEmit.Preserve, 16 | jsxImportSource: 'solid-js', 17 | allowNonTsExtensions: true, 18 | }; 19 | 20 | languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions); 21 | languages.typescript.javascriptDefaults.setCompilerOptions(compilerOptions); 22 | 23 | let loadingWasm: Promise; 24 | 25 | const registry = new Registry({ 26 | async getGrammarDefinition(scopeName) { 27 | return { 28 | format: 'json', 29 | content: scopeName === 'source.tsx' ? typescriptReactTM : cssTM, 30 | }; 31 | }, 32 | }); 33 | 34 | const grammars = new Map(); 35 | grammars.set('typescript', 'source.tsx'); 36 | grammars.set('javascript', 'source.tsx'); 37 | grammars.set('css', 'source.css'); 38 | 39 | // monaco's built-in themes aren't powereful enough to handle TM tokens 40 | // https://github.com/Nishkalkashyap/monaco-vscode-textmate-theme-converter#monaco-vscode-textmate-theme-converter 41 | editor.defineTheme('vs-dark-plus', vsDark as editor.IStandaloneThemeData); 42 | editor.defineTheme('vs-light-plus', vsLight as editor.IStandaloneThemeData); 43 | 44 | const hookLanguages = languages.setLanguageConfiguration; 45 | 46 | languages.setLanguageConfiguration = (languageId: string, configuration: languages.LanguageConfiguration) => { 47 | liftOff(); 48 | return hookLanguages(languageId, configuration); 49 | }; 50 | 51 | export async function liftOff(): Promise { 52 | if (!loadingWasm) loadingWasm = loadWASM(window.MonacoEnvironment.onigasm); 53 | await loadingWasm; 54 | 55 | // wireTmGrammars only cares about the language part, but asks for all of monaco 56 | // we fool it by just passing in an object with languages 57 | await wireTmGrammars({ languages } as any, registry, grammars); 58 | } 59 | -------------------------------------------------------------------------------- /packages/solid-repl/src/components/editor/vs_dark_good.json: -------------------------------------------------------------------------------- 1 | { 2 | "inherit": false, 3 | "base": "vs-dark", 4 | "colors": { 5 | "editor.background": "#1E1E1E", 6 | "editor.foreground": "#D4D4D4", 7 | "editor.inactiveSelectionBackground": "#3A3D41", 8 | "editorIndentGuide.background": "#404040", 9 | "editorIndentGuide.activeBackground": "#707070", 10 | "editor.selectionHighlightBackground": "#ADD6FF26", 11 | "list.dropBackground": "#383B3D", 12 | "activityBarBadge.background": "#007ACC", 13 | "sideBarTitle.foreground": "#BBBBBB", 14 | "input.placeholderForeground": "#A6A6A6", 15 | "menu.background": "#252526", 16 | "menu.foreground": "#CCCCCC", 17 | "statusBarItem.remoteForeground": "#FFF", 18 | "statusBarItem.remoteBackground": "#16825D", 19 | "sideBarSectionHeader.background": "#0000", 20 | "sideBarSectionHeader.border": "#ccc3", 21 | "tab.lastPinnedBorder": "#ccc3" 22 | }, 23 | "rules": [ 24 | { "foreground": "#D4D4D4", "token": "" }, 25 | { "foreground": "#D4D4D4", "token": "meta.embedded" }, 26 | { "foreground": "#D4D4D4", "token": "source.groovy.embedded" }, 27 | { "fontStyle": "italic", "token": "emphasis" }, 28 | { "fontStyle": "bold", "token": "strong" }, 29 | { "foreground": "#000080", "token": "header" }, 30 | { "foreground": "#6A9955", "token": "comment" }, 31 | { "foreground": "#569cd6", "token": "constant.language" }, 32 | { "foreground": "#b5cea8", "token": "constant.numeric" }, 33 | { "foreground": "#b5cea8", "token": "variable.other.enummember" }, 34 | { "foreground": "#b5cea8", "token": "keyword.operator.plus.exponent" }, 35 | { "foreground": "#b5cea8", "token": "keyword.operator.minus.exponent" }, 36 | { "foreground": "#646695", "token": "constant.regexp" }, 37 | { "foreground": "#569cd6", "token": "entity.name.tag" }, 38 | { "foreground": "#d7ba7d", "token": "entity.name.tag.css" }, 39 | { "foreground": "#9cdcfe", "token": "entity.other.attribute-name" }, 40 | { 41 | "foreground": "#d7ba7d", 42 | "token": "entity.other.attribute-name.class.css" 43 | }, 44 | { 45 | "foreground": "#d7ba7d", 46 | "token": "entity.other.attribute-name.class.mixin.css" 47 | }, 48 | { "foreground": "#d7ba7d", "token": "entity.other.attribute-name.id.css" }, 49 | { 50 | "foreground": "#d7ba7d", 51 | "token": "entity.other.attribute-name.parent-selector.css" 52 | }, 53 | { 54 | "foreground": "#d7ba7d", 55 | "token": "entity.other.attribute-name.pseudo-class.css" 56 | }, 57 | { 58 | "foreground": "#d7ba7d", 59 | "token": "entity.other.attribute-name.pseudo-element.css" 60 | }, 61 | { 62 | "foreground": "#d7ba7d", 63 | "token": "source.css.less entity.other.attribute-name.id" 64 | }, 65 | { "foreground": "#d7ba7d", "token": "entity.other.attribute-name.scss" }, 66 | { "foreground": "#f44747", "token": "invalid" }, 67 | { "fontStyle": "underline", "token": "markup.underline" }, 68 | { "fontStyle": "bold", "foreground": "#569cd6", "token": "markup.bold" }, 69 | { "fontStyle": "bold", "foreground": "#569cd6", "token": "markup.heading" }, 70 | { "fontStyle": "italic", "token": "markup.italic" }, 71 | { "foreground": "#b5cea8", "token": "markup.inserted" }, 72 | { "foreground": "#ce9178", "token": "markup.deleted" }, 73 | { "foreground": "#569cd6", "token": "markup.changed" }, 74 | { 75 | "foreground": "#6A9955", 76 | "token": "punctuation.definition.quote.begin.markdown" 77 | }, 78 | { 79 | "foreground": "#6796e6", 80 | "token": "punctuation.definition.list.begin.markdown" 81 | }, 82 | { "foreground": "#ce9178", "token": "markup.inline.raw" }, 83 | { "foreground": "#808080", "token": "punctuation.definition.tag" }, 84 | { "foreground": "#569cd6", "token": "meta.preprocessor" }, 85 | { "foreground": "#569cd6", "token": "entity.name.function.preprocessor" }, 86 | { "foreground": "#ce9178", "token": "meta.preprocessor.string" }, 87 | { "foreground": "#b5cea8", "token": "meta.preprocessor.numeric" }, 88 | { 89 | "foreground": "#9cdcfe", 90 | "token": "meta.structure.dictionary.key.python" 91 | }, 92 | { "foreground": "#569cd6", "token": "meta.diff.header" }, 93 | { "foreground": "#569cd6", "token": "storage" }, 94 | { "foreground": "#569cd6", "token": "storage.type" }, 95 | { "foreground": "#569cd6", "token": "storage.modifier" }, 96 | { "foreground": "#569cd6", "token": "keyword.operator.noexcept" }, 97 | { "foreground": "#ce9178", "token": "string" }, 98 | { "foreground": "#ce9178", "token": "meta.embedded.assembly" }, 99 | { "foreground": "#ce9178", "token": "string.tag" }, 100 | { "foreground": "#ce9178", "token": "string.value" }, 101 | { "foreground": "#d16969", "token": "string.regexp" }, 102 | { 103 | "foreground": "#569cd6", 104 | "token": "punctuation.definition.template-expression.begin" 105 | }, 106 | { 107 | "foreground": "#569cd6", 108 | "token": "punctuation.definition.template-expression.end" 109 | }, 110 | { "foreground": "#569cd6", "token": "punctuation.section.embedded" }, 111 | { "foreground": "#d4d4d4", "token": "meta.template.expression" }, 112 | { "foreground": "#9cdcfe", "token": "support.type.vendored.property-name" }, 113 | { "foreground": "#9cdcfe", "token": "support.type.property-name" }, 114 | { "foreground": "#9cdcfe", "token": "variable.css" }, 115 | { "foreground": "#9cdcfe", "token": "variable.scss" }, 116 | { "foreground": "#9cdcfe", "token": "variable.other.less" }, 117 | { "foreground": "#9cdcfe", "token": "source.coffee.embedded" }, 118 | { "foreground": "#569cd6", "token": "keyword" }, 119 | { "foreground": "#569cd6", "token": "keyword.control" }, 120 | { "foreground": "#d4d4d4", "token": "keyword.operator" }, 121 | { "foreground": "#569cd6", "token": "keyword.operator.new" }, 122 | { "foreground": "#569cd6", "token": "keyword.operator.expression" }, 123 | { "foreground": "#569cd6", "token": "keyword.operator.cast" }, 124 | { "foreground": "#569cd6", "token": "keyword.operator.sizeof" }, 125 | { "foreground": "#569cd6", "token": "keyword.operator.alignof" }, 126 | { "foreground": "#569cd6", "token": "keyword.operator.typeid" }, 127 | { "foreground": "#569cd6", "token": "keyword.operator.alignas" }, 128 | { "foreground": "#569cd6", "token": "keyword.operator.instanceof" }, 129 | { "foreground": "#569cd6", "token": "keyword.operator.logical.python" }, 130 | { "foreground": "#569cd6", "token": "keyword.operator.wordlike" }, 131 | { "foreground": "#b5cea8", "token": "keyword.other.unit" }, 132 | { 133 | "foreground": "#569cd6", 134 | "token": "punctuation.section.embedded.begin.php" 135 | }, 136 | { 137 | "foreground": "#569cd6", 138 | "token": "punctuation.section.embedded.end.php" 139 | }, 140 | { "foreground": "#9cdcfe", "token": "support.function.git-rebase" }, 141 | { "foreground": "#b5cea8", "token": "constant.sha.git-rebase" }, 142 | { "foreground": "#d4d4d4", "token": "storage.modifier.import.java" }, 143 | { "foreground": "#d4d4d4", "token": "variable.language.wildcard.java" }, 144 | { "foreground": "#d4d4d4", "token": "storage.modifier.package.java" }, 145 | { "foreground": "#569cd6", "token": "variable.language" }, 146 | { "foreground": "#DCDCAA", "token": "entity.name.function" }, 147 | { "foreground": "#DCDCAA", "token": "support.function" }, 148 | { "foreground": "#DCDCAA", "token": "support.constant.handlebars" }, 149 | { 150 | "foreground": "#DCDCAA", 151 | "token": "source.powershell variable.other.member" 152 | }, 153 | { "foreground": "#DCDCAA", "token": "entity.name.operator.custom-literal" }, 154 | { "foreground": "#4EC9B0", "token": "meta.return-type" }, 155 | { "foreground": "#4EC9B0", "token": "support.class" }, 156 | { "foreground": "#4EC9B0", "token": "support.type" }, 157 | { "foreground": "#4EC9B0", "token": "entity.name.type" }, 158 | { "foreground": "#4EC9B0", "token": "entity.name.namespace" }, 159 | { "foreground": "#4EC9B0", "token": "entity.other.attribute" }, 160 | { "foreground": "#4EC9B0", "token": "entity.name.scope-resolution" }, 161 | { "foreground": "#4EC9B0", "token": "entity.name.class" }, 162 | { "foreground": "#4EC9B0", "token": "storage.type.numeric.go" }, 163 | { "foreground": "#4EC9B0", "token": "storage.type.byte.go" }, 164 | { "foreground": "#4EC9B0", "token": "storage.type.boolean.go" }, 165 | { "foreground": "#4EC9B0", "token": "storage.type.string.go" }, 166 | { "foreground": "#4EC9B0", "token": "storage.type.uintptr.go" }, 167 | { "foreground": "#4EC9B0", "token": "storage.type.error.go" }, 168 | { "foreground": "#4EC9B0", "token": "storage.type.rune.go" }, 169 | { "foreground": "#4EC9B0", "token": "storage.type.cs" }, 170 | { "foreground": "#4EC9B0", "token": "storage.type.generic.cs" }, 171 | { "foreground": "#4EC9B0", "token": "storage.type.modifier.cs" }, 172 | { "foreground": "#4EC9B0", "token": "storage.type.variable.cs" }, 173 | { "foreground": "#4EC9B0", "token": "storage.type.annotation.java" }, 174 | { "foreground": "#4EC9B0", "token": "storage.type.generic.java" }, 175 | { "foreground": "#4EC9B0", "token": "storage.type.java" }, 176 | { "foreground": "#4EC9B0", "token": "storage.type.object.array.java" }, 177 | { "foreground": "#4EC9B0", "token": "storage.type.primitive.array.java" }, 178 | { "foreground": "#4EC9B0", "token": "storage.type.primitive.java" }, 179 | { "foreground": "#4EC9B0", "token": "storage.type.token.java" }, 180 | { "foreground": "#4EC9B0", "token": "storage.type.groovy" }, 181 | { "foreground": "#4EC9B0", "token": "storage.type.annotation.groovy" }, 182 | { "foreground": "#4EC9B0", "token": "storage.type.parameters.groovy" }, 183 | { "foreground": "#4EC9B0", "token": "storage.type.generic.groovy" }, 184 | { "foreground": "#4EC9B0", "token": "storage.type.object.array.groovy" }, 185 | { "foreground": "#4EC9B0", "token": "storage.type.primitive.array.groovy" }, 186 | { "foreground": "#4EC9B0", "token": "storage.type.primitive.groovy" }, 187 | { "foreground": "#4EC9B0", "token": "meta.type.cast.expr" }, 188 | { "foreground": "#4EC9B0", "token": "meta.type.new.expr" }, 189 | { "foreground": "#4EC9B0", "token": "support.constant.math" }, 190 | { "foreground": "#4EC9B0", "token": "support.constant.dom" }, 191 | { "foreground": "#4EC9B0", "token": "support.constant.json" }, 192 | { "foreground": "#4EC9B0", "token": "entity.other.inherited-class" }, 193 | { "foreground": "#C586C0", "token": "keyword.control" }, 194 | { "foreground": "#C586C0", "token": "source.cpp keyword.operator.new" }, 195 | { "foreground": "#C586C0", "token": "keyword.operator.delete" }, 196 | { "foreground": "#C586C0", "token": "keyword.other.using" }, 197 | { "foreground": "#C586C0", "token": "keyword.other.operator" }, 198 | { "foreground": "#C586C0", "token": "entity.name.operator" }, 199 | { "foreground": "#9CDCFE", "token": "variable" }, 200 | { "foreground": "#9CDCFE", "token": "meta.definition.variable.name" }, 201 | { "foreground": "#9CDCFE", "token": "support.variable" }, 202 | { "foreground": "#9CDCFE", "token": "entity.name.variable" }, 203 | { "foreground": "#4FC1FF", "token": "variable.other.constant" }, 204 | { "foreground": "#4FC1FF", "token": "variable.other.enummember" }, 205 | { "foreground": "#9CDCFE", "token": "meta.object-literal.key" }, 206 | { "foreground": "#CE9178", "token": "support.constant.property-value" }, 207 | { "foreground": "#CE9178", "token": "support.constant.font-name" }, 208 | { "foreground": "#CE9178", "token": "support.constant.media-type" }, 209 | { "foreground": "#CE9178", "token": "support.constant.media" }, 210 | { "foreground": "#CE9178", "token": "constant.other.color.rgb-value" }, 211 | { "foreground": "#CE9178", "token": "constant.other.rgb-value" }, 212 | { "foreground": "#CE9178", "token": "support.constant.color" }, 213 | { "foreground": "#CE9178", "token": "punctuation.definition.group.regexp" }, 214 | { 215 | "foreground": "#CE9178", 216 | "token": "punctuation.definition.group.assertion.regexp" 217 | }, 218 | { 219 | "foreground": "#CE9178", 220 | "token": "punctuation.definition.character-class.regexp" 221 | }, 222 | { 223 | "foreground": "#CE9178", 224 | "token": "punctuation.character.set.begin.regexp" 225 | }, 226 | { 227 | "foreground": "#CE9178", 228 | "token": "punctuation.character.set.end.regexp" 229 | }, 230 | { "foreground": "#CE9178", "token": "keyword.operator.negation.regexp" }, 231 | { "foreground": "#CE9178", "token": "support.other.parenthesis.regexp" }, 232 | { 233 | "foreground": "#d16969", 234 | "token": "constant.character.character-class.regexp" 235 | }, 236 | { 237 | "foreground": "#d16969", 238 | "token": "constant.other.character-class.set.regexp" 239 | }, 240 | { 241 | "foreground": "#d16969", 242 | "token": "constant.other.character-class.regexp" 243 | }, 244 | { "foreground": "#d16969", "token": "constant.character.set.regexp" }, 245 | { "foreground": "#DCDCAA", "token": "keyword.operator.or.regexp" }, 246 | { "foreground": "#DCDCAA", "token": "keyword.control.anchor.regexp" }, 247 | { "foreground": "#d7ba7d", "token": "keyword.operator.quantifier.regexp" }, 248 | { "foreground": "#569cd6", "token": "constant.character" }, 249 | { "foreground": "#d7ba7d", "token": "constant.character.escape" }, 250 | { "foreground": "#C8C8C8", "token": "entity.name.label" } 251 | ], 252 | "encodedTokensColors": [] 253 | } 254 | -------------------------------------------------------------------------------- /packages/solid-repl/src/components/editor/vs_light_good.json: -------------------------------------------------------------------------------- 1 | { 2 | "inherit": false, 3 | "base": "vs", 4 | "colors": { 5 | "activityBar.activeBorder": "#f9826c", 6 | "activityBar.background": "#ffffff", 7 | "activityBar.border": "#e1e4e8", 8 | "activityBar.foreground": "#2f363d", 9 | "activityBar.inactiveForeground": "#959da5", 10 | "activityBarBadge.background": "#2188ff", 11 | "activityBarBadge.foreground": "#ffffff", 12 | "badge.background": "#dbedff", 13 | "badge.foreground": "#005cc5", 14 | "breadcrumb.activeSelectionForeground": "#586069", 15 | "breadcrumb.focusForeground": "#2f363d", 16 | "breadcrumb.foreground": "#6a737d", 17 | "breadcrumbPicker.background": "#fafbfc", 18 | "button.background": "#159739", 19 | "button.foreground": "#ffffff", 20 | "button.hoverBackground": "#138934", 21 | "checkbox.background": "#fafbfc", 22 | "checkbox.border": "#d1d5da", 23 | "debugToolBar.background": "#ffffff", 24 | "descriptionForeground": "#6a737d", 25 | "diffEditor.insertedTextBackground": "#34d05822", 26 | "diffEditor.removedTextBackground": "#d73a4922", 27 | "dropdown.background": "#fafbfc", 28 | "dropdown.border": "#e1e4e8", 29 | "dropdown.foreground": "#2f363d", 30 | "dropdown.listBackground": "#ffffff", 31 | "editor.background": "#ffffff", 32 | "editor.findMatchBackground": "#ffdf5d", 33 | "editor.findMatchHighlightBackground": "#ffdf5d66", 34 | "editor.focusedStackFrameHighlightBackground": "#fff5b1", 35 | "editor.foldBackground": "#fafbfc", 36 | "editor.foreground": "#24292e", 37 | "editor.inactiveSelectionBackground": "#0366d611", 38 | "editor.lineHighlightBackground": "#f6f8fa", 39 | "editor.selectionBackground": "#0366d625", 40 | "editor.selectionHighlightBackground": "#34d05840", 41 | "editor.selectionHighlightBorder": "#34d05800", 42 | "editor.stackFrameHighlightBackground": "#fffbdd", 43 | "editor.wordHighlightBackground": "#34d05800", 44 | "editor.wordHighlightBorder": "#24943e99", 45 | "editor.wordHighlightStrongBackground": "#34d05800", 46 | "editor.wordHighlightStrongBorder": "#24943e50", 47 | "editorBracketMatch.background": "#34d05840", 48 | "editorBracketMatch.border": "#34d05800", 49 | "editorCursor.foreground": "#044289", 50 | "editorGroup.border": "#e1e4e8", 51 | "editorGroupHeader.tabsBackground": "#f6f8fa", 52 | "editorGroupHeader.tabsBorder": "#e1e4e8", 53 | "editorGutter.addedBackground": "#28a745", 54 | "editorGutter.deletedBackground": "#d73a49", 55 | "editorGutter.modifiedBackground": "#2188ff", 56 | "editorIndentGuide.activeBackground": "#d7dbe0", 57 | "editorIndentGuide.background": "#eff2f6", 58 | "editorLineNumber.activeForeground": "#24292e", 59 | "editorLineNumber.foreground": "#1b1f234d", 60 | "editorOverviewRuler.border": "#ffffff", 61 | "editorWhitespace.foreground": "#d1d5da", 62 | "editorWidget.background": "#f6f8fa", 63 | "errorForeground": "#cb2431", 64 | "focusBorder": "#2188ff", 65 | "foreground": "#444d56", 66 | "gitDecoration.addedResourceForeground": "#28a745", 67 | "gitDecoration.conflictingResourceForeground": "#e36209", 68 | "gitDecoration.deletedResourceForeground": "#d73a49", 69 | "gitDecoration.ignoredResourceForeground": "#959da5", 70 | "gitDecoration.modifiedResourceForeground": "#005cc5", 71 | "gitDecoration.submoduleResourceForeground": "#959da5", 72 | "gitDecoration.untrackedResourceForeground": "#28a745", 73 | "input.background": "#fafbfc", 74 | "input.border": "#e1e4e8", 75 | "input.foreground": "#2f363d", 76 | "input.placeholderForeground": "#959da5", 77 | "list.activeSelectionBackground": "#e2e5e9", 78 | "list.activeSelectionForeground": "#2f363d", 79 | "list.focusBackground": "#cce5ff", 80 | "list.hoverBackground": "#ebf0f4", 81 | "list.hoverForeground": "#2f363d", 82 | "list.inactiveFocusBackground": "#dbedff", 83 | "list.inactiveSelectionBackground": "#e8eaed", 84 | "list.inactiveSelectionForeground": "#2f363d", 85 | "notificationCenterHeader.background": "#e1e4e8", 86 | "notificationCenterHeader.foreground": "#6a737d", 87 | "notifications.background": "#fafbfc", 88 | "notifications.border": "#e1e4e8", 89 | "notifications.foreground": "#2f363d", 90 | "notificationsErrorIcon.foreground": "#d73a49", 91 | "notificationsInfoIcon.foreground": "#005cc5", 92 | "notificationsWarningIcon.foreground": "#e36209", 93 | "panel.background": "#f6f8fa", 94 | "panel.border": "#e1e4e8", 95 | "panelInput.border": "#e1e4e8", 96 | "panelTitle.activeBorder": "#f9826c", 97 | "panelTitle.activeForeground": "#2f363d", 98 | "panelTitle.inactiveForeground": "#6a737d", 99 | "pickerGroup.border": "#e1e4e8", 100 | "pickerGroup.foreground": "#2f363d", 101 | "progressBar.background": "#2188ff", 102 | "quickInput.background": "#fafbfc", 103 | "quickInput.foreground": "#2f363d", 104 | "scrollbar.shadow": "#6a737d33", 105 | "scrollbarSlider.activeBackground": "#959da588", 106 | "scrollbarSlider.background": "#959da533", 107 | "scrollbarSlider.hoverBackground": "#959da544", 108 | "settings.headerForeground": "#2f363d", 109 | "settings.modifiedItemIndicator": "#2188ff", 110 | "sideBar.background": "#f6f8fa", 111 | "sideBar.border": "#e1e4e8", 112 | "sideBar.foreground": "#586069", 113 | "sideBarSectionHeader.background": "#f6f8fa", 114 | "sideBarSectionHeader.border": "#e1e4e8", 115 | "sideBarSectionHeader.foreground": "#2f363d", 116 | "sideBarTitle.foreground": "#2f363d", 117 | "statusBar.background": "#ffffff", 118 | "statusBar.border": "#e1e4e8", 119 | "statusBar.debuggingBackground": "#f9826c", 120 | "statusBar.debuggingForeground": "#ffffff", 121 | "statusBar.foreground": "#586069", 122 | "statusBar.noFolderBackground": "#ffffff", 123 | "statusBarItem.prominentBackground": "#e8eaed", 124 | "tab.activeBackground": "#ffffff", 125 | "tab.activeBorder": "#ffffff", 126 | "tab.activeBorderTop": "#f9826c", 127 | "tab.activeForeground": "#2f363d", 128 | "tab.border": "#e1e4e8", 129 | "tab.hoverBackground": "#ffffff", 130 | "tab.inactiveBackground": "#f6f8fa", 131 | "tab.inactiveForeground": "#6a737d", 132 | "tab.unfocusedActiveBorder": "#ffffff", 133 | "tab.unfocusedActiveBorderTop": "#e1e4e8", 134 | "tab.unfocusedHoverBackground": "#ffffff", 135 | "terminal.foreground": "#586069", 136 | "textBlockQuote.background": "#fafbfc", 137 | "textBlockQuote.border": "#e1e4e8", 138 | "textCodeBlock.background": "#f6f8fa", 139 | "textLink.activeForeground": "#005cc5", 140 | "textLink.foreground": "#0366d6", 141 | "textPreformat.foreground": "#586069", 142 | "textSeparator.foreground": "#d1d5da", 143 | "titleBar.activeBackground": "#ffffff", 144 | "titleBar.activeForeground": "#2f363d", 145 | "titleBar.border": "#e1e4e8", 146 | "titleBar.inactiveBackground": "#f6f8fa", 147 | "titleBar.inactiveForeground": "#6a737d", 148 | "tree.indentGuidesStroke": "#e1e4e8", 149 | "welcomePage.buttonBackground": "#f6f8fa", 150 | "welcomePage.buttonHoverBackground": "#e1e4e8" 151 | }, 152 | "rules": [ 153 | { "foreground": "#6A737D", "token": "comment" }, 154 | { "foreground": "#6A737D", "token": "punctuation.definition.comment" }, 155 | { "foreground": "#6A737D", "token": "string.comment" }, 156 | { "foreground": "#005CC5", "token": "constant" }, 157 | { "foreground": "#005CC5", "token": "entity.name.constant" }, 158 | { "foreground": "#005CC5", "token": "variable.other.constant" }, 159 | { "foreground": "#005CC5", "token": "variable.language" }, 160 | { "foreground": "#6F42C1", "token": "entity" }, 161 | { "foreground": "#6F42C1", "token": "entity.name" }, 162 | { "foreground": "#24292E", "token": "variable.parameter.function" }, 163 | { "foreground": "#22863A", "token": "entity.name.tag" }, 164 | { "foreground": "#D73A49", "token": "keyword" }, 165 | { "foreground": "#D73A49", "token": "storage" }, 166 | { "foreground": "#D73A49", "token": "storage.type" }, 167 | { "foreground": "#24292E", "token": "storage.modifier.package" }, 168 | { "foreground": "#24292E", "token": "storage.modifier.import" }, 169 | { "foreground": "#24292E", "token": "storage.type.java" }, 170 | { "foreground": "#032F62", "token": "string" }, 171 | { "foreground": "#032F62", "token": "punctuation.definition.string" }, 172 | { 173 | "foreground": "#032F62", 174 | "token": "string punctuation.section.embedded source" 175 | }, 176 | { "foreground": "#005CC5", "token": "support" }, 177 | { "foreground": "#005CC5", "token": "meta.property-name" }, 178 | { "foreground": "#E36209", "token": "variable" }, 179 | { "foreground": "#24292E", "token": "variable.other" }, 180 | { 181 | "foreground": "#B31D28", 182 | "fontStyle": "italic", 183 | "token": "invalid.broken" 184 | }, 185 | { 186 | "foreground": "#B31D28", 187 | "fontStyle": "italic", 188 | "token": "invalid.deprecated" 189 | }, 190 | { 191 | "foreground": "#B31D28", 192 | "fontStyle": "italic", 193 | "token": "invalid.illegal" 194 | }, 195 | { 196 | "foreground": "#B31D28", 197 | "fontStyle": "italic", 198 | "token": "invalid.unimplemented" 199 | }, 200 | { 201 | "foreground": "#FAFBFC", 202 | "background": "#D73A49", 203 | "fontStyle": "italic underline", 204 | "token": "carriage-return" 205 | }, 206 | { "foreground": "#B31D28", "token": "message.error" }, 207 | { "foreground": "#24292E", "token": "string source" }, 208 | { "foreground": "#005CC5", "token": "string variable" }, 209 | { "foreground": "#032F62", "token": "source.regexp" }, 210 | { "foreground": "#032F62", "token": "string.regexp" }, 211 | { "foreground": "#032F62", "token": "string.regexp.character-class" }, 212 | { 213 | "foreground": "#032F62", 214 | "token": "string.regexp constant.character.escape" 215 | }, 216 | { 217 | "foreground": "#032F62", 218 | "token": "string.regexp source.ruby.embedded" 219 | }, 220 | { 221 | "foreground": "#032F62", 222 | "token": "string.regexp string.regexp.arbitrary-repitition" 223 | }, 224 | { 225 | "foreground": "#22863A", 226 | "fontStyle": "bold", 227 | "token": "string.regexp constant.character.escape" 228 | }, 229 | { "foreground": "#005CC5", "token": "support.constant" }, 230 | { "foreground": "#005CC5", "token": "support.variable" }, 231 | { "foreground": "#005CC5", "token": "meta.module-reference" }, 232 | { 233 | "foreground": "#E36209", 234 | "token": "punctuation.definition.list.begin.markdown" 235 | }, 236 | { 237 | "foreground": "#005CC5", 238 | "fontStyle": "bold", 239 | "token": "markup.heading" 240 | }, 241 | { 242 | "foreground": "#005CC5", 243 | "fontStyle": "bold", 244 | "token": "markup.heading entity.name" 245 | }, 246 | { "foreground": "#22863A", "token": "markup.quote" }, 247 | { 248 | "foreground": "#24292E", 249 | "fontStyle": "italic", 250 | "token": "markup.italic" 251 | }, 252 | { "foreground": "#24292E", "fontStyle": "bold", "token": "markup.bold" }, 253 | { "foreground": "#005CC5", "token": "markup.raw" }, 254 | { 255 | "foreground": "#B31D28", 256 | "background": "#FFEEF0", 257 | "token": "markup.deleted" 258 | }, 259 | { 260 | "foreground": "#B31D28", 261 | "background": "#FFEEF0", 262 | "token": "meta.diff.header.from-file" 263 | }, 264 | { 265 | "foreground": "#B31D28", 266 | "background": "#FFEEF0", 267 | "token": "punctuation.definition.deleted" 268 | }, 269 | { 270 | "foreground": "#22863A", 271 | "background": "#F0FFF4", 272 | "token": "markup.inserted" 273 | }, 274 | { 275 | "foreground": "#22863A", 276 | "background": "#F0FFF4", 277 | "token": "meta.diff.header.to-file" 278 | }, 279 | { 280 | "foreground": "#22863A", 281 | "background": "#F0FFF4", 282 | "token": "punctuation.definition.inserted" 283 | }, 284 | { 285 | "foreground": "#E36209", 286 | "background": "#FFEBDA", 287 | "token": "markup.changed" 288 | }, 289 | { 290 | "foreground": "#E36209", 291 | "background": "#FFEBDA", 292 | "token": "punctuation.definition.changed" 293 | }, 294 | { 295 | "foreground": "#F6F8FA", 296 | "background": "#005CC5", 297 | "token": "markup.ignored" 298 | }, 299 | { 300 | "foreground": "#F6F8FA", 301 | "background": "#005CC5", 302 | "token": "markup.untracked" 303 | }, 304 | { 305 | "foreground": "#6F42C1", 306 | "fontStyle": "bold", 307 | "token": "meta.diff.range" 308 | }, 309 | { "foreground": "#005CC5", "token": "meta.diff.header" }, 310 | { 311 | "foreground": "#005CC5", 312 | "fontStyle": "bold", 313 | "token": "meta.separator" 314 | }, 315 | { "foreground": "#005CC5", "token": "meta.output" }, 316 | { "foreground": "#586069", "token": "brackethighlighter.tag" }, 317 | { "foreground": "#586069", "token": "brackethighlighter.curly" }, 318 | { "foreground": "#586069", "token": "brackethighlighter.round" }, 319 | { "foreground": "#586069", "token": "brackethighlighter.square" }, 320 | { "foreground": "#586069", "token": "brackethighlighter.angle" }, 321 | { "foreground": "#586069", "token": "brackethighlighter.quote" }, 322 | { "foreground": "#B31D28", "token": "brackethighlighter.unmatched" }, 323 | { 324 | "foreground": "#032F62", 325 | "fontStyle": "underline", 326 | "token": "constant.other.reference.link" 327 | }, 328 | { 329 | "foreground": "#032F62", 330 | "fontStyle": "underline", 331 | "token": "string.other.link" 332 | }, 333 | { "foreground": "#316BCD", "token": "token.info-token" }, 334 | { "foreground": "#CD9731", "token": "token.warn-token" }, 335 | { "foreground": "#CD3131", "token": "token.error-token" }, 336 | { "foreground": "#800080", "token": "token.debug-token" } 337 | ], 338 | "encodedTokensColors": [] 339 | } 340 | -------------------------------------------------------------------------------- /packages/solid-repl/src/components/error.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createEffect, createSignal } from 'solid-js'; 2 | 3 | import { Icon } from 'solid-heroicons'; 4 | import { chevronDown, chevronRight } from 'solid-heroicons/solid'; 5 | 6 | function doSomethingWithError(message: string) { 7 | const [firstLine, setFirstLine] = createSignal(''); 8 | const [stackTrace, setStackTrace] = createSignal(''); 9 | 10 | createEffect(() => { 11 | const [first, ...stack] = message.split('\n'); 12 | setFirstLine(first); 13 | setStackTrace(stack.join('\n')); 14 | }); 15 | 16 | return [firstLine, stackTrace] as const; 17 | } 18 | 19 | export const Error: Component<{ 20 | onDismiss: (...args: unknown[]) => unknown; 21 | message: string; 22 | }> = (props) => { 23 | const [firstLine, stackTrace] = doSomethingWithError(props.message); 24 | const [isOpen, setIsOpen] = createSignal(false); 25 | 26 | return ( 27 |
setIsOpen(event.currentTarget.open)} 30 | > 31 | 32 | 33 | 34 | 35 | 36 |
37 |         
38 |       
39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /packages/solid-repl/src/components/gridResizer.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createSignal, createEffect, onCleanup } from 'solid-js'; 2 | import { throttle } from '@solid-primitives/scheduled'; 3 | import { isFirefox } from '@solid-primitives/platform'; 4 | 5 | const Dot: Component<{ isDragging: boolean }> = (props) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | 17 | type SolidRef = (el: HTMLDivElement) => void; 18 | 19 | export const GridResizer: Component<{ 20 | ref: HTMLDivElement | SolidRef; 21 | isHorizontal: boolean; 22 | onResize: (clientX: number, clientY: number) => void; 23 | }> = (props) => { 24 | const [isDragging, setIsDragging] = createSignal(false); 25 | 26 | const onResizeStart = () => setIsDragging(true); 27 | const onResizeEnd = () => setIsDragging(false); 28 | 29 | const onMouseMove = throttle((e: MouseEvent) => { 30 | props.onResize(e.clientX, e.clientY); 31 | }, 10); 32 | 33 | const onTouchMove = throttle((e: TouchEvent) => { 34 | const touch = e.touches[0]; 35 | props.onResize(touch.clientX, touch.clientY); 36 | }, 10); 37 | 38 | const setRef = (el: HTMLDivElement) => { 39 | (props.ref as SolidRef)(el); 40 | 41 | el.addEventListener('mousedown', onResizeStart, { passive: true }); 42 | el.addEventListener('touchstart', onResizeStart, { passive: true }); 43 | 44 | onCleanup(() => { 45 | el.removeEventListener('mousedown', onResizeStart); 46 | el.removeEventListener('touchstart', onResizeStart); 47 | }); 48 | }; 49 | 50 | createEffect(() => { 51 | if (isDragging()) { 52 | // Fixes Firefox issue where dragging cursor fails to emit events to overlay, and instead to iframe, resulting in resizer bar not moving. 53 | if (isFirefox) { 54 | document.querySelectorAll('iframe').forEach((el) => (el.style.pointerEvents = 'none')); 55 | } 56 | 57 | window.addEventListener('mousemove', onMouseMove); 58 | window.addEventListener('mouseup', onResizeEnd); 59 | window.addEventListener('touchmove', onTouchMove); 60 | window.addEventListener('touchend', onResizeEnd); 61 | } else { 62 | if (isFirefox) { 63 | document.querySelectorAll('iframe').forEach((el) => (el.style.pointerEvents = '')); 64 | } 65 | 66 | window.removeEventListener('mousemove', onMouseMove); 67 | window.removeEventListener('mouseup', onResizeEnd); 68 | window.removeEventListener('touchmove', onTouchMove); 69 | window.removeEventListener('touchend', onResizeEnd); 70 | } 71 | }); 72 | 73 | return ( 74 |
84 |
92 | 93 | 94 | 95 |
96 | ); 97 | }; 98 | -------------------------------------------------------------------------------- /packages/solid-repl/src/components/preview.tsx: -------------------------------------------------------------------------------- 1 | import { Component, JSX, Show, createEffect, createMemo, createSignal, onCleanup, untrack } from 'solid-js'; 2 | import { useZoom } from '../hooks/useZoom'; 3 | import { GridResizer } from './gridResizer'; 4 | 5 | const dispatchKeyboardEventToParentZoomState = () => ` 6 | document.addEventListener('keydown', (e) => { 7 | if (!(e.ctrlKey || e.metaKey)) return; 8 | if (!['=', '-'].includes(e.key)) return; 9 | 10 | const options = { 11 | key: e.key, 12 | ctrlKey: e.ctrlKey, 13 | metaKey: e.metaKey, 14 | }; 15 | const keyboardEvent = new KeyboardEvent('keydown', options); 16 | window.parent.document.dispatchEvent(keyboardEvent); 17 | 18 | e.preventDefault(); 19 | }, true); 20 | `; 21 | 22 | const generateHTML = (isDark: boolean, importMap: string) => ` 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 81 | 82 | 83 | 168 | 169 | 170 |
171 |

Loading the playground...

172 |
173 |
174 | 175 | 176 | `; 177 | 178 | const useDevtoolsSrc = () => { 179 | const html = ` 180 | 181 | 182 | 183 | DevTools 184 | 191 | 194 | 195 | 196 | 197 | `; 198 | const devtoolsRawUrl = URL.createObjectURL(new Blob([html], { type: 'text/html' })); 199 | onCleanup(() => URL.revokeObjectURL(devtoolsRawUrl)); 200 | return `${devtoolsRawUrl}#?embedded=${encodeURIComponent(location.origin)}`; 201 | }; 202 | 203 | export const Preview: Component = (props) => { 204 | const { zoomState } = useZoom(); 205 | 206 | let iframe!: HTMLIFrameElement; 207 | let devtoolsIframe!: HTMLIFrameElement; 208 | let resizer!: HTMLDivElement; 209 | let outerContainer!: HTMLDivElement; 210 | 211 | let devtoolsLoaded = false; 212 | let isIframeReady = false; 213 | 214 | // This is the createWriteable paradigm in action 215 | // We have that the iframe src is entangled with its loading state 216 | const iframeSrcUrl = createMemo(() => { 217 | const html = generateHTML( 218 | untrack(() => props.isDark), 219 | JSON.stringify({ imports: props.importMap }), 220 | ); 221 | const url = URL.createObjectURL( 222 | new Blob([html], { 223 | type: 'text/html', 224 | }), 225 | ); 226 | onCleanup(() => URL.revokeObjectURL(url)); 227 | return url; 228 | }); 229 | 230 | createEffect(() => { 231 | const dark = props.isDark; 232 | 233 | if (!isIframeReady) return; 234 | 235 | iframe.contentDocument!.documentElement.classList.toggle('dark', dark); 236 | }); 237 | 238 | createEffect(() => { 239 | if (!props.reloadSignal) return; 240 | 241 | isIframeReady = false; 242 | iframe.src = untrack(iframeSrcUrl); 243 | }); 244 | 245 | createEffect(() => { 246 | const code = props.code; 247 | 248 | if (!isIframeReady) return; 249 | 250 | iframe.contentWindow!.postMessage({ event: 'CODE_UPDATE', value: code }, '*'); 251 | }); 252 | 253 | const devtoolsSrc = useDevtoolsSrc(); 254 | 255 | const messageListener = (event: MessageEvent) => { 256 | if (event.source === iframe.contentWindow) { 257 | devtoolsIframe.contentWindow!.postMessage(event.data, '*'); 258 | } 259 | if (event.source === devtoolsIframe.contentWindow) { 260 | iframe.contentWindow!.postMessage({ event: 'DEV', data: event.data }, '*'); 261 | } 262 | }; 263 | window.addEventListener('message', messageListener); 264 | onCleanup(() => window.removeEventListener('message', messageListener)); 265 | 266 | const styleScale = (): JSX.CSSProperties => { 267 | if (zoomState.scale === 100 || !zoomState.scaleIframe) return {}; 268 | 269 | const sizePercentage = `${zoomState.scale}%`; 270 | const width = sizePercentage; 271 | const height = sizePercentage; 272 | const transform = `scale(${zoomState.zoom / 100})`; 273 | const transformOrigin = `0 0`; 274 | 275 | return { 276 | width, 277 | height, 278 | transform, 279 | 'transform-origin': transformOrigin, 280 | }; 281 | }; 282 | 283 | const [iframeHeight, setIframeHeight] = createSignal(0.625); 284 | 285 | const changeIframeHeight = (clientY: number) => { 286 | let position: number; 287 | let size: number; 288 | 289 | const rect = outerContainer.getBoundingClientRect(); 290 | 291 | position = clientY - rect.top - resizer.offsetHeight / 2; 292 | size = outerContainer.offsetHeight - resizer.offsetHeight; 293 | const percentage = position / size; 294 | 295 | setIframeHeight(percentage); 296 | }; 297 | 298 | createEffect(() => { 299 | localStorage.setItem('uiTheme', props.isDark ? '"dark"' : '"default"'); 300 | devtoolsIframe.contentWindow!.location.reload(); 301 | }); 302 | return ( 303 |
304 |
305 |