feat: dynamic extension loading via routes.json (#3605)
This commit is contained in:
parent
5f86627eae
commit
fd765e2060
7 changed files with 91 additions and 31 deletions
2
lnbits/static/bundle-components.min.js
vendored
2
lnbits/static/bundle-components.min.js
vendored
File diff suppressed because one or more lines are too long
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -9,27 +9,18 @@ window.app.component('lnbits-manage-extension-list', {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
'g.user.extensions': {
|
||||
async handler() {
|
||||
await this.loadExtensions()
|
||||
}
|
||||
'g.user.extensions'() {
|
||||
this.loadExtensions()
|
||||
},
|
||||
searchTerm() {
|
||||
this.filterUserExtensionsByTerm()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
map(data) {
|
||||
const obj = {...data}
|
||||
obj.url = ['/', obj.code, '/'].join('')
|
||||
return obj
|
||||
},
|
||||
async loadExtensions() {
|
||||
try {
|
||||
const {data} = await LNbits.api.request('GET', '/api/v1/extension')
|
||||
this.extensions = data
|
||||
.map(extension => this.map(extension))
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
res = await LNbits.api.request('GET', '/api/v1/extension')
|
||||
this.extensions = res.data.sort((a, b) => a.name.localeCompare(b.name))
|
||||
this.filterUserExtensionsByTerm()
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,46 @@ const quasarConfig = {
|
|||
}
|
||||
}
|
||||
|
||||
const DynamicComponent = {
|
||||
async created() {
|
||||
const name = this.$route.path.split('/')[1]
|
||||
const path = `/${name}/`
|
||||
const routesPath = `/${name}/static/routes.json`
|
||||
const hasPath = this.$router.getRoutes().some(r => r.path === path)
|
||||
if (hasPath) {
|
||||
console.log('Dynamic route already exists for extension:', name)
|
||||
return
|
||||
}
|
||||
fetch(routesPath)
|
||||
.then(async res => {
|
||||
if (!res.ok) {
|
||||
throw new Error('No dynamic routes found')
|
||||
}
|
||||
const routes = await res.json()
|
||||
routes.forEach(r => {
|
||||
console.log('Adding dynamic route:', r.path)
|
||||
window.router.addRoute({
|
||||
path: r.path,
|
||||
name: r.name,
|
||||
component: async () => {
|
||||
await LNbits.utils.loadTemplate(r.template)
|
||||
await LNbits.utils.loadScript(r.component)
|
||||
return window[r.name]
|
||||
}
|
||||
})
|
||||
window.router.push(this.$route.fullPath)
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
if (RENDERED_ROUTE !== path) {
|
||||
console.log('Redirecting to non-vue route:', path)
|
||||
window.location = path
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/node',
|
||||
|
|
@ -87,6 +127,11 @@ const routes = [
|
|||
path: '/error',
|
||||
name: 'PageError',
|
||||
component: PageError
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'DynamicComponent',
|
||||
component: DynamicComponent
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -107,18 +152,9 @@ window.app.mixin({
|
|||
api: window._lnbitsApi,
|
||||
utils: window._lnbitsUtils,
|
||||
g: window.g,
|
||||
utils: window._lnbitsUtils,
|
||||
...WINDOW_SETTINGS
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isVueRoute() {
|
||||
const currentPath = window.location.pathname
|
||||
const matchedRoute = window.router.resolve(currentPath)
|
||||
const isVueRoute = matchedRoute?.matched?.length > 0
|
||||
return isVueRoute
|
||||
}
|
||||
},
|
||||
// backwards compatibility for extensions, should not be used in the future
|
||||
methods: {
|
||||
copyText: window._lnbitsUtils.copyText,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,31 @@
|
|||
window._lnbitsUtils = {
|
||||
loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.src = src
|
||||
script.onload = () => {
|
||||
resolve()
|
||||
}
|
||||
script.onerror = () => {
|
||||
reject(new Error(`Failed to load script ${src}`))
|
||||
}
|
||||
document.body.appendChild(script)
|
||||
})
|
||||
},
|
||||
async loadTemplate(url) {
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load template from ${url}`)
|
||||
}
|
||||
return response.text()
|
||||
})
|
||||
.then(html => {
|
||||
const template = document.createElement('div')
|
||||
template.innerHTML = html.trim()
|
||||
document.body.appendChild(template)
|
||||
})
|
||||
},
|
||||
copyText(text, message, position) {
|
||||
Quasar.copyToClipboard(text).then(() => {
|
||||
Quasar.Notify.create({
|
||||
|
|
|
|||
|
|
@ -51,9 +51,12 @@
|
|||
<lnbits-header-wallets
|
||||
v-if="g.user && !g.isPublicPage"
|
||||
></lnbits-header-wallets>
|
||||
<router-view v-if="isVueRoute" :key="$route.path"></router-view>
|
||||
<!-- FastAPI Content from extensions -->
|
||||
<div v-else>{% block page %}{% endblock %}</div>
|
||||
<!-- block page content from static extensions -->
|
||||
<div v-if="$route.path === '{{ request.path }}'">
|
||||
{% block page %}{% endblock %}
|
||||
</div>
|
||||
<!-- vue router-view -->
|
||||
<router-view :key="$route.path"></router-view>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
{% endblock %}
|
||||
|
|
@ -64,6 +67,7 @@
|
|||
</q-layout>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const RENDERED_ROUTE = '{{ request.path }}'
|
||||
const WINDOW_SETTINGS = {{ WINDOW_SETTINGS | tojson }}
|
||||
Object.keys(WINDOW_SETTINGS).forEach(key => {
|
||||
window[key] = WINDOW_SETTINGS[key]
|
||||
|
|
|
|||
|
|
@ -17,11 +17,10 @@
|
|||
</q-item>
|
||||
<q-item
|
||||
v-for="extension in userExtensions"
|
||||
:key="extension.code"
|
||||
clickable
|
||||
:active="$route.path.startsWith(extension.url)"
|
||||
:active="$route.path.startsWith('/' + extension.code)"
|
||||
tag="a"
|
||||
:href="extension.url"
|
||||
:to="'/' + extension.code + '/'"
|
||||
>
|
||||
<q-item-section side>
|
||||
<q-avatar size="md">
|
||||
|
|
@ -33,7 +32,10 @@
|
|||
><span v-text="extension.name"></span>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side v-show="$route.path.startsWith(extension.url)">
|
||||
<q-item-section
|
||||
side
|
||||
v-show="$route.path.startsWith('/' + extension.code)"
|
||||
>
|
||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue