Working commit

This commit is contained in:
Dean 2023-03-16 09:07:21 +00:00
parent c7e54947a8
commit 253ee413a2
14 changed files with 296 additions and 164 deletions

View File

@ -4,73 +4,35 @@
import Icon from "../../Icon/Icon.svelte"
import { createEventDispatcher } from "svelte"
export let value = null //support multi?
export let value = null
export let title = "Upload file"
export let disabled = false
export let extensions = "*"
export let extensions = null
export let handleFileTooLarge = null
export let handleTooManyFiles = null
export let fileSizeLimit = BYTES_IN_MB * 20 // Centralise
export let fileSizeLimit = BYTES_IN_MB * 20
export let id = null
export let previewUrl = null
const dispatch = createEventDispatcher()
//Is this necessary?
const fieldId = id || uuid()
// Centralise all in new file section?
const BYTES_IN_KB = 1000
const BYTES_IN_MB = 1000000
const dispatch = createEventDispatcher()
let fileInput
// $: file = value[0] || null
// const imageExtensions = [
// "png",
// "tiff",
// "gif",
// "raw",
// "jpg",
// "jpeg",
// "svg",
// "bmp",
// "jfif",
// ]
$: inputAccept = Array.isArray(extensions) ? extensions.join(",") : "*"
// Should support only 1 file for now.
// Files[0]
//What is the limit? 50mb?
async function processFileList(fileList) {
if (
handleFileTooLarge &&
Array.from(fileList).some(file => file.size >= fileSizeLimit)
) {
handleFileTooLarge(fileSizeLimit, value)
async function processFile(targetFile) {
if (handleFileTooLarge && targetFile?.size >= fileSizeLimit) {
handleFileTooLarge(targetFile)
return
}
const fileCount = fileList.length + value.length
if (handleTooManyFiles && maximum && fileCount > maximum) {
handleTooManyFiles(maximum)
return
}
if (processFiles) {
const processedFiles = await processFiles(fileList)
const newValue = [...value, ...processedFiles]
dispatch("change", newValue)
selectedImageIdx = newValue.length - 1
} else {
dispatch("change", fileList)
}
dispatch("change", targetFile)
}
function handleFile(evt) {
console.log("Hello ", evt.target.files[0])
dispatch("change", evt.target.files[0])
//processFileList(evt.target.files)
processFile(evt.target.files[0])
}
function clearFile() {
@ -82,7 +44,7 @@
id={fieldId}
{disabled}
type="file"
accept={extensions}
accept={inputAccept}
bind:this={fileInput}
on:change={handleFile}
/>
@ -90,7 +52,9 @@
<div class="field">
{#if value}
<div class="file-view">
<!-- <img alt="" src={value.url} /> -->
{#if previewUrl}
<img class="preview" alt="" src={previewUrl} />
{/if}
<div class="filename">{value.name}</div>
{#if value.size}
<div class="filesize">
@ -128,6 +92,23 @@
display: none;
}
.delete-button {
transition: all 0.3s;
margin-left: 10px;
display: flex;
}
.delete-button:hover {
cursor: pointer;
color: var(--red);
}
.filesize {
white-space: nowrap;
}
.filename {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.preview {
height: 1.5em;
}
</style>

View File

@ -6,6 +6,9 @@
export let label = null
export let labelPosition = "above"
export let disabled = false
export let handleFileTooLarge = () => {}
export let previewUrl = null
export let extensions = null
export let error = null
export let title = null
export let value = null
@ -19,5 +22,14 @@
</script>
<Field {label} {labelPosition} {error} {tooltip}>
<CoreFile {error} {disabled} {title} {value} on:change={onChange} />
<CoreFile
{error}
{disabled}
{title}
{value}
{previewUrl}
{handleFileTooLarge}
{extensions}
on:change={onChange}
/>
</Field>

View File

@ -0,0 +1,59 @@
<script>
import { organisation, auth, admin } from "stores/portal"
import { onMount } from "svelte"
let loaded = false
let platformTitle = null
$: platformTitleText = $organisation.platformTitle
$: platformTitleText,
(platformTitle =
!$admin.isCloud && !$auth.user ? platformTitleText : "Budibase")
onMount(async () => {
await organisation.init()
loaded = true
})
</script>
<!--
In order to update the org elements, an update will have to be made to clear them.
-->
<svelte:head>
<title>{platformTitle}</title>
{#if loaded && !$auth.user}
<link
rel="icon"
href={$organisation.faviconUrl || "https://i.imgur.com/Xhdt1YP.png"}
/>
{:else}
<!-- A default must be set or the browser defaults to favicon.ico behaviour -->
<link rel="icon" href={"https://i.imgur.com/Xhdt1YP.png"} />
{/if}
<!-- Primary Meta Tags -->
<!-- <meta name="title" content={metaTitle} /> -->
<!--
metaTitle should match the title...
should title override metaTitle, if set??
-->
<!-- <meta name="description" content={metaDescription} /> -->
<!-- Opengraph Meta Tags -->
<!-- <meta property="og:site_name" content="Budibase" />
<meta property="og:title" content="{title} - built with Budibase" />
<meta property="og:description" content={metaDescription} />
<meta property="og:type" content="website" />
<meta property="og:image" content={metaImage} /> -->
<!-- Twitter -->
<!-- <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@budibase" />
<meta name="twitter:image" content={metaImage} /> -->
<!-- Consider adding this twitter:image:alt -->
<!-- <meta name="twitter:title" content="{title} - built with Budibase" />
<meta property="twitter:description" content={metaDescription} /> -->
</svelte:head>

View File

@ -147,7 +147,7 @@
</Layout>
{/if}
{#if cloud && $organisation.licenceAgreementEnabled}
{#if cloud && $organisation.licenseAgreementEnabled}
<Body size="xs" textAlign="center">
By using Budibase Cloud
<br />

View File

@ -12,15 +12,34 @@
Toggle,
Input,
Label,
TextArea,
} from "@budibase/bbui"
import { auth, organisation, licensing, admin } from "stores/portal"
import { API } from "api"
import { onMount } from "svelte"
const imageExtensions = [
".png",
".tiff",
".gif",
".raw",
".jpg",
".jpeg",
".svg",
".bmp",
".jfif",
]
const faviconExtensions = [".png", ".ico", ".gif"]
let loaded = false
let saving = false
let logoFile = null
let logoPreview = null
let faviconFile = null
let faviconPreview = null
let config = {}
let updated = false
@ -39,9 +58,45 @@
: null
$: favicon = config.faviconUrl
? { url: config.logoUrl, type: "image", name: "Favicon" }
? { url: config.faviconUrl, type: "image", name: "Favicon" }
: null
//If type of file do this IN the picker
//If string use the string
//If object?.url us that
const previewUrl = async localFile => {
if (!localFile) {
return Promise.resolve(null)
}
return new Promise(resolve => {
let reader = new FileReader()
try {
reader.onload = e => {
resolve({
result: e.target.result,
})
}
reader.readAsDataURL(localFile)
} catch (error) {
console.error(error)
resolve(null)
}
})
}
$: previewUrl(logoFile).then(response => {
if (response) {
logoPreview = response.result
}
})
$: previewUrl(faviconFile).then(response => {
if (response) {
faviconPreview = response.result
}
})
async function uploadLogo(file) {
let response = {}
try {
@ -54,8 +109,6 @@
return response
}
// Limit file types
// PNG, GIF, or ICO?
async function uploadFavicon(file) {
let response = {}
try {
@ -70,9 +123,7 @@
async function saveConfig() {
saving = true
console.log("Save Config")
console.log("SAVING CONFIG ")
if (logoFile) {
const logoResp = await uploadLogo(logoFile)
if (logoResp.url) {
@ -80,8 +131,8 @@
...config,
logoUrl: logoResp.url,
}
} else {
//would have to delete
logoFile = null
logoPreview = null
}
}
@ -92,6 +143,8 @@
...config,
faviconUrl: faviconResp.url,
}
faviconFile = null
faviconPreview = null
}
}
console.log("SAVE CONFIG ", config)
@ -111,28 +164,19 @@
onMount(async () => {
await organisation.init()
const {
faviconUrl,
logoUrl,
platformTitle,
emailBrandingEnabled,
appFooterEnabled,
loginHeading,
loginButton,
licenceAgreementEnabled,
testimonialsEnabled,
} = $organisation
config = {
faviconUrl,
logoUrl,
platformTitle,
emailBrandingEnabled,
appFooterEnabled,
loginHeading,
loginButton,
licenceAgreementEnabled,
testimonialsEnabled,
faviconUrl: $organisation.faviconUrl,
logoUrl: $organisation.logoUrl,
platformTitle: $organisation.platformTitle,
emailBrandingEnabled: $organisation.emailBrandingEnabled,
appFooterEnabled: $organisation.appFooterEnabled,
loginHeading: $organisation.loginHeading,
loginButton: $organisation.loginButton,
licenseAgreementEnabled: $organisation.licenseAgreementEnabled,
testimonialsEnabled: $organisation.testimonialsEnabled,
metaDescription: $organisation.metaDescription,
metaImageUrl: $organisation.metaImageUrl,
metaTitle: $organisation.metaTitle,
}
loaded = true
@ -158,17 +202,24 @@
<Label size="L">Logo</Label>
<File
title="Upload image"
handleFileTooLarge={() => {
notifications.warn("File too large. 20mb limit")
}}
extensions={imageExtensions}
previewUrl={logoPreview || logo?.url}
on:change={e => {
console.log("Updated Logo")
let clone = { ...config }
if (e.detail) {
logoFile = e.detail
logoPreview = null
} else {
logoFile = null
clone.logoUrl = ""
}
config = clone
}}
value={logo}
value={logoFile || logo}
/>
</div>
@ -176,17 +227,22 @@
<Label size="L">Favicon</Label>
<File
title="Upload image"
handleFileTooLarge={() => {
notifications.warn("File too large. 20mb limit")
}}
extensions={faviconExtensions}
previewUrl={faviconPreview || favicon?.url}
on:change={e => {
console.log("Updated Favicon")
let clone = { ...config }
if (e.detail) {
faviconFile = e.detail
faviconPreview = null
} else {
clone.faviconUrl = ""
}
config = clone
}}
value={favicon}
value={faviconFile || favicon}
/>
</div>
<div class="field">
@ -197,12 +253,12 @@
clone.platformTitle = e.detail ? e.detail : ""
config = clone
}}
value={config.platformTitle || "Budibase"}
value={config.platformTitle || ""}
/>
</div>
<div>
<Toggle
text={"Remove Buidbase brand from emails"}
text={"Remove Budibase brand from emails"}
on:change={e => {
let clone = { ...config }
clone.emailBrandingEnabled = !e.detail
@ -222,7 +278,6 @@
</div>
</div>
<!-- Should this be displayed? -->
{#if !$admin.cloud}
<Divider />
<Layout gap="XS" noPadding>
@ -239,7 +294,7 @@
clone.loginHeading = e.detail ? e.detail : ""
config = clone
}}
value={config.loginHeading || "Log in to Budibase"}
value={config.loginHeading || ""}
/>
</div>
@ -251,7 +306,7 @@
clone.loginButton = e.detail ? e.detail : ""
config = clone
}}
value={config.loginButton || "Log in to Budibase"}
value={config.loginButton || ""}
/>
</div>
<div>
@ -265,13 +320,13 @@
value={!config.testimonialsEnabled}
/>
<Toggle
text={"Remove licence agreement"}
text={"Remove license agreement"}
on:change={e => {
let clone = { ...config }
clone.licenceAgreementEnabled = !e.detail
clone.licenseAgreementEnabled = !e.detail
config = clone
}}
value={!config.licenceAgreementEnabled}
value={!config.licenseAgreementEnabled}
/>
</div>
</div>
@ -285,15 +340,37 @@
<div class="app-previews">
<div class="fields">
<div class="field">
<!-- <Label size="L">Header</Label>
<Input
on:change={e => {
let clone = { ...config }
clone.loginHeading = e.detail ? e.detail : ""
config = clone
}}
value={config.loginHeading || "Log in to Budibase"}
/> -->
<Label size="L">Image URL</Label>
<Input
on:change={e => {
let clone = { ...config }
clone.metaImageUrl = e.detail ? e.detail : ""
config = clone
}}
value={config.metaImageUrl}
/>
</div>
<div class="field">
<Label size="L">Title</Label>
<Input
on:change={e => {
let clone = { ...config }
clone.metaTitle = e.detail ? e.detail : ""
config = clone
}}
value={config.metaTitle}
/>
</div>
<div class="field">
<Label size="L">Description</Label>
<TextArea
on:change={e => {
let clone = { ...config }
clone.metaDescription = e.detail ? e.detail : ""
config = clone
}}
value={config.metaDescription}
/>
</div>
</div>
</div>
@ -316,7 +393,8 @@
.branding,
.login {
width: 60%;
width: 70%;
max-width: 70%;
}
.fields {
display: grid;
@ -324,7 +402,7 @@
}
.field {
display: grid;
grid-template-columns: 80px 1fr;
grid-template-columns: 80px auto;
grid-gap: var(--spacing-l);
align-items: center;
}

View File

@ -7,12 +7,10 @@
Divider,
Label,
Input,
// Dropzone,
notifications,
Toggle,
} from "@budibase/bbui"
import { auth, organisation, admin } from "stores/portal"
import { API } from "api"
import { writable } from "svelte/store"
import { redirect } from "@roxi/routify"
@ -28,21 +26,9 @@
company: $organisation.company,
platformUrl: $organisation.platformUrl,
analyticsEnabled: $organisation.analyticsEnabled,
// logo: $organisation.logoUrl
// ? { url: $organisation.logoUrl, type: "image", name: "Logo" }
// : null,
})
let loading = false
// async function uploadLogo(file) {
// try {
// let data = new FormData()
// data.append("file", file)
// await API.uploadLogo(data)
// } catch (error) {
// notifications.error("Error uploading logo")
// }
// }
let loading = false
async function saveConfig() {
loading = true

View File

@ -7,17 +7,20 @@ const DEFAULT_CONFIG = {
platformUrl: "",
logoUrl: undefined,
faviconUrl: undefined,
faviconUrl: undefined,
emailBrandingEnabled: true,
appFooterEnabled: true,
// Self host only
testimonialsEnabled: true,
licenceAgreementEnabled: true,
licenseAgreementEnabled: true,
platformTitle: "Budibase",
loginHeading: undefined,
loginButton: undefined,
metaDescription: undefined,
metaImageUrl: undefined,
metaTitle: undefined,
docsUrl: undefined,
company: "Budibase",
oidc: undefined,

View File

@ -11,10 +11,11 @@ import {
} from "../../../utilities/fileSystem"
import env from "../../../environment"
import { DocumentType } from "../../../db/utils"
import { context, objectStore, utils } from "@budibase/backend-core"
import { context, objectStore, utils, configs } from "@budibase/backend-core"
import AWS from "aws-sdk"
import fs from "fs"
import sdk from "../../../sdk"
const send = require("koa-send")
async function prepareUpload({ s3Key, bucket, metadata, file }: any) {
@ -98,6 +99,8 @@ export const deleteObjects = async function (ctx: any) {
}
export const serveApp = async function (ctx: any) {
const { config } = await configs.getSettingsConfigDoc()
const db = context.getAppDB({ skip_setup: true })
const appInfo = await db.get(DocumentType.APP_METADATA)
let appId = context.getAppId()
@ -105,16 +108,19 @@ export const serveApp = async function (ctx: any) {
if (!env.isJest()) {
const App = require("./templates/BudibaseApp.svelte").default
const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
console.log(appInfo)
const { head, html, css } = App.render({
metaImage:
config?.metaImageUrl ||
"https://res.cloudinary.com/daog6scxm/image/upload/v1666109324/meta-images/budibase-meta-image_uukc1m.png",
title: appInfo.name, //Replace Title here?
metaDescription: config?.metaDescription || "",
metaTitle: `${appInfo.name} - built with Budibase` || config?.metaTitle,
title: appInfo.name,
production: env.isProd(),
appId,
clientLibPath: objectStore.clientLibraryUrl(appId!, appInfo.version),
usedPlugins: plugins,
favicon: objectStore.getGlobalFileUrl("settings", "faviconUrl"),
//logo: objectStore.getGlobalFileUrl("settings", "logoUrl"),
})
const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)

View File

@ -1,7 +1,10 @@
<script>
export let title = ""
export let favicon = ""
export let metaImage = ""
export let metaTitle = ""
export let metaDescription = ""
export let clientLibPath
export let usedPlugins
@ -13,15 +16,31 @@
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<!-- Primary Meta Tags -->
<meta name="title" content={metaTitle} />
<meta name="description" content={metaDescription} />
<!--
metaTitle should match the title...
should title override metaTitle, if set??
-->
<!-- Opengraph Meta Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@budibase" />
<meta name="twitter:image" content={metaImage} />
<meta name="twitter:title" content="{title} - built with Budibase" />
<meta property="og:site_name" content="Budibase" />
<meta property="og:title" content="{title} - built with Budibase" />
<meta property="og:description" content={metaDescription} />
<meta property="og:type" content="website" />
<meta property="og:image" content={metaImage} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:site" content="@budibase" />
<meta property="twitter:image" content={metaImage} />
<!-- Consider adding this twitter:image:alt -->
<meta property="twitter:title" content="{title} - built with Budibase" />
<meta property="twitter:description" content={metaDescription} />
<title>{title}</title>
<link rel="icon" type="image/png" href={favicon} />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />

View File

@ -32,15 +32,17 @@ export interface SettingsInnerConfig {
faviconUrlEtag?: string
emailBrandingEnabled?: boolean
// Self host only
appFooterEnabled?: boolean
testimonialsEnabled?: boolean
licenceAgreementEnabled?: boolean
licenseAgreementEnabled?: boolean
platformTitle?: string
loginHeading?: string
loginButton?: string
metaDescription?: string
metaImageUrl?: string
metaTitle?: string
uniqueTenantId?: string
analyticsEnabled?: boolean
isSSOEnforced?: boolean

View File

@ -20,6 +20,7 @@ export enum TemplateType {
}
export enum EmailTemplatePurpose {
CORE = "core",
BASE = "base",
PASSWORD_RECOVERY = "password_recovery",
INVITATION = "invitation",

View File

@ -10,18 +10,18 @@
>
<tbody>
<tr>
<td
style="padding:0 35px;vertical-align: middle;"
cellpadding="0"
cellspacing="0"
>
<td style="padding:0 35px;" cellpadding="0" cellspacing="0">
<img
width="32px"
style="margin-right: 16px;"
style="margin-right:16px; vertical-align: middle;"
alt="Budibase Logo"
src="https://i.imgur.com/Xhdt1YP.png"
/>
<h2 style="margin: 0px">Budibase</h2>
<strong
style="color:#333333; vertical-align: middle; font-size: 1.1em"
>
Budibase
</strong>
</td>
</tr>
</tbody>

View File

@ -21,8 +21,7 @@ export const EmailTemplates = {
join(__dirname, "welcome.hbs")
),
[EmailTemplatePurpose.CUSTOM]: readStaticFile(join(__dirname, "custom.hbs")),
//Core wrapper
["branding"]: readStaticFile(join(__dirname, "core.hbs")),
[EmailTemplatePurpose.CORE]: readStaticFile(join(__dirname, "core.hbs")),
}
export function addBaseTemplates(templates: Template[], type?: string) {

View File

@ -108,53 +108,39 @@ async function buildEmail(
let [base, body] = await Promise.all([
getTemplateByPurpose(TYPE, EmailTemplatePurpose.BASE),
getTemplateByPurpose(TYPE, purpose),
//getTemplateByPurpose(TYPE, "branding"), //should generalise to 'branding'
])
let branding = EmailTemplates["branding"]
// Change from branding to core
let core = EmailTemplates.core
if (!base || !body || !branding) {
if (!base || !body || !core) {
throw "Unable to build email, missing base components"
}
base = base.contents
body = body.contents
//branding = branding.contents
let name = user ? user.name : undefined
if (user && !name && user.firstName) {
name = user.lastName ? `${user.firstName} ${user.lastName}` : user.firstName
}
context = {
...context, //enableEmailBranding
...context,
contents,
email,
name,
user: user || {},
}
const core = branding + body
// Prepend the core template
const fullBody = core + body
body = await processString(core, context)
// Conditional elements
// branding = await processString(branding, {
// ...context,
// body,
// })
body = await processString(fullBody, context)
// this should now be the core email HTML
return processString(base, {
...context,
body, //: branding, // pass as body as usual
body,
})
// body = await processString(body, context)
// // this should now be the coree email HTML
// return processString(base, {
// ...context,
// body,
// })
}
/**