Skip to content

Commit 85a9443

Browse files
committed
Add version selector to web demo
This involves switching to dynamic loading of the WASM module, and also involves changing how the GitHub pages is built and deployed. The idea is that the latest version of the web demo site should be backwards compatible with all the previous versions of the WASM module, and the `gh-pages` branch will simply keep all the old WASM module builds, while new ones are added and the web demo site is updated. The default module version loaded is whatever is first in the versions.json file that exists at the root of the `gh-pages` branch, which the workflow makes sure to sort according to `sort -Vr` (version sort in descending order). This means that the latest tagged version should be first, and all the branches will be after all the tags. - https://www.gnu.org/software/coreutils/manual/html_node/Version-sort-overview.html In order to make switching between and comparing versions more streamlined, I also implemented a function to migrate the settings as you change the selected version. This comments and uncomments lines that correspond to settings that were removed or added when moving between versions. The selected version is also a query parameter now, so old examples shared via URL should remain stable as new versions are released.
1 parent a9ac1a0 commit 85a9443

File tree

7 files changed

+241
-22
lines changed

7 files changed

+241
-22
lines changed

.github/workflows/build-web-demo/action.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
name: Build Web Demo
22
description: Build the web demo
33

4+
on:
5+
workflow_call:
6+
inputs:
7+
git_ref:
8+
description: 'The git ref to checkout and build'
9+
required: false
10+
default: ''
411

512
runs:
613
using: "composite"
714
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v2
17+
if: ${{ inputs.git_ref != '' }}
18+
with:
19+
fetch-depth: 0
20+
ref: ${{ inputs.git_ref }}
21+
822
- name: Install Rust
923
uses: dtolnay/rust-toolchain@stable
1024

.github/workflows/deploy-web-demo.yaml

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ name: Build and Deploy Web Demo to GitHub Pages
33
on:
44
# Allows you to run this workflow manually from the Actions tab
55
workflow_dispatch:
6+
inputs:
7+
git_ref:
8+
description: 'The git ref to checkout and build'
9+
required: false
610

711
push:
812
branches:
913
- master
14+
tags:
15+
- v*
1016

1117
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
1218
permissions:
@@ -22,27 +28,94 @@ concurrency:
2228
jobs:
2329
build:
2430
runs-on: ubuntu-latest
31+
outputs:
32+
version: ${{ steps.set-version.outputs.VERSION }}
2533

2634
steps:
2735
- name: Checkout repository
2836
uses: actions/checkout@v2
2937

38+
- name: Determine version
39+
id: set-version
40+
run: |
41+
VERSION="${{ github.event.inputs.git_ref || github.ref_name }}"
42+
echo "VERSION=$VERSION" >> $GITHUB_ENV
43+
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
44+
3045
- name: Build Web Demo
3146
uses: ./.github/workflows/build-web-demo/
47+
with:
48+
git_ref: ${{ env.VERSION }}
3249

3350
- name: Setup Pages
3451
uses: actions/configure-pages@v5
3552

36-
- name: Upload artifact
53+
- name: Upload wasm-pack artifact
54+
uses: actions/upload-pages-artifact@v3
55+
with:
56+
path: 'web/pkg/'
57+
name: 'wasm'
58+
59+
- name: Upload Vite artifact
3760
uses: actions/upload-pages-artifact@v3
3861
with:
3962
path: 'web/demo/dist/'
63+
name: 'vite'
4064

4165
deploy:
4266
runs-on: ubuntu-latest
4367
needs: build
68+
permissions:
69+
contents: write
70+
environment:
71+
name: github-pages
72+
url: ${{ steps.deployment.outputs.page_url }}
4473

4574
steps:
46-
- name: Deploy to GitHub Pages
47-
id: deployment
48-
uses: actions/deploy-pages@v4
75+
- name: Set VERSION environment variable
76+
run: echo "VERSION=${{ needs.build.outputs.VERSION }}" >> $GITHUB_ENV
77+
78+
- name: Checkout gh-pages branch
79+
uses: actions/checkout@v4
80+
with:
81+
ref: gh-pages
82+
fetch-depth: 0
83+
84+
- name: Download artifacts
85+
uses: actions/download-artifact@v4
86+
with:
87+
path: ./
88+
89+
- name: Extract artifact
90+
run: |
91+
set -e
92+
93+
PKG_DIR=pkg/${VERSION:?}
94+
rm -rf "$PKG_DIR"
95+
mkdir -p "$PKG_DIR"
96+
tar -xvf wasm/artifact.tar -C "$PKG_DIR" --wildcards "*.js" "*.wasm"
97+
rm wasm/artifact.tar
98+
99+
tar -xvf vite/artifact.tar
100+
rm vite/artifact.tar
101+
102+
- name: Update versions.json
103+
run: |
104+
set -e
105+
106+
readarray -t versions < <(find pkg -mindepth 1 -maxdepth 1 -type d -not -name '.*' -printf '%P\n' | sort -Vr)
107+
echo "[$(printf '"%s",' "${versions[@]}" | sed 's/,$//')]" > versions.json
108+
109+
- name: Create .nojekyll file
110+
run: |
111+
touch .nojekyll
112+
113+
- name: Commit and push changes
114+
run: |
115+
set -e
116+
117+
git config user.name "github-actions[bot]"
118+
git config user.email "github-actions[bot]@users.noreply.github.com"
119+
git add -A
120+
git commit -m "Deploy ${VERSION:?}" || echo "No changes to commit"
121+
git push origin gh-pages

web/demo/index.html

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>pasfmt demo</title>
7-
<link rel="icon" type="image/ico" href="/img/pasfmt.ico">
7+
<link rel="icon" type="image/ico" href="/img/pasfmt.ico" />
88
<style>
99
@media (prefers-color-scheme: light) {
1010
:root {
@@ -43,13 +43,16 @@
4343
display: flex;
4444
}
4545
#github-logo {
46-
margin-left: auto;
4746
margin-right: 1em;
4847
}
4948
#github-logo img {
5049
height: 1.5em;
5150
vertical-align: middle;
5251
}
52+
#version-picker {
53+
margin-right: 1em;
54+
margin-left: auto;
55+
}
5356
#editpane {
5457
display: flex;
5558
height: 100%;
@@ -105,6 +108,31 @@
105108
.label {
106109
margin: 10px 0 5px;
107110
}
111+
112+
.loading {
113+
--stripe1: color-mix(in srgb, Canvas, CanvasText 10%);
114+
--stripe2: color-mix(in srgb, Canvas, CanvasText 20%);
115+
background-image: repeating-linear-gradient(
116+
45deg,
117+
var(--stripe1) 0,
118+
var(--stripe1) 10px,
119+
var(--stripe2) 10px,
120+
var(--stripe2) 20px
121+
);
122+
background-size: 28px 60px;
123+
animation: stripes 1s linear infinite;
124+
text-shadow: 0 0 0 CanvasText;
125+
border-radius: 4px;
126+
cursor: wait;
127+
}
128+
@keyframes stripes {
129+
0% {
130+
background-position: 0 0;
131+
}
132+
100% {
133+
background-position: 28px 0;
134+
}
135+
}
108136
</style>
109137
</head>
110138
<body>
@@ -124,7 +152,11 @@ <h2>Settings</h2>
124152
<option value="/pasfmt/examples/simple.pas">simple</option>
125153
</select>
126154
<button id="share-example">Share Example</button>
127-
<a id="github-logo" href="https://github.com/integrated-application-development/pasfmt/">
155+
<select name="version picker" id="version-picker"></select>
156+
<a
157+
id="github-logo"
158+
href="https://github.com/integrated-application-development/pasfmt/"
159+
>
128160
<img src="/img/github.svg" alt="github logo" />
129161
</a>
130162
</div>

web/demo/public/versions.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

web/demo/src/index.ts

Lines changed: 110 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import init, { fmt, default_settings_toml, SettingsWrapper } from "../../pkg";
2-
31
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
42

53
// support for ini, good enough for our TOML config
@@ -21,7 +19,106 @@ import "monaco-editor/esm/vs/editor/contrib/wordOperations/browser/wordOperation
2119
// custom delphi tokenizer
2220
import * as delphi from "./delphi";
2321

24-
await init();
22+
const BASE_URL = import.meta.env.BASE_URL;
23+
24+
const url = new URL(window.location.href);
25+
const version_param = url.searchParams.get("version");
26+
const source_param = url.searchParams.get("source");
27+
const settings_param = url.searchParams.get("settings");
28+
29+
// All use of this needs to be compatible with all supported versions of the WASM module.
30+
let pasfmt: any;
31+
32+
// Load a specific version
33+
const loadWasmVersion = async (version: string) => {
34+
// Dynamically import the JS glue code
35+
const mod = import.meta.env.DEV
36+
? await import("../../pkg/web.js")
37+
: await import(/* @vite-ignore */ `${BASE_URL}pkg/${version}/web.js`);
38+
39+
await mod.default();
40+
41+
console.log(`Loaded WASM module version: ${version}`);
42+
43+
pasfmt = mod;
44+
};
45+
46+
const loadVersion = async () => {
47+
await loadWasmVersion(versionPicker.value);
48+
};
49+
50+
const versionPicker = document.getElementById(
51+
"version-picker"
52+
)! as HTMLSelectElement;
53+
54+
await fetch(`${BASE_URL}versions.json`)
55+
.then((res) => res.json())
56+
.then((versions: Array<string>) => {
57+
versions.forEach((v) => {
58+
const opt = document.createElement("option");
59+
opt.value = v;
60+
opt.textContent = v;
61+
versionPicker.appendChild(opt);
62+
});
63+
64+
if (version_param !== null) {
65+
const decoded = atob(version_param);
66+
versionPicker.value = decoded;
67+
if (!versionPicker.value) {
68+
console.log(`Invalid version from search parameters: ${decoded}`);
69+
}
70+
} else {
71+
versionPicker.value = versions[0];
72+
}
73+
})
74+
.then(loadVersion);
75+
76+
const showLoadingSpinner = () => {
77+
versionPicker.disabled = true;
78+
versionPicker.classList.add('loading')
79+
}
80+
81+
const hideLoadingSpinner = () => {
82+
versionPicker.disabled = false;
83+
versionPicker.classList.remove('loading')
84+
}
85+
86+
versionPicker.addEventListener("change", async () => {
87+
showLoadingSpinner();
88+
await loadVersion();
89+
hideLoadingSpinner();
90+
updateSetingsOnVersionChange();
91+
formatEditors();
92+
});
93+
94+
const updateSetingsOnVersionChange = () => {
95+
const new_valid_keys = defaultSettings()
96+
.split("\n")
97+
.map((line) => line.split("=")[0].trim());
98+
const new_settings = settingsEditor
99+
.getValue()
100+
.split("\n")
101+
.map((line) => {
102+
if (!line.includes("=")) {
103+
return line;
104+
}
105+
106+
const commented_out = "#(unavailable)";
107+
if (line.startsWith(commented_out)) {
108+
line = line.substring(commented_out.length);
109+
}
110+
111+
let key = line.split("=")[0].trim();
112+
if (new_valid_keys.includes(key)) {
113+
return line;
114+
} else {
115+
return commented_out + line;
116+
}
117+
})
118+
.join("\n");
119+
120+
settingsEditor.setValue(new_settings);
121+
};
25122

26123
const diffEditorContainer = document.getElementById("diffpane")!;
27124
const sideBySideContainer = document.getElementById("editpane")!;
@@ -52,13 +149,14 @@ const settingsEditor = monaco.editor.create(settingsDiv, {
52149
const resetDefaultSettingsButton = document.getElementById(
53150
"resetToDefaultSettings"
54151
)!;
55-
const resetSettings = () => settingsEditor.setValue(default_settings_toml());
152+
const defaultSettings = (): string => pasfmt.default_settings_toml();
153+
const resetSettings = () => settingsEditor.setValue(defaultSettings());
56154
resetSettings();
57155
resetDefaultSettingsButton.onclick = resetSettings;
58156

59157
const parseSettings = () => {
60158
try {
61-
return new SettingsWrapper(settingsEditor.getValue());
159+
return new pasfmt.SettingsWrapper(settingsEditor.getValue());
62160
} catch (error) {
63161
throw new Error("Failed to parse settings", {
64162
cause: error,
@@ -208,7 +306,7 @@ const formatEditors = () => {
208306
try {
209307
let settingsObj = parseSettings();
210308
updateRulers(settingsObj.max_line_len());
211-
formattedModel.setValue(fmt(originalModel.getValue(), settingsObj));
309+
formattedModel.setValue(pasfmt.fmt(originalModel.getValue(), settingsObj));
212310
} catch (error) {
213311
console.log(error);
214312
renderErrorInModel(error, formattedModel);
@@ -245,19 +343,15 @@ document
245343
.getElementById("sample-picker")!
246344
.addEventListener("change", loadSample);
247345

248-
const url = new URL(window.location.href);
249-
let source = url.searchParams.get("source");
250-
let settings = url.searchParams.get("settings");
251-
252-
if (source !== null) {
253-
let decoded = atob(source);
346+
if (source_param !== null) {
347+
let decoded = atob(source_param);
254348
originalEditor.setValue(decoded);
255349
} else {
256-
loadSampleFile("/pasfmt/examples/simple.pas");
350+
loadSampleFile(`${BASE_URL}examples/simple.pas`);
257351
}
258352

259-
if (settings !== null) {
260-
let decoded = atob(settings);
353+
if (settings_param !== null) {
354+
let decoded = atob(settings_param);
261355
settingsEditor.setValue(decoded);
262356
}
263357

@@ -267,6 +361,7 @@ const shareExample = document.getElementById(
267361
shareExample.onclick = () => {
268362
url.searchParams.set("source", btoa(originalEditor.getValue()));
269363
url.searchParams.set("settings", btoa(settingsEditor.getValue()));
364+
url.searchParams.set("version", btoa(versionPicker.value));
270365
window.history.replaceState(null, "", url);
271366
navigator.clipboard.writeText(window.location.href);
272367
};

web/demo/src/vite-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

0 commit comments

Comments
 (0)