From 0fc4638dc02644f58ebd940b50f5347714ada283 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 9 Apr 2024 10:25:31 +0800 Subject: [PATCH] move to host static assets off assets.pvq.app --- MyApp/Components/App.razor | 30 +- MyApp/Components/Layout/MainLayout.razor | 5 + .../Shared/LiteYoutubeIncludes.razor | 4 +- MyApp/Program.cs | 1 + MyApp/wwwroot/lib/js/default.js | 4 +- MyApp/wwwroot/lib/js/servicestack-blazor.js | 370 ++++++++++++++++++ MyApp/wwwroot/mjs/app.mjs | 4 + 7 files changed, 402 insertions(+), 16 deletions(-) create mode 100644 MyApp/wwwroot/lib/js/servicestack-blazor.js diff --git a/MyApp/Components/App.razor b/MyApp/Components/App.razor index e27d60f..2d78c48 100644 --- a/MyApp/Components/App.razor +++ b/MyApp/Components/App.razor @@ -1,21 +1,25 @@ - +@{ + var baseUrl = BlazorConfig.Instance.AssetsBasePath ?? ""; +} + - - - - + + + + + @BlazorHtml.ImportMap(new() { - ["app.mjs"] = ("/mjs/app.mjs", "/mjs/app.mjs"), - ["dtos.mjs"] = ("/mjs/dtos.mjs", "/mjs/dtos.mjs"), - ["vue"] = ("/lib/mjs/vue.mjs", "/lib/mjs/vue.min.mjs"), - ["@servicestack/client"] = ("/lib/mjs/servicestack-client.mjs", "/lib/mjs/servicestack-client.min.mjs"), - ["@servicestack/vue"] = ("/lib/mjs/servicestack-vue.mjs", "/lib/mjs/servicestack-vue.min.mjs"), + ["app.mjs"] = ($"{baseUrl}/mjs/app.mjs", $"{baseUrl}/mjs/app.mjs"), + ["dtos.mjs"] = ($"{baseUrl}mjs/dtos.mjs", $"{baseUrl}/mjs/dtos.mjs"), + ["vue"] = ($"{baseUrl}/lib/mjs/vue.mjs", $"{baseUrl}/lib/mjs/vue.min.mjs"), + ["@servicestack/client"] = ($"{baseUrl}/lib/mjs/servicestack-client.mjs", $"{baseUrl}/lib/mjs/servicestack-client.min.mjs"), + ["@servicestack/vue"] = ($"{baseUrl}/lib/mjs/servicestack-vue.mjs", $"{baseUrl}/lib/mjs/servicestack-vue.min.mjs"), }) @@ -24,15 +28,15 @@ - + - - + + diff --git a/MyApp/Components/Layout/MainLayout.razor b/MyApp/Components/Layout/MainLayout.razor index ec54124..e799810 100644 --- a/MyApp/Components/Layout/MainLayout.razor +++ b/MyApp/Components/Layout/MainLayout.razor @@ -15,6 +15,11 @@ const { loadMetadata } = useMetadata() loadMetadata({ olderThan: window.Server ? null : location.search.includes('clear=metadata') ? 0 : 60 * 60 * 1000 //1hr }) +function assetsUrl(url) { + return url.startsWith('http') + ? url + : '@BlazorConfig.Instance.AssetsBasePath' + url +}
diff --git a/MyApp/Components/Shared/LiteYoutubeIncludes.razor b/MyApp/Components/Shared/LiteYoutubeIncludes.razor index fa17b3a..22aa176 100644 --- a/MyApp/Components/Shared/LiteYoutubeIncludes.razor +++ b/MyApp/Components/Shared/LiteYoutubeIncludes.razor @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/MyApp/Program.cs b/MyApp/Program.cs index 5e56b38..7bf28f8 100644 --- a/MyApp/Program.cs +++ b/MyApp/Program.cs @@ -106,6 +106,7 @@ IsDevelopment = app.Environment.IsDevelopment(), EnableLogging = app.Environment.IsDevelopment(), EnableVerboseLogging = app.Environment.IsDevelopment(), + AssetsBasePath = app.Environment.IsDevelopment() ? null : "https://assets.pvq.app" }); app.Run(); diff --git a/MyApp/wwwroot/lib/js/default.js b/MyApp/wwwroot/lib/js/default.js index 9bd438c..939b7ae 100644 --- a/MyApp/wwwroot/lib/js/default.js +++ b/MyApp/wwwroot/lib/js/default.js @@ -1,8 +1,10 @@ +import { assetsUrl } from "../../mjs/app.mjs"; + window.hljs?.highlightAll() if (!localStorage.getItem('data:tags.txt')) { - fetch('/data/tags.txt') + fetch(assetsUrl('/data/tags.txt')) .then(r => r.text()) .then(txt => localStorage.setItem('data:tags.txt', txt.replace(/\r\n/g,'\n'))); } diff --git a/MyApp/wwwroot/lib/js/servicestack-blazor.js b/MyApp/wwwroot/lib/js/servicestack-blazor.js new file mode 100644 index 0000000..6b91f9d --- /dev/null +++ b/MyApp/wwwroot/lib/js/servicestack-blazor.js @@ -0,0 +1,370 @@ +/* DOM functions used in Blazor Components */ +JS = (function () { + function map(o, f) { return o == null ? null : f(o) } + let dotnetRefs = [] + let NavKeys = 'Escape,ArrowLeft,ArrowRight,ArrowUp,ArrowDown,Home,End'.split(',') + let InputTags = 'INPUT,SELECT,TEXTAREA'.split(',') + let isInput = e => e && InputTags.indexOf(e.tagName) !== -1 + let onKeyNav = e => { + let hasModifierKey = e.shiftKey || e.ctrlKey || e.altKey || e.metaKey || e.code === 'MetaLeft' || e.code === 'MetaRight' + if (hasModifierKey || isInput(e.target)) return + if (NavKeys.indexOf(e.key) == -1) return + e.preventDefault() + e.stopPropagation() + dotnetRefs.forEach(dotnetRef => { + dotnetRef.invokeMethodAsync('OnKeyNav', e.key) + }) + } + let SelectorAliases = { document } + let el = sel => typeof sel == "string" + ? SelectorAliases[sel] || document.querySelector(sel) + : sel + + let origScrollTo = null + let skipAutoScroll = true + + function elVisible(el, container) { + if (!el) return false + container = container || el.parentElement || document.body + const { top, bottom, height } = el.getBoundingClientRect() + const holderRect = container.getBoundingClientRect() + return top <= holderRect.top + ? holderRect.top - top <= height + : bottom - holderRect.bottom <= height + } + function matchesTest(t, sel) { + let not = t.startsWith('!') + if (not) t = t.substring(1) + let ret = t === 'input' + ? map(el(sel), isInput) | map(document.activeElement, isInput) + : t === 'visible' + ? elVisible(el(sel)) + : t === 'scrollIntoViewIfNeeded' + ? !!document.body.scrollIntoViewIfNeeded + : false + return not ? !ret : ret + } + function useFn(fnName, args) { + if (fnName === 'scrollIntoView' && args && args.scrollMode == 'if-needed' && document.body.scrollIntoViewIfNeeded) + return 'scrollIntoViewIfNeeded' + return fnName + } + function setCookie({ name, value, path, expires }) { + let expiryStr = expires ? `;expires=${expires}` : '' + let pathStr = path ? `;path=${path}` : '' + document.cookie = name + '=' + value + pathStr + expiryStr + } + function getCookie(name) { + var kvp = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)') + return kvp ? kvp[2] : null + } + function deleteCookie(name) { + setCookie({ name, value: getCookie(name), expires: new Date(0).toUTCString() }) + } + function getBreakpoints() { + let resolutions = { '2xl': 1536, xl: 1280, lg: 1024, md: 768, sm: 640 } + let w = document.body.clientWidth + let o = {} + Object.keys(resolutions).forEach(res => o[res] = w > resolutions[res]) + return o + } + return { + SelectorAliases, + get(name) { return window[name] }, + /* Loading */ + prerenderedPage() { + const el = document.querySelector('#app-loading .content') + return el && el.innerHTML || '' + }, + invoke(target, fnName, args) { + if (!target || !fnName) return + let f = target[fnName] + if (typeof f == 'function') { + let ret = f.apply(target, args || []) + return ret + } + if (args !== undefined) + target[fnName] = args + return target[fnName] + }, + invokeDelay(target, fnName, args, ms) { + setTimeout(() => JS.invoke(target, fnName, args), isNaN(ms) ? 0 : ms) + }, + elInvoke(sel, fnName, args) { + let $el = el(sel) + if (!$el) return + return JS.invoke($el, useFn(fnName, args), args) + }, + elInvokeObjectMethod(sel, objAccessor, fnName, args) { + let obj = el(sel) + if (!obj) return + objAccessor.split('.').forEach(name => { + if (!obj) return + obj = obj[name] + }) + return JS.invoke(obj, useFn(fnName, args), args) + }, + invokeObjectMethod(objAccessor, fnName, args) { + let obj = window + objAccessor.split('.').forEach(name => { + if (!obj) return + obj = obj[name] + }) + return JS.invoke(obj, useFn(fnName, args), args) + }, + elInvokeDelayIf(test, sel, fnName, args) { + if (matchesTest(test, sel)) JS.elInvoke(sel, sel, fnName, args) + }, + elInvokeDelay(sel, fnName, args, ms) { + setTimeout(() => JS.elInvoke(sel, fnName, args), isNaN(ms) ? 0 : ms) + }, + elInvokeDelayIf(test, sel, fnName, args, ms) { + if (matchesTest(test, sel)) JS.elInvokeDelay(sel, fnName, args, ms) + }, + addClass(sel, ...classes) { + map(el(sel), el => el.classList.add(...classes)) + }, + removeClass(sel, ...classes) { + map(el(sel), el => el.classList.remove(...classes)) + }, + containsClass(sel, cls) { + return map(el(sel), el => el.classList.contains(cls)) || false + }, + registerKeyNav(dotnetRef) { + dotnetRefs.push(dotnetRef) + if (dotnetRefs.length == 1) { + document.addEventListener('keydown', onKeyNav) + } + }, + disposeKeyNav(dotnetRef) { + dotnetRefs = dotnetRefs.filter(x => x != dotnetRef) + if (dotnetRefs.length == 0) { + document.removeEventListener('keydown', onKeyNav) + } + }, + focusNextElement() { + let elActive = document.activeElement + let form = elActive && elActive.form + if (!form) return + let sel = ':not([disabled]):not([tabindex="-1"])' + let els = form.querySelectorAll(`a:not([disabled]), button${sel}, input[type=text]${sel}, [tabindex]${sel}`) + let focusable = Array.prototype.filter.call(els, + el => el.offsetWidth > 0 || el.offsetHeight > 0 || el === elActive); + let index = focusable.indexOf(elActive); + if (index > -1) { + let elNext = focusable[index + 1] || focusable[0]; + elNext.focus(); + } + }, + enableAutoScroll() { skipAutoScroll = false }, + disableAutoScroll() { + if (origScrollTo == null) { + origScrollTo = window.scrollTo + window.scrollTo = (x, y) => { + if (x === 0 && y === 0 && skipAutoScroll) + return + return origScrollTo.apply(this, arguments) + } + } + skipAutoScroll = true + }, + setCookie, + getCookie, + deleteCookie, + setCookies(cookies) { + if (cookies) { + Array.from(cookies).forEach(setCookie) + } + }, + getBreakpoints, + init(opt) { + if (!opt || opt.colorScheme !== false) { + let colorScheme = opt && typeof opt.colorScheme === 'string' + ? opt.colorScheme + : location.search === "?dark" + ? "dark" + : location.search === "?light" + ? "light" + : localStorage.getItem('color-scheme') + let darkMode = colorScheme != null + ? colorScheme === 'dark' + : window.matchMedia('(prefers-color-scheme: dark)').matches + let html = document.documentElement + html.classList.toggle('dark', darkMode) + html.style.setProperty('color-scheme', darkMode ? 'dark' : null) + if (localStorage.getItem('color-scheme') === null) { + localStorage.setItem('color-scheme', darkMode ? 'dark' : 'light') + } + } + }, + } +})() + +Files = (function () { + function lastRightPart(s, needle) { + if (s == null) return null + let pos = s.lastIndexOf(needle) + return pos == -1 + ? s + : s.substring(pos + needle.length) + } + function leftPart(s, needle) { + if (s == null) return null + let pos = s.indexOf(needle) + return pos == -1 + ? s + : s.substring(0, pos) + } + function map(o, f) { return o == null ? null : f(o) } + + let web = 'png,jpg,jpeg,gif,svg,webp'.split(',') + const Ext = { + img: 'png,jpg,jpeg,gif,svg,webp,png,jpg,jpeg,gif,bmp,tif,tiff,webp,ai,psd,ps'.split(','), + vid: 'avi,m4v,mov,mp4,mpg,mpeg,wmv,webm'.split(','), + aud: 'mp3,mpa,ogg,wav,wma,mid,webm'.split(','), + ppt: 'key,odp,pps,ppt,pptx'.split(','), + xls: 'xls,xlsm,xlsx,ods,csv,tsv'.split(','), + doc: 'doc,docx,pdf,rtf,tex,txt,md,rst,xls,xlsm,xlsx,ods,key,odp,pps,ppt,pptx'.split(','), + zip: 'zip,tar,gz,7z,rar,gzip,deflate,br,iso,dmg,z,lz,lz4,lzh,s7z,apl,arg,jar,war'.split(','), + exe: 'exe,bat,sh,cmd,com,app,msi,run,vb,vbs,js,ws,wsh'.split(','), + att: 'bin,oct,dat'.split(','), //attachment + } + const ExtKeys = Object.keys(Ext) + let S = (viewBox, body) => `` + const Icons = { + img: S("4 4 16 16", ""), + vid: S("0 0 24 24", ""), + aud: S("0 0 24 24", ""), + ppt: S("0 0 48 48", ""), + xls: S("0 0 256 256", ""), + doc: S("0 0 32 32", ""), + zip: S("0 0 16 16", ""), + exe: S("0 0 16 16", ""), + att: S("0 0 24 24", ""), + } + const symbols = /[\r\n%#()<>?[\\\]^`{|}]/g + + function encodeSvg(s) { + s = s.replace(/"/g, `'`) + s = s.replace(/>\s+<`) + s = s.replace(/\s{2,}/g, ` `) + return s.replace(symbols, encodeURIComponent) + } + function svgToDataUri(svg) { + return "data:image/svg+xml;utf8," + encodeSvg(svg) + } + let Track = [] + + function objectUrl(file) { + let ret = URL.createObjectURL(file) + Track.push(ret) + return ret + } + + function flush() { + Track.forEach(x => { + try { + URL.revokeObjectURL(x) + } catch (e) { + console.error('URL.revokeObjectURL', e) + } + }) + Track = [] + } + + function getFileName(path) { + if (!path) return null + let noQs = leftPart(path, '?') + return lastRightPart(noQs, '/') + } + + function getExt(path) { + let fileName = getFileName(path) + if (fileName == null || fileName.indexOf('.') === -1) + return null + return lastRightPart(fileName, '.').toLowerCase() + } + + function fileImageUri(file) { + let ext = getExt(file.name) + if (web.indexOf(ext) >= 0) + return objectUrl(file) + return filePathUri(file.name) + } + + function canPreview(path) { + if (!path) return false + if (path.startsWith('blob:') || path.startsWith('data:')) + return true + let ext = getExt(path) + return ext && web.indexOf(ext) >= 0; + } + + function toAppUrl(url) { return url } + + function filePathUri(path) { + if (!path) return null + let ext = getExt(path) + if (ext == null || canPreview(path)) + return toAppUrl(path) + return extSrc(ext) || svgToDataUri(Icons.doc) + } + + function extSrc(ext) { + return map(extSvg(ext), svg => svgToDataUri(svg)) + } + + function extSvg(ext) { + if (Icons[ext]) + return Icons[ext] + for (let i = 0; i < ExtKeys.length; i++) { + let k = ExtKeys[i] + if (Ext[k].indexOf(ext) >= 0) + return Icons[k] + } + return null + } + + const k = 1024 + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] + + function formatBytes(bytes, d = 2) { + if (bytes === 0) return '0 Bytes' + const dm = d < 0 ? 0 : d + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] + } + function inputFiles(input) { + return Array.from(input.files) + .map(f => ({ fileName: f.name, contentLength: f.size, filePath: fileImageUri(f) })) + } + + function iconOnError(img, fallbackSrc) { + img.onerror = null + img.src = iconFallbackSrc(img.src, fallbackSrc) + } + function iconFallbackSrc(src, fallbackSrc) { + return extSrc(lastRightPart(src, '.').toLowerCase()) + || (fallbackSrc + ? extSrc(fallbackSrc) || fallbackSrc + : null) + || extSrc('doc') + } + + return { + extSvg, + extSrc, + getExt, + encodeSvg, + canPreview, + getFileName, + formatBytes, + filePathUri, + svgToDataUri, + fileImageUri, + flush, + inputFiles, + iconOnError, + iconFallbackSrc, + } +})() \ No newline at end of file diff --git a/MyApp/wwwroot/mjs/app.mjs b/MyApp/wwwroot/mjs/app.mjs index dad9e63..81a83ea 100644 --- a/MyApp/wwwroot/mjs/app.mjs +++ b/MyApp/wwwroot/mjs/app.mjs @@ -15,6 +15,10 @@ const CustomElements = [ 'lite-youtube' ] +export function assetsUrl(path) { + return globalThis.assetsUrl(path) +} + export const alreadyMounted = el => el.__vue_app__ const mockArgs = { attrs:{}, slots:{}, emit:() => {}, expose: () => {} }