Add attachment cell

This commit is contained in:
Andrew Kingston 2023-03-14 11:53:01 +00:00
parent b60eca9588
commit f290d758ba
7 changed files with 168 additions and 10 deletions

View File

@ -138,7 +138,7 @@
}
</script>
<div class="container">
<div class="container" class:compact>
{#if selectedImage}
{#if gallery}
<div class="gallery">
@ -379,6 +379,10 @@
object-fit: contain;
margin: 20px 30px;
}
.compact .placeholder,
.compact img {
margin: 12px 16px;
}
.title {
display: flex;
flex-direction: row;
@ -447,6 +451,13 @@
.disabled .spectrum-Heading--sizeL {
color: var(--spectrum-alias-text-color-disabled);
}
.compact .spectrum-Dropzone {
padding-top: 8px;
padding-bottom: 8px;
}
.compact .spectrum-IllustratedMessage-description {
margin: 0;
}
.tags {
margin-top: 20px;

View File

@ -16,6 +16,7 @@
export let gallery = true
export let fileTags = []
export let maximum = undefined
export let compact = false
const dispatch = createEventDispatcher()
const onChange = e => {
@ -37,6 +38,7 @@
{gallery}
{fileTags}
{maximum}
{compact}
on:change={onChange}
/>
</Field>

View File

@ -1,5 +1,6 @@
<script>
import { getContext, onMount } from "svelte"
import { debounce } from "../../utils/utils"
const {
rows,
@ -11,15 +12,16 @@
} = getContext("sheet")
const handleKeyDown = e => {
// If nothing selected avoid processing further key presses
if (!$selectedCellId) {
if (e.key === "Tab") {
selectFirstCell()
}
return
}
const api = $selectedCellAPI
// Always intercept certain key presses
const api = $selectedCellAPI
if (e.key === "Escape") {
api?.blur?.()
} 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) {
return
}
const [rowId, column] = $selectedCellId.split("-")
rows.actions.updateRow(rowId, column, null)
}
}, 100)
const focusSelectedCell = () => {
$selectedCellAPI?.focus?.()

View File

@ -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>

View File

@ -1 +0,0 @@
<div>[MISSING]</div>

View File

@ -4,11 +4,11 @@ import MultiSelectCell from "./cells/MultiSelectCell.svelte"
import NumberCell from "./cells/NumberCell.svelte"
import RelationshipCell from "./cells/RelationshipCell.svelte"
import TextCell from "./cells/TextCell.svelte"
import BlankCell from "./cells/BlankCell.svelte"
import LongFormCell from "./cells/LongFormCell.svelte"
import BooleanCell from "./cells/BooleanCell.svelte"
import FormulaCell from "./cells/FormulaCell.svelte"
import JSONCell from "./cells/JSONCell.svelte"
import AttachmentCell from "./cells/AttachmentCell.svelte"
const TypeComponentMap = {
text: TextCell,
@ -19,7 +19,7 @@ const TypeComponentMap = {
array: MultiSelectCell,
number: NumberCell,
boolean: BooleanCell,
attachment: BlankCell,
attachment: AttachmentCell,
link: RelationshipCell,
formula: FormulaCell,
json: JSONCell,

View File

@ -9,11 +9,9 @@ export const createMaxScrollStores = context => {
cellHeight,
scroll,
selectedCellRow,
scrolledRowCount,
visualRowCapacity,
selectedCellId,
} = context
const padding = 180
const padding = 250
// Memoize store primitives
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)