Add ability to use vite dev alongside the go backend

The point is to not have to rebuild every time to test a change, but make use of Vite's HMR and other goodies.
Some things are not yet working in dev mode:
- Upload is stuck and fails
- Reloading page while on a file will give 404 error
- Prob more that i didnt catch
This commit is contained in:
Kloon ImKloon 2023-08-18 18:25:24 +02:00
parent 74d15d08ad
commit 8c60137fe4
No known key found for this signature in database
GPG Key ID: CCF1C86A995C5B6A
10 changed files with 315 additions and 210 deletions

View File

@ -8,25 +8,19 @@
content="width=device-width, initial-scale=1, user-scalable=no" content="width=device-width, initial-scale=1, user-scalable=no"
/> />
[{[ if .ReCaptcha -]}] <title>File Browser</title>
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
[{[ end ]}]
<title>
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
</title>
<link <link
rel="icon" rel="icon"
type="image/png" type="image/png"
sizes="32x32" sizes="32x32"
href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png" href="/img/icons/favicon-32x32.png"
/> />
<link <link
rel="icon" rel="icon"
type="image/png" type="image/png"
sizes="16x16" sizes="16x16"
href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png" href="/img/icons/favicon-16x16.png"
/> />
<!-- Add to home screen for Android and modern mobile browsers --> <!-- Add to home screen for Android and modern mobile browsers -->
@ -35,49 +29,63 @@
id="manifestPlaceholder" id="manifestPlaceholder"
crossorigin="use-credentials" crossorigin="use-credentials"
/> />
<meta <meta name="theme-color" content="#2979ff" />
name="theme-color"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/>
<!-- Add to home screen for Safari on iOS/iPadOS --> <!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="assets" /> <meta name="apple-mobile-web-app-title" content="assets" />
<link <link rel="apple-touch-icon" href="/img/icons/apple-touch-icon.png" />
rel="apple-touch-icon"
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
/>
<!-- Add to home screen for Windows --> <!-- Add to home screen for Windows -->
<meta <meta
name="msapplication-TileImage" name="msapplication-TileImage"
content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png" content="/img/icons/mstile-144x144.png"
/>
<meta
name="msapplication-TileColor"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/> />
<meta name="msapplication-TileColor" content="#2979ff" />
<!-- Inject Some Variables and generate the manifest json --> <!-- Inject Some Variables and generate the manifest json -->
<script> <script>
<!-- Json is actually a JS object, assign it directly --> // We can assign JSON directly
window.FileBrowser = [{[ .Json ]}]; window.FileBrowser = {
<!-- Global function to prepend static url --> AuthMethod: "json",
BaseURL: "http://localhost:8080",
CSS: false,
Color: "",
DisableExternal: false,
DisableUsedPercentage: false,
EnableExec: true,
EnableThumbs: true,
LoginPage: true,
Name: "",
NoAuth: false,
ReCaptcha: false,
ResizePreview: true,
Signup: false,
StaticURL: "",
Theme: "",
TusSettings: { chunkSize: 10485760, retryCount: 5 },
Version: "(untracked)",
};
// Global function to prepend static url
window.__prependStaticUrl = (url) => { window.__prependStaticUrl = (url) => {
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, '')}`; return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
}; };
var dynamicManifest = { var dynamicManifest = {
name: window.FileBrowser.Name || "File Browser", name: window.FileBrowser.Name || "File Browser",
short_name: window.FileBrowser.Name || "File Browser", short_name: window.FileBrowser.Name || "File Browser",
icons: [ icons: [
{ {
src: window.__prependStaticUrl("/img/icons/android-chrome-192x192.png"), src: window.__prependStaticUrl(
"/img/icons/android-chrome-192x192.png"
),
sizes: "192x192", sizes: "192x192",
type: "image/png", type: "image/png",
}, },
{ {
src: window.__prependStaticUrl("/img/icons/android-chrome-512x512.png"), src: window.__prependStaticUrl(
"/img/icons/android-chrome-512x512.png"
),
sizes: "512x512", sizes: "512x512",
type: "image/png", type: "image/png",
}, },
@ -180,14 +188,5 @@
</div> </div>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
[{[ if .Theme -]}]
<link
rel="stylesheet"
href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css"
/>
[{[ end ]}] [{[ if .CSS -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}]
</body> </body>
</html> </html>

View File

@ -1,144 +1,193 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
[{[ if .ReCaptcha -]}] [{[ if .ReCaptcha -]}]
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script> <script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
[{[ end ]}] [{[ end ]}]
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title> <title>
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
</title>
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"> <link
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"> rel="icon"
type="image/png"
sizes="32x32"
href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"
/>
<!-- Add to home screen for Android and modern mobile browsers --> <!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials"> <link
<meta name="theme-color" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"> rel="manifest"
id="manifestPlaceholder"
crossorigin="use-credentials"
/>
<meta
name="theme-color"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/>
<!-- Add to home screen for Safari on iOS/iPadOS --> <!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="assets"> <meta name="apple-mobile-web-app-title" content="assets" />
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"> <link
rel="apple-touch-icon"
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
/>
<!-- Add to home screen for Windows --> <!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png"> <meta
<meta name="msapplication-TileColor" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"> name="msapplication-TileImage"
content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png"
/>
<meta
name="msapplication-TileColor"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/>
<!-- Inject Some Variables and generate the manifest json --> <!-- Inject Some Variables and generate the manifest json -->
<script> <script>
window.FileBrowser = JSON.parse('[{[ .Json ]}]'); // We can assign JSON directly
window.FileBrowser = [{[ .Json ]}];
// Global function to prepend static url
window.__prependStaticUrl = (url) => {
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
};
var dynamicManifest = {
name: window.FileBrowser.Name || "File Browser",
short_name: window.FileBrowser.Name || "File Browser",
icons: [
{
src: window.__prependStaticUrl("/img/icons/android-chrome-192x192.png"),
sizes: "192x192",
type: "image/png",
},
{
src: window.__prependStaticUrl("/img/icons/android-chrome-512x512.png"),
sizes: "512x512",
type: "image/png",
},
],
start_url: window.location.origin + window.FileBrowser.BaseURL,
display: "standalone",
background_color: "#ffffff",
theme_color: window.FileBrowser.Color || "#455a64",
};
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL; const stringManifest = JSON.stringify(dynamicManifest);
var dynamicManifest = { const blob = new Blob([stringManifest], { type: "application/json" });
"name": window.FileBrowser.Name || 'File Browser', const manifestURL = URL.createObjectURL(blob);
"short_name": window.FileBrowser.Name || 'File Browser', document
"icons": [ .querySelector("#manifestPlaceholder")
{ .setAttribute("href", manifestURL);
"src": fullStaticURL + "/img/icons/android-chrome-192x192.png", </script>
"sizes": "192x192",
"type": "image/png" <style>
}, #loading {
{ position: fixed;
"src": fullStaticURL + "/img/icons/android-chrome-512x512.png", top: 0;
"sizes": "512x512", left: 0;
"type": "image/png" width: 100%;
height: 100%;
background: #fff;
z-index: 9999;
transition: 0.1s ease opacity;
-webkit-transition: 0.1s ease opacity;
}
#loading.done {
opacity: 0;
}
#loading .spinner {
width: 70px;
text-align: center;
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
#loading .spinner > div {
width: 18px;
height: 18px;
background-color: #333;
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
#loading .spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
#loading .spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%,
80%,
100% {
-webkit-transform: scale(0);
} }
], 40% {
"start_url": window.location.origin + window.FileBrowser.BaseURL, -webkit-transform: scale(1);
"display": "standalone", }
"background_color": "#ffffff", }
"theme_color": window.FileBrowser.Color || "#455a64"
}
const stringManifest = JSON.stringify(dynamicManifest); @keyframes sk-bouncedelay {
const blob = new Blob([stringManifest], {type: 'application/json'}); 0%,
const manifestURL = URL.createObjectURL(blob); 80%,
document.querySelector('#manifestPlaceholder').setAttribute('href', manifestURL); 100% {
</script> -webkit-transform: scale(0);
transform: scale(0);
}
40% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
</style>
</head>
<body>
<div id="app"></div>
<style> <div id="loading">
#loading { <div class="spinner">
position: fixed; <div class="bounce1"></div>
top: 0; <div class="bounce2"></div>
left: 0; <div class="bounce3"></div>
width: 100%; </div>
height: 100%;
background: #fff;
z-index: 9999;
transition: .1s ease opacity;
-webkit-transition: .1s ease opacity;
}
#loading.done {
opacity: 0;
}
#loading .spinner {
width: 70px;
text-align: center;
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
#loading .spinner > div {
width: 18px;
height: 18px;
background-color: #333;
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
#loading .spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
#loading .spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
</style>
</head>
<body>
<div id="app"></div>
<div id="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div> </div>
</div>
[{[ if .Theme -]}] <script type="module" src="/src/main.js"></script>
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" />
[{[ end ]}] [{[ if .Theme -]}]
[{[ if .CSS -]}] <link
rel="stylesheet"
href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css"
/>
[{[ end ]}] [{[ if .CSS -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" /> <link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}] [{[ end ]}]
</body> </body>
</html> </html>

View File

@ -33,7 +33,7 @@ const titles = {
}; };
const router = new Router({ const router = new Router({
base: baseURL, base: import.meta.env.PROD ? baseURL : "",
mode: "history", mode: "history",
routes: [ routes: [
{ {

View File

@ -1,7 +1,7 @@
import store from "@/store"; import store from "@/store";
import router from "@/router"; import router from "@/router";
import { Base64 } from "js-base64"; import { Base64 } from "js-base64";
import { baseURL } from "@/utils/constants"; import { fetchURL } from "@/api/utils";
export function parseToken(token) { export function parseToken(token) {
const parts = token.split("."); const parts = token.split(".");
@ -25,20 +25,24 @@ export async function validateLogin() {
await renew(localStorage.getItem("jwt")); await renew(localStorage.getItem("jwt"));
} }
} catch (_) { } catch (_) {
console.warn('Invalid JWT token in storage') // eslint-disable-line console.warn("Invalid JWT token in storage"); // eslint-disable-line
} }
} }
export async function login(username, password, recaptcha) { export async function login(username, password, recaptcha) {
const data = { username, password, recaptcha }; const data = { username, password, recaptcha };
const res = await fetch(`${baseURL}/api/login`, { const res = await fetchURL(
method: "POST", `/api/login`,
headers: { {
"Content-Type": "application/json", method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}, },
body: JSON.stringify(data), false
}); );
const body = await res.text(); const body = await res.text();
@ -50,7 +54,7 @@ export async function login(username, password, recaptcha) {
} }
export async function renew(jwt) { export async function renew(jwt) {
const res = await fetch(`${baseURL}/api/renew`, { const res = await fetchURL(`/api/renew`, {
method: "POST", method: "POST",
headers: { headers: {
"X-Auth": jwt, "X-Auth": jwt,
@ -69,13 +73,17 @@ export async function renew(jwt) {
export async function signup(username, password) { export async function signup(username, password) {
const data = { username, password }; const data = { username, password };
const res = await fetch(`${baseURL}/api/signup`, { const res = await fetchURL(
method: "POST", `/api/signup`,
headers: { {
"Content-Type": "application/json", method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}, },
body: JSON.stringify(data), false
}); );
if (res.status !== 200) { if (res.status !== 200) {
throw new Error(res.status); throw new Error(res.status);

View File

@ -5,32 +5,55 @@ import legacy from "@vitejs/plugin-legacy";
import vue2 from "@vitejs/plugin-vue2"; import vue2 from "@vitejs/plugin-vue2";
import { compression } from "vite-plugin-compression2"; import { compression } from "vite-plugin-compression2";
const plugins = [
vue2(),
legacy({
targets: ["ie >= 11"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
}),
compression({ include: /\.js$/i, deleteOriginalAssets: true }),
];
const resolve = {
alias: {
vue: "vue/dist/vue.esm.js",
"@/": fileURLToPath(new URL("./src/", import.meta.url)),
},
};
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig(({ command }) => {
plugins: [ if (command === "serve") {
vue2(), return {
legacy({ plugins,
targets: ["ie >= 11"], resolve,
additionalLegacyPolyfills: ["regenerator-runtime/runtime"], };
}), } else {
compression({ include: /\.js$/i, deleteOriginalAssets: true }), // command === 'build'
], return {
resolve: { plugins,
alias: { resolve,
vue: "vue/dist/vue.esm.js", base: "",
"@/": fileURLToPath(new URL("./src/", import.meta.url)), build: {
}, rollupOptions: {
}, input: {
base: "", index: fileURLToPath(
experimental: { new URL(`./public/index.html`, import.meta.url)
renderBuiltUrl(filename, { hostType }) { ),
if (hostType === "js") { },
return { runtime: `window.__prependStaticUrl("${filename}")` }; },
} else if (hostType === "html") { },
return `[{[ .StaticURL ]}]/${filename}`; experimental: {
} else { renderBuiltUrl(filename, { hostType }) {
return { relative: true }; if (hostType === "js") {
} return { runtime: `window.__prependStaticUrl("${filename}")` };
}, } else if (hostType === "html") {
}, return `[{[ .StaticURL ]}]/${filename}`;
} else {
return { relative: true };
}
},
},
};
}
}); });

View File

@ -49,7 +49,9 @@ func (d *data) Check(path string) bool {
func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler { func handle(fn handleFunc, prefix string, store *storage.Storage, server *settings.Server) http.Handler {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") for k, v := range global_headers {
w.Header().Set(k, v)
}
settings, err := store.Settings.Get() settings, err := store.Settings.Get()
if err != nil { if err != nil {

9
http/headers.go Normal file
View File

@ -0,0 +1,9 @@
//go:build !dev
// +build !dev
package http
// global headers to append to every response
var global_headers = map[string]string{
"Cache-Control": "no-cache, no-store, must-revalidate",
}

15
http/headers_dev.go Normal file
View File

@ -0,0 +1,15 @@
//go:build dev
// +build dev
package http
// global headers to append to every response
// cross-origin headers are necessary to be able to
// access them from a different URL during development
var global_headers = map[string]string{
"Cache-Control": "no-cache, no-store, must-revalidate",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Credentials": "true",
}

View File

@ -27,7 +27,7 @@ func NewHandler(
r := mux.NewRouter() r := mux.NewRouter()
r.Use(func(next http.Handler) http.Handler { r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", `default-src 'self'; style-src 'unsafe-inline';`) // w.Header().Set("Content-Security-Policy", `default-src 'self'; style-src 'unsafe-inline';`)
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
}) })

View File

@ -105,7 +105,7 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
} }
w.Header().Set("x-xss-protection", "1; mode=block") w.Header().Set("x-xss-protection", "1; mode=block")
return handleWithStaticData(w, r, d, assetsFs, "index.html", "text/html; charset=utf-8") return handleWithStaticData(w, r, d, assetsFs, "public/index.html", "text/html; charset=utf-8")
}, "", store, server) }, "", store, server)
static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {