swap out monaco and babel-walk with codemirror and @babel/traverse' (#1)
Reviewed-on: #1
This commit is contained in:
commit
96535f9771
24 changed files with 478 additions and 291 deletions
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"useTabs": true,
|
"useTabs": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
"semi": false,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[install.scopes]
|
||||||
|
"@jsr" = "https://npm.jsr.io"
|
11
package.json
11
package.json
|
@ -15,20 +15,25 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/parser": "^7.26.5",
|
"@babel/parser": "^7.26.5",
|
||||||
|
"@babel/traverse": "^7.26.5",
|
||||||
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
|
"@std/async": "npm:@jsr/std__async",
|
||||||
"@sveltejs/adapter-static": "^3.0.6",
|
"@sveltejs/adapter-static": "^3.0.6",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"@tailwindcss/postcss": "^4.0.0-beta.9",
|
"@tailwindcss/postcss": "^4.0.0-beta.9",
|
||||||
"babel-walk": "^3.0.1",
|
"@types/babel__traverse": "^7.20.6",
|
||||||
"constrained-editor-plugin": "^1.3.0",
|
"@typescript/vfs": "^1.6.0",
|
||||||
|
"@valtown/codemirror-ts": "^2.3.1",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
"daisyui": "^5.0.0-beta.1",
|
"daisyui": "^5.0.0-beta.1",
|
||||||
"esbuild-wasm": "^0.24.2",
|
"esbuild-wasm": "^0.24.2",
|
||||||
"monaco-editor": "^0.52.2",
|
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.3.2",
|
||||||
"prettier-plugin-svelte": "^3.2.6",
|
"prettier-plugin-svelte": "^3.2.6",
|
||||||
"sprig": "^1.1.3",
|
"sprig": "^1.1.3",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
|
"svelte-codemirror-editor": "^1.4.1",
|
||||||
"tailwindcss": "^4.0.0-beta.9",
|
"tailwindcss": "^4.0.0-beta.9",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vite": "^5.4.11",
|
"vite": "^5.4.11",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
'@tailwindcss/postcss': {},
|
'@tailwindcss/postcss': {}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
14
src/app.css
14
src/app.css
|
@ -11,11 +11,11 @@
|
||||||
color utility to any element that depends on these defaults.
|
color utility to any element that depends on these defaults.
|
||||||
*/
|
*/
|
||||||
@layer base {
|
@layer base {
|
||||||
*,
|
*,
|
||||||
::after,
|
::after,
|
||||||
::before,
|
::before,
|
||||||
::backdrop,
|
::backdrop,
|
||||||
::file-selector-button {
|
::file-selector-button {
|
||||||
border-color: var(--color-gray-200, currentColor);
|
border-color: var(--color-gray-200, currentColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
2
src/app.d.ts
vendored
2
src/app.d.ts
vendored
|
@ -10,4 +10,4 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {}
|
||||||
|
|
|
@ -1,153 +1,39 @@
|
||||||
<script lang=ts>
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte'
|
||||||
import * as monaco from 'monaco-editor';
|
import { buildProject } from './build/esbuild'
|
||||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
import checkBabel, { setupBabel } from './text-editor/babel'
|
||||||
import {} from 'sprig/web';
|
|
||||||
import { constrainedEditor } from 'constrained-editor-plugin';
|
import { basicSetup, EditorView } from 'codemirror'
|
||||||
import { parse } from '@babel/parser';
|
|
||||||
import { buildProject } from './build/esbuild';
|
import CodeMirror from 'svelte-codemirror-editor'
|
||||||
|
import { env, typescriptExtensions } from './text-editor/extensions/typescript'
|
||||||
|
let { code = $bindable() }: { code: string } = $props()
|
||||||
|
let editor = $state<EditorView>();
|
||||||
|
let value = $state<string>(
|
||||||
|
`/// <reference types="lib.sprig.d.ts" />
|
||||||
|
async function game(api: sprig.FullSprigAPI): void {
|
||||||
|
// Code here
|
||||||
|
}`)
|
||||||
|
|
||||||
|
const extensions = [basicSetup, ...typescriptExtensions]
|
||||||
|
|
||||||
let editorContainer: HTMLDivElement;
|
|
||||||
let editor: import('monaco-editor').editor.IStandaloneCodeEditor;
|
|
||||||
// const monaco = await import ('monaco-editor')
|
|
||||||
// Initial code inside the function
|
|
||||||
function debounce<A, T = void>(func: (this: T, ...args: A[]) => any, timeout = 300) {
|
|
||||||
let timer: number;
|
|
||||||
return function (this: T, ...args: A[]) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
func.apply(this, args);
|
|
||||||
}, timeout);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let { code = $bindable() }: { code: string } = $props();
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
window.build = () => buildProject(code, true);
|
// @ts-expect-error: @babel/types uses process.env without `?.` (ironic that babel is only implemented to warn against using `?.` )
|
||||||
globalThis.process = {};
|
window.process = { env: {} }
|
||||||
globalThis.process.env = {};
|
// @ts-expect-error
|
||||||
const { recursive } = await import('babel-walk');
|
window.env = env
|
||||||
await addGlobalTypes();
|
|
||||||
setupEditor();
|
|
||||||
self.MonacoEnvironment = {
|
|
||||||
getWorker: function (_: any, label: string) {
|
|
||||||
return new tsWorker();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function addGlobalTypes() {
|
await setupBabel()
|
||||||
const sprigTypeDef = (await import('./editorcontext/sprig.d.ts?raw')).default;
|
})
|
||||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
|
||||||
sprigTypeDef,
|
|
||||||
'context/sprig.d.ts'
|
|
||||||
);
|
|
||||||
|
|
||||||
// IMPLEMENT ONCE v2 IS DONE!
|
|
||||||
// const sprigsyTypeDef = (await import('./editorcontext/sprigsy.d.ts?raw')).default;
|
|
||||||
// monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
|
||||||
// sprigsyTypeDef,
|
|
||||||
// 'context/sprigsy.d.ts'
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
const disallowedIdentifiers = ['window', 'globalThis'];
|
|
||||||
type ASTState = {
|
|
||||||
disallowedWarnings: { identifier: string; loc: [number, number, number, number] }[];
|
|
||||||
errors: { reason: string; loc: [number, number, number, number] }[];
|
|
||||||
};
|
|
||||||
const ASTWalker = recursive<ASTState>({
|
|
||||||
Identifier(node, state, c) {
|
|
||||||
if (disallowedIdentifiers.includes(node.name)) {
|
|
||||||
state.disallowedWarnings.push({
|
|
||||||
identifier: node.name,
|
|
||||||
loc: [
|
|
||||||
node.loc?.start?.line!,
|
|
||||||
node.loc?.start?.column!,
|
|
||||||
node.loc?.end?.line!,
|
|
||||||
node.loc?.end?.column!
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
MemberExpression(node, state, c) {
|
|
||||||
if (node.optional) {
|
|
||||||
state.errors.push({
|
|
||||||
reason: 'optional chaining (?.) will work in the editor but it will not work on the sprig console',
|
|
||||||
loc: [
|
|
||||||
node.loc?.start.line!,
|
|
||||||
node.loc?.start.column!,
|
|
||||||
node.loc?.end.line!,
|
|
||||||
node.loc?.end.column!
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const checkBabel = debounce(() => {
|
|
||||||
let ast;
|
|
||||||
try {
|
|
||||||
ast = parse(editor.getValue(), {
|
|
||||||
plugins: ['typescript']
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const state: ASTState = { disallowedWarnings: [], errors: [] };
|
|
||||||
ASTWalker(ast, state);
|
|
||||||
state.disallowedWarnings.forEach((v) => {
|
|
||||||
monaco.editor.setModelMarkers(editor.getModel()!, editor.getId(), [
|
|
||||||
{
|
|
||||||
message: `'${v.identifier}' is not allowed.`,
|
|
||||||
severity: monaco.MarkerSeverity.Error,
|
|
||||||
startLineNumber: v.loc[0],
|
|
||||||
startColumn: v.loc[1] + 1,
|
|
||||||
endLineNumber: v.loc[2],
|
|
||||||
endColumn: v.loc[3] + 1
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function setupEditor() {
|
|
||||||
editor = monaco.editor.create(editorContainer, {
|
|
||||||
value: `async function game(api: sprig.FullSprigAPI): void {
|
|
||||||
// Code here
|
|
||||||
}`,
|
|
||||||
language: 'typescript',
|
|
||||||
automaticLayout: true,
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
theme: 'vs-dark'
|
|
||||||
});
|
|
||||||
|
|
||||||
const model = editor.getModel()!;
|
|
||||||
const constrainedInstance = constrainedEditor(monaco);
|
|
||||||
constrainedInstance.initializeIn(editor);
|
|
||||||
constrainedInstance.addRestrictionsTo(model, [
|
|
||||||
{
|
|
||||||
range: [2, 1, 2, 14], // Range of Function definition
|
|
||||||
allowMultiline: true,
|
|
||||||
label: 'funcDefinition'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
model.updateValueInEditableRanges({
|
|
||||||
funcDefinition: code
|
|
||||||
});
|
|
||||||
editor.onDidChangeModelContent((event) => {
|
|
||||||
code = model.getValueInEditableRanges().funcDefinition;
|
|
||||||
checkBabel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (editor) {
|
|
||||||
editor.dispose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={editorContainer} class="editor-container"></div>
|
<!-- <div bind:this={editorContainer} class="editor-container"></div> -->
|
||||||
|
<CodeMirror on:ready={(e) => editor = e.detail} on:change={(e) => {
|
||||||
|
console.log(e)
|
||||||
|
}} bind:value {extensions} class="editor-container" />
|
||||||
|
|
||||||
<style lang=postcss>
|
<style lang="postcss">
|
||||||
.editor-container {
|
:global(.editor-container) {
|
||||||
@apply h-screen w-full;
|
@apply h-screen w-full;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
147
src/lib/CodeEditorOld.svelte
Normal file
147
src/lib/CodeEditorOld.svelte
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import { parse } from '@babel/parser'
|
||||||
|
import { buildProject } from './build/esbuild'
|
||||||
|
|
||||||
|
let editorContainer: HTMLDivElement
|
||||||
|
let editor: import('monaco-editor').editor.IStandaloneCodeEditor
|
||||||
|
// const monaco = await import ('monaco-editor')
|
||||||
|
// Initial code inside the function
|
||||||
|
function debounce<A, T = void>(func: (this: T, ...args: A[]) => any, timeout = 300) {
|
||||||
|
let timer: number
|
||||||
|
return function (this: T, ...args: A[]) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
func.apply(this, args)
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let { code = $bindable() }: { code: string } = $props()
|
||||||
|
onMount(async () => {
|
||||||
|
window.build = () => buildProject(code, true)
|
||||||
|
globalThis.process = {}
|
||||||
|
globalThis.process.env = {}
|
||||||
|
const { recursive } = await import('babel-walk')
|
||||||
|
await addGlobalTypes()
|
||||||
|
setupEditor()
|
||||||
|
self.MonacoEnvironment = {
|
||||||
|
getWorker: function (_: any, label: string) {
|
||||||
|
return new tsWorker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addGlobalTypes() {
|
||||||
|
const sprigTypeDef = (await import('./editorcontext/sprig.d.ts?raw')).default
|
||||||
|
monaco.languages.typescript.typescriptDefaults.addExtraLib(sprigTypeDef, 'context/sprig.d.ts')
|
||||||
|
|
||||||
|
// IMPLEMENT ONCE v2 IS DONE!
|
||||||
|
// const sprigsyTypeDef = (await import('./editorcontext/sprigsy.d.ts?raw')).default;
|
||||||
|
// monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||||
|
// sprigsyTypeDef,
|
||||||
|
// 'context/sprigsy.d.ts'
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
const disallowedIdentifiers = ['window', 'globalThis']
|
||||||
|
type ASTState = {
|
||||||
|
disallowedWarnings: { identifier: string; loc: [number, number, number, number] }[]
|
||||||
|
errors: { reason: string; loc: [number, number, number, number] }[]
|
||||||
|
}
|
||||||
|
const ASTWalker = recursive<ASTState>({
|
||||||
|
Identifier(node, state, c) {
|
||||||
|
if (disallowedIdentifiers.includes(node.name)) {
|
||||||
|
state.disallowedWarnings.push({
|
||||||
|
identifier: node.name,
|
||||||
|
loc: [
|
||||||
|
node.loc?.start?.line!,
|
||||||
|
node.loc?.start?.column!,
|
||||||
|
node.loc?.end?.line!,
|
||||||
|
node.loc?.end?.column!
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
MemberExpression(node, state, c) {
|
||||||
|
if (node.optional) {
|
||||||
|
state.errors.push({
|
||||||
|
reason:
|
||||||
|
'optional chaining (?.) will work in the editor but it will not work on the sprig console',
|
||||||
|
loc: [
|
||||||
|
node.loc?.start.line!,
|
||||||
|
node.loc?.start.column!,
|
||||||
|
node.loc?.end.line!,
|
||||||
|
node.loc?.end.column!
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const checkBabel = debounce(() => {
|
||||||
|
let ast
|
||||||
|
try {
|
||||||
|
ast = parse(editor.getValue(), {
|
||||||
|
plugins: ['typescript']
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const state: ASTState = { disallowedWarnings: [], errors: [] }
|
||||||
|
ASTWalker(ast, state)
|
||||||
|
state.disallowedWarnings.forEach((v) => {
|
||||||
|
monaco.editor.setModelMarkers(editor.getModel()!, editor.getId(), [
|
||||||
|
{
|
||||||
|
message: `'${v.identifier}' is not allowed.`,
|
||||||
|
severity: monaco.MarkerSeverity.Error,
|
||||||
|
startLineNumber: v.loc[0],
|
||||||
|
startColumn: v.loc[1] + 1,
|
||||||
|
endLineNumber: v.loc[2],
|
||||||
|
endColumn: v.loc[3] + 1
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function setupEditor() {
|
||||||
|
editor = monaco.editor.create(editorContainer, {
|
||||||
|
value: `async function game(api: sprig.FullSprigAPI): void {
|
||||||
|
// Code here
|
||||||
|
}`,
|
||||||
|
language: 'typescript',
|
||||||
|
automaticLayout: true,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
theme: 'vs-dark'
|
||||||
|
})
|
||||||
|
|
||||||
|
const model = editor.getModel()!
|
||||||
|
const constrainedInstance = constrainedEditor(monaco)
|
||||||
|
constrainedInstance.initializeIn(editor)
|
||||||
|
constrainedInstance.addRestrictionsTo(model, [
|
||||||
|
{
|
||||||
|
range: [2, 1, 2, 14], // Range of Function definition
|
||||||
|
allowMultiline: true,
|
||||||
|
label: 'funcDefinition'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
model.updateValueInEditableRanges({
|
||||||
|
funcDefinition: code
|
||||||
|
})
|
||||||
|
editor.onDidChangeModelContent((event) => {
|
||||||
|
code = model.getValueInEditableRanges().funcDefinition
|
||||||
|
checkBabel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (editor) {
|
||||||
|
editor.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={editorContainer} class="editor-container"></div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.editor-container {
|
||||||
|
@apply h-screen w-full;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,23 +1,23 @@
|
||||||
import { transform, initialize } from 'esbuild-wasm';
|
import { transform, initialize } from 'esbuild-wasm'
|
||||||
|
|
||||||
await initialize({
|
await initialize({
|
||||||
wasmURL: (await import('esbuild-wasm/esbuild.wasm?url')).default
|
wasmURL: (await import('esbuild-wasm/esbuild.wasm?url')).default
|
||||||
});
|
})
|
||||||
|
|
||||||
export async function buildProject(code: string, vanilla: boolean) {
|
export async function buildProject(code: string, vanilla: boolean) {
|
||||||
let template = '';
|
let template = ''
|
||||||
if (vanilla) {
|
if (vanilla) {
|
||||||
template += `async function game(api) {
|
template += `async function game(api) {
|
||||||
${code}
|
${code}
|
||||||
}
|
}
|
||||||
console.log("Made with Sprigsy")
|
console.log("Made with Sprigsy")
|
||||||
game({ addSprite, addText, afterInput, bitmap, clearInterval, clearText, clearTile, clearTimeout, color, getAll, getFirst, getGrid, getTile, height, map, onInput, playTune, setBackground, setInterval, setLegend, setMap, setPushables, setSolids, setTimeout, tilesWith, tune, width })
|
game({ addSprite, addText, afterInput, bitmap, clearInterval, clearText, clearTile, clearTimeout, color, getAll, getFirst, getGrid, getTile, height, map, onInput, playTune, setBackground, setInterval, setLegend, setMap, setPushables, setSolids, setTimeout, tilesWith, tune, width })
|
||||||
`;
|
`
|
||||||
} else {
|
} else {
|
||||||
throw new Error('not implemented');
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
return await transform(template, {
|
return await transform(template, {
|
||||||
loader: 'ts',
|
loader: 'ts'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte'
|
||||||
|
|
||||||
let {
|
let {
|
||||||
tab = $bindable(),
|
tab = $bindable(),
|
||||||
|
@ -7,17 +7,17 @@
|
||||||
map,
|
map,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
tab: string | undefined;
|
tab: string | undefined
|
||||||
disabled?: boolean;
|
disabled?: boolean
|
||||||
map?: Record<string, string>;
|
map?: Record<string, string>
|
||||||
class?: string;
|
class?: string
|
||||||
[x: `_${string}`]: Snippet<[]>;
|
[x: `_${string}`]: Snippet<[]>
|
||||||
} = $props();
|
} = $props()
|
||||||
const tabs = Object.fromEntries(
|
const tabs = Object.fromEntries(
|
||||||
Object.entries(props)
|
Object.entries(props)
|
||||||
.filter((v) => v[0].startsWith('_'))
|
.filter((v) => v[0].startsWith('_'))
|
||||||
.map(([name, snippet]): [string, Snippet<[]>] => [name.replace('_', ''), snippet])
|
.map(([name, snippet]): [string, Snippet<[]>] => [name.replace('_', ''), snippet])
|
||||||
);
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div role="tablist" class="tabs tabs-boxed {props.class}">
|
<div role="tablist" class="tabs tabs-boxed {props.class}">
|
||||||
|
|
79
src/lib/editorcontext/sprig.d.ts
vendored
79
src/lib/editorcontext/sprig.d.ts
vendored
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* normal sprig api
|
|
||||||
* this is provided because your project type is
|
|
||||||
*/
|
|
||||||
declare namespace sprig {
|
|
||||||
export declare const VALID_INPUTS: readonly ['w', 's', 'a', 'd', 'i', 'j', 'k', 'l'];
|
|
||||||
export type InputKey = (typeof VALID_INPUTS)[number];
|
|
||||||
export interface AddTextOptions {
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
color?: string;
|
|
||||||
}
|
|
||||||
export declare class SpriteType {
|
|
||||||
type: string;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
readonly dx: number;
|
|
||||||
readonly dy: number;
|
|
||||||
remove(): void;
|
|
||||||
}
|
|
||||||
export type Rgba = [number, number, number, number];
|
|
||||||
export interface TextElement {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
color: Rgba;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
export interface GameState {
|
|
||||||
legend: [string, string][];
|
|
||||||
texts: TextElement[];
|
|
||||||
dimensions: {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
sprites: SpriteType[];
|
|
||||||
solids: string[];
|
|
||||||
pushable: Record<string, string[]>;
|
|
||||||
background: string | null;
|
|
||||||
}
|
|
||||||
export interface PlayTuneRes {
|
|
||||||
end(): void;
|
|
||||||
isPlaying(): boolean;
|
|
||||||
}
|
|
||||||
// export declare const tones: Record<string, number>;
|
|
||||||
// export declare const instruments: readonly ["sine", "triangle", "square", "sawtooth"];
|
|
||||||
export type InstrumentType = (typeof instruments)[number];
|
|
||||||
// export declare const instrumentKey: Record<string, InstrumentType>;
|
|
||||||
// export declare const reverseInstrumentKey: Record<"sine" | "triangle" | "square" | "sawtooth", string>;
|
|
||||||
export type Tune = [number, ...(InstrumentType | number | string)[]][];
|
|
||||||
export interface FullSprigAPI {
|
|
||||||
map(template: TemplateStringsArray, ...params: string[]): string;
|
|
||||||
bitmap(template: TemplateStringsArray, ...params: string[]): string;
|
|
||||||
color(template: TemplateStringsArray, ...params: string[]): string;
|
|
||||||
tune(template: TemplateStringsArray, ...params: string[]): string;
|
|
||||||
setMap(string: string): void;
|
|
||||||
addText(str: string, opts?: AddTextOptions): void;
|
|
||||||
clearText(): void;
|
|
||||||
addSprite(x: number, y: number, type: string): void;
|
|
||||||
getGrid(): SpriteType[][];
|
|
||||||
getTile(x: number, y: number): SpriteType[];
|
|
||||||
tilesWith(...matchingTypes: string[]): SpriteType[][];
|
|
||||||
clearTile(x: number, y: number): void;
|
|
||||||
setSolids(types: string[]): void;
|
|
||||||
setPushables(map: Record<string, string[]>): void;
|
|
||||||
setBackground(type: string): void;
|
|
||||||
getFirst(type: string): SpriteType | undefined;
|
|
||||||
getAll(type?: string): SpriteType[];
|
|
||||||
width(): number;
|
|
||||||
height(): number;
|
|
||||||
setLegend(...bitmaps: [string, string][]): void;
|
|
||||||
onInput(key: InputKey, fn: () => void): void;
|
|
||||||
afterInput(fn: () => void): void;
|
|
||||||
playTune(text: string, n?: number): PlayTuneRes;
|
|
||||||
setTimeout(fn: TimerHandler, ms: number): number;
|
|
||||||
setInterval(fn: TimerHandler, ms: number): number;
|
|
||||||
clearTimeout(id: number): void;
|
|
||||||
clearInterval(id: number): void;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +1,26 @@
|
||||||
import { buildProject } from "$lib/build/esbuild"
|
import { buildProject } from '$lib/build/esbuild'
|
||||||
|
|
||||||
enum Capability {
|
enum Capability {
|
||||||
BuildToVite,
|
BuildToVite,
|
||||||
ProjectDB
|
ProjectDB
|
||||||
}
|
}
|
||||||
|
|
||||||
export default abstract class Provider {
|
export default abstract class Provider {
|
||||||
constructor(
|
constructor() {}
|
||||||
|
|
||||||
) {
|
abstract capabilities(): Capability[]
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* Builds HTML project
|
||||||
|
*/
|
||||||
|
async buildHTML(input: string): Promise<string> {
|
||||||
|
throw new TypeError('BuildToVite capability is not available')
|
||||||
|
}
|
||||||
|
|
||||||
abstract capabilities(): Capability[]
|
/**
|
||||||
|
* Build JS
|
||||||
/**
|
*/
|
||||||
* Builds HTML project
|
async buildJS(input: string): Promise<string> {
|
||||||
*/
|
return (await buildProject(input, true)).code
|
||||||
async buildHTML(input: string): Promise<string> {
|
}
|
||||||
throw new TypeError("BuildToVite capability is not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build JS
|
|
||||||
*/
|
|
||||||
async buildJS(input: string): Promise<string> {
|
|
||||||
return (await buildProject(input, true)).code
|
|
||||||
}
|
|
||||||
}
|
}
|
6
src/lib/text-editor/Tooltip.svelte
Normal file
6
src/lib/text-editor/Tooltip.svelte
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<script lang=ts>
|
||||||
|
import type { HoverInfo } from "@valtown/codemirror-ts"
|
||||||
|
|
||||||
|
let { info }: { info: HoverInfo } = $props();
|
||||||
|
console.log(info)
|
||||||
|
</script>
|
71
src/lib/text-editor/babel.ts
Normal file
71
src/lib/text-editor/babel.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { debounce } from '@std/async'
|
||||||
|
import { parse } from '@babel/parser'
|
||||||
|
|
||||||
|
let traverse: typeof import("@babel/traverse")['default']
|
||||||
|
|
||||||
|
export async function setupBabel() {
|
||||||
|
traverse = (await import("@babel/traverse")).default
|
||||||
|
}
|
||||||
|
|
||||||
|
const disallowedIdentifiers = ['window', 'globalThis']
|
||||||
|
type ASTState = {
|
||||||
|
disallowedWarnings: { identifier: string; loc: [number, number, number, number] }[]
|
||||||
|
errors: { reason: string; loc: [number, number, number, number] }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default debounce((code: string) => {
|
||||||
|
let ast
|
||||||
|
try {
|
||||||
|
ast = parse(code, {
|
||||||
|
plugins: ['typescript']
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const state: ASTState = { disallowedWarnings: [], errors: [] }
|
||||||
|
traverse(ast, {
|
||||||
|
MemberExpression: {
|
||||||
|
enter: (path, state) => {
|
||||||
|
if (path.node.optional) {
|
||||||
|
state.errors.push({
|
||||||
|
reason:
|
||||||
|
'optional chaining (?.) will work in the editor but it will not work on the sprig console',
|
||||||
|
loc: [
|
||||||
|
path.node.loc?.start.line!,
|
||||||
|
path.node.loc?.start.column!,
|
||||||
|
path.node.loc?.end.line!,
|
||||||
|
path.node.loc?.end.column!
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Identifier: {
|
||||||
|
enter: (path, state) => {
|
||||||
|
if (disallowedIdentifiers.includes(path.node.name)) {
|
||||||
|
state.disallowedWarnings.push({
|
||||||
|
identifier: path.node.name,
|
||||||
|
loc: [
|
||||||
|
path.node.loc?.start?.line!,
|
||||||
|
path.node.loc?.start?.column!,
|
||||||
|
path.node.loc?.end?.line!,
|
||||||
|
path.node.loc?.end?.column!
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, undefined, state)
|
||||||
|
state.disallowedWarnings.forEach((v) => {
|
||||||
|
monaco.editor.setModelMarkers(editor.getModel()!, editor.getId(), [
|
||||||
|
{
|
||||||
|
message: `'${v.identifier}' is not allowed.`,
|
||||||
|
severity: monaco.MarkerSeverity.Error,
|
||||||
|
startLineNumber: v.loc[0],
|
||||||
|
startColumn: v.loc[1] + 1,
|
||||||
|
endLineNumber: v.loc[2],
|
||||||
|
endColumn: v.loc[3] + 1
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}, 300)
|
84
src/lib/text-editor/context/sprig.d.ts
vendored
Normal file
84
src/lib/text-editor/context/sprig.d.ts
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* basic sprig API
|
||||||
|
* this is provided because your project type is base
|
||||||
|
*/
|
||||||
|
declare global {
|
||||||
|
declare namespace sprig {
|
||||||
|
export declare const VALID_INPUTS: readonly ['w', 's', 'a', 'd', 'i', 'j', 'k', 'l']
|
||||||
|
export type InputKey = (typeof VALID_INPUTS)[number]
|
||||||
|
export interface AddTextOptions {
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
export declare class SpriteType {
|
||||||
|
type: string
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
readonly dx: number
|
||||||
|
readonly dy: number
|
||||||
|
remove(): void
|
||||||
|
}
|
||||||
|
export type Rgba = [number, number, number, number]
|
||||||
|
export interface TextElement {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
color: Rgba
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
export interface GameState {
|
||||||
|
legend: [string, string][]
|
||||||
|
texts: TextElement[]
|
||||||
|
dimensions: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
sprites: SpriteType[]
|
||||||
|
solids: string[]
|
||||||
|
pushable: Record<string, string[]>
|
||||||
|
background: string | null
|
||||||
|
}
|
||||||
|
export interface PlayTuneRes {
|
||||||
|
end(): void
|
||||||
|
isPlaying(): boolean
|
||||||
|
}
|
||||||
|
// export declare const tones: Record<string, number>;
|
||||||
|
// export declare const instruments: readonly ["sine", "triangle", "square", "sawtooth"];
|
||||||
|
export type InstrumentType = (typeof instruments)[number]
|
||||||
|
// export declare const instrumentKey: Record<string, InstrumentType>;
|
||||||
|
// export declare const reverseInstrumentKey: Record<"sine" | "triangle" | "square" | "sawtooth", string>;
|
||||||
|
export type Tune = [number, ...(InstrumentType | number | string)[]][]
|
||||||
|
export interface FullSprigAPI {
|
||||||
|
map(template: TemplateStringsArray, ...params: string[]): string
|
||||||
|
bitmap(template: TemplateStringsArray, ...params: string[]): string
|
||||||
|
color(template: TemplateStringsArray, ...params: string[]): string
|
||||||
|
tune(template: TemplateStringsArray, ...params: string[]): string
|
||||||
|
setMap(string: string): void
|
||||||
|
addText(str: string, opts?: AddTextOptions): void
|
||||||
|
clearText(): void
|
||||||
|
addSprite(x: number, y: number, type: string): void
|
||||||
|
getGrid(): SpriteType[][]
|
||||||
|
getTile(x: number, y: number): SpriteType[]
|
||||||
|
tilesWith(...matchingTypes: string[]): SpriteType[][]
|
||||||
|
clearTile(x: number, y: number): void
|
||||||
|
setSolids(types: string[]): void
|
||||||
|
setPushables(map: Record<string, string[]>): void
|
||||||
|
setBackground(type: string): void
|
||||||
|
getFirst(type: string): SpriteType | undefined
|
||||||
|
getAll(type?: string): SpriteType[]
|
||||||
|
width(): number
|
||||||
|
height(): number
|
||||||
|
setLegend(...bitmaps: [string, string][]): void
|
||||||
|
onInput(key: InputKey, fn: () => void): void
|
||||||
|
afterInput(fn: () => void): void
|
||||||
|
playTune(text: string, n?: number): PlayTuneRes
|
||||||
|
setTimeout(fn: TimerHandler, ms: number): number
|
||||||
|
setInterval(fn: TimerHandler, ms: number): number
|
||||||
|
clearTimeout(id: number): void
|
||||||
|
clearInterval(id: number): void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
|
@ -1,4 +1,5 @@
|
||||||
/**
|
/**
|
||||||
|
* THIS SHOULD NOT BE HERE
|
||||||
* API for sprigsy
|
* API for sprigsy
|
||||||
* this is provided because your project has sprigsy v2 enabeled
|
* this is provided because your project has sprigsy v2 enabeled
|
||||||
*/
|
*/
|
||||||
|
@ -8,6 +9,6 @@ declare namespace sprigsy {
|
||||||
* Retrieve a sprite key for the sprite name
|
* Retrieve a sprite key for the sprite name
|
||||||
* @param name Sprite name
|
* @param name Sprite name
|
||||||
*/
|
*/
|
||||||
spriteKeyFor(name: string): string;
|
spriteKeyFor(name: string): string
|
||||||
}
|
}
|
||||||
}
|
}
|
0
src/lib/text-editor/extensions/babel.ts
Normal file
0
src/lib/text-editor/extensions/babel.ts
Normal file
62
src/lib/text-editor/extensions/typescript.ts
Normal file
62
src/lib/text-editor/extensions/typescript.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { javascript } from '@codemirror/lang-javascript'
|
||||||
|
import { autocompletion } from '@codemirror/autocomplete'
|
||||||
|
import Tooltip from '../Tooltip.svelte'
|
||||||
|
import { mount, unmount } from 'svelte'
|
||||||
|
import {
|
||||||
|
createDefaultMapFromCDN,
|
||||||
|
createSystem,
|
||||||
|
createVirtualTypeScriptEnvironment
|
||||||
|
} from '@typescript/vfs'
|
||||||
|
import ts from 'typescript'
|
||||||
|
import { tsFacet, tsLinter, tsHover, tsAutocomplete, tsSync } from '@valtown/codemirror-ts'
|
||||||
|
|
||||||
|
import sprigDeclarations from '$lib/text-editor/context/sprig.d.ts?raw'
|
||||||
|
|
||||||
|
const fsMap = await createDefaultMapFromCDN({ target: ts.ScriptTarget.ES2022 }, '5.7.3', true, ts)
|
||||||
|
fsMap.set('lib.sprig.d.ts', sprigDeclarations)
|
||||||
|
const system = createSystem(fsMap)
|
||||||
|
const compilerOpts = {}
|
||||||
|
const env = createVirtualTypeScriptEnvironment(system, ['lib.sprig.d.ts'], ts, compilerOpts)
|
||||||
|
|
||||||
|
console.log(fsMap.entries().toArray())
|
||||||
|
const path = 'index.ts'
|
||||||
|
|
||||||
|
export const typescriptExtensions = [
|
||||||
|
javascript({
|
||||||
|
jsx: false,
|
||||||
|
typescript: true
|
||||||
|
}),
|
||||||
|
tsFacet.of({ env, path }),
|
||||||
|
tsSync(),
|
||||||
|
tsLinter(),
|
||||||
|
autocompletion({
|
||||||
|
override: [tsAutocomplete()],
|
||||||
|
|
||||||
|
}),
|
||||||
|
tsHover({
|
||||||
|
renderTooltip: (info) => {
|
||||||
|
console.log("rendering tooltip")
|
||||||
|
let tooltip: Tooltip;
|
||||||
|
const div = document.createElement('div')
|
||||||
|
return {
|
||||||
|
dom: div,
|
||||||
|
mount() {
|
||||||
|
tooltip = mount(Tooltip, {
|
||||||
|
target: div,
|
||||||
|
intro: true,
|
||||||
|
props: {
|
||||||
|
info
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
unmount(tooltip, { outro: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
export {
|
||||||
|
env
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '../app.css';
|
import '../app.css'
|
||||||
const { children } = $props();
|
const { children } = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export const csr = true;
|
export const csr = true
|
||||||
export const prerender = true;
|
export const prerender = true
|
||||||
|
export const ssr = false
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import CodeEditor from '$lib/CodeEditor.svelte';
|
import CodeEditor from '$lib/CodeEditor.svelte'
|
||||||
import Tab from '$lib/components/Tab.svelte';
|
import Tab from '$lib/components/Tab.svelte'
|
||||||
let tab = 'code';
|
let tab = 'code'
|
||||||
let code = '';
|
let code = ''
|
||||||
|
function keypress() {
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full! w-full select-none flex-col bg-base-100">
|
<div class="flex h-full! w-full select-none flex-col bg-base-100">
|
||||||
|
@ -56,3 +59,5 @@
|
||||||
This will have the game screen
|
This will have the game screen
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<svelte:window on:keypress={keypress}></svelte:window>
|
|
@ -1,18 +1,17 @@
|
||||||
import adapter from '@sveltejs/adapter-static';
|
import adapter from '@sveltejs/adapter-static'
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
// Consult https://svelte.dev/docs/kit/integrations
|
// Consult https://svelte.dev/docs/kit/integrations
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
adapter: adapter()
|
adapter: adapter()
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default config;
|
export default config
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite'
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
build: {
|
build: {
|
||||||
target: ['es2022']
|
target: ['es2022']
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
Loading…
Reference in a new issue