Add attachment cell
This commit is contained in:
parent
b60eca9588
commit
f290d758ba
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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?.()
|
||||
|
|
|
@ -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 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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue