aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects')
-rw-r--r--subprojects/frontend/assets-src/favicon.svg76
-rw-r--r--subprojects/frontend/assets-src/icon.svg99
-rw-r--r--subprojects/frontend/assets-src/mask-icon.svg54
-rw-r--r--subprojects/frontend/build.gradle4
-rw-r--r--subprojects/frontend/index.html8
-rw-r--r--subprojects/frontend/package.json15
-rw-r--r--subprojects/frontend/public/apple-touch-icon.pngbin0 -> 3711 bytes
-rw-r--r--subprojects/frontend/public/favicon-96x96.pngbin0 -> 2044 bytes
-rw-r--r--subprojects/frontend/public/favicon.pngbin0 -> 822 bytes
-rw-r--r--subprojects/frontend/public/favicon.svg1
-rw-r--r--subprojects/frontend/public/icon-192x192.pngbin0 -> 3985 bytes
-rw-r--r--subprojects/frontend/public/icon-512x512.pngbin0 -> 10879 bytes
-rw-r--r--subprojects/frontend/public/icon-any.svg1
-rw-r--r--subprojects/frontend/public/mask-icon.svg1
-rw-r--r--subprojects/frontend/public/robots.txt2
-rw-r--r--subprojects/frontend/src/RegisterServiceWorker.tsx85
-rw-r--r--subprojects/frontend/src/index.tsx19
-rw-r--r--subprojects/frontend/tsconfig.json2
-rw-r--r--subprojects/frontend/vite.config.ts59
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java9
20 files changed, 415 insertions, 20 deletions
diff --git a/subprojects/frontend/assets-src/favicon.svg b/subprojects/frontend/assets-src/favicon.svg
new file mode 100644
index 00000000..39079ef3
--- /dev/null
+++ b/subprojects/frontend/assets-src/favicon.svg
@@ -0,0 +1,76 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 width="512"
6 height="512"
7 viewBox="0 0 512 512"
8 version="1.1"
9 id="svg5"
10 inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
11 sodipodi:docname="favicon.svg"
12 inkscape:export-filename="../public/favicon.png"
13 inkscape:export-xdpi="6"
14 inkscape:export-ydpi="6"
15 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
16 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
17 xmlns="http://www.w3.org/2000/svg"
18 xmlns:svg="http://www.w3.org/2000/svg">
19 <sodipodi:namedview
20 id="namedview7"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:showpageshadow="2"
25 inkscape:pageopacity="0.0"
26 inkscape:pagecheckerboard="0"
27 inkscape:deskcolor="#d1d1d1"
28 inkscape:document-units="mm"
29 showgrid="false"
30 inkscape:zoom="0.54060743"
31 inkscape:cx="-107.28672"
32 inkscape:cy="510.53682"
33 inkscape:window-width="2560"
34 inkscape:window-height="1415"
35 inkscape:window-x="0"
36 inkscape:window-y="0"
37 inkscape:window-maximized="1"
38 inkscape:current-layer="layer2">
39 <inkscape:grid
40 type="xygrid"
41 id="grid1699" />
42 </sodipodi:namedview>
43 <defs
44 id="defs2" />
45 <g
46 inkscape:groupmode="layer"
47 id="layer3"
48 inkscape:label="Background">
49 <rect
50 style="fill:#21252b;fill-opacity:1;stroke:none;stroke-width:0.264582;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:0.6;paint-order:stroke fill markers;stop-color:#000000"
51 id="rect453"
52 width="512"
53 height="512"
54 x="0"
55 y="0"
56 inkscape:label="fill" />
57 </g>
58 <g
59 inkscape:groupmode="layer"
60 id="layer2"
61 inkscape:label="Icon">
62 <g
63 id="g1119-3-1"
64 transform="matrix(5.6319708,0,0,5.5934499,-125.4721,-122.86304)"
65 style="display:inline;stroke:none;stroke-width:0;stroke-dasharray:none">
66 <path
67 style="font-size:114.487px;line-height:1.25;font-family:'XITS Math';-inkscape-font-specification:'XITS Math';letter-spacing:0px;word-spacing:0px;fill:#ebebff;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
68 d="M 101.82441,54.027469 C 76.975254,52.314235 48.331396,44.925914 33.642238,27.686497 v 8.244938 c 6.120483,9.208633 25.828436,17.88188 40.884823,21.629579 -15.056387,3.747699 -34.76434,12.420945 -40.884823,21.629578 V 87.43553 C 48.331396,70.196113 76.975254,62.807792 101.82441,61.094559 Z"
69 id="path1113-6-8" />
70 <path
71 style="font-size:114.487px;line-height:1.25;font-family:'XITS Math';-inkscape-font-specification:'XITS Math';letter-spacing:0px;word-spacing:0px;display:inline;fill:#56b6c2;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
72 d="M 101.82441,75.014584 C 81.626821,75.764124 50.779589,82.295828 33.642238,99.42817 v 8.35201 C 50.53477,89.255841 83.340556,83.152445 101.82441,82.081674 Z"
73 id="path1061-7-7" />
74 </g>
75 </g>
76</svg>
diff --git a/subprojects/frontend/assets-src/icon.svg b/subprojects/frontend/assets-src/icon.svg
new file mode 100644
index 00000000..f16c9f62
--- /dev/null
+++ b/subprojects/frontend/assets-src/icon.svg
@@ -0,0 +1,99 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 width="512"
6 height="512"
7 viewBox="0 0 512 512"
8 version="1.1"
9 id="svg5"
10 inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
11 sodipodi:docname="icon.svg"
12 inkscape:export-filename="../apple-touch-icon.png"
13 inkscape:export-xdpi="33.75"
14 inkscape:export-ydpi="33.75"
15 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
16 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
17 xmlns="http://www.w3.org/2000/svg"
18 xmlns:svg="http://www.w3.org/2000/svg">
19 <sodipodi:namedview
20 id="namedview7"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:showpageshadow="2"
25 inkscape:pageopacity="0.0"
26 inkscape:pagecheckerboard="0"
27 inkscape:deskcolor="#d1d1d1"
28 inkscape:document-units="mm"
29 showgrid="false"
30 inkscape:zoom="0.7971076"
31 inkscape:cx="194.45305"
32 inkscape:cy="286.03416"
33 inkscape:window-width="2560"
34 inkscape:window-height="1415"
35 inkscape:window-x="0"
36 inkscape:window-y="0"
37 inkscape:window-maximized="1"
38 inkscape:current-layer="layer3" />
39 <defs
40 id="defs2" />
41 <g
42 inkscape:groupmode="layer"
43 id="layer3"
44 inkscape:label="Background">
45 <rect
46 style="fill:#21252b;fill-opacity:1;stroke:none;stroke-width:0.264582;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:0.6;paint-order:stroke fill markers;stop-color:#000000"
47 id="rect453"
48 width="512"
49 height="512"
50 x="0"
51 y="0"
52 inkscape:label="fill" />
53 </g>
54 <g
55 inkscape:groupmode="layer"
56 id="layer2"
57 inkscape:label="Icon">
58 <g
59 id="g1119-3"
60 transform="matrix(3.8719803,0,0,3.8954379,-6.2620826,-7.8510277)"
61 style="display:inline;opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none">
62 <path
63 style="font-size:114.487px;line-height:1.25;font-family:'XITS Math';-inkscape-font-specification:'XITS Math';letter-spacing:0px;word-spacing:0px;fill:#181a1f;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
64 d="M 101.82441,58.134836 C 76.975254,56.421602 48.331396,49.033281 33.642238,31.793864 v 8.244938 c 6.120483,9.208633 25.828436,17.88188 40.884823,21.629579 -15.056387,3.747699 -34.76434,12.420945 -40.884823,21.629578 v 8.244938 C 48.331396,74.30348 76.975254,66.915159 101.82441,65.201926 Z"
65 id="path1113-6" />
66 <path
67 style="font-size:114.487px;line-height:1.25;font-family:'XITS Math';-inkscape-font-specification:'XITS Math';letter-spacing:0px;word-spacing:0px;display:inline;fill:#181a1f;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
68 d="m 101.82441,79.121951 c -20.197589,0.74954 -51.044821,7.281244 -68.182172,24.413589 v 8.35201 C 50.53477,93.363208 83.340556,87.259812 101.82441,86.189041 Z"
69 id="path1061-7" />
70 </g>
71 <g
72 id="g1119-3-1"
73 transform="matrix(3.8719803,0,0,3.8954379,-6.2620826,-7.8510277)"
74 style="display:inline;stroke:none;stroke-width:0;stroke-dasharray:none">
75 <path
76 style="font-size:114.487px;line-height:1.25;font-family:'XITS Math';-inkscape-font-specification:'XITS Math';letter-spacing:0px;word-spacing:0px;fill:#ebebff;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
77 d="M 101.82441,54.027469 C 76.975254,52.314235 48.331396,44.925914 33.642238,27.686497 v 8.244938 c 6.120483,9.208633 25.828436,17.88188 40.884823,21.629579 -15.056387,3.747699 -34.76434,12.420945 -40.884823,21.629578 V 87.43553 C 48.331396,70.196113 76.975254,62.807792 101.82441,61.094559 Z"
78 id="path1113-6-8" />
79 <path
80 style="font-size:114.487px;line-height:1.25;font-family:'XITS Math';-inkscape-font-specification:'XITS Math';letter-spacing:0px;word-spacing:0px;display:inline;fill:#56b6c2;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
81 d="M 101.82441,75.014584 C 81.626821,75.764124 50.779589,82.295828 33.642238,99.42817 v 8.35201 C 50.53477,89.255841 83.340556,83.152445 101.82441,82.081674 Z"
82 id="path1061-7-7" />
83 </g>
84 </g>
85 <g
86 inkscape:label="Guides"
87 inkscape:groupmode="layer"
88 id="layer1"
89 style="display:none;stroke:#ff00ff">
90 <ellipse
91 style="opacity:1;fill:none;stroke:#ff00ff;stroke-width:0.264582;stroke-linecap:square;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:0.6;paint-order:stroke fill markers;stop-color:#000000"
92 id="path111"
93 cx="256"
94 cy="255.99997"
95 inkscape:label="safe_zone"
96 rx="204.14394"
97 ry="204.14391" />
98 </g>
99</svg>
diff --git a/subprojects/frontend/assets-src/mask-icon.svg b/subprojects/frontend/assets-src/mask-icon.svg
new file mode 100644
index 00000000..fc30ed61
--- /dev/null
+++ b/subprojects/frontend/assets-src/mask-icon.svg
@@ -0,0 +1,54 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 width="512"
6 height="512"
7 viewBox="0 0 512 512"
8 version="1.1"
9 id="svg5"
10 inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
11 sodipodi:docname="mask-icon.svg"
12 inkscape:export-filename="../public/favicon.png"
13 inkscape:export-xdpi="6"
14 inkscape:export-ydpi="6"
15 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
16 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
17 xmlns="http://www.w3.org/2000/svg"
18 xmlns:svg="http://www.w3.org/2000/svg">
19 <sodipodi:namedview
20 id="namedview7"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:showpageshadow="2"
25 inkscape:pageopacity="0.0"
26 inkscape:pagecheckerboard="0"
27 inkscape:deskcolor="#d1d1d1"
28 inkscape:document-units="mm"
29 showgrid="false"
30 inkscape:zoom="0.54060743"
31 inkscape:cx="-107.28672"
32 inkscape:cy="510.53682"
33 inkscape:window-width="2560"
34 inkscape:window-height="1415"
35 inkscape:window-x="0"
36 inkscape:window-y="0"
37 inkscape:window-maximized="1"
38 inkscape:current-layer="layer2">
39 <inkscape:grid
40 type="xygrid"
41 id="grid1699" />
42 </sodipodi:namedview>
43 <defs
44 id="defs2" />
45 <g
46 inkscape:groupmode="layer"
47 id="layer2"
48 inkscape:label="Icon">
49 <path
50 id="path1113-6-8"
51 style="font-size:114.487px;line-height:1.25;font-family:'XITS Math';-inkscape-font-specification:'XITS Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
52 d="M 64 32 L 64 78.117188 C 98.470348 129.62516 209.46467 178.13901 294.26172 199.10156 C 209.46467 220.0641 98.470348 268.57796 64 320.08594 L 64 366.20312 C 146.72883 269.7754 308.05042 228.45007 448 218.86719 L 448 179.33594 C 308.05042 169.75306 146.72883 128.42772 64 32 z M 448 296.72656 C 334.24788 300.91907 160.51696 337.4544 64 433.2832 L 64 480 C 159.13815 376.38513 343.89958 342.24516 448 336.25586 L 448 296.72656 z " />
53 </g>
54</svg>
diff --git a/subprojects/frontend/build.gradle b/subprojects/frontend/build.gradle
index 5ed90c31..da237411 100644
--- a/subprojects/frontend/build.gradle
+++ b/subprojects/frontend/build.gradle
@@ -23,7 +23,9 @@ def installFrontend = tasks.named('installFrontend')
23 23
24def assembleFrontend = tasks.named('assembleFrontend') 24def assembleFrontend = tasks.named('assembleFrontend')
25assembleFrontend.configure { 25assembleFrontend.configure {
26 inputs.dir 'public'
26 inputs.dir 'src' 27 inputs.dir 'src'
28 inputs.file 'index.html'
27 inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'vite.config.ts') 29 inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'vite.config.ts')
28 inputs.file rootProject.file('yarn.lock') 30 inputs.file rootProject.file('yarn.lock')
29 outputs.dir productionResources 31 outputs.dir productionResources
@@ -80,7 +82,9 @@ tasks.named('check') {
80 82
81tasks.register('serveFrontend', RunYarn) { 83tasks.register('serveFrontend', RunYarn) {
82 dependsOn installFrontend 84 dependsOn installFrontend
85 inputs.dir 'public'
83 inputs.dir 'src' 86 inputs.dir 'src'
87 inputs.file 'index.html'
84 inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'vite.config.ts') 88 inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'vite.config.ts')
85 inputs.file rootProject.file('yarn.lock') 89 inputs.file rootProject.file('yarn.lock')
86 outputs.dir "${viteOutputDir}/development" 90 outputs.dir "${viteOutputDir}/development"
diff --git a/subprojects/frontend/index.html b/subprojects/frontend/index.html
index 999e69a3..92cc94c7 100644
--- a/subprojects/frontend/index.html
+++ b/subprojects/frontend/index.html
@@ -4,6 +4,14 @@
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1"> 5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <title>Refinery</title> 6 <title>Refinery</title>
7 <meta name="description" content="An efficient graph solver for generating well-formed models">
8 <link rel="icon" href="/favicon.svg" type="image/svg+xml">
9 <link rel="icon" href="/favicon.png" type="image/png" sizes="32x32">
10 <link rel="icon" href="/favicon-96x96.png" type="image/png" sizes="96x96">
11 <link rel="apple-touch-icon" href="/apple-touch-icon.png" type="image/png" sizes="180x180">
12 <link rel="mask-icon" href="/mask-icon.svg" type="image/svg+xml" color="#038a99">
13 <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#21252b">
14 <meta name="theme-color" media="(prefers-color-scheme: light)" content="#fafafa">
7 </head> 15 </head>
8 <body> 16 <body>
9 <noscript> 17 <noscript>
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index 69ff74c6..693f3d06 100644
--- a/subprojects/frontend/package.json
+++ b/subprojects/frontend/package.json
@@ -23,7 +23,7 @@
23 "homepage": "https://refinery.tools", 23 "homepage": "https://refinery.tools",
24 "dependencies": { 24 "dependencies": {
25 "@codemirror/autocomplete": "^6.1.0", 25 "@codemirror/autocomplete": "^6.1.0",
26 "@codemirror/commands": "^6.0.1", 26 "@codemirror/commands": "^6.1.0",
27 "@codemirror/language": "^6.2.1", 27 "@codemirror/language": "^6.2.1",
28 "@codemirror/lint": "^6.0.0", 28 "@codemirror/lint": "^6.0.0",
29 "@codemirror/search": "^6.1.0", 29 "@codemirror/search": "^6.1.0",
@@ -47,14 +47,15 @@
47 "mobx": "^6.6.1", 47 "mobx": "^6.6.1",
48 "mobx-react-lite": "^3.4.0", 48 "mobx-react-lite": "^3.4.0",
49 "nanoid": "^4.0.0", 49 "nanoid": "^4.0.0",
50 "notistack": "^2.0.5",
50 "react": "^18.2.0", 51 "react": "^18.2.0",
51 "react-dom": "^18.2.0", 52 "react-dom": "^18.2.0",
52 "zod": "^3.18.0" 53 "zod": "^3.18.0"
53 }, 54 },
54 "devDependencies": { 55 "devDependencies": {
55 "@lezer/generator": "^1.1.1", 56 "@lezer/generator": "^1.1.1",
56 "@types/eslint": "^8.4.5", 57 "@types/eslint": "^8.4.6",
57 "@types/node": "^18.7.6", 58 "@types/node": "^18.7.7",
58 "@types/prettier": "^2.7.0", 59 "@types/prettier": "^2.7.0",
59 "@types/react": "^18.0.17", 60 "@types/react": "^18.0.17",
60 "@types/react-dom": "^18.0.6", 61 "@types/react-dom": "^18.0.6",
@@ -66,7 +67,7 @@
66 "eslint-config-airbnb": "^19.0.4", 67 "eslint-config-airbnb": "^19.0.4",
67 "eslint-config-airbnb-typescript": "^17.0.0", 68 "eslint-config-airbnb-typescript": "^17.0.0",
68 "eslint-config-prettier": "^8.5.0", 69 "eslint-config-prettier": "^8.5.0",
69 "eslint-import-resolver-typescript": "^3.4.1", 70 "eslint-import-resolver-typescript": "^3.4.2",
70 "eslint-plugin-import": "^2.26.0", 71 "eslint-plugin-import": "^2.26.0",
71 "eslint-plugin-jsx-a11y": "^6.6.1", 72 "eslint-plugin-jsx-a11y": "^6.6.1",
72 "eslint-plugin-prettier": "^4.2.1", 73 "eslint-plugin-prettier": "^4.2.1",
@@ -74,7 +75,9 @@
74 "eslint-plugin-react-hooks": "^4.6.0", 75 "eslint-plugin-react-hooks": "^4.6.0",
75 "prettier": "^2.7.1", 76 "prettier": "^2.7.1",
76 "typescript": "~4.7.4", 77 "typescript": "~4.7.4",
77 "vite": "^3.0.8", 78 "vite": "^3.0.9",
78 "vite-plugin-inject-preload": "^1.0.1" 79 "vite-plugin-inject-preload": "^1.0.1",
80 "vite-plugin-pwa": "^0.12.3",
81 "workbox-window": "^6.5.4"
79 } 82 }
80} 83}
diff --git a/subprojects/frontend/public/apple-touch-icon.png b/subprojects/frontend/public/apple-touch-icon.png
new file mode 100644
index 00000000..de8549e7
--- /dev/null
+++ b/subprojects/frontend/public/apple-touch-icon.png
Binary files differ
diff --git a/subprojects/frontend/public/favicon-96x96.png b/subprojects/frontend/public/favicon-96x96.png
new file mode 100644
index 00000000..353fe18a
--- /dev/null
+++ b/subprojects/frontend/public/favicon-96x96.png
Binary files differ
diff --git a/subprojects/frontend/public/favicon.png b/subprojects/frontend/public/favicon.png
new file mode 100644
index 00000000..18e67636
--- /dev/null
+++ b/subprojects/frontend/public/favicon.png
Binary files differ
diff --git a/subprojects/frontend/public/favicon.svg b/subprojects/frontend/public/favicon.svg
new file mode 100644
index 00000000..b5d1d217
--- /dev/null
+++ b/subprojects/frontend/public/favicon.svg
@@ -0,0 +1 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg"><style>@media(prefers-color-scheme:dark){#a{fill:#ebebff}#b{fill:#56b6c2}}</style><path d="M447.98 179.335c-139.95-9.583-301.272-50.91-384-147.336v46.117C98.45 129.623 209.442 178.137 294.243 199.1c-84.796 20.963-195.791 69.476-230.265 120.985v46.117c82.73-96.422 244.053-137.752 384.002-147.334z" fill="#35373e" id="a"/><path d="M447.98 296.729c-113.755 4.192-287.485 40.727-384 136.557v46.716c95.14-103.612 279.898-137.754 384-143.745z" fill="#038a99" id="b"/></svg>
diff --git a/subprojects/frontend/public/icon-192x192.png b/subprojects/frontend/public/icon-192x192.png
new file mode 100644
index 00000000..7c04fb37
--- /dev/null
+++ b/subprojects/frontend/public/icon-192x192.png
Binary files differ
diff --git a/subprojects/frontend/public/icon-512x512.png b/subprojects/frontend/public/icon-512x512.png
new file mode 100644
index 00000000..40c602c0
--- /dev/null
+++ b/subprojects/frontend/public/icon-512x512.png
Binary files differ
diff --git a/subprojects/frontend/public/icon-any.svg b/subprojects/frontend/public/icon-any.svg
new file mode 100644
index 00000000..3f6517b3
--- /dev/null
+++ b/subprojects/frontend/public/icon-any.svg
@@ -0,0 +1 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h512v512H0z" fill="#21252b"/><path d="M387.985 218.608c-96.215-6.674-207.125-35.454-264-102.609v32.118c23.698 35.87 100.005 69.657 158.306 84.257-58.297 14.599-134.606 48.385-158.307 84.258v32.117c56.876-67.153 167.786-95.936 264-102.609zm0 81.752c-78.207 2.92-197.646 28.364-264 95.103v32.535c65.409-72.159 192.43-95.936 264-100.108z" fill="#181a1f"/><path d="M387.985 202.606c-96.215-6.674-207.125-35.455-264-102.609v32.117c23.698 35.871 100.005 69.658 158.306 84.258-58.297 14.599-134.606 48.384-158.307 84.257v32.117c56.876-67.152 167.786-95.935 264-102.608z" fill="#ebebff"/><path d="M387.985 284.362c-78.207 2.92-197.646 28.364-264 95.103v32.534c65.409-72.158 192.43-95.936 264-100.108z" fill="#56b6c2"/></svg> \ No newline at end of file
diff --git a/subprojects/frontend/public/mask-icon.svg b/subprojects/frontend/public/mask-icon.svg
new file mode 100644
index 00000000..86052c6e
--- /dev/null
+++ b/subprojects/frontend/public/mask-icon.svg
@@ -0,0 +1 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg"><path d="M64 32v46.117c34.47 51.508 145.46 100.02 230.26 120.98C209.463 220.06 98.47 268.573 64 320.077v46.117c82.729-96.428 244.05-137.75 384-147.34v-39.531c-139.95-9.583-301.27-50.908-384-147.34zm384 264.73c-113.75 4.192-287.48 40.728-384 136.56v46.717c95.138-103.61 279.9-137.75 384-143.74v-39.529z"/></svg>
diff --git a/subprojects/frontend/public/robots.txt b/subprojects/frontend/public/robots.txt
new file mode 100644
index 00000000..c2a49f4f
--- /dev/null
+++ b/subprojects/frontend/public/robots.txt
@@ -0,0 +1,2 @@
1User-agent: *
2Allow: /
diff --git a/subprojects/frontend/src/RegisterServiceWorker.tsx b/subprojects/frontend/src/RegisterServiceWorker.tsx
new file mode 100644
index 00000000..c9b2e353
--- /dev/null
+++ b/subprojects/frontend/src/RegisterServiceWorker.tsx
@@ -0,0 +1,85 @@
1import Button from '@mui/material/Button';
2import {
3 type OptionsObject as SnackbarOptionsObject,
4 useSnackbar,
5} from 'notistack';
6import React, { useEffect } from 'react';
7// eslint-disable-next-line import/no-unresolved -- Importing virtual module.
8import { registerSW } from 'virtual:pwa-register';
9
10import { ContrastThemeProvider } from './theme/ThemeProvider';
11import getLogger from './utils/getLogger';
12
13const log = getLogger('RegisterServiceWorker');
14
15function UpdateSnackbarActions({
16 closeCurrentSnackbar,
17 enqueueSnackbar,
18 updateSW,
19}: {
20 closeCurrentSnackbar: () => void;
21 enqueueSnackbar: (
22 message: string,
23 options?: SnackbarOptionsObject | undefined,
24 ) => void;
25 updateSW: (reloadPage: boolean) => Promise<void>;
26}): JSX.Element {
27 return (
28 <ContrastThemeProvider>
29 <Button
30 color="primary"
31 onClick={() => {
32 closeCurrentSnackbar();
33 updateSW(true).catch((error) => {
34 log.error('Failed to update service worker', error);
35 enqueueSnackbar('Failed to download update', {
36 variant: 'error',
37 });
38 });
39 }}
40 >
41 Reload
42 </Button>
43 <Button color="inherit" onClick={closeCurrentSnackbar}>
44 Dismiss
45 </Button>
46 </ContrastThemeProvider>
47 );
48}
49
50export default function RegisterServiceWorker(): null {
51 const { enqueueSnackbar, closeSnackbar } = useSnackbar();
52 useEffect(() => {
53 if (import.meta.env.DEV) {
54 return;
55 }
56 if (!('serviceWorker' in navigator)) {
57 log.debug('No service worker support found');
58 return;
59 }
60 const updateSW = registerSW({
61 onNeedRefresh() {
62 const key = enqueueSnackbar('An update for Refinery is available', {
63 persist: true,
64 action: (
65 <UpdateSnackbarActions
66 closeCurrentSnackbar={() => closeSnackbar(key)}
67 enqueueSnackbar={enqueueSnackbar}
68 updateSW={updateSW}
69 />
70 ),
71 });
72 },
73 onOfflineReady() {
74 log.debug('Service worker is ready for offline use');
75 },
76 onRegistered() {
77 log.debug('Registered service worker');
78 },
79 onRegisterError(error) {
80 log.error('Failed to register service worker', error);
81 },
82 });
83 }, [enqueueSnackbar, closeSnackbar]);
84 return null;
85}
diff --git a/subprojects/frontend/src/index.tsx b/subprojects/frontend/src/index.tsx
index 2176b277..b108df6d 100644
--- a/subprojects/frontend/src/index.tsx
+++ b/subprojects/frontend/src/index.tsx
@@ -1,6 +1,4 @@
1import CssBaseline from '@mui/material/CssBaseline'; 1import CssBaseline from '@mui/material/CssBaseline';
2import React, { Suspense, lazy } from 'react';
3import { createRoot } from 'react-dom/client';
4import '@fontsource/jetbrains-mono/400.css'; 2import '@fontsource/jetbrains-mono/400.css';
5import '@fontsource/jetbrains-mono/400-italic.css'; 3import '@fontsource/jetbrains-mono/400-italic.css';
6import '@fontsource/jetbrains-mono/700.css'; 4import '@fontsource/jetbrains-mono/700.css';
@@ -8,15 +6,15 @@ import '@fontsource/jetbrains-mono/700-italic.css';
8import '@fontsource/jetbrains-mono/variable.css'; 6import '@fontsource/jetbrains-mono/variable.css';
9import '@fontsource/jetbrains-mono/variable-italic.css'; 7import '@fontsource/jetbrains-mono/variable-italic.css';
10import '@fontsource/roboto/300.css'; 8import '@fontsource/roboto/300.css';
11import '@fontsource/roboto/300-italic.css';
12import '@fontsource/roboto/400.css'; 9import '@fontsource/roboto/400.css';
13import '@fontsource/roboto/400-italic.css';
14import '@fontsource/roboto/500.css'; 10import '@fontsource/roboto/500.css';
15import '@fontsource/roboto/500-italic.css';
16import '@fontsource/roboto/700.css'; 11import '@fontsource/roboto/700.css';
17import '@fontsource/roboto/700-italic.css'; 12import { SnackbarProvider } from 'notistack';
13import React, { Suspense, lazy } from 'react';
14import { createRoot } from 'react-dom/client';
18 15
19import Loading from './Loading'; 16import Loading from './Loading';
17import RegisterServiceWorker from './RegisterServiceWorker';
20import RootStore, { RootStoreProvider } from './RootStore'; 18import RootStore, { RootStoreProvider } from './RootStore';
21import ThemeProvider from './theme/ThemeProvider'; 19import ThemeProvider from './theme/ThemeProvider';
22import getLogger from './utils/getLogger'; 20import getLogger from './utils/getLogger';
@@ -79,9 +77,12 @@ const app = (
79 <RootStoreProvider rootStore={rootStore}> 77 <RootStoreProvider rootStore={rootStore}>
80 <ThemeProvider> 78 <ThemeProvider>
81 <CssBaseline enableColorScheme /> 79 <CssBaseline enableColorScheme />
82 <Suspense fallback={<Loading />}> 80 <SnackbarProvider>
83 <App /> 81 <RegisterServiceWorker />
84 </Suspense> 82 <Suspense fallback={<Loading />}>
83 <App />
84 </Suspense>
85 </SnackbarProvider>
85 </ThemeProvider> 86 </ThemeProvider>
86 </RootStoreProvider> 87 </RootStoreProvider>
87 </React.StrictMode> 88 </React.StrictMode>
diff --git a/subprojects/frontend/tsconfig.json b/subprojects/frontend/tsconfig.json
index fcde9939..e8053768 100644
--- a/subprojects/frontend/tsconfig.json
+++ b/subprojects/frontend/tsconfig.json
@@ -4,7 +4,7 @@
4 "jsx": "react", 4 "jsx": "react",
5 "noEmit": true, 5 "noEmit": true,
6 "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 "lib": ["DOM", "DOM.Iterable", "ESNext"],
7 "types": ["vite/client"] 7 "types": ["vite/client", "vite-plugin-pwa/client"]
8 }, 8 },
9 "include": [ 9 "include": [
10 "src", 10 "src",
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts
index d97b0ac9..7c0c2605 100644
--- a/subprojects/frontend/vite.config.ts
+++ b/subprojects/frontend/vite.config.ts
@@ -6,11 +6,13 @@ import { lezer } from '@lezer/generator/rollup';
6import react from '@vitejs/plugin-react'; 6import react from '@vitejs/plugin-react';
7import { defineConfig } from 'vite'; 7import { defineConfig } from 'vite';
8import injectPreload from 'vite-plugin-inject-preload'; 8import injectPreload from 'vite-plugin-inject-preload';
9import { VitePWA } from 'vite-plugin-pwa';
9 10
10const thisDir = path.dirname(fileURLToPath(import.meta.url)); 11const thisDir = path.dirname(fileURLToPath(import.meta.url));
11 12
12const mode = process.env.MODE || 'development'; 13const mode = process.env.MODE || 'development';
13const isDevelopment = mode === 'development'; 14const isDevelopment = mode === 'development';
15process.env.NODE_ENV ??= mode;
14 16
15function portNumberOrElse(envName: string, fallback: number): number { 17function portNumberOrElse(envName: string, fallback: number): number {
16 const value = process.env[envName]; 18 const value = process.env[envName];
@@ -29,7 +31,7 @@ const { name: packageName, version: packageVersion } = JSON.parse(
29 readFileSync(path.join(thisDir, 'package.json'), 'utf8'), 31 readFileSync(path.join(thisDir, 'package.json'), 'utf8'),
30) as { name: string; version: string }; 32) as { name: string; version: string };
31process.env.VITE_PACKAGE_NAME ??= packageName; 33process.env.VITE_PACKAGE_NAME ??= packageName;
32process.env.VITE_PACKAGE_VERSIOn ??= packageVersion; 34process.env.VITE_PACKAGE_VERSION ??= packageVersion;
33 35
34export default defineConfig({ 36export default defineConfig({
35 logLevel: 'info', 37 logLevel: 'info',
@@ -50,7 +52,7 @@ export default defineConfig({
50 files: [ 52 files: [
51 { 53 {
52 match: 54 match:
53 /(?:jetbrains-mono-latin-variable-wghtOnly-(?:italic|normal)|roboto-latin-(?:400|500)-normal).+\.woff2/, 55 /(?:jetbrains-mono-latin-variable-wghtOnly-(?:italic|normal)|roboto-latin-(?:400|500)-normal).+\.woff2$/,
54 attributes: { 56 attributes: {
55 type: 'font/woff2', 57 type: 'font/woff2',
56 as: 'font', 58 as: 'font',
@@ -60,6 +62,56 @@ export default defineConfig({
60 ], 62 ],
61 }), 63 }),
62 lezer(), 64 lezer(),
65 VitePWA({
66 strategies: 'generateSW',
67 registerType: 'prompt',
68 injectRegister: null,
69 workbox: {
70 globPatterns: [
71 '**/*.{css,html,js}',
72 'roboto-latin-{300,400,500,700}-normal.*.woff2',
73 'jetbrains-mono-latin-variable-wghtOnly-{normal,italic}.*.woff2',
74 ],
75 dontCacheBustURLsMatching: /\.(?:css|js|woff2?)$/,
76 navigateFallbackDenylist: [/^\/xtext-service/],
77 },
78 includeAssets: ['apple-touch-icon.png', 'favicon.svg', 'mask-icon.svg'],
79 manifest: {
80 lang: 'en-US',
81 name: 'Refinery',
82 short_name: 'Refinery',
83 description:
84 'An efficient graph sovler for generating well-formed models',
85 theme_color: '#21252b',
86 background_color: '#21252b',
87 icons: [
88 {
89 src: 'icon-192x192.png',
90 sizes: '192x192',
91 type: 'image/png',
92 purpose: 'any maskable',
93 },
94 {
95 src: 'icon-512x512.png',
96 sizes: '512x512',
97 type: 'image/png',
98 purpose: 'any maskable',
99 },
100 {
101 src: 'icon-any.svg',
102 sizes: 'any',
103 type: 'image/svg+xml',
104 purpose: 'any maskable',
105 },
106 {
107 src: 'mask-icon.svg',
108 sizes: 'any',
109 type: 'image/svg+xml',
110 purpose: 'monochrome',
111 },
112 ],
113 },
114 }),
63 ], 115 ],
64 base: '', 116 base: '',
65 define: { 117 define: {
@@ -67,6 +119,9 @@ export default defineConfig({
67 }, 119 },
68 build: { 120 build: {
69 assetsDir: '.', 121 assetsDir: '.',
122 // If we don't control inlining manually,
123 // web fonts will randomly get inlined into the CSS, degrading performance.
124 assetsInlineLimit: 0,
70 outDir: path.join('build/vite', mode), 125 outDir: path.join('build/vite', mode),
71 emptyOutDir: true, 126 emptyOutDir: true,
72 sourcemap: isDevelopment, 127 sourcemap: isDevelopment,
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java
index b13ae95d..8ac8a21e 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java
@@ -2,6 +2,7 @@ package tools.refinery.language.web;
2 2
3import java.io.IOException; 3import java.io.IOException;
4import java.time.Duration; 4import java.time.Duration;
5import java.util.Set;
5import java.util.regex.Pattern; 6import java.util.regex.Pattern;
6 7
7import org.eclipse.jetty.http.HttpHeader; 8import org.eclipse.jetty.http.HttpHeader;
@@ -16,7 +17,10 @@ import jakarta.servlet.http.HttpServletRequest;
16import jakarta.servlet.http.HttpServletResponse; 17import jakarta.servlet.http.HttpServletResponse;
17 18
18public class CacheControlFilter implements Filter { 19public class CacheControlFilter implements Filter {
19 private static final Pattern CACHE_URI_PATTERN = Pattern.compile(".*\\.(css|gif|js|map|png|svg|woff2)"); 20 private static final Pattern CACHE_URI_PATTERN = Pattern.compile(".*\\.(css|gif|js|map|png|svg|woff2?)");
21
22 private static final Set<String> CACHE_URI_DENYLIST = Set.of("apple-touch-icon.png", "favicon.png", "favicon.svg",
23 "favicon-96x96.png", "icon-any.svg", "icon-192x192.png", "icon-512x512.png", "mask-icon.svg", "sw.js");
20 24
21 private static final Duration EXPIRY = Duration.ofDays(365); 25 private static final Duration EXPIRY = Duration.ofDays(365);
22 26
@@ -33,7 +37,8 @@ public class CacheControlFilter implements Filter {
33 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 37 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
34 throws IOException, ServletException { 38 throws IOException, ServletException {
35 if (request instanceof HttpServletRequest httpRequest && response instanceof HttpServletResponse httpResponse) { 39 if (request instanceof HttpServletRequest httpRequest && response instanceof HttpServletResponse httpResponse) {
36 if (CACHE_URI_PATTERN.matcher(httpRequest.getRequestURI()).matches()) { 40 var requestURI = httpRequest.getRequestURI();
41 if (CACHE_URI_PATTERN.matcher(requestURI).matches() && !CACHE_URI_DENYLIST.contains(requestURI)) {
37 httpResponse.setHeader(HttpHeader.CACHE_CONTROL.asString(), CACHE_CONTROL_CACHE_VALUE); 42 httpResponse.setHeader(HttpHeader.CACHE_CONTROL.asString(), CACHE_CONTROL_CACHE_VALUE);
38 httpResponse.setDateHeader(HttpHeader.EXPIRES.asString(), 43 httpResponse.setDateHeader(HttpHeader.EXPIRES.asString(),
39 System.currentTimeMillis() + EXPIRY.toMillis()); 44 System.currentTimeMillis() + EXPIRY.toMillis());