Skip to content

Commit 0b2f0c0

Browse files
committed
POC
1 parent 5014ec0 commit 0b2f0c0

File tree

5 files changed

+188
-5
lines changed

5 files changed

+188
-5
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ netbeansSwing = { module = "org.netbeans.api:org-netbeans-swing-outline", versio
2121
ant = { module = "org.apache.ant:ant", version = "1.10.14" }
2222
lsp4j = { module = "org.eclipse.lsp4j:org.eclipse.lsp4j", version = "0.22.0" }
2323
jsoup = { module = "org.jsoup:jsoup", version = "1.17.2" }
24+
ktxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.3" }
2425

2526
[plugins]
2627
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }

p5js/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
kotlin("jvm") version "2.0.20"
3+
kotlin("plugin.serialization") version "1.9.0"
34
}
45

56
group = "org.processing"
@@ -13,6 +14,9 @@ repositories {
1314

1415
dependencies {
1516
compileOnly(project(":app"))
17+
implementation(libs.ktxCoroutines)
18+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
19+
1620

1721
testImplementation(kotlin("test"))
1822
}

p5js/src/main/kotlin/processing/p5js/p5jsEditor.kt

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,94 @@ import processing.app.ui.Editor
77
import processing.app.ui.EditorState
88
import processing.app.ui.EditorToolbar
99
import javax.swing.JMenu
10+
import kotlinx.coroutines.*
11+
import java.io.BufferedReader
12+
import java.io.File
13+
import java.io.InputStreamReader
1014

1115
class p5jsEditor(base: Base, path: String?, state: EditorState?, mode: Mode?): Editor(base, path, state, mode) {
16+
17+
val scope = CoroutineScope(Dispatchers.Default)
18+
init {
19+
scope.launch {
20+
val folder = sketch.folder
21+
val name = sketch.name
22+
23+
val packageJsonName = "package.json"
24+
25+
val packageJson = loadPackageJson("$folder/$packageJsonName")
26+
packageJson.devDependencies["electron"] = "^33.2.1"
27+
packageJson.sketch = "$name.js"
28+
savePackageJson("$folder/$packageJsonName", packageJson)
29+
30+
runNpmActions(folder, TYPE.npm, listOf("install"))
31+
32+
val indexHtml = """
33+
<!DOCTYPE html>
34+
<html lang="en">
35+
<head>
36+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/p5.js"></script>
37+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/addons/p5.sound.min.js"></script>
38+
<meta charset="utf-8" />
39+
<style>
40+
html, body {
41+
margin: 0;
42+
padding: 0;
43+
}
44+
canvas {
45+
display: block;
46+
}
47+
</style>
48+
</head>
49+
50+
<body>
51+
<main>
52+
<script src="${packageJson.sketch}"></script>
53+
</main>
54+
</body>
55+
</html>
56+
""".trimIndent()
57+
val indexJS = """
58+
const { app, BrowserWindow, globalShortcut } = require('electron')
59+
60+
const createWindow = () => {
61+
const win = new BrowserWindow({
62+
width: 400,
63+
height: 400,
64+
webPreferences: {
65+
nodeIntegration: true,
66+
contextIsolation: false
67+
},
68+
})
69+
70+
win.loadFile('index.html')
71+
72+
// Register the 'Escape' key shortcut
73+
globalShortcut.register('Escape', () => {
74+
win.close()
75+
})
76+
77+
// Unregister the shortcut when window is closed
78+
win.on('closed', () => {
79+
globalShortcut.unregister('Escape')
80+
})
81+
}
82+
83+
app.on('window-all-closed', () => {
84+
// Unregister all shortcuts when app is closing
85+
globalShortcut.unregisterAll()
86+
app.quit()
87+
})
88+
89+
app.whenReady().then(() => {
90+
createWindow()
91+
})
92+
""".trimIndent()
93+
94+
File("$folder/index.html").writeText(indexHtml)
95+
File("$folder/index.js").writeText(indexJS)
96+
}
97+
}
1298
override fun createToolbar(): EditorToolbar {
1399
return p5jsEditorToolbar(this)
14100
}
@@ -42,10 +128,53 @@ class p5jsEditor(base: Base, path: String?, state: EditorState?, mode: Mode?): E
42128
}
43129

44130
override fun internalCloseRunner() {
45-
// TODO("Not yet implemented")
131+
processes.forEach { it.destroy() }
46132
}
47133

134+
48135
override fun deactivateRun() {
49-
// TODO("Not yet implemented")
136+
processes.forEach { it.destroy() }
137+
}
138+
139+
enum class TYPE{
140+
npm, npx
141+
}
142+
143+
val processes = mutableListOf<Process>()
144+
fun runNpmActions(directory: File, type: TYPE, actions: List<String>, onFinished: () -> Unit = {}) {
145+
val processBuilder = ProcessBuilder()
146+
147+
// Set the command based on the operating system
148+
val command = if (System.getProperty("os.name").lowercase().contains("windows")) {
149+
listOf("cmd", "/c", type.name , *actions.toTypedArray())
150+
} else {
151+
listOf(type.name, *actions.toTypedArray())
152+
}
153+
154+
processBuilder.command(command)
155+
processBuilder.directory(directory)
156+
157+
try {
158+
val process = processBuilder.start()
159+
processes.add(process)
160+
161+
// Handle output stream
162+
val reader = BufferedReader(InputStreamReader(process.inputStream))
163+
var line: String?
164+
while (reader.readLine().also { line = it } != null) {
165+
println(line)
166+
}
167+
168+
169+
// Wait for the process to complete
170+
val exitCode = process.waitFor()
171+
processes.remove(process)
172+
onFinished()
173+
if (exitCode != 0) {
174+
throw RuntimeException("npm install failed with exit code $exitCode")
175+
}
176+
} catch (e: Exception) {
177+
throw RuntimeException("Failed to run npm install", e)
178+
}
50179
}
51180
}
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
package processing.p5js
22

3+
import kotlinx.coroutines.launch
34
import processing.app.ui.Editor
45
import processing.app.ui.EditorToolbar
56

6-
class p5jsEditorToolbar(editor: Editor?) : EditorToolbar(editor) {
7+
class p5jsEditorToolbar(editor: p5jsEditor?) : EditorToolbar(editor) {
78
override fun handleRun(modifiers: Int) {
8-
TODO("Not yet implemented")
9+
val editor = editor as p5jsEditor
10+
11+
editor.scope.launch {
12+
editor.sketch.save()
13+
editor.processes.forEach { it.destroy() }
14+
runButton.setSelected(true)
15+
editor.runNpmActions(editor.sketch.folder, p5jsEditor.TYPE.npx, listOf("electron", ".")){
16+
runButton.setSelected(false)
17+
}
18+
19+
}
920
}
1021

1122
override fun handleStop() {
12-
TODO("Not yet implemented")
23+
val editor = editor as p5jsEditor
24+
editor.processes.forEach { it.destroy() }
1325
}
1426
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package processing.p5js
2+
3+
import kotlinx.serialization.ExperimentalSerializationApi
4+
import kotlinx.serialization.Serializable
5+
import kotlinx.serialization.encodeToString
6+
import kotlinx.serialization.json.Json
7+
import java.io.File
8+
9+
@Serializable
10+
data class PackageJson(
11+
val name: String,
12+
val version: String,
13+
val dependencies: MutableMap<String, String> = mutableMapOf(),
14+
val devDependencies: MutableMap<String, String> = mutableMapOf(),
15+
var sketch: String = "sketch.js"
16+
)
17+
18+
fun loadPackageJson(path: String): PackageJson {
19+
if(!File(path).exists()) {
20+
return PackageJson("p5js", "1.0.0")
21+
}
22+
val jsonString = File(path).readText()
23+
return Json.decodeFromString<PackageJson>(jsonString)
24+
}
25+
26+
@OptIn(ExperimentalSerializationApi::class)
27+
fun savePackageJson(path: String, packageJson: PackageJson) {
28+
val json = Json {
29+
prettyPrint = true
30+
prettyPrintIndent = " " // Use 2 spaces for indentation (npm standard)
31+
encodeDefaults = true // Include default values in output
32+
explicitNulls = false // Don't include null values in output
33+
ignoreUnknownKeys = true // Don't fail on unknown keys during serialization
34+
}
35+
val jsonString = json.encodeToString(PackageJson.serializer(), packageJson)
36+
File(path).writeText(jsonString)
37+
}

0 commit comments

Comments
 (0)