Merge branch 'develop' of github.com:Budibase/budibase into frontend-core
This commit is contained in:
commit
e2fb5f51f5
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/backend-core",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"description": "Budibase backend core libraries used in server and worker",
|
||||
"main": "src/index.js",
|
||||
"author": "Budibase",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
|
|
@ -35,7 +35,13 @@ Cypress.Commands.add("login", () => {
|
|||
Cypress.Commands.add("createApp", name => {
|
||||
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
||||
cy.wait(500)
|
||||
cy.contains(/Start from scratch/).dblclick()
|
||||
cy.request(`${Cypress.config().baseUrl}api/applications?status=all`)
|
||||
.its("body")
|
||||
.then(body => {
|
||||
if (body.length > 0) {
|
||||
cy.get(".spectrum-Button").contains("Create app").click({ force: true })
|
||||
}
|
||||
})
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get("input").eq(0).type(name).should("have.value", name).blur()
|
||||
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -65,11 +65,11 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.0.44-alpha.7",
|
||||
"@budibase/client": "^1.0.44-alpha.7",
|
||||
"@budibase/frontend-core": "^1.0.44-alpha.7",
|
||||
"@budibase/bbui": "^1.0.46-alpha.0",
|
||||
"@budibase/client": "^1.0.46-alpha.0",
|
||||
"@budibase/frontend-core": "^1.0.46-alpha.0",
|
||||
"@budibase/colorpicker": "1.1.2",
|
||||
"@budibase/string-templates": "^1.0.44-alpha.7",
|
||||
"@budibase/string-templates": "^1.0.46-alpha.0",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -55,8 +55,8 @@
|
|||
<div>
|
||||
<BindingBuilder
|
||||
bind:customParams={parameters.queryParams}
|
||||
bindings={query.parameters}
|
||||
bind:bindableOptions={bindings}
|
||||
queryBindings={query.parameters}
|
||||
bind:bindings
|
||||
/>
|
||||
<IntegrationQueryEditor
|
||||
height={200}
|
||||
|
|
|
@ -169,8 +169,8 @@
|
|||
{#if getQueryParams(value).length > 0}
|
||||
<BindingBuilder
|
||||
bind:customParams={tmpQueryParams}
|
||||
bindings={getQueryParams(value)}
|
||||
bind:bindableOptions={bindings}
|
||||
queryBindings={getQueryParams(value)}
|
||||
bind:bindings
|
||||
/>
|
||||
{/if}
|
||||
<IntegrationQueryEditor
|
||||
|
|
|
@ -7,27 +7,26 @@
|
|||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
|
||||
export let bindable = true
|
||||
export let queryBindings = []
|
||||
export let bindings = []
|
||||
export let bindableOptions = []
|
||||
export let customParams = {}
|
||||
|
||||
function newQueryBinding() {
|
||||
bindings = [...bindings, {}]
|
||||
queryBindings = [...queryBindings, {}]
|
||||
}
|
||||
|
||||
$: console.log(bindings)
|
||||
|
||||
function deleteQueryBinding(idx) {
|
||||
bindings.splice(idx, 1)
|
||||
bindings = bindings
|
||||
queryBindings.splice(idx, 1)
|
||||
queryBindings = queryBindings
|
||||
}
|
||||
|
||||
// This is necessary due to the way readable and writable bindings are stored.
|
||||
// The readable binding in the UI gets converted to a UUID value that the client understands
|
||||
// for parsing, then converted back so we can display it the readable form in the UI
|
||||
function onBindingChange(param, valueToParse) {
|
||||
customParams[param] = readableToRuntimeBinding(
|
||||
bindableOptions,
|
||||
valueToParse
|
||||
)
|
||||
customParams[param] = readableToRuntimeBinding(bindings, valueToParse)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -49,7 +48,7 @@
|
|||
{/if}
|
||||
</Body>
|
||||
<div class="bindings" class:bindable>
|
||||
{#each bindings as binding, idx}
|
||||
{#each queryBindings as binding, idx}
|
||||
<Input
|
||||
placeholder="Binding Name"
|
||||
thin
|
||||
|
@ -69,10 +68,10 @@
|
|||
thin
|
||||
on:change={evt => onBindingChange(binding.name, evt.detail)}
|
||||
value={runtimeToReadableBinding(
|
||||
bindableOptions,
|
||||
bindings,
|
||||
customParams?.[binding.name]
|
||||
)}
|
||||
{bindableOptions}
|
||||
{bindings}
|
||||
/>
|
||||
{:else}
|
||||
<Icon hoverable name="Close" on:click={() => deleteQueryBinding(idx)} />
|
||||
|
|
|
@ -120,7 +120,7 @@
|
|||
config={integrationInfo.extra}
|
||||
/>
|
||||
{/if}
|
||||
<BindingBuilder bind:bindings={query.parameters} bindable={false} />
|
||||
<BindingBuilder bind:queryBindings={query.parameters} bindable={false} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if shouldShowQueryConfig}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
import { capitalise } from "helpers"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { APP_NAME_REGEX } from "constants"
|
||||
import TemplateList from "./TemplateList.svelte"
|
||||
|
||||
export let template
|
||||
export let inline
|
||||
|
@ -147,29 +146,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if showTemplateSelection}
|
||||
<ModalContent
|
||||
title={"Get started quickly"}
|
||||
showConfirmButton={false}
|
||||
size="L"
|
||||
onConfirm={() => {
|
||||
template = {}
|
||||
return false
|
||||
}}
|
||||
showCancelButton={!inline}
|
||||
showCloseIcon={!inline}
|
||||
>
|
||||
<TemplateList
|
||||
onSelect={(selected, { useImport } = {}) => {
|
||||
if (!selected) {
|
||||
template = useImport ? { fromFile: true } : {}
|
||||
return
|
||||
}
|
||||
template = selected
|
||||
}}
|
||||
/>
|
||||
</ModalContent>
|
||||
{:else}
|
||||
<ModalContent
|
||||
title={"Name your app"}
|
||||
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
||||
|
@ -201,4 +177,3 @@
|
|||
: "My app"}
|
||||
/>
|
||||
</ModalContent>
|
||||
{/if}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
<script>
|
||||
import { Heading, Layout, Icon } from "@budibase/bbui"
|
||||
|
||||
export let onSelect
|
||||
</script>
|
||||
|
||||
<Layout gap="XS" noPadding>
|
||||
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
|
||||
<div
|
||||
class="background-icon"
|
||||
style={`background: rgb(50, 50, 50); color: white;`}
|
||||
>
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
<Heading size="XS">Start from scratch</Heading>
|
||||
<p class="detail">BLANK</p>
|
||||
</div>
|
||||
<div
|
||||
class="template import"
|
||||
on:click={() => onSelect(null, { useImport: true })}
|
||||
>
|
||||
<div
|
||||
class="background-icon"
|
||||
style={`background: rgb(50, 50, 50); color: white;`}
|
||||
>
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
<Heading size="XS">Import an app</Heading>
|
||||
<p class="detail">BLANK</p>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.background-icon {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.template {
|
||||
min-height: 60px;
|
||||
display: grid;
|
||||
grid-gap: var(--layout-s);
|
||||
grid-template-columns: auto 1fr auto;
|
||||
border: 1px solid #494949;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
background: var(--background-alt);
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.detail {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.start-from-scratch {
|
||||
background: var(--spectrum-global-color-gray-50);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.import {
|
||||
background: var(--spectrum-global-color-gray-50);
|
||||
}
|
||||
</style>
|
|
@ -11,6 +11,7 @@
|
|||
notifications,
|
||||
Body,
|
||||
Search,
|
||||
Icon,
|
||||
} from "@budibase/bbui"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
|
@ -27,6 +28,7 @@
|
|||
import AppRow from "components/start/AppRow.svelte"
|
||||
import { AppStatus } from "constants"
|
||||
import analytics, { Events } from "analytics"
|
||||
import Logo from "assets/bb-space-man.svg"
|
||||
|
||||
let sortBy = "name"
|
||||
let template
|
||||
|
@ -78,6 +80,7 @@
|
|||
}
|
||||
|
||||
const initiateAppCreation = () => {
|
||||
template = {}
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}
|
||||
|
@ -300,14 +303,26 @@
|
|||
|
||||
<div class="buttons">
|
||||
{#if cloud}
|
||||
<Button icon="Export" quiet secondary on:click={initiateAppsExport}>
|
||||
<Button
|
||||
size="L"
|
||||
icon="Export"
|
||||
quiet
|
||||
secondary
|
||||
on:click={initiateAppsExport}
|
||||
>
|
||||
Export apps
|
||||
</Button>
|
||||
{/if}
|
||||
<Button icon="Import" quiet secondary on:click={initiateAppImport}>
|
||||
<Button
|
||||
icon="Import"
|
||||
size="L"
|
||||
quiet
|
||||
secondary
|
||||
on:click={initiateAppImport}
|
||||
>
|
||||
Import app
|
||||
</Button>
|
||||
<Button icon="Add" cta on:click={initiateAppCreation}>
|
||||
<Button size="L" icon="Add" cta on:click={initiateAppCreation}>
|
||||
Create app
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -389,9 +404,24 @@
|
|||
|
||||
{#if !enrichedApps.length && !creatingApp && loaded}
|
||||
<div class="empty-wrapper">
|
||||
<Modal inline>
|
||||
<CreateAppModal {template} inline={true} />
|
||||
</Modal>
|
||||
<div class="centered">
|
||||
<div class="main">
|
||||
<Layout gap="S" justifyItems="center">
|
||||
<img class="img-size" alt="logo" src={Logo} />
|
||||
<div class="new-screen-text">
|
||||
<Detail size="M">Create a business app in minutes!</Detail>
|
||||
</div>
|
||||
<Button on:click={() => initiateAppCreation()} size="M" cta>
|
||||
<div class="new-screen-button">
|
||||
<div class="background-icon" style="color: white;">
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
Create App
|
||||
</div></Button
|
||||
>
|
||||
</Layout>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -474,12 +504,15 @@
|
|||
}
|
||||
|
||||
.grid {
|
||||
height: 200px;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
grid-gap: var(--spacing-xl);
|
||||
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
|
||||
grid-template-rows: minmax(70px, 1fr) minmax(100px, 1fr) minmax(0px, 0);
|
||||
}
|
||||
.template-card {
|
||||
height: 80px;
|
||||
height: 70px;
|
||||
border-radius: var(--border-radius-s);
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
cursor: pointer;
|
||||
|
@ -533,4 +566,42 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.centered {
|
||||
width: calc(100% - 350px);
|
||||
height: calc(100% - 100px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.new-screen-text {
|
||||
width: 160px;
|
||||
text-align: center;
|
||||
color: #2c2c2c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.new-screen-button {
|
||||
margin-left: 5px;
|
||||
height: 20px;
|
||||
width: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.img-size {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.background-icon {
|
||||
margin-top: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -19,10 +19,10 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.0.44-alpha.7",
|
||||
"@budibase/frontend-core": "^1.0.44-alpha.7",
|
||||
"@budibase/bbui": "^1.0.46-alpha.0",
|
||||
"@budibase/frontend-core": "^1.0.46-alpha.0",
|
||||
"@budibase/standard-components": "^0.9.139",
|
||||
"@budibase/string-templates": "^1.0.44-alpha.7",
|
||||
"@budibase/string-templates": "^1.0.46-alpha.0",
|
||||
"regexparam": "^1.3.0",
|
||||
"shortid": "^2.2.15",
|
||||
"svelte-spa-router": "^3.0.5"
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@budibase/frontend-core",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"description": "Budibase frontend core libraries used in builder and client",
|
||||
"author": "Budibase",
|
||||
"license": "GPL-3.0",
|
||||
"svelte": "src/index.js",
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.0.44-alpha.7",
|
||||
"@budibase/bbui": "^1.0.46-alpha.0",
|
||||
"lodash": "^4.17.21",
|
||||
"svelte": "^3.46.2"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.ts",
|
||||
"repository": {
|
||||
|
@ -70,9 +70,9 @@
|
|||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.0.3",
|
||||
"@budibase/backend-core": "^1.0.44-alpha.7",
|
||||
"@budibase/client": "^1.0.44-alpha.7",
|
||||
"@budibase/string-templates": "^1.0.44-alpha.7",
|
||||
"@budibase/backend-core": "^1.0.46-alpha.0",
|
||||
"@budibase/client": "^1.0.46-alpha.0",
|
||||
"@budibase/string-templates": "^1.0.46-alpha.0",
|
||||
"@bull-board/api": "^3.7.0",
|
||||
"@bull-board/koa": "^3.7.0",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
|
|
|
@ -71,7 +71,7 @@ exports.getDeployedApps = async () => {
|
|||
for (let [key, value] of Object.entries(json)) {
|
||||
if (value.url) {
|
||||
value.url = value.url.toLowerCase()
|
||||
apps[key] = value
|
||||
apps[key.toLowerCase()] = value
|
||||
}
|
||||
}
|
||||
return apps
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.cjs",
|
||||
"module": "dist/bundle.mjs",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const { atob } = require("../utilities")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
const { LITERAL_MARKER } = require("../helpers/constants")
|
||||
|
||||
// The method of executing JS scripts depends on the bundle being built.
|
||||
// This setter is used in the entrypoint (either index.cjs or index.mjs).
|
||||
|
@ -46,8 +47,9 @@ module.exports.processJS = (handlebars, context) => {
|
|||
$: path => getContextValue(path, cloneDeep(context)),
|
||||
}
|
||||
|
||||
// Create a sandbox with out context and run the JS
|
||||
return runJS(js, sandboxContext)
|
||||
// Create a sandbox with our context and run the JS
|
||||
const res = { data: runJS(js, sandboxContext) }
|
||||
return `{{${LITERAL_MARKER} js_result-${JSON.stringify(res)}}}`
|
||||
} catch (error) {
|
||||
return "Error while executing JS"
|
||||
}
|
||||
|
|
|
@ -36,6 +36,11 @@ module.exports.processors = [
|
|||
return value === "true"
|
||||
case "object":
|
||||
return JSON.parse(value)
|
||||
case "js_result":
|
||||
// We use the literal helper to process the result of JS expressions
|
||||
// as we want to be able to return any types.
|
||||
// We wrap the value in an abject to be able to use undefined properly.
|
||||
return JSON.parse(value).data
|
||||
}
|
||||
return value
|
||||
}),
|
||||
|
|
|
@ -7,7 +7,7 @@ const processJS = (js, context) => {
|
|||
describe("Test the JavaScript helper", () => {
|
||||
it("should execute a simple expression", () => {
|
||||
const output = processJS(`return 1 + 2`)
|
||||
expect(output).toBe("3")
|
||||
expect(output).toBe(3)
|
||||
})
|
||||
|
||||
it("should be able to use primitive bindings", () => {
|
||||
|
@ -50,6 +50,52 @@ describe("Test the JavaScript helper", () => {
|
|||
expect(output).toBe("shazbat")
|
||||
})
|
||||
|
||||
it("should be able to return an object", () => {
|
||||
const output = processJS(`return $("foo")`, {
|
||||
foo: {
|
||||
bar: {
|
||||
baz: "shazbat",
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(output.bar.baz).toBe("shazbat")
|
||||
})
|
||||
|
||||
it("should be able to return an array", () => {
|
||||
const output = processJS(`return $("foo")`, {
|
||||
foo: ["a", "b", "c"],
|
||||
})
|
||||
expect(output[2]).toBe("c")
|
||||
})
|
||||
|
||||
it("should be able to return null", () => {
|
||||
const output = processJS(`return $("foo")`, {
|
||||
foo: null,
|
||||
})
|
||||
expect(output).toBe(null)
|
||||
})
|
||||
|
||||
it("should be able to return undefined", () => {
|
||||
const output = processJS(`return $("foo")`, {
|
||||
foo: undefined,
|
||||
})
|
||||
expect(output).toBe(undefined)
|
||||
})
|
||||
|
||||
it("should be able to return 0", () => {
|
||||
const output = processJS(`return $("foo")`, {
|
||||
foo: 0,
|
||||
})
|
||||
expect(output).toBe(0)
|
||||
})
|
||||
|
||||
it("should be able to return an empty string", () => {
|
||||
const output = processJS(`return $("foo")`, {
|
||||
foo: "",
|
||||
})
|
||||
expect(output).toBe("")
|
||||
})
|
||||
|
||||
it("should be able to use a deep array binding", () => {
|
||||
const output = processJS(`return $("foo.0.bar")`, {
|
||||
foo: [
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/worker",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "1.0.44-alpha.7",
|
||||
"version": "1.0.46-alpha.0",
|
||||
"description": "Budibase background service",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -29,8 +29,8 @@
|
|||
"author": "Budibase",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@budibase/backend-core": "^1.0.44-alpha.7",
|
||||
"@budibase/string-templates": "^1.0.44-alpha.7",
|
||||
"@budibase/backend-core": "^1.0.46-alpha.0",
|
||||
"@budibase/string-templates": "^1.0.46-alpha.0",
|
||||
"@koa/router": "^8.0.0",
|
||||
"@sentry/node": "^6.0.0",
|
||||
"@techpass/passport-openidconnect": "^0.3.0",
|
||||
|
|
|
@ -7,6 +7,6 @@ exports.fetch = async ctx => {
|
|||
accountPortalUrl: env.ACCOUNT_PORTAL_URL,
|
||||
disableAccountPortal: env.DISABLE_ACCOUNT_PORTAL,
|
||||
// in test need to pretend its in production for the UI (Cypress)
|
||||
isDev: env.isDev() && !env.isTest(),
|
||||
isDev: env.isDev(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue