diff --git a/packages/builder/package.json b/packages/builder/package.json
index fcf3d7a9f6..d46504f918 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -63,7 +63,7 @@
}
},
"dependencies": {
- "@budibase/bbui": "^1.34.2",
+ "@budibase/bbui": "^1.34.6",
"@budibase/client": "^0.1.21",
"@budibase/colorpicker": "^1.0.1",
"@fortawesome/fontawesome-free": "^5.14.0",
diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte
index a356ff811f..53cd606143 100644
--- a/packages/builder/src/components/common/Dropzone.svelte
+++ b/packages/builder/src/components/common/Dropzone.svelte
@@ -1,299 +1,32 @@
-
-
- {#if selectedImage}
- -
-
-
-
- {selectedImage.name}
-
-
- {#if selectedImage.size <= BYTES_IN_MB}
- {selectedImage.size / BYTES_IN_KB}KB
- {:else}{selectedImage.size / BYTES_IN_MB}MB{/if}
-
-
-
-
-
- {#if selectedImageIdx !== 0}
-
-
-
- {/if}
-
- {#if selectedImageIdx !== files.length - 1}
-
-
-
- {/if}
-
- {/if}
-
-
-
-
-
-
-
+
diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock
index 27bc9d6dbd..2a5ee3d275 100644
--- a/packages/builder/yarn.lock
+++ b/packages/builder/yarn.lock
@@ -709,10 +709,10 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
-"@budibase/bbui@^1.34.2":
- version "1.34.2"
- resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.34.2.tgz#e4fcc728dc8d51a918f8ebd5c3f0b0afacfa4047"
- integrity sha512-6RusGPZnEpHx1gtGcjk/lFLgMgFdDpSIxB8v2MiA+kp+uP1pFlzegbaDh+/JXyqFwK7HO91I0yXXBoPjibi7Aw==
+"@budibase/bbui@^1.34.6":
+ version "1.34.6"
+ resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.34.6.tgz#d94a9c0af52244ded20dfcd7c93dbd6b184460dc"
+ integrity sha512-FLYKst1WDjQWpZPOm5w31M5mpdc4FZaHNT5UPyE+LTOtVJquUPycyS1Y/lhGjt/QjwP/Gn8wSvwwsD0gCNJvvg==
dependencies:
sirv-cli "^0.4.6"
svelte-flatpickr "^2.4.0"
diff --git a/packages/server/package.json b/packages/server/package.json
index 93d4ff9e7c..2d2deca1a9 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -59,7 +59,7 @@
"joi": "^17.2.1",
"jsonwebtoken": "^8.5.1",
"koa": "^2.7.0",
- "koa-body": "^4.1.0",
+ "koa-body": "^4.2.0",
"koa-compress": "^4.0.1",
"koa-pino-logger": "^3.0.0",
"koa-send": "^5.0.0",
diff --git a/packages/server/src/api/controllers/deploy/aws.js b/packages/server/src/api/controllers/deploy/aws.js
index 91f28793da..2f89d97742 100644
--- a/packages/server/src/api/controllers/deploy/aws.js
+++ b/packages/server/src/api/controllers/deploy/aws.js
@@ -64,19 +64,30 @@ function walkDir(dirPath, callback) {
}
}
-function prepareUploadForS3({ filePath, s3Key, metadata, s3 }) {
- const fileExtension = [...filePath.split(".")].pop()
- const fileBytes = fs.readFileSync(filePath)
- return s3
+async function prepareUploadForS3({ s3Key, metadata, s3, file }) {
+ const extension = [...file.name.split(".")].pop()
+ const fileBytes = fs.readFileSync(file.path)
+
+ const upload = await s3
.upload({
Key: s3Key,
Body: fileBytes,
- ContentType: CONTENT_TYPE_MAP[fileExtension.toLowerCase()],
+ ContentType: file.type || CONTENT_TYPE_MAP[extension.toLowerCase()],
Metadata: metadata,
})
.promise()
+
+ return {
+ size: file.size,
+ name: file.name,
+ extension,
+ url: upload.Location,
+ key: upload.Key,
+ }
}
+exports.prepareUploadForS3 = prepareUploadForS3
+
exports.uploadAppAssets = async function({
appId,
instanceId,
@@ -107,7 +118,10 @@ exports.uploadAppAssets = async function({
// Upload HTML, CSS and JS for each page of the web app
walkDir(`${appAssetsPath}/${page}`, function(filePath) {
const appAssetUpload = prepareUploadForS3({
- filePath,
+ file: {
+ path: filePath,
+ name: [...filePath.split("/")].pop(),
+ },
s3Key: filePath.replace(appAssetsPath, `assets/${appId}`),
s3,
metadata: { accountId },
@@ -124,8 +138,8 @@ exports.uploadAppAssets = async function({
if (file.uploaded) continue
const attachmentUpload = prepareUploadForS3({
- filePath: file.path,
- s3Key: `assets/${appId}/attachments/${file.name}`,
+ file,
+ s3Key: `assets/${appId}/attachments/${file.processedFileName}`,
s3,
metadata: { accountId },
})
diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js
index fd4afb42e7..663c4c7257 100644
--- a/packages/server/src/api/controllers/static.js
+++ b/packages/server/src/api/controllers/static.js
@@ -4,6 +4,8 @@ const jwt = require("jsonwebtoken")
const fetch = require("node-fetch")
const fs = require("fs")
const uuid = require("uuid")
+const AWS = require("aws-sdk")
+const { prepareUploadForS3 } = require("./deploy/aws")
const {
budibaseAppsDir,
@@ -22,8 +24,12 @@ exports.serveBuilder = async function(ctx) {
await send(ctx, ctx.file, { root: ctx.devPath || builderPath })
}
-exports.processLocalFileUpload = async function(ctx) {
- const { files } = ctx.request.body
+exports.uploadFile = async function(ctx) {
+ let files
+ files =
+ ctx.request.files.file.length > 1
+ ? Array.from(ctx.request.files.file)
+ : [ctx.request.files.file]
const attachmentsPath = resolve(
budibaseAppsDir(),
@@ -31,52 +37,99 @@ exports.processLocalFileUpload = async function(ctx) {
"attachments"
)
+ if (process.env.CLOUD) {
+ // remote upload
+ const s3 = new AWS.S3({
+ params: {
+ Bucket: "prod-budi-app-assets",
+ },
+ })
+
+ const uploads = files.map(file => {
+ const fileExtension = [...file.name.split(".")].pop()
+ const processedFileName = `${uuid.v4()}.${fileExtension}`
+
+ return prepareUploadForS3({
+ file,
+ s3Key: `assets/${ctx.user.appId}/attachments/${processedFileName}`,
+ s3,
+ })
+ })
+
+ ctx.body = await Promise.all(uploads)
+ return
+ }
+
+ ctx.body = await processLocalFileUploads({
+ files,
+ outputPath: attachmentsPath,
+ instanceId: ctx.user.instanceId,
+ })
+}
+
+async function processLocalFileUploads({ files, outputPath, instanceId }) {
// create attachments dir if it doesnt exist
- !fs.existsSync(attachmentsPath) &&
- fs.mkdirSync(attachmentsPath, { recursive: true })
+ !fs.existsSync(outputPath) && fs.mkdirSync(outputPath, { recursive: true })
const filesToProcess = files.map(file => {
- const fileExtension = [...file.path.split(".")].pop()
+ const fileExtension = [...file.name.split(".")].pop()
// filenames converted to UUIDs so they are unique
- const fileName = `${uuid.v4()}.${fileExtension}`
+ const processedFileName = `${uuid.v4()}.${fileExtension}`
return {
- ...file,
- fileName,
+ name: file.name,
+ path: file.path,
+ size: file.size,
+ type: file.type,
+ processedFileName,
extension: fileExtension,
- outputPath: join(attachmentsPath, fileName),
- url: join("/attachments", fileName),
+ outputPath: join(outputPath, processedFileName),
+ url: join("/attachments", processedFileName),
}
})
- const fileProcessOperations = filesToProcess.map(file =>
- fileProcessor.process(file)
+ const fileProcessOperations = filesToProcess.map(fileProcessor.process)
+
+ const processedFiles = await Promise.all(fileProcessOperations)
+
+ let pendingFileUploads
+ // local document used to track which files need to be uploaded
+ // db.get throws an error if the document doesn't exist
+ // need to use a promise to default
+ const db = new CouchDB(instanceId)
+ await db
+ .get("_local/fileuploads")
+ .then(data => {
+ pendingFileUploads = data
+ })
+ .catch(() => {
+ pendingFileUploads = { _id: "_local/fileuploads", uploads: [] }
+ })
+
+ pendingFileUploads.uploads = [
+ ...processedFiles,
+ ...pendingFileUploads.uploads,
+ ]
+ await db.put(pendingFileUploads)
+
+ return processedFiles
+}
+
+exports.performLocalFileProcessing = async function(ctx) {
+ const { files } = ctx.request.body
+
+ const processedFileOutputPath = resolve(
+ budibaseAppsDir(),
+ ctx.user.appId,
+ "attachments"
)
try {
- const processedFiles = await Promise.all(fileProcessOperations)
-
- let pendingFileUploads
- // local document used to track which files need to be uploaded
- // db.get throws an error if the document doesn't exist
- // need to use a promise to default
- const db = new CouchDB(ctx.user.instanceId)
- await db
- .get("_local/fileuploads")
- .then(data => {
- pendingFileUploads = data
- })
- .catch(() => {
- pendingFileUploads = { _id: "_local/fileuploads", uploads: [] }
- })
-
- pendingFileUploads.uploads = [
- ...processedFiles,
- ...pendingFileUploads.uploads,
- ]
- await db.put(pendingFileUploads)
-
- ctx.body = processedFiles
+ ctx.body = await processLocalFileUploads({
+ files,
+ outputPath: processedFileOutputPath,
+ instanceId: ctx.user.instanceId,
+ })
} catch (err) {
ctx.throw(500, err)
}
diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js
index 0ce6a62668..aa136a3d15 100644
--- a/packages/server/src/api/routes/static.js
+++ b/packages/server/src/api/routes/static.js
@@ -26,8 +26,9 @@ router
.post(
"/api/attachments/process",
authorized(BUILDER),
- controller.processLocalFileUpload
+ controller.performLocalFileProcessing
)
+ .post("/api/attachments/upload", controller.uploadFile)
.get("/componentlibrary", controller.serveComponentLibrary)
.get("/assets/:file*", controller.serveAppAsset)
.get("/attachments/:file*", controller.serveAttachment)
diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock
index cbdf6cd865..de283385a8 100644
--- a/packages/server/yarn.lock
+++ b/packages/server/yarn.lock
@@ -530,10 +530,12 @@
"@types/events@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
+ integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
"@types/formidable@^1.0.31":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.0.31.tgz#274f9dc2d0a1a9ce1feef48c24ca0859e7ec947b"
+ integrity sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q==
dependencies:
"@types/events" "*"
"@types/node" "*"
@@ -3946,9 +3948,10 @@ kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
-koa-body@^4.1.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.1.1.tgz#50686d290891fc6f1acb986cf7cfcd605f855ef0"
+koa-body@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.2.0.tgz#37229208b820761aca5822d14c5fc55cee31b26f"
+ integrity sha512-wdGu7b9amk4Fnk/ytH8GuWwfs4fsB5iNkY8kZPpgQVb04QZSv85T0M8reb+cJmvLE8cjPYvBzRikD3s6qz8OoA==
dependencies:
"@types/formidable" "^1.0.31"
co-body "^5.1.1"
diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json
index 7048f9af63..ba3305d293 100644
--- a/packages/standard-components/package.json
+++ b/packages/standard-components/package.json
@@ -36,6 +36,7 @@
"gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691",
"dependencies": {
"@beyonk/svelte-googlemaps": "^2.2.0",
+ "@budibase/bbui": "^1.34.6",
"@fortawesome/fontawesome-free": "^5.14.0",
"@budibase/bbui": "^1.34.2",
"britecharts": "^2.16.1",
diff --git a/packages/standard-components/src/DataForm.svelte b/packages/standard-components/src/DataForm.svelte
index ba5998e5c6..0b8267c87f 100644
--- a/packages/standard-components/src/DataForm.svelte
+++ b/packages/standard-components/src/DataForm.svelte
@@ -2,6 +2,7 @@
import { onMount } from "svelte"
import { fade } from "svelte/transition"
import { Label, DatePicker } from "@budibase/bbui"
+ import Dropzone from "./attachments/Dropzone.svelte"
import debounce from "lodash.debounce"
export let _bb
@@ -54,8 +55,9 @@
const save = debounce(async () => {
for (let field of fields) {
// Assign defaults to empty fields to prevent validation issues
- if (!(field in record))
+ if (!(field in record)) {
record[field] = DEFAULTS_FOR_TYPE[schema[field].type]
+ }
}
const SAVE_RECORD_URL = `/api/${model}/records`
@@ -132,6 +134,8 @@
{:else if schema[field].type === 'string'}
+ {:else if schema[field].type === 'attachment'}
+
{/if}
diff --git a/packages/standard-components/src/DataTable.svelte b/packages/standard-components/src/DataTable.svelte
index 10f132d017..fe967338f5 100644
--- a/packages/standard-components/src/DataTable.svelte
+++ b/packages/standard-components/src/DataTable.svelte
@@ -6,6 +6,7 @@
import fsort from "fast-sort"
import fetchData from "./fetchData.js"
import { isEmpty } from "lodash/fp"
+ import AttachmentList from "./attachments/AttachmentList.svelte"
export let backgroundColor
export let color
@@ -17,6 +18,7 @@
let headers = []
let sort = {}
let sorted = []
+ let schema = {}
$: cssVariables = {
backgroundColor,
@@ -83,7 +85,10 @@
{#each sorted as row (row._id)}
{#each headers as header}
- {#if row[header]}
+
+ {#if Array.isArray(row[header])}
+
+ {:else if row[header]}
{row[header]} |
{/if}
{/each}
diff --git a/packages/standard-components/src/api.js b/packages/standard-components/src/api.js
index da29c70578..45e7a8f134 100644
--- a/packages/standard-components/src/api.js
+++ b/packages/standard-components/src/api.js
@@ -1,7 +1,10 @@
-const apiCall = method => async (url, body) => {
- const headers = {
+const apiCall = method => async (
+ url,
+ body,
+ headers = {
"Content-Type": "application/json",
}
+) => {
const response = await fetch(url, {
method: method,
body: body && JSON.stringify(body),
diff --git a/packages/standard-components/src/attachments/AttachmentList.svelte b/packages/standard-components/src/attachments/AttachmentList.svelte
new file mode 100644
index 0000000000..950c1e43b6
--- /dev/null
+++ b/packages/standard-components/src/attachments/AttachmentList.svelte
@@ -0,0 +1,64 @@
+
+
+
+
+
diff --git a/packages/standard-components/src/attachments/Dropzone.svelte b/packages/standard-components/src/attachments/Dropzone.svelte
new file mode 100644
index 0000000000..9d68c920c2
--- /dev/null
+++ b/packages/standard-components/src/attachments/Dropzone.svelte
@@ -0,0 +1,35 @@
+
+
+
diff --git a/packages/standard-components/src/attachments/fileTypes.js b/packages/standard-components/src/attachments/fileTypes.js
new file mode 100644
index 0000000000..2ce6958f2d
--- /dev/null
+++ b/packages/standard-components/src/attachments/fileTypes.js
@@ -0,0 +1,5 @@
+export const FILE_TYPES = {
+ IMAGE: ["png", "tiff", "gif", "raw", "jpg", "jpeg"],
+ CODE: ["js", "rs", "py", "java", "rb", "hs", "yml"],
+ DOCUMENT: ["odf", "docx", "doc", "pdf", "csv"],
+}