Skip to content

Commit 6341fdb

Browse files
committed
feat(editor): show next maps (wip)
1 parent 803d9ba commit 6341fdb

14 files changed

Lines changed: 19263 additions & 17615 deletions

.vscode/launch.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Run Extension",
6+
"type": "extensionHost",
7+
"request": "launch",
8+
"runtimeExecutable": "${execPath}",
9+
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
10+
"outFiles": ["${workspaceFolder}/editor/out/**/*.js"],
11+
"preLaunchTask": "Build"
12+
}
13+
]
14+
}

.vscode/tasks.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// See https://go.microsoft.com/fwlink/?LinkId=733558
2+
// for the documentation about the tasks.json format
3+
{
4+
"version": "2.0.0",
5+
"tasks": [
6+
{
7+
"label": "Build",
8+
"type": "shell",
9+
"command": "deno task --cwd editor compile"
10+
}
11+
]
12+
}

editor/deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"webview": "deno -A jsr:@kt3k/pack@0.1.14 webview.ts -o out/webview.js",
1515
"webview-style": "deno -A npm:tailwindcss@3 -o out/style.css",
1616
"package": "deno task compile && deno -A --unstable-unsafe-proto npm:@vscode/vsce package",
17-
"update-package": "deno task package && code --install-extension vscode-bw-block-editor-0.4.0.vsix",
17+
"update-package": "deno task package && code --install-extension vscode-bw-block-editor-0.4.0.vsix"
1818
},
1919
"lint": {
2020
"rules": {

editor/extension.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function resolveCustomTextEditor(
3535
</head>
3636
<body class="key-handler">
3737
<div class="spacer h-10"></div>
38-
<div class="main-container relative mt-5 w-[3230px]"></div>
38+
<div class="main-container relative mt-5 w-[3360px]"></div>
3939
<div class="toolbox fixed left-0 top-0 flex items-center gap-2 bg-neutral-900/50 w-full">
4040
<div class="cell-switch flex items-center relative">
4141
</div>
@@ -80,8 +80,17 @@ function resolveCustomTextEditor(
8080
}
8181

8282
function onMessage(e: type.Webview.Message) {
83-
if (e.type === "loadImage") loadImage(e)
84-
else if (e.type === "update") update(e)
83+
switch (e.type) {
84+
case "update":
85+
update(e)
86+
break
87+
case "loadImage":
88+
loadImage(e)
89+
break
90+
case "loadText":
91+
loadText(e)
92+
break
93+
}
8594
}
8695

8796
function update(e: type.Webview.MessageUpdate) {
@@ -95,8 +104,28 @@ function resolveCustomTextEditor(
95104
}
96105

97106
async function loadImage({ uri, id }: type.Webview.MessageLoadImage) {
98-
const data = await workspace.fs.readFile(Uri.parse(uri))
99-
const text = "data:image/png;base64," + encodeBase64(data)
100-
postMessage({ type: "loadImageResponse", text, id })
107+
postMessage({
108+
type: "loadImageResponse",
109+
text: "data:image/png;base64," +
110+
encodeBase64(await workspace.fs.readFile(Uri.parse(uri))),
111+
id,
112+
})
113+
}
114+
115+
async function loadText({ uri, id }: type.Webview.MessageLoadText) {
116+
try {
117+
const text = (await workspace.fs.readFile(Uri.parse(uri))).toString()
118+
postMessage({
119+
type: "loadTextResponse",
120+
text,
121+
id,
122+
})
123+
} catch (error) {
124+
postMessage({
125+
type: "loadTextResponse",
126+
error: (error as Error).message,
127+
id,
128+
})
129+
}
101130
}
102131
}

editor/types.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,19 @@ export namespace Extension {
1212
id: string
1313
text: string
1414
}
15-
export type Message = MessageUpdate | MessageLoadImageResponse
15+
export type MessageLoadTextResponse = {
16+
type: "loadTextResponse"
17+
id: string
18+
text: string
19+
} | {
20+
type: "loadTextResponse"
21+
id: string
22+
error: string
23+
}
24+
export type Message =
25+
| MessageUpdate
26+
| MessageLoadImageResponse
27+
| MessageLoadTextResponse
1628
}
1729

1830
// deno-lint-ignore no-namespace
@@ -22,10 +34,15 @@ export namespace Webview {
2234
id: string
2335
uri: string
2436
}
37+
export type MessageLoadText = {
38+
type: "loadText"
39+
id: string
40+
uri: string
41+
}
2542
export type MessageUpdate = {
2643
type: "update"
2744
// deno-lint-ignore no-explicit-any
2845
map: any
2946
}
30-
export type Message = MessageLoadImage | MessageUpdate
47+
export type Message = MessageLoadImage | MessageLoadText | MessageUpdate
3148
}

editor/webview.ts

Lines changed: 127 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { memoizedLoading } from "../util/memo.ts"
1212
import { CanvasWrapper } from "../util/canvas-wrapper.ts"
1313
import { type Context, GroupSignal, mount, register, Signal } from "@kt3k/cell"
1414
import type * as type from "./types.ts"
15+
import { BLOCK_SIZE } from "../util/constants.ts"
1516

1617
const blockMapSource = new GroupSignal({ uri: "", text: "" })
1718
const fieldBlock = new Signal<FieldBlock | null>(null)
@@ -29,25 +30,34 @@ blockMapSource.subscribe(({ uri, text }) => {
2930
)
3031
})
3132

33+
function getGridFromUri(uri: string) {
34+
const m = uri.match(/block_(-?\d+)\.(-?\d+)\.json$/)
35+
if (!m) return { i: NaN, j: NaN }
36+
return { i: parseInt(m[1]), j: parseInt(m[2]) }
37+
}
38+
3239
function MainContainer({ subscribe, el, query }: Context) {
3340
subscribe(fieldBlock, async (fieldBlock) => {
3441
const prev = prevFieldBlock
3542
prevFieldBlock = fieldBlock
3643
if (fieldBlock === null) return
3744

3845
if (prev === null) {
46+
const FULL_SIZE = 3200
47+
const SIDE_CANVAS_SIZE = 80
48+
3949
await fieldBlock.loadAssets()
4050
const canvas = fieldBlock.canvas
41-
canvas.style.left = ""
42-
canvas.style.top = ""
43-
canvas.style.position = ""
51+
canvas.style.left = SIDE_CANVAS_SIZE + "px"
52+
canvas.style.top = SIDE_CANVAS_SIZE + "px"
53+
canvas.style.position = "absolute"
4454
canvas.classList.add("field-block-canvas")
4555
el.innerHTML = ""
4656
el.appendChild(canvas)
4757
fieldBlock.renderAll()
4858
mount("field-block-canvas", el)
4959

50-
const { i, j } = fieldBlock
60+
const { i, j } = getGridFromUri(fieldBlock.toMap().url)
5161
const x = [
5262
[-1, 0],
5363
[0, -1],
@@ -59,36 +69,99 @@ function MainContainer({ subscribe, el, query }: Context) {
5969
const l = j + dy * 200
6070
const href =
6171
new URL(`block_${k}.${l}.json`, blockMapSource.get().uri).href
72+
let text
73+
try {
74+
text = await loadText(href)
75+
} catch {
76+
text = await loadText(
77+
new URL("block_not_found.json", blockMapSource.get().uri).href,
78+
)
79+
}
80+
const blockMap = new BlockMap(href, JSON.parse(text))
81+
const fieldBlock = new FieldBlock(blockMap, loadImage)
82+
await fieldBlock.loadAssets()
6283
const a = document.createElement("a")
6384
a.href = href.replace(/^file:\/\//, "vscode://file")
6485
a.textContent = `block_${k}.${l}.json`
65-
a.style.width = "20px"
66-
a.style.height = "20px"
6786
a.classList.add(
6887
"absolute",
88+
"opacity-50",
6989
"hover:bg-neutral-700",
7090
"bg-neutral-800",
7191
"break-all",
7292
)
73-
const FULL_SIZE = "3200px"
7493
if (dx === -1) {
94+
const imageData = fieldBlock.createImageDataForRange(
95+
BLOCK_SIZE - 5,
96+
0,
97+
5,
98+
BLOCK_SIZE,
99+
)
100+
const canvas = document.createElement("canvas")
101+
canvas.width = imageData.width
102+
canvas.height = imageData.height
103+
canvas.classList.add("absolute", "top-0", "left-0")
104+
const ctx = canvas.getContext("2d")!
105+
ctx.putImageData(imageData, 0, 0)
106+
a.appendChild(canvas)
75107
a.style.left = "0"
76-
a.style.top = "0"
77-
a.style.height = FULL_SIZE
78-
a.style.marginLeft = "-20px"
108+
a.style.top = SIDE_CANVAS_SIZE + "px"
109+
a.style.height = FULL_SIZE + "px"
110+
a.style.width = SIDE_CANVAS_SIZE + "px"
79111
} else if (dx === 1) {
80-
a.style.left = FULL_SIZE
81-
a.style.top = "0"
82-
a.style.height = FULL_SIZE
112+
const imageData = fieldBlock.createImageDataForRange(
113+
0,
114+
0,
115+
5,
116+
BLOCK_SIZE,
117+
)
118+
const canvas = document.createElement("canvas")
119+
canvas.width = imageData.width
120+
canvas.height = imageData.height
121+
canvas.classList.add("absolute", "top-0", "left-0")
122+
const ctx = canvas.getContext("2d")!
123+
ctx.putImageData(imageData, 0, 0)
124+
a.appendChild(canvas)
125+
a.style.left = (FULL_SIZE + SIDE_CANVAS_SIZE) + "px"
126+
a.style.top = SIDE_CANVAS_SIZE + "px"
127+
a.style.height = FULL_SIZE + "px"
128+
a.style.width = SIDE_CANVAS_SIZE + "px"
83129
} else if (dy === -1) {
130+
const imageData = fieldBlock.createImageDataForRange(
131+
0,
132+
BLOCK_SIZE - 5,
133+
BLOCK_SIZE,
134+
5,
135+
)
136+
const canvas = document.createElement("canvas")
137+
canvas.width = imageData.width
138+
canvas.height = imageData.height
139+
canvas.classList.add("absolute", "top-0", "left-0")
140+
const ctx = canvas.getContext("2d")!
141+
ctx.putImageData(imageData, 0, 0)
142+
a.appendChild(canvas)
84143
a.style.top = "0"
85-
a.style.left = "0"
86-
a.style.width = FULL_SIZE
87-
a.style.marginTop = "-20px"
144+
a.style.left = SIDE_CANVAS_SIZE + "px"
145+
a.style.width = FULL_SIZE + "px"
146+
a.style.height = SIDE_CANVAS_SIZE + "px"
88147
} else if (dy === 1) {
89-
a.style.top = FULL_SIZE
90-
a.style.left = "0"
91-
a.style.width = FULL_SIZE
148+
const imageData = fieldBlock.createImageDataForRange(
149+
0,
150+
0,
151+
BLOCK_SIZE,
152+
5,
153+
)
154+
const canvas = document.createElement("canvas")
155+
canvas.width = imageData.width
156+
canvas.height = imageData.height
157+
canvas.classList.add("absolute", "top-0", "left-0")
158+
const ctx = canvas.getContext("2d")!
159+
ctx.putImageData(imageData, 0, 0)
160+
a.appendChild(canvas)
161+
a.style.top = (FULL_SIZE + SIDE_CANVAS_SIZE) + "px"
162+
a.style.left = SIDE_CANVAS_SIZE + "px"
163+
a.style.width = FULL_SIZE + "px"
164+
a.style.height = SIDE_CANVAS_SIZE + "px"
92165
}
93166
el.appendChild(a)
94167
}
@@ -245,23 +318,15 @@ function KeyHandler({ on }: Context) {
245318
})
246319
}
247320

321+
type ResolveImage = (image: ImageBitmap) => void
322+
const loadImageMap: Record<string, { resolve: ResolveImage }> = {}
248323
const loadImage = memoizedLoading((uri: string) => {
249324
const { resolve, promise } = Promise.withResolvers<ImageBitmap>()
250325
const id = Math.random().toString()
251326
loadImageMap[id] = { resolve }
252327
vscode.postMessage({ type: "loadImage", uri, id })
253328
return promise
254329
})
255-
256-
type ResolveImage = (image: ImageBitmap) => void
257-
const loadImageMap: Record<string, { resolve: ResolveImage }> = {}
258-
259-
function onUpdate(message: type.Extension.MessageUpdate) {
260-
const { uri, text } = message
261-
blockMapSource.update({ uri, text })
262-
vscode.setState({ uri, text })
263-
}
264-
265330
function onLoadImageResponse(message: type.Extension.MessageLoadImageResponse) {
266331
const image = new Image()
267332
image.src = message.text
@@ -274,6 +339,36 @@ function onLoadImageResponse(message: type.Extension.MessageLoadImageResponse) {
274339
}
275340
}
276341

342+
type ResolveText = (text: string) => void
343+
type RejectText = (error: Error) => void
344+
const loadTextMap: Record<
345+
string,
346+
{ resolve: ResolveText; reject: RejectText }
347+
> = {}
348+
const loadText = (uri: string) => {
349+
const { resolve, reject, promise } = Promise.withResolvers<string>()
350+
const id = Math.random().toString()
351+
loadTextMap[id] = { resolve, reject }
352+
vscode.postMessage({ type: "loadText", uri, id })
353+
return promise
354+
}
355+
function onLoadTextResponse(message: type.Extension.MessageLoadTextResponse) {
356+
if ("text" in message) {
357+
const text = message.text
358+
loadTextMap[message.id].resolve(text)
359+
} else {
360+
const error = message.error
361+
loadTextMap[message.id].reject(new Error(error))
362+
}
363+
delete loadTextMap[message.id]
364+
}
365+
366+
function onUpdate(message: type.Extension.MessageUpdate) {
367+
const { uri, text } = message
368+
blockMapSource.update({ uri, text })
369+
vscode.setState({ uri, text })
370+
}
371+
277372
globalThis.addEventListener(
278373
"message",
279374
(event: MessageEvent<type.Extension.Message>) => {
@@ -285,6 +380,9 @@ globalThis.addEventListener(
285380
case "loadImageResponse":
286381
onLoadImageResponse(data)
287382
break
383+
case "loadTextResponse":
384+
onLoadTextResponse(data)
385+
break
288386
}
289387
},
290388
)

0 commit comments

Comments
 (0)