Add attachment cell
This commit is contained in:
parent
b60eca9588
commit
f290d758ba
|
@ -138,7 +138,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container" class:compact>
|
||||||
{#if selectedImage}
|
{#if selectedImage}
|
||||||
{#if gallery}
|
{#if gallery}
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
|
@ -379,6 +379,10 @@
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin: 20px 30px;
|
margin: 20px 30px;
|
||||||
}
|
}
|
||||||
|
.compact .placeholder,
|
||||||
|
.compact img {
|
||||||
|
margin: 12px 16px;
|
||||||
|
}
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -447,6 +451,13 @@
|
||||||
.disabled .spectrum-Heading--sizeL {
|
.disabled .spectrum-Heading--sizeL {
|
||||||
color: var(--spectrum-alias-text-color-disabled);
|
color: var(--spectrum-alias-text-color-disabled);
|
||||||
}
|
}
|
||||||
|
.compact .spectrum-Dropzone {
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.compact .spectrum-IllustratedMessage-description {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let gallery = true
|
export let gallery = true
|
||||||
export let fileTags = []
|
export let fileTags = []
|
||||||
export let maximum = undefined
|
export let maximum = undefined
|
||||||
|
export let compact = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
{gallery}
|
{gallery}
|
||||||
{fileTags}
|
{fileTags}
|
||||||
{maximum}
|
{maximum}
|
||||||
|
{compact}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
|
import { debounce } from "../../utils/utils"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
rows,
|
rows,
|
||||||
|
@ -11,15 +12,16 @@
|
||||||
} = getContext("sheet")
|
} = getContext("sheet")
|
||||||
|
|
||||||
const handleKeyDown = e => {
|
const handleKeyDown = e => {
|
||||||
|
// If nothing selected avoid processing further key presses
|
||||||
if (!$selectedCellId) {
|
if (!$selectedCellId) {
|
||||||
if (e.key === "Tab") {
|
if (e.key === "Tab") {
|
||||||
selectFirstCell()
|
selectFirstCell()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const api = $selectedCellAPI
|
|
||||||
|
|
||||||
// Always intercept certain key presses
|
// Always intercept certain key presses
|
||||||
|
const api = $selectedCellAPI
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
api?.blur?.()
|
api?.blur?.()
|
||||||
} else if (e.key === "Tab") {
|
} else if (e.key === "Tab") {
|
||||||
|
@ -105,13 +107,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteSelectedCell = () => {
|
// Debounce to avoid holding down delete and spamming requests
|
||||||
|
const deleteSelectedCell = debounce(() => {
|
||||||
if (!$selectedCellId) {
|
if (!$selectedCellId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const [rowId, column] = $selectedCellId.split("-")
|
const [rowId, column] = $selectedCellId.split("-")
|
||||||
rows.actions.updateRow(rowId, column, null)
|
rows.actions.updateRow(rowId, column, null)
|
||||||
}
|
}, 100)
|
||||||
|
|
||||||
const focusSelectedCell = () => {
|
const focusSelectedCell = () => {
|
||||||
$selectedCellAPI?.focus?.()
|
$selectedCellAPI?.focus?.()
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { Dropzone, notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
export let selected = false
|
||||||
|
export let onChange
|
||||||
|
export let readonly = false
|
||||||
|
export let api
|
||||||
|
|
||||||
|
const { API } = getContext("sheet")
|
||||||
|
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
||||||
|
|
||||||
|
let isOpen = false
|
||||||
|
|
||||||
|
$: editable = selected && !readonly
|
||||||
|
$: {
|
||||||
|
if (!selected) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onKeyDown = () => {
|
||||||
|
return isOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
isOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
isOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const isImage = extension => {
|
||||||
|
return imageExtensions.includes(extension?.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileTooLarge = fileSizeLimit => {
|
||||||
|
notifications.error(
|
||||||
|
`Files cannot exceed ${
|
||||||
|
fileSizeLimit / 1000000
|
||||||
|
}MB. Please try again with smaller files.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const processFiles = async fileList => {
|
||||||
|
let data = new FormData()
|
||||||
|
for (let i = 0; i < fileList.length; i++) {
|
||||||
|
data.append("file", fileList[i])
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await API.uploadBuilderAttachment(data)
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Failed to upload attachment")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAttachments = async fileList => {
|
||||||
|
try {
|
||||||
|
return await API.deleteBuilderAttachments(fileList)
|
||||||
|
} catch (error) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
api = {
|
||||||
|
focus: () => open(),
|
||||||
|
blur: () => close(),
|
||||||
|
onKeyDown,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="attachment-cell" class:editable on:click={editable ? open : null}>
|
||||||
|
{#each value || [] as attachment}
|
||||||
|
{#if isImage(attachment.extension)}
|
||||||
|
<img src={attachment.url} alt={attachment.extension} />
|
||||||
|
{:else}
|
||||||
|
<div class="file" title={attachment.name}>
|
||||||
|
{attachment.extension}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if isOpen}
|
||||||
|
<div class="dropzone">
|
||||||
|
<Dropzone
|
||||||
|
{value}
|
||||||
|
compact
|
||||||
|
on:change={e => onChange(e.detail)}
|
||||||
|
{processFiles}
|
||||||
|
{deleteAttachments}
|
||||||
|
{handleFileTooLarge}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.attachment-cell {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 var(--cell-padding);
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: var(--cell-spacing);
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
.attachment-cell.editable:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.file {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
height: calc(var(--cell-height) - 12px);
|
||||||
|
padding: 0 8px;
|
||||||
|
color: var(--spectrum-global-color-gray-800);
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
height: calc(var(--cell-height) - 12px);
|
||||||
|
max-width: 64px;
|
||||||
|
}
|
||||||
|
.dropzone {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
width: 320px;
|
||||||
|
background: var(--cell-background);
|
||||||
|
border: var(--cell-border);
|
||||||
|
box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.15);
|
||||||
|
padding: var(--cell-padding);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1 +0,0 @@
|
||||||
<div>[MISSING]</div>
|
|
|
@ -4,11 +4,11 @@ import MultiSelectCell from "./cells/MultiSelectCell.svelte"
|
||||||
import NumberCell from "./cells/NumberCell.svelte"
|
import NumberCell from "./cells/NumberCell.svelte"
|
||||||
import RelationshipCell from "./cells/RelationshipCell.svelte"
|
import RelationshipCell from "./cells/RelationshipCell.svelte"
|
||||||
import TextCell from "./cells/TextCell.svelte"
|
import TextCell from "./cells/TextCell.svelte"
|
||||||
import BlankCell from "./cells/BlankCell.svelte"
|
|
||||||
import LongFormCell from "./cells/LongFormCell.svelte"
|
import LongFormCell from "./cells/LongFormCell.svelte"
|
||||||
import BooleanCell from "./cells/BooleanCell.svelte"
|
import BooleanCell from "./cells/BooleanCell.svelte"
|
||||||
import FormulaCell from "./cells/FormulaCell.svelte"
|
import FormulaCell from "./cells/FormulaCell.svelte"
|
||||||
import JSONCell from "./cells/JSONCell.svelte"
|
import JSONCell from "./cells/JSONCell.svelte"
|
||||||
|
import AttachmentCell from "./cells/AttachmentCell.svelte"
|
||||||
|
|
||||||
const TypeComponentMap = {
|
const TypeComponentMap = {
|
||||||
text: TextCell,
|
text: TextCell,
|
||||||
|
@ -19,7 +19,7 @@ const TypeComponentMap = {
|
||||||
array: MultiSelectCell,
|
array: MultiSelectCell,
|
||||||
number: NumberCell,
|
number: NumberCell,
|
||||||
boolean: BooleanCell,
|
boolean: BooleanCell,
|
||||||
attachment: BlankCell,
|
attachment: AttachmentCell,
|
||||||
link: RelationshipCell,
|
link: RelationshipCell,
|
||||||
formula: FormulaCell,
|
formula: FormulaCell,
|
||||||
json: JSONCell,
|
json: JSONCell,
|
||||||
|
|
|
@ -9,11 +9,9 @@ export const createMaxScrollStores = context => {
|
||||||
cellHeight,
|
cellHeight,
|
||||||
scroll,
|
scroll,
|
||||||
selectedCellRow,
|
selectedCellRow,
|
||||||
scrolledRowCount,
|
|
||||||
visualRowCapacity,
|
|
||||||
selectedCellId,
|
selectedCellId,
|
||||||
} = context
|
} = context
|
||||||
const padding = 180
|
const padding = 250
|
||||||
|
|
||||||
// Memoize store primitives
|
// Memoize store primitives
|
||||||
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
|
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
|
||||||
|
|
Loading…
Reference in New Issue