diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json
index 5385a8852b..0b888ba10b 100644
--- a/packages/builder/src/components/design/AppPreview/componentStructure.json
+++ b/packages/builder/src/components/design/AppPreview/componentStructure.json
@@ -44,7 +44,8 @@
"relationshipfield",
"daterangepicker",
"multifieldselect",
- "jsonfield"
+ "jsonfield",
+ "s3upload"
]
},
{
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/S3Upload.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/S3Upload.svelte
new file mode 100644
index 0000000000..76cccf58c5
--- /dev/null
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/S3Upload.svelte
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/index.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/index.js
index 0aaebb3361..416ebffb1a 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/index.js
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/index.js
@@ -11,3 +11,4 @@ export { default as ChangeFormStep } from "./ChangeFormStep.svelte"
export { default as UpdateState } from "./UpdateState.svelte"
export { default as RefreshDataProvider } from "./RefreshDataProvider.svelte"
export { default as DuplicateRow } from "./DuplicateRow.svelte"
+export { default as S3Upload } from "./S3Upload.svelte"
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json
index 28b4967d83..509e700bb8 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json
@@ -70,6 +70,10 @@
"name": "Update State",
"component": "UpdateState",
"dependsOnFeature": "state"
+ },
+ {
+ "name": "Upload File to S3",
+ "component": "S3Upload"
}
]
}
\ No newline at end of file
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/S3DataSourceSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/S3DataSourceSelect.svelte
new file mode 100644
index 0000000000..41f9347c6e
--- /dev/null
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/S3DataSourceSelect.svelte
@@ -0,0 +1,15 @@
+
+
+
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js
index e752240302..dea7ca95b7 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js
@@ -1,5 +1,6 @@
import { Checkbox, Select, Stepper } from "@budibase/bbui"
import DataSourceSelect from "./DataSourceSelect.svelte"
+import S3DataSourceSelect from "./S3DataSourceSelect.svelte"
import DataProviderSelect from "./DataProviderSelect.svelte"
import ButtonActionEditor from "./ButtonActionEditor/ButtonActionEditor.svelte"
import TableSelect from "./TableSelect.svelte"
@@ -21,6 +22,7 @@ const componentMap = {
text: DrawerBindableCombobox,
select: Select,
dataSource: DataSourceSelect,
+ "dataSource/s3": S3DataSourceSelect,
dataProvider: DataProviderSelect,
boolean: Checkbox,
number: Stepper,
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index dfe9ae5a91..0607eb50d7 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -3315,5 +3315,44 @@
"suffix": "repeater"
}
]
+ },
+ "s3upload": {
+ "name": "S3 File Upload",
+ "icon": "UploadToCloud",
+ "styles": ["size"],
+ "editable": true,
+ "settings": [
+ {
+ "type": "field/attachment",
+ "label": "Field",
+ "key": "field"
+ },
+ {
+ "type": "text",
+ "label": "Label",
+ "key": "label"
+ },
+ {
+ "type": "dataSource/s3",
+ "label": "S3 Datasource",
+ "key": "datasource"
+ },
+ {
+ "type": "text",
+ "label": "Bucket",
+ "key": "bucket"
+ },
+ {
+ "type": "boolean",
+ "label": "Disabled",
+ "key": "disabled",
+ "defaultValue": false
+ },
+ {
+ "type": "validation/attachment",
+ "label": "Validation",
+ "key": "validation"
+ }
+ ]
}
}
diff --git a/packages/client/src/components/app/forms/S3Upload.svelte b/packages/client/src/components/app/forms/S3Upload.svelte
new file mode 100644
index 0000000000..645736838b
--- /dev/null
+++ b/packages/client/src/components/app/forms/S3Upload.svelte
@@ -0,0 +1,79 @@
+
+
+
+ {#if fieldState}
+ {
+ fieldApi.setValue(e.detail)
+ }}
+ {processFiles}
+ {handleFileTooLarge}
+ maximum={1}
+ fileSizeLimit={MaxFileSize}
+ />
+ {/if}
+
diff --git a/packages/client/src/components/app/forms/index.js b/packages/client/src/components/app/forms/index.js
index ab1f7d20ed..0ff82cea94 100644
--- a/packages/client/src/components/app/forms/index.js
+++ b/packages/client/src/components/app/forms/index.js
@@ -12,3 +12,4 @@ export { default as relationshipfield } from "./RelationshipField.svelte"
export { default as passwordfield } from "./PasswordField.svelte"
export { default as formstep } from "./FormStep.svelte"
export { default as jsonfield } from "./JSONField.svelte"
+export { default as s3upload } from "./S3Upload.svelte"
diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js
index ab1150658f..42c2057ca6 100644
--- a/packages/client/src/sdk.js
+++ b/packages/client/src/sdk.js
@@ -5,6 +5,7 @@ import {
routeStore,
screenStore,
builderStore,
+ uploadStore,
} from "stores"
import { styleable } from "utils/styleable"
import { linkable } from "utils/linkable"
@@ -19,6 +20,7 @@ export default {
routeStore,
screenStore,
builderStore,
+ uploadStore,
styleable,
linkable,
getAction,
diff --git a/packages/client/src/stores/index.js b/packages/client/src/stores/index.js
index 02c848120c..9f6e5f6f50 100644
--- a/packages/client/src/stores/index.js
+++ b/packages/client/src/stores/index.js
@@ -9,6 +9,7 @@ export { confirmationStore } from "./confirmation"
export { peekStore } from "./peek"
export { stateStore } from "./state"
export { themeStore } from "./theme"
+export { uploadStore } from "./uploads.js"
// Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context"
diff --git a/packages/client/src/stores/uploads.js b/packages/client/src/stores/uploads.js
new file mode 100644
index 0000000000..419d1e1c11
--- /dev/null
+++ b/packages/client/src/stores/uploads.js
@@ -0,0 +1,42 @@
+import { writable, get } from "svelte/store"
+
+export const createUploadStore = () => {
+ const store = writable([])
+
+ // Registers a new file upload component
+ const registerFileUpload = (componentId, callback) => {
+ if (!componentId || !callback) {
+ return
+ }
+
+ store.update(state => {
+ state.push({
+ componentId,
+ callback,
+ })
+ return state
+ })
+ }
+
+ // Unregisters a file upload component
+ const unregisterFileUpload = componentId => {
+ store.update(state => state.filter(c => c.componentId !== componentId))
+ }
+
+ // Processes a file upload for a given component ID
+ const processFileUpload = async componentId => {
+ if (!componentId) {
+ return
+ }
+
+ const component = get(store).find(c => c.componentId === componentId)
+ await component?.callback()
+ }
+
+ return {
+ subscribe: store.subscribe,
+ actions: { registerFileUpload, unregisterFileUpload, processFileUpload },
+ }
+}
+
+export const uploadStore = createUploadStore()
diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js
index 6b4dd4235a..6085fafec5 100644
--- a/packages/client/src/utils/buttonActions.js
+++ b/packages/client/src/utils/buttonActions.js
@@ -5,6 +5,7 @@ import {
confirmationStore,
authStore,
stateStore,
+ uploadStore,
} from "stores"
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "api"
import { ActionTypes } from "constants"
@@ -157,6 +158,14 @@ const updateStateHandler = action => {
}
}
+const s3UploadHandler = async action => {
+ const { componentId } = action.parameters
+ if (!componentId) {
+ return
+ }
+ await uploadStore.actions.processFileUpload(componentId)
+}
+
const handlerMap = {
["Save Row"]: saveRowHandler,
["Duplicate Row"]: duplicateRowHandler,
@@ -171,6 +180,7 @@ const handlerMap = {
["Close Screen Modal"]: closeScreenModalHandler,
["Change Form Step"]: changeFormStepHandler,
["Update State"]: updateStateHandler,
+ ["Upload File to S3"]: s3UploadHandler,
}
const confirmTextMap = {