Skip to content

Commit

Permalink
Beautiful & advanced web UI (#355)
Browse files Browse the repository at this point in the history
Such beauty, wow!
  • Loading branch information
laurentlb authored Apr 22, 2024
1 parent f1e3fd5 commit d3c57a9
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 28 deletions.
41 changes: 24 additions & 17 deletions SMBolero.Client/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ type Model =
{
page: Page
shaderInput: string
shaderOutput: string
shaderSize: int
flags: Flags
error: string option
Expand All @@ -35,7 +34,6 @@ let initModel =
{
page = Home
shaderInput = "out vec4 fragColor;\nvoid main() {\n fragColor = vec4(1.,1.,1.,1.);\n}"
shaderOutput = ""
shaderSize = 0
flags = {Flags.inlining=true; removeUnused=true; renaming=true; other=""; format="text"}
error = None
Expand All @@ -50,16 +48,19 @@ type Message =
| ClearError

let minify flags content =
try
Options.init flags
let shaders, exportedNames = ShaderMinifier.minify [|"input", content|]
let out = new System.IO.StringWriter()
Formatter.print out shaders exportedNames Options.Globals.options.outputFormat
out.ToString(), ShaderMinifier.getSize shaders
with
| e -> e.Message, 0

let update message model =
Options.init flags
let shaders, exportedNames = ShaderMinifier.minify [|"input", content|]
let out = new System.IO.StringWriter()
Formatter.withLocations <- false
Formatter.print out shaders exportedNames Options.Globals.options.outputFormat

let withLoc = new System.IO.StringWriter()
Formatter.withLocations <- true
Formatter.print withLoc shaders exportedNames Options.Globals.options.outputFormat

out.ToString(), ShaderMinifier.getSize shaders, withLoc.ToString()

let update (jsRuntime: Microsoft.JSInterop.IJSRuntime) message model =
match message with
| SetPage page ->
{ model with page = page }, Cmd.none
Expand All @@ -72,8 +73,15 @@ let update message model =
yield! model.flags.other.Split(' ')
|]
printfn "Minify %s" (String.concat " " allFlags)
let out, size = minify allFlags model.shaderInput
{ model with shaderOutput = out ; shaderSize = size }, Cmd.none
try
let out, size, withLoc = minify allFlags model.shaderInput
jsRuntime.InvokeAsync("updateShader", [|model.shaderInput; withLoc|]) |> ignore
{ model with shaderSize = size }, Cmd.none
with
| e ->
printfn "Error: %s" e.Message
jsRuntime.InvokeAsync("updateShader", [|model.shaderInput; e.Message|]) |> ignore
{ model with shaderSize = 0 }, Cmd.none
| SetShader value ->
{ model with shaderInput = value }, Cmd.none
| Error exn ->
Expand All @@ -99,8 +107,7 @@ let homePage model dispatch =
Main.Home()
.Minify(fun _ -> dispatch Minify)
.ShaderInput(model.shaderInput, fun v -> dispatch (SetShader v))
.ShaderOutput(model.shaderOutput)
.ShaderSize(if model.shaderSize = 0 then "" else $"size: {model.shaderSize}")
.ShaderSize(if model.shaderSize = 0 then "" else $"generated, size: {model.shaderSize}")
.Format(model.flags.format, fun v -> model.flags.format <- v)
.Inlining(model.flags.inlining, fun v -> model.flags.inlining <- v)
.RemoveUnused(model.flags.removeUnused, fun v -> model.flags.removeUnused <- v)
Expand Down Expand Up @@ -142,7 +149,7 @@ type MyApp() =
inherit ProgramComponent<Model, Message>()

override this.Program =
Program.mkProgram (fun _ -> initModel, Cmd.ofMsg (Message.SetPage Home)) update view
Program.mkProgram (fun _ -> initModel, Cmd.ofMsg (Message.SetPage Home)) (update this.JSRuntime) view
|> Program.withRouter router
#if DEBUG
|> Program.withHotReload
Expand Down
67 changes: 61 additions & 6 deletions SMBolero.Client/wwwroot/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
--border: #404080;
--text-color: #f0f0f0;
--alt-text-color: #aac;
scrollbar-width: thin;
scrollbar-color: var(--alt-text-color) transparent;
}

body {
Expand Down Expand Up @@ -99,7 +101,7 @@ details li {
}

#content {
max-width: 800px;
max-width: 1500px;
margin: 0px auto;
}

Expand Down Expand Up @@ -146,12 +148,16 @@ button, textarea, input, select {

button {
cursor: pointer;
background-color: #404080;
font-variant: small-caps;
font-size: 1em;
}

textarea {
box-sizing: border-box;
width: 100%;
padding: 4px;
scrollbar-width: thin;
}

code {
Expand All @@ -169,20 +175,69 @@ code {
}

#minify-btn {
background-color: #404080;
margin: 8px 0;
padding: 8px;
font-variant: small-caps;
font-size: 1em;
float: right;
}

.size {
text-align: right;
.output-header {
font-size: 0.8em;
color: var(--alt-text-color);
width: 33.3%;
text-align: center;
line-height: 32px;
height: 32px;
}


.clickable {
cursor: pointer;
}

/* Style for the source/minified columns. */

.comment {
color: #888;
}

.output {
width: 100%;
display: flex;
flex-wrap: wrap;
place-content: space-between;
}

.source, .minified {
padding: 3em 0em 3em 1em;
font-size: 1.2em;
font-family: monospace;
white-space: pre-wrap;

line-break: anywhere;
box-sizing: border-box;
width: 49.5%;

overflow-y: scroll;
scrollbar-width: thin;
max-height: 100vh;
background-color: rgb(26, 26, 42);
border: 1px solid var(--border);
}

.highlight {
background-color: var(--bg-light);
color: #ff0 !important;
text-decoration-line: underline !important;
}

.ident {
color: #88c;
font-weight: bold;
cursor: pointer;
}

/* */


/* Let's be modern and support mobile*/
@media (max-width: 750px) {
Expand Down
1 change: 1 addition & 0 deletions SMBolero.Client/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
<body>
<div id="main">Loading...</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="minifier.js"></script>
</body>
</html>
19 changes: 16 additions & 3 deletions SMBolero.Client/wwwroot/main.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="header">Ctrl-Alt-Test - Shader Minifier</div>

<div class="columns">
<aside class="left column sidebar is-narrow">
<h1>Menu</h1>
Expand Down Expand Up @@ -51,8 +51,21 @@ <h1>Menu</h1>
</ul>
</details>
</div>
<textarea rows="15" id="output" readonly>${ShaderOutput}</textarea>
<div class="size">${ShaderSize}</div>

<div class="output">
<div class="output-header">original</div>

<div class="output-header">
<svg width="32px" height="32px" onclick="copyBtn()" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="clickable">
<title>Copy output to clipboard</title>
<path d="M10 8V7C10 6.05719 10 5.58579 10.2929 5.29289C10.5858 5 11.0572 5 12 5H17C17.9428 5 18.4142 5 18.7071 5.29289C19 5.58579 19 6.05719 19 7V12C19 12.9428 19 13.4142 18.7071 13.7071C18.4142 14 17.9428 14 17 14H16M7 19H12C12.9428 19 13.4142 19 13.7071 18.7071C14 18.4142 14 17.9428 14 17V12C14 11.0572 14 10.5858 13.7071 10.2929C13.4142 10 12.9428 10 12 10H7C6.05719 10 5.58579 10 5.29289 10.2929C5 10.5858 5 11.0572 5 12V17C5 17.9428 5 18.4142 5.29289 18.7071C5.58579 19 6.05719 19 7 19Z" stroke="var(--alt-text-color)" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</div>

<div class="output-header">${ShaderSize}</div>
<div class="source"></div>
<div class="minified"></div>
</div>
</template>

<template id="FlagPage">
Expand Down
98 changes: 98 additions & 0 deletions SMBolero.Client/wwwroot/minifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const highlights = [];
const min2src = [];
const src2min = [];

function getHash(line, col, isMin) {
return line * 1000 + col;
}

function extractAnnotations(str) {
const lines = str.split('\n');
for (let i = 0; i < lines.length; i++) {
var hasReplaced = true;
while (hasReplaced) {
hasReplaced = false;
lines[i] = lines[i].replace(/(\w+)@(-?\d+),(-?\d+)@/, function (match, ident, line, col, offset) {
hasReplaced = true;
line--;
col--;
min2src[getHash(i, offset)] = { ident, line, col };
src2min[getHash(line, col)] = { ident, line: i, col: offset };
return ident;
});
}
}
return lines.join('\n');
}

function getPrefix(isMin) {
return isMin ? 'min' : 'src';
}

function addAnnotations(str, sourceMap, div, isMin) {
const lines = str.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineDiv = document.createElement('div');
lineDiv.classList.add('line');
var html = line.replace(/\w+/g, function (match, offset) {
// console.log(match, i, offset);
const hash1 = getHash(i, offset);
if (sourceMap[hash1]) {
const hash2 = getHash(sourceMap[hash1].line, sourceMap[hash1].col);
return `<a id=${getPrefix(isMin)}${hash1} class="ident" onclick="show(this, '${getPrefix(!isMin)}${hash2}')">${match}</a>`;
}
return match;
});

const comment = html.indexOf('//');
if (comment !== -1) {
html = html.substring(0, comment) + '<span class="comment">' + html.substring(comment) + '</span>';
}

lineDiv.innerHTML = html;
div.appendChild(lineDiv);
}
}

function show(x, hash) {
// TODO: support multiple references
const link = document.getElementById(hash);
removeHighlights();
addHighlight(link);
addHighlight(x);
}
function removeHighlights() {
for (const hl of highlights) {
hl.classList.remove('highlight');
}
highlights.length = 0;
}


function addHighlight(item) {
item.classList.add('highlight');
item.scrollIntoView({ block: "center" });
highlights.push(item);
}

function updateShader(shaderSource, shaderMinified) {
const source = document.querySelector('.source');
const minified = document.querySelector('.minified');

removeHighlights();
source.innerHTML = '';
minified.innerHTML = '';
min2src.length = 0;
src2min.length = 0;

const shaderMinified2 = extractAnnotations(shaderMinified);
addAnnotations(shaderMinified2, min2src, minified, true);
addAnnotations(shaderSource, src2min, source, false);
}

function copyBtn() {
const minified = document.querySelector('.minified');
console.log("Copy", minified.textContent);
navigator.clipboard.writeText(minified.textContent);
}
13 changes: 11 additions & 2 deletions src/formatter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ open System
open System.IO
open Options.Globals

// TODO(rubix): clean up the mess
let mutable withLocations = false

module private Impl =

let private minify shader =
Expand All @@ -14,9 +17,15 @@ module private Impl =

match options.outputFormat with
| Options.Text | Options.JS ->
Printer.print shader.code
if withLocations then
Printer.printWithLoc shader.code |> Printer.stripIndentation
else
Printer.print shader.code
| Options.IndentedText | Options.CVariables | Options.CArray | Options.JS | Options.Nasm | Options.Rust ->
Printer.printIndented shader.code
if withLocations then
Printer.printWithLoc shader.code
else
Printer.printIndented shader.code

let private formatPrefix = function
| Ast.ExportPrefix.Variable -> "var"
Expand Down

0 comments on commit d3c57a9

Please sign in to comment.