Skip to content

Commit 15577ea

Browse files
committed
UI - add monaco liveview component
1 parent 044f28d commit 15577ea

File tree

5 files changed

+252
-24
lines changed

5 files changed

+252
-24
lines changed

server/assets/js/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import { Socket } from "phoenix";
2222
import { LiveSocket } from "phoenix_live_view";
2323
import topbar from "../vendor/topbar";
2424
import { Tau5Shader } from "./lib/tau5_shader.js";
25+
import { MonacoEditor } from "./lib/monaco_hook.js";
2526

2627
let Hooks = {
28+
MonacoEditor: MonacoEditor,
2729
LuaShell: {
2830
mounted() {
2931
this.setupResize();
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Monaco Editor Hook for LiveView
2+
// Handles initialization and lifecycle of Monaco editor instances
3+
4+
export const MonacoEditor = {
5+
async mounted() {
6+
// Set up Monaco environment
7+
self.MonacoEnvironment = {
8+
getWorkerUrl: function (_moduleId, label) {
9+
return '/assets/js/monaco-worker/editor.worker.js';
10+
}
11+
};
12+
13+
try {
14+
// Dynamically import Monaco
15+
const monaco = await import('../../vendor/monaco-editor/esm/vs/editor/editor.main.js');
16+
17+
// Get initial code content
18+
const initialCode = getInitialCodeForPanel();
19+
20+
// Define custom transparent theme
21+
monaco.editor.defineTheme('tau5-transparent', {
22+
base: 'vs-dark',
23+
inherit: true,
24+
rules: [
25+
{ token: 'comment', fontStyle: 'italic' },
26+
{ token: 'comment.line', fontStyle: 'italic' },
27+
{ token: 'comment.block', fontStyle: 'italic' },
28+
],
29+
colors: {
30+
'editor.background': '#00000000', // Transparent background
31+
'editor.lineHighlightBackground': '#ffffff10', // Subtle line highlight
32+
'editorLineNumber.foreground': '#858585',
33+
'editorLineNumber.activeForeground': '#c6c6c6',
34+
'editorGutter.background': '#00000000', // Transparent gutter (line numbers area)
35+
'minimap.background': '#00000000', // Transparent minimap background
36+
'minimapSlider.background': '#ffffff20', // Subtle slider
37+
'minimapSlider.hoverBackground': '#ffffff30',
38+
'minimapSlider.activeBackground': '#ffffff40',
39+
'scrollbar.shadow': '#00000000', // No scrollbar shadow
40+
'scrollbarSlider.background': '#ffffff20', // Semi-transparent scrollbar
41+
'scrollbarSlider.hoverBackground': '#ffffff30',
42+
'scrollbarSlider.activeBackground': '#ffffff40',
43+
'editorOverviewRuler.border': '#00000000', // No overview ruler border
44+
'editorOverviewRuler.background': '#00000000', // Transparent overview ruler background
45+
}
46+
});
47+
48+
// Create the editor
49+
this.editor = monaco.editor.create(this.el, {
50+
value: initialCode,
51+
language: 'lua',
52+
theme: 'tau5-transparent',
53+
automaticLayout: true,
54+
minimap: {
55+
enabled: true,
56+
renderCharacters: false, // Render blocks instead of characters (thinner)
57+
maxColumn: 80 // Limit minimap width
58+
},
59+
fontSize: 18,
60+
fontWeight: 'bold',
61+
lineNumbers: 'on',
62+
roundedSelection: false,
63+
scrollBeyondLastLine: false,
64+
readOnly: false,
65+
cursorStyle: 'line',
66+
wordWrap: 'off',
67+
tabSize: 2,
68+
insertSpaces: true,
69+
fontFamily: "'Cascadia Code PL', 'Consolas', 'Monaco', monospace",
70+
fontLigatures: true,
71+
renderWhitespace: 'selection',
72+
bracketPairColorization: {
73+
enabled: true
74+
},
75+
});
76+
77+
// Store reference
78+
this.monaco = monaco;
79+
80+
// Handle editor changes
81+
this.editor.onDidChangeModelContent((e) => {
82+
const value = this.editor.getValue();
83+
// Optionally push changes to server
84+
// this.pushEvent("editor_changed", { value: value });
85+
});
86+
87+
// Bind and listen for resize events
88+
this.handleResize = this.handleResize.bind(this);
89+
window.addEventListener('resize', this.handleResize);
90+
91+
console.log('Monaco editor initialized for Lua');
92+
} catch (error) {
93+
console.error('Failed to initialize Monaco editor:', error);
94+
this.showError(error.message);
95+
}
96+
},
97+
98+
updated() {
99+
// Layout the editor when the component updates
100+
if (this.editor) {
101+
this.editor.layout();
102+
}
103+
},
104+
105+
destroyed() {
106+
// Clean up
107+
if (this.editor) {
108+
this.editor.dispose();
109+
this.editor = null;
110+
}
111+
window.removeEventListener('resize', this.handleResize);
112+
},
113+
114+
handleResize() {
115+
if (this.editor) {
116+
this.editor.layout();
117+
}
118+
},
119+
120+
showError(message) {
121+
this.el.innerHTML = `
122+
<div style="
123+
display: flex;
124+
align-items: center;
125+
justify-content: center;
126+
height: 100%;
127+
color: #f44336;
128+
font-family: monospace;
129+
padding: 20px;
130+
text-align: center;
131+
">
132+
<div>
133+
<h3>Monaco Editor Error</h3>
134+
<p>${message}</p>
135+
</div>
136+
</div>
137+
`;
138+
}
139+
};
140+
141+
// Helper function to determine language based on panel index
142+
function getLanguageForPanel(index) {
143+
return 'lua';
144+
}
145+
146+
// Helper function to get initial code for each panel
147+
function getInitialCodeForPanel(index) {
148+
return `-- Welcome to Tau5!
149+
-- This is a Lua code editor powered by the Monaco Editor.
150+
`;
151+
}

server/config/dev.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ config :tau5, Tau5Web.Endpoint,
1919
watchers: [
2020
esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
2121
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]},
22-
tailwind_console: {Tailwind, :install_and_run, [:console, ~w(--watch)]}
22+
tailwind_console: {Tailwind, :install_and_run, [:console, ~w(--watch)]},
23+
monaco_esbuild: {Esbuild, :install_and_run, [:monaco_worker, ~w(--sourcemap=inline --watch)]}
2324
]
2425

2526
# ## SSL Support

server/lib/tau5_web/live/main_live.ex

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule Tau5Web.MainLive do
66

77
use Tau5Web, :live_view
88
alias Tau5.TiledLayout
9+
alias Tau5Web.Widgets.MonacoEditorWidget
910
require Logger
1011

1112
@impl true
@@ -26,7 +27,7 @@ defmodule Tau5Web.MainLive do
2627
|> assign(:layout_state, TiledLayout.new())
2728
|> assign(:show_controls, true)
2829
|> assign(:show_lua_console, false)
29-
|> assign(:widget_type, :transform)
30+
|> assign(:widget_type, :monaco)
3031

3132
{:ok, socket}
3233
end
@@ -104,12 +105,15 @@ defmodule Tau5Web.MainLive do
104105
105106
<button
106107
phx-click="toggle_widget_type"
107-
title={if @widget_type == :shader, do: "Switch to Transform", else: "Switch to Shader"}
108+
title="Switch Widget Type"
108109
>
109-
<%= if @widget_type == :shader do %>
110-
<span>Shader</span>
111-
<% else %>
112-
<span>Blend</span>
110+
<%= case @widget_type do %>
111+
<% :monaco -> %>
112+
<span>Monaco</span>
113+
<% :shader -> %>
114+
<span>Shader</span>
115+
<% :transform -> %>
116+
<span>Blend</span>
113117
<% end %>
114118
</button>
115119
@@ -190,22 +194,31 @@ defmodule Tau5Web.MainLive do
190194
phx-click="focus"
191195
phx-value-panel={to_string(@panel_index)}
192196
>
193-
<%= if @widget_type == :shader do %>
194-
<.live_component
195-
module={Tau5Web.Widgets.ShaderPanelWidget}
196-
id={@panel_id}
197-
panel_id={@panel_id}
198-
index={@panel_index}
199-
active={@is_active}
200-
/>
201-
<% else %>
202-
<.live_component
203-
module={Tau5Web.Widgets.TransformWidget}
204-
id={@panel_id}
205-
panel_id={@panel_id}
206-
index={@panel_index}
207-
active={@is_active}
208-
/>
197+
<%= case @widget_type do %>
198+
<% :monaco -> %>
199+
<.live_component
200+
module={Tau5Web.Widgets.MonacoEditorWidget}
201+
id={@panel_id}
202+
panel_id={@panel_id}
203+
index={@panel_index}
204+
active={@is_active}
205+
/>
206+
<% :shader -> %>
207+
<.live_component
208+
module={Tau5Web.Widgets.ShaderPanelWidget}
209+
id={@panel_id}
210+
panel_id={@panel_id}
211+
index={@panel_index}
212+
active={@is_active}
213+
/>
214+
<% :transform -> %>
215+
<.live_component
216+
module={Tau5Web.Widgets.TransformWidget}
217+
id={@panel_id}
218+
panel_id={@panel_id}
219+
index={@panel_index}
220+
active={@is_active}
221+
/>
209222
<% end %>
210223
</div>
211224
"""
@@ -295,7 +308,13 @@ defmodule Tau5Web.MainLive do
295308

296309
@impl true
297310
def handle_event("toggle_widget_type", _, socket) do
298-
new_type = if socket.assigns.widget_type == :shader, do: :transform, else: :shader
311+
new_type =
312+
case socket.assigns.widget_type do
313+
:monaco -> :shader
314+
:shader -> :transform
315+
:transform -> :monaco
316+
end
317+
299318
{:noreply, assign(socket, :widget_type, new_type)}
300319
end
301320

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
defmodule Tau5Web.Widgets.MonacoEditorWidget do
2+
@moduledoc """
3+
A widget that embeds the Monaco Editor for code editing.
4+
"""
5+
6+
use Tau5Web, :live_component
7+
8+
@doc false
9+
def render(assigns) do
10+
~H"""
11+
<div
12+
id={"monaco-container-#{@id}"}
13+
class="monaco-widget"
14+
style="width: 100%; height: 100%; position: relative; overflow: hidden; background: transparent;"
15+
>
16+
<!-- Monaco editor container -->
17+
<div
18+
id={"monaco-editor-#{@id}"}
19+
phx-hook="MonacoEditor"
20+
phx-update="ignore"
21+
data-editor-id={@id}
22+
data-panel-index={@index}
23+
style="width: 100%; height: 100%; position: absolute; top: 0; left: 0;"
24+
>
25+
</div>
26+
27+
<!-- Panel info overlay (top-right) -->
28+
<div style="position: absolute; top: 10px; right: 10px; z-index: 10; pointer-events: none;">
29+
<div style="font-size: 14px; font-weight: bold; color: rgba(255,255,255,0.5); font-family: 'Cascadia Code PL', monospace;">
30+
Panel {@index}
31+
</div>
32+
</div>
33+
</div>
34+
"""
35+
end
36+
37+
@doc false
38+
def update(assigns, socket) do
39+
socket =
40+
socket
41+
|> assign(assigns)
42+
|> assign_new(:initial_value, fn -> get_initial_code() end)
43+
44+
{:ok, socket}
45+
end
46+
47+
# Provide initial code
48+
defp get_initial_code do
49+
"""
50+
-- Welcome to Tau5!
51+
-- This is a Lua code editor powered by the Monaco Editor.
52+
"""
53+
end
54+
55+
end

0 commit comments

Comments
 (0)