Skip to content

Commit

Permalink
initial ui
Browse files Browse the repository at this point in the history
  • Loading branch information
acouvreur committed Oct 20, 2024
1 parent 27ed5a9 commit 30d1055
Show file tree
Hide file tree
Showing 8 changed files with 1,016 additions and 674 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
on:
push:
branches:
- main

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go 1.22
uses: actions/setup-go@v5
with:
go-version: ^1.22
cache-dependency-path: go.sum

- name: Build artifacts
run: make all

- name: Upload static files as artifact
id: deployment
uses: actions/upload-pages-artifact@v3 # or specific "vX.X.X" version tag for this action
with:
path: webui

# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
webui/scripts
1,348 changes: 674 additions & 674 deletions LICENSE

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
TARGET := webui/scripts

.PHONY: all
all: \
$(TARGET)/wasm_exec.js \
$(TARGET)/template.wasm

$(TARGET)/wasm_exec.js:
mkdir -p $(dir $@)
cp $(shell go env GOROOT)/misc/wasm/wasm_exec.js $@

$(TARGET)/template.wasm: $(wildcard *.go **/*.go)
mkdir -p $(dir $@)
GOOS=js GOARCH=wasm go build \
-o $@
du -sh $@

.PHONY: dev
dev:
$(MAKE) ARGS=--watch

.PHONY: clean
clean:
rm -rf $(TARGET)
Empty file added README.md
Empty file.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/sablierapp/theme-editor

go 1.22.3
76 changes: 76 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//go:build js && wasm

package main

import (
"encoding/json"
"fmt"
"html/template"
"strings"
"syscall/js"
)

func main() {
// Render provides the ability to take in a template string and input
// data and render the corresponding output.
render := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 2 {
return "Must provide exactly two arguments: inputTmpl, inputData"
}
inputTmpl := args[0].String()
inputData := args[1].String()

tmpl := template.New("base")
tmpl, err := tmpl.Parse(inputTmpl)
if err != nil {
return fmt.Sprintf("error parsing template: %v", err)
}

data, err := decode(inputData)
if err != nil {
return fmt.Sprintf("error decoding: %v", err)
}

var b strings.Builder
if err := tmpl.Execute(&b, data); err != nil {
return fmt.Sprintf("error executing template: %v", err)
}

return b.String()
})
js.Global().Set("template", render)

convertData := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 3 {
return "Must provide three arguments: inputData, fromFormat, toFormat"
}
inputData := args[0].String()
fromFmt := args[1].String()
toFmt := args[2].String()

data, err := decode(inputData)
if err != nil {
return fmt.Sprintf("Error decoding from '%s': %v", fromFmt, err)
}
output, err := encode(data)
if err != nil {
return fmt.Sprintf("Error encoding to '%s': %v", toFmt, err)
}
return string(output)
})
js.Global().Set("ExpConvertData", convertData)

// Wait forever
<-make(chan bool)
}

func decode(inputData string) (data interface{}, err error) {
if err := json.Unmarshal([]byte(inputData), &data); err != nil {
return nil, err
}
return
}

func encode(input interface{}) (output []byte, err error) {
return json.MarshalIndent(input, "", " ")
}
200 changes: 200 additions & 0 deletions webui/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="index, follow">
<meta name="format-detection" content="telephone=no">
<meta name="google" content="nositelinkssearchbox">
<link rel="canonical" href="https://editor.sablierapp.dev/">

<title>Sablier Theme Editor</title>
<meta name="description" content="Edit and live preview HTML theme for Sablier. Requires WebAssembly support.">

<meta property="og:title" content="Sablier Theme Editor">
<meta property="og:description" content="Edit and live preview HTML theme for Sablier. Requires WebAssembly support.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://editor.sablierapp.dev/">

<link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/sablierapp/artwork/refs/heads/main/icon/sablier-icon-color.png">

<style>
* {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
border: 0;
box-sizing: border-box;
color: inherit;
margin: 0;
outline: none;
padding: 0;
}
section {
margin: 0 auto;
width: 100%;
padding: 1rem;
}
select {
background: transparent;
}
textarea {
background: transparent;
display: block;
font-family: 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
font-size: .85rem;
line-height: 1.5;
height: 100%;
resize: none;
white-space: pre;
width: 100%;
}
h2{font-size:.7rem;letter-spacing:.033rem;font-weight:400}
a {display: inline-block; text-decoration: none}
a:hover {text-decoration: underline}
.textarea {
background: rgb(55 57 83 / 0.3);
border-radius: 1rem;
border: 1px solid rgb(221 214 254 / 0.1);
padding: 1rem;
}
.textarea-light {
background-color: aliceblue;
border: 1px solid rgb(221 214 254 / 0.05);
padding: .02rem;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.2/ace.js" integrity="sha512-yjhIcftV1KZyd3rLPujicRV6NpFEuCqQaOBBdrey6vFdU1zVkJYgJf9a+15YrOuzzSXYNV2GU4xdQ8Xy9Zj/fA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="/scripts/wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("/scripts/template.wasm"), go.importObject).then((result) => {
go.run(result.instance);
loadEditors()
});
function render() {
var output = template(ace.edit("input-editor").getValue(), ace.edit("data-editor").getValue());
console.log(output)
var doc = document.getElementById('output').contentWindow.document;
doc.open();
doc.write(output);
doc.close();
}
function loadFromLocalStorage() {
var input_editor = ace.edit("input-editor");
var value = localStorage.getItem("input_editor");
if (typeof value == "string") {
input_editor.session.setValue(value);
} else {
loadFromGithub({value: "shuffle"})
}

var data_editor = ace.edit("data-editor");
value = localStorage.getItem("data_editor");
if (typeof value == "string") {
data_editor.session.setValue(value);
} else {
data_editor.session.setValue(JSON.stringify({
"DisplayName": "Test",
"InstanceStates": [
{
"Name": "starting-instance",
"Status": "instance is starting...",
"Error": null,
"CurrentReplicas": 0,
"DesiredReplicas": 1
}
],
"ShowDetails": true,
"SessionDuration": "10 minutes",
"RefreshFrequency": 500000000
}, null, 2));
}
}
function loadFromGithub(theme) {
fetch(`https://raw.githubusercontent.com/acouvreur/sablier/refs/heads/main/app/theme/embedded/${theme.value}.html`)
.then(value => value.text().then(body => {
ace.edit("input-editor").setValue(body)
}))
}
function saveInputToLocalStorage() {
var input_editor = ace.edit("input-editor");
localStorage.setItem(
"input_editor",
input_editor.session.getValue()
);
}
function saveDataToLocalStorage() {
var data_editor = ace.edit("data-editor");
localStorage.setItem(
"data_editor",
data_editor.session.getValue()
);
}
function loadEditors() {
var input_editor = ace.edit("input-editor");
input_editor.setTheme("ace/theme/monokai");
input_editor.session.setMode("ace/mode/html");
input_editor.session.on('change', function (delta) {
// delta.start, delta.end, delta.lines, delta.action
render()
saveInputToLocalStorage()
});


var data_editor = ace.edit("data-editor");
data_editor.setTheme("ace/theme/monokai");
data_editor.session.setMode("ace/mode/json");
data_editor.session.on('change', function (delta) {
// delta.start, delta.end, delta.lines, delta.action
render()
saveDataToLocalStorage()
})

loadFromLocalStorage()
}
</script>
</head>

<body style="
background: black;
background-image:
radial-gradient(farthest-corner at 0 100vh, #2700828F 0%, transparent 67%),
radial-gradient(farthest-corner at 100vw 100vh, #7A0BC05F 0%, transparent 67%);
color: white;
font-family: 'BlinkMacSystemFont', '-apple-system', 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
min-width: 600px;
">
<section id="header" style="display:flex;align-items:center;height:calc(90px + 2rem)">
<div>
<h1 style="font-size:1.8rem;font-weight:600">Sablier Theme Editor</h1>
<label>
Import theme:
<select onchange="loadFromGithub(this)">
<option value="ghost">ghost</option>
<option value="hacker-terminal">hacker-terminal</option>
<option value="matrix">matrix</option>
<option value="shuffle">shuffle</option>
</select>
</label>
</div>
</section>

<section id="main" style="display:flex;align-items:center;height:calc(calc(100vh - calc(calc(90px + 2rem) * 1)) - 2rem);min-height:calc(600px + 2rem);padding-bottom:1rem">
<div style="margin:0 auto;height:100%;width:calc(35% - .5rem);margin-right:1rem">
<div id="input-editor" style="height:calc(80% - .5rem);margin-bottom:1rem">
</div>
<div id="data-editor" style="height:calc(20% - .5rem)">
</div>
</div>
<div style="margin:0 auto;height:100%;width:calc(65% - .5rem)">
<div class="textarea-light" style="height:calc(100%);display: flex;">
<iframe style="flex-grow: 1; border: none; margin: 0; padding: 0;" id="output"></iframe>
</div>
</div>
</section>
<section id="footer" style="padding-top:0;font-size:.8rem">
<p style="text-align: center;opacity:.67"><a href="https://github.com/sablierapp/theme-editor" target="_blank">Github</a></p>
</section>
</body>
</html>

0 comments on commit 30d1055

Please sign in to comment.