mirror of https://github.com/Budibase/budibase.git
15 changed files with 418 additions and 517 deletions
@ -1,295 +0,0 @@ |
|||
<script> |
|||
import { Heading, Body, Button } from "../" |
|||
import { FILE_TYPES } from "./fileTypes" |
|||
|
|||
const BYTES_IN_KB = 1000 |
|||
const BYTES_IN_MB = 1000000 |
|||
|
|||
export let icons = { |
|||
image: "fas fa-file-image", |
|||
code: "fas fa-file-code", |
|||
file: "fas fa-file", |
|||
fileUpload: "fas fa-upload", |
|||
} |
|||
|
|||
export let files = [] |
|||
export let fileSizeLimit = BYTES_IN_MB * 20 |
|||
export let processFiles |
|||
export let handleFileTooLarge |
|||
|
|||
let selectedImageIdx = 0 |
|||
let fileDragged = false |
|||
// Generate a random ID so that multiple dropzones on the page don't conflict |
|||
let id = Math.random() |
|||
.toString(36) |
|||
.substring(7) |
|||
|
|||
$: selectedImage = files ? files[selectedImageIdx] : null |
|||
|
|||
function determineFileIcon(extension) { |
|||
const ext = extension.toLowerCase() |
|||
|
|||
if (FILE_TYPES.IMAGE.includes(ext)) return icons.image |
|||
if (FILE_TYPES.CODE.includes(ext)) return icons.code |
|||
|
|||
return icons.file |
|||
} |
|||
|
|||
async function processFileList(fileList) { |
|||
if (Array.from(fileList).some(file => file.size >= fileSizeLimit)) { |
|||
handleFileTooLarge(fileSizeLimit, file) |
|||
return |
|||
} |
|||
|
|||
const processedFiles = await processFiles(fileList) |
|||
|
|||
files = [...processedFiles, ...files] |
|||
selectedImageIdx = 0 |
|||
} |
|||
|
|||
async function removeFile() { |
|||
files.splice(selectedImageIdx, 1) |
|||
files = files |
|||
selectedImageIdx = 0 |
|||
} |
|||
|
|||
function navigateLeft() { |
|||
selectedImageIdx -= 1 |
|||
} |
|||
|
|||
function navigateRight() { |
|||
selectedImageIdx += 1 |
|||
} |
|||
|
|||
function handleFile(evt) { |
|||
processFileList(evt.target.files) |
|||
} |
|||
|
|||
function handleDragOver(evt) { |
|||
evt.preventDefault() |
|||
fileDragged = true |
|||
} |
|||
|
|||
function handleDragLeave(evt) { |
|||
evt.preventDefault() |
|||
fileDragged = false |
|||
} |
|||
|
|||
function handleDrop(evt) { |
|||
evt.preventDefault() |
|||
processFileList(evt.dataTransfer.files) |
|||
fileDragged = false |
|||
} |
|||
</script> |
|||
|
|||
<div |
|||
class="dropzone" |
|||
on:dragover={handleDragOver} |
|||
on:dragleave={handleDragLeave} |
|||
on:dragenter={handleDragOver} |
|||
on:drop={handleDrop} |
|||
class:fileDragged> |
|||
{#if selectedImage} |
|||
<ul> |
|||
<li> |
|||
<header> |
|||
<div> |
|||
<i class={determineFileIcon(selectedImage.extension)} /> |
|||
<span class="filename">{selectedImage.name}</span> |
|||
</div> |
|||
<p> |
|||
{#if selectedImage.size <= BYTES_IN_MB} |
|||
{selectedImage.size / BYTES_IN_KB}KB |
|||
{:else}{selectedImage.size / BYTES_IN_MB}MB{/if} |
|||
</p> |
|||
</header> |
|||
<div class="delete-button" on:click={removeFile}> |
|||
<i class="ri-close-circle-fill" /> |
|||
</div> |
|||
{#if selectedImageIdx !== 0} |
|||
<div class="nav left" on:click={navigateLeft}> |
|||
<i class="ri-arrow-left-circle-fill" /> |
|||
</div> |
|||
{/if} |
|||
<img alt="preview" src={selectedImage.url} /> |
|||
{#if selectedImageIdx !== files.length - 1} |
|||
<div class="nav right" on:click={navigateRight}> |
|||
<i class="ri-arrow-right-circle-fill" /> |
|||
</div> |
|||
{/if} |
|||
</li> |
|||
</ul> |
|||
{/if} |
|||
<i class={icons.fileUpload} /> |
|||
<input {id} type="file" multiple on:change={handleFile} {...$$restProps} /> |
|||
<i class="ri-upload-cloud-line" /> |
|||
<p class="drop">Drop your files here</p> |
|||
<label for={id}>Select a file from your computer</label> |
|||
</div> |
|||
|
|||
<style> |
|||
.dropzone { |
|||
padding: var(--spacing-l); |
|||
border: 2px dashed var(--grey-4); |
|||
text-align: center; |
|||
display: flex; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
border-radius: 10px; |
|||
transition: all 0.3s; |
|||
} |
|||
|
|||
.fileDragged { |
|||
border: 2px dashed var(--grey-7); |
|||
background: var(--blue-light); |
|||
} |
|||
|
|||
input[type="file"] { |
|||
display: none; |
|||
} |
|||
|
|||
label { |
|||
font-family: var(--font-sans); |
|||
font-size: var(--font-size-s); |
|||
cursor: pointer; |
|||
overflow: hidden; |
|||
color: var(--grey-7); |
|||
text-rendering: optimizeLegibility; |
|||
min-width: auto; |
|||
outline: none; |
|||
font-feature-settings: "case" 1, "rlig" 1, "calt" 0; |
|||
-webkit-box-align: center; |
|||
flex-shrink: 0; |
|||
align-items: center; |
|||
justify-content: center; |
|||
width: 100%; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.drop { |
|||
font-family: var(--font-sans); |
|||
font-size: var(--font-size-s); |
|||
margin: 12px 0; |
|||
} |
|||
|
|||
div.nav { |
|||
padding: var(--spacing-xs); |
|||
position: absolute; |
|||
display: flex; |
|||
align-items: center; |
|||
bottom: var(--spacing-s); |
|||
border-radius: 5px; |
|||
transition: 0.2s transform; |
|||
} |
|||
|
|||
.nav:hover { |
|||
cursor: pointer; |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.left { |
|||
left: var(--spacing-s); |
|||
} |
|||
|
|||
.right { |
|||
right: var(--spacing-s); |
|||
} |
|||
|
|||
li { |
|||
position: relative; |
|||
height: 300px; |
|||
background: var(--grey-7); |
|||
display: flex; |
|||
justify-content: center; |
|||
border-radius: 10px; |
|||
} |
|||
|
|||
img { |
|||
border-radius: 10px; |
|||
width: 100%; |
|||
box-shadow: 0 var(--spacing-s) 12px rgba(0, 0, 0, 0.15); |
|||
object-fit: contain; |
|||
} |
|||
|
|||
i { |
|||
font-size: 2rem; |
|||
color: var(--ink); |
|||
} |
|||
|
|||
i:hover { |
|||
cursor: pointer; |
|||
color: var(--background); |
|||
} |
|||
|
|||
.file-icon { |
|||
color: var(--background); |
|||
font-size: 2em; |
|||
margin-right: var(--spacing-s); |
|||
} |
|||
|
|||
ul { |
|||
padding: 0; |
|||
display: grid; |
|||
grid-gap: var(--spacing-s); |
|||
list-style-type: none; |
|||
width: 100%; |
|||
} |
|||
|
|||
header { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
position: absolute; |
|||
background: linear-gradient( |
|||
180deg, |
|||
rgb(255, 255, 255), |
|||
rgba(255, 255, 255, 0) |
|||
); |
|||
width: 100%; |
|||
border-top-left-radius: 10px; |
|||
border-top-right-radius: 10px; |
|||
height: 60px; |
|||
} |
|||
|
|||
header > div { |
|||
color: var(--ink); |
|||
display: flex; |
|||
align-items: center; |
|||
font-size: 12px; |
|||
margin-left: var(--spacing-m); |
|||
width: 60%; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
.filename { |
|||
overflow: hidden; |
|||
margin-left: 5px; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
header > p { |
|||
color: var(--grey-5); |
|||
margin-right: var(--spacing-m); |
|||
} |
|||
|
|||
.delete-button { |
|||
position: absolute; |
|||
top: var(--spacing-s); |
|||
right: var(--spacing-s); |
|||
padding: var(--spacing-s); |
|||
border-radius: 10px; |
|||
transition: all 0.3s; |
|||
} |
|||
|
|||
.delete-button i { |
|||
font-size: 2em; |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.delete-button:hover { |
|||
cursor: pointer; |
|||
color: var(--red); |
|||
} |
|||
</style> |
|||
@ -1,17 +0,0 @@ |
|||
<script> |
|||
import { View } from "svench"; |
|||
import Dropzone from "./Dropzone.svelte"; |
|||
|
|||
async function processFiles(files) { |
|||
console.log("Processing", files); |
|||
return files; |
|||
} |
|||
|
|||
function handleFileTooLarge() { |
|||
alert("File too large."); |
|||
} |
|||
</script> |
|||
|
|||
<View name="dropzone"> |
|||
<Dropzone {processFiles} {handleFileTooLarge} /> |
|||
</View> |
|||
@ -1,5 +0,0 @@ |
|||
export const FILE_TYPES = { |
|||
IMAGE: ["png", "tiff", "gif", "raw", "jpg", "jpeg", "svg"], |
|||
CODE: ["js", "rs", "py", "java", "rb", "hs", "yml"], |
|||
DOCUMENT: ["odf", "docx", "doc", "pdf", "csv"], |
|||
} |
|||
@ -0,0 +1,334 @@ |
|||
<script> |
|||
import "@spectrum-css/dropzone/dist/index-vars.css" |
|||
import "@spectrum-css/typography/dist/index-vars.css" |
|||
import "@spectrum-css/illustratedmessage/dist/index-vars.css" |
|||
import { createEventDispatcher } from "svelte" |
|||
import { generateID } from "../../utils/helpers" |
|||
import Icon from "../../Icon/Icon.svelte" |
|||
|
|||
const BYTES_IN_KB = 1000 |
|||
const BYTES_IN_MB = 1000000 |
|||
|
|||
export let value = [] |
|||
export let id = null |
|||
export let disabled = false |
|||
export let fileSizeLimit = BYTES_IN_MB * 20 |
|||
export let processFiles = null |
|||
export let handleFileTooLarge = null |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
const imageExtensions = [ |
|||
"png", |
|||
"tiff", |
|||
"gif", |
|||
"raw", |
|||
"jpg", |
|||
"jpeg", |
|||
"svg", |
|||
"bmp", |
|||
"jfif", |
|||
] |
|||
const onChange = event => { |
|||
dispatch("change", event.target.checked) |
|||
} |
|||
|
|||
const fieldId = id || generateID() |
|||
let selectedImageIdx = 0 |
|||
let fileDragged = false |
|||
$: selectedImage = value?.[selectedImageIdx] ?? null |
|||
$: fileCount = value?.length ?? 0 |
|||
$: isImage = imageExtensions.includes(selectedImage?.extension?.toLowerCase()) |
|||
|
|||
async function processFileList(fileList) { |
|||
if ( |
|||
handleFileTooLarge && |
|||
Array.from(fileList).some(file => file.size >= fileSizeLimit) |
|||
) { |
|||
handleFileTooLarge(fileSizeLimit, value) |
|||
return |
|||
} |
|||
if (processFiles) { |
|||
const processedFiles = await processFiles(fileList) |
|||
const newValue = [...value, ...processedFiles] |
|||
dispatch("change", newValue) |
|||
selectedImageIdx = newValue.length - 1 |
|||
} |
|||
} |
|||
|
|||
async function removeFile() { |
|||
dispatch( |
|||
"change", |
|||
value.filter((x, idx) => idx !== selectedImageIdx) |
|||
) |
|||
selectedImageIdx = 0 |
|||
} |
|||
|
|||
function navigateLeft() { |
|||
selectedImageIdx -= 1 |
|||
} |
|||
|
|||
function navigateRight() { |
|||
selectedImageIdx += 1 |
|||
} |
|||
|
|||
function handleFile(evt) { |
|||
processFileList(evt.target.files) |
|||
} |
|||
|
|||
function handleDragOver(evt) { |
|||
evt.preventDefault() |
|||
fileDragged = true |
|||
} |
|||
|
|||
function handleDragLeave(evt) { |
|||
evt.preventDefault() |
|||
fileDragged = false |
|||
} |
|||
|
|||
function handleDrop(evt) { |
|||
evt.preventDefault() |
|||
processFileList(evt.dataTransfer.files) |
|||
fileDragged = false |
|||
} |
|||
</script> |
|||
|
|||
<div class="container"> |
|||
{#if selectedImage} |
|||
<div class="gallery"> |
|||
<div class="title"> |
|||
<div class="filename">{selectedImage.name}</div> |
|||
<div class="filesize"> |
|||
{#if selectedImage.size <= BYTES_IN_MB} |
|||
{`${selectedImage.size / BYTES_IN_KB} KB`} |
|||
{:else}{`${selectedImage.size / BYTES_IN_MB} MB`}{/if} |
|||
</div> |
|||
{#if !disabled} |
|||
<div class="delete-button" on:click={removeFile}> |
|||
<Icon name="Close" /> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
{#if isImage} |
|||
<img alt="preview" src={selectedImage.url} /> |
|||
{:else} |
|||
<div class="placeholder"> |
|||
<div class="extension">{selectedImage.extension}</div> |
|||
<div>Preview not supported</div> |
|||
</div> |
|||
{/if} |
|||
<div |
|||
class="nav left" |
|||
class:visible={selectedImageIdx > 0} |
|||
on:click={navigateLeft}> |
|||
<Icon name="ChevronLeft" /> |
|||
</div> |
|||
<div |
|||
class="nav right" |
|||
class:visible={selectedImageIdx < fileCount - 1} |
|||
on:click={navigateRight}> |
|||
<Icon name="ChevronRight" /> |
|||
</div> |
|||
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div> |
|||
</div> |
|||
{/if} |
|||
<div |
|||
class="spectrum-Dropzone" |
|||
class:disabled |
|||
role="region" |
|||
tabindex="0" |
|||
on:dragover={handleDragOver} |
|||
on:dragleave={handleDragLeave} |
|||
on:dragenter={handleDragOver} |
|||
on:drop={handleDrop} |
|||
class:is-dragged={fileDragged}> |
|||
<div class="spectrum-IllustratedMessage spectrum-IllustratedMessage--cta"> |
|||
<input |
|||
id={fieldId} |
|||
{disabled} |
|||
type="file" |
|||
multiple |
|||
on:change={handleFile} /> |
|||
<svg |
|||
class="spectrum-IllustratedMessage-illustration" |
|||
width="125" |
|||
height="60" |
|||
viewBox="0 0 199 97.7"><defs> |
|||
<style> |
|||
.cls-1, |
|||
.cls-2 { |
|||
fill: none; |
|||
stroke-linecap: round; |
|||
stroke-linejoin: round; |
|||
} |
|||
.cls-1 { |
|||
stroke-width: 3px; |
|||
} |
|||
.cls-2 { |
|||
stroke-width: 2px; |
|||
} |
|||
</style> |
|||
</defs> |
|||
<path |
|||
class="cls-1" |
|||
d="M110.53,85.66,100.26,95.89a1.09,1.09,0,0,1-1.52,0L88.47,85.66" /> |
|||
<line class="cls-1" x1="99.5" y1="95.5" x2="99.5" y2="58.5" /> |
|||
<path class="cls-1" d="M105.5,73.5h19a2,2,0,0,0,2-2v-43" /> |
|||
<path |
|||
class="cls-1" |
|||
d="M126.5,22.5h-19a2,2,0,0,1-2-2V1.5h-31a2,2,0,0,0-2,2v68a2,2,0,0,0,2,2h19" /> |
|||
<line class="cls-1" x1="105.5" y1="1.5" x2="126.5" y2="22.5" /> |
|||
<path |
|||
class="cls-2" |
|||
d="M47.93,50.49a5,5,0,1,0-4.83-5A4.93,4.93,0,0,0,47.93,50.49Z" /> |
|||
<path |
|||
class="cls-2" |
|||
d="M36.6,65.93,42.05,60A2.06,2.06,0,0,1,45,60l12.68,13.2" /> |
|||
<path |
|||
class="cls-2" |
|||
d="M3.14,73.23,22.42,53.76a1.65,1.65,0,0,1,2.38,0l19.05,19.7" /> |
|||
<path |
|||
class="cls-1" |
|||
d="M139.5,36.5H196A1.49,1.49,0,0,1,197.5,38V72A1.49,1.49,0,0,1,196,73.5H141A1.49,1.49,0,0,1,139.5,72V32A1.49,1.49,0,0,1,141,30.5H154a2.43,2.43,0,0,1,1.67.66l6,5.66" /> |
|||
<rect |
|||
class="cls-1" |
|||
x="1.5" |
|||
y="34.5" |
|||
width="58" |
|||
height="39" |
|||
rx="2" |
|||
ry="2" /> |
|||
</svg> |
|||
<h2 |
|||
class="spectrum-Heading spectrum-Heading--sizeL spectrum-Heading--light spectrum-IllustratedMessage-heading"> |
|||
Drag and drop your file |
|||
</h2> |
|||
{#if !disabled} |
|||
<p |
|||
class="spectrum-Body spectrum-Body--sizeS spectrum-IllustratedMessage-description"> |
|||
<label for={fieldId} class="spectrum-Link">Select a file to upload</label> |
|||
<br /> |
|||
from your computer |
|||
</p> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.container { |
|||
--spectrum-dropzone-padding: var(--spectrum-global-dimension-size-400); |
|||
--spectrum-heading-l-text-size: var( |
|||
--spectrum-global-dimension-font-size-400 |
|||
); |
|||
} |
|||
.container * { |
|||
font-family: "Inter", sans-serif !important; |
|||
} |
|||
|
|||
.gallery, |
|||
.spectrum-Dropzone { |
|||
user-select: none; |
|||
} |
|||
input[type="file"] { |
|||
display: none; |
|||
} |
|||
|
|||
.gallery { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: flex-start; |
|||
align-items: stretch; |
|||
background-color: var(--spectrum-global-color-gray-50); |
|||
color: var(--spectrum-alias-text-color); |
|||
font-size: var(--spectrum-alias-item-text-size-m); |
|||
box-sizing: border-box; |
|||
border: var(--spectrum-alias-border-size-thin) |
|||
var(--spectrum-alias-border-color) solid; |
|||
border-radius: var(--spectrum-alias-border-radius-regular); |
|||
padding: 10px; |
|||
margin-bottom: 10px; |
|||
position: relative; |
|||
} |
|||
.placeholder, |
|||
img { |
|||
height: 120px; |
|||
max-width: 100%; |
|||
object-fit: contain; |
|||
margin: 20px 30px; |
|||
} |
|||
.title { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: flex-start; |
|||
align-items: stretch; |
|||
} |
|||
.filename { |
|||
flex: 1 1 auto; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
width: 0; |
|||
margin-right: 10px; |
|||
} |
|||
.placeholder { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.extension { |
|||
color: var(--spectrum-global-color-gray-600); |
|||
text-transform: uppercase; |
|||
font-weight: 500; |
|||
margin-bottom: 5px; |
|||
} |
|||
|
|||
.nav { |
|||
padding: var(--spacing-xs); |
|||
position: absolute; |
|||
top: 50%; |
|||
border-radius: 5px; |
|||
display: none; |
|||
transition: all 0.3s; |
|||
} |
|||
.nav.visible { |
|||
display: block; |
|||
} |
|||
.nav:hover { |
|||
cursor: pointer; |
|||
color: var(--blue); |
|||
} |
|||
.left { |
|||
left: 5px; |
|||
} |
|||
.right { |
|||
right: 5px; |
|||
} |
|||
i { |
|||
font-size: 2rem; |
|||
color: var(--ink); |
|||
} |
|||
i:hover { |
|||
cursor: pointer; |
|||
color: var(--background); |
|||
} |
|||
.delete-button { |
|||
transition: all 0.3s; |
|||
margin-left: 10px; |
|||
} |
|||
.delete-button i { |
|||
font-size: 2em; |
|||
} |
|||
.delete-button:hover { |
|||
cursor: pointer; |
|||
color: var(--red); |
|||
} |
|||
|
|||
.spectrum-Dropzone.disabled { |
|||
pointer-events: none; |
|||
background-color: var(--spectrum-global-color-gray-200); |
|||
} |
|||
.disabled .spectrum-Heading--sizeL { |
|||
color: var(--spectrum-alias-text-color-disabled); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,31 @@ |
|||
<script> |
|||
import Field from "./Field.svelte" |
|||
import CoreDropzone from "./Core/Dropzone.svelte" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
export let value = [] |
|||
export let label = null |
|||
export let labelPosition = "above" |
|||
export let disabled = false |
|||
export let error = null |
|||
export let fileSizeLimit = undefined |
|||
export let processFiles = undefined |
|||
export let handleFileTooLarge = undefined |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
const onChange = e => { |
|||
value = e.detail |
|||
dispatch("change", e.detail) |
|||
} |
|||
</script> |
|||
|
|||
<Field {label} {labelPosition} {disabled} {error}> |
|||
<CoreDropzone |
|||
{error} |
|||
{disabled} |
|||
{value} |
|||
{fileSizeLimit} |
|||
{processFiles} |
|||
{handleFileTooLarge} |
|||
on:change={onChange} /> |
|||
</Field> |
|||
@ -1,127 +0,0 @@ |
|||
<script> |
|||
import { Modal, ModalContent } from "@budibase/bbui" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
import { FILE_TYPES } from "./fileTypes" |
|||
|
|||
export let files = [] |
|||
export let height = "70" |
|||
export let width = "70" |
|||
|
|||
let modal |
|||
let currentFile |
|||
|
|||
const openModal = file => { |
|||
currentFile = file |
|||
modal.show() |
|||
} |
|||
|
|||
const handleConfirm = () => { |
|||
dispatch("delete", currentFile) |
|||
} |
|||
</script> |
|||
|
|||
<div class="file-list"> |
|||
{#each files as file} |
|||
<div class="file-container"> |
|||
<a href={file.url} target="_blank" class="file"> |
|||
{#if FILE_TYPES.IMAGE.includes(file.extension.toLowerCase())} |
|||
<img {width} {height} src={file.url} alt="preview of {file.name}" /> |
|||
{:else}<i class="far fa-file" />{/if} |
|||
</a> |
|||
<span>{file.name}</span> |
|||
<div class="button-placement"> |
|||
<button primary on:click|stopPropagation={() => openModal(file)}> |
|||
× |
|||
</button> |
|||
</div> |
|||
</div> |
|||
{/each} |
|||
</div> |
|||
<Modal bind:this={modal}> |
|||
<ModalContent |
|||
title="Confirm File Deletion" |
|||
confirmText="Delete" |
|||
onConfirm={handleConfirm}> |
|||
<span>Are you sure you want to delete this attachment?</span> |
|||
</ModalContent> |
|||
</Modal> |
|||
|
|||
<style> |
|||
.file-list { |
|||
display: grid; |
|||
justify-content: start; |
|||
grid-auto-flow: column; |
|||
grid-gap: var(--spacing-m); |
|||
grid-template-columns: repeat(auto-fill, 1fr); |
|||
} |
|||
|
|||
img { |
|||
object-fit: contain; |
|||
} |
|||
|
|||
i { |
|||
margin-bottom: var(--spacing-m); |
|||
} |
|||
|
|||
a { |
|||
color: var(--ink); |
|||
text-decoration: none; |
|||
} |
|||
|
|||
.file-container { |
|||
position: relative; |
|||
} |
|||
|
|||
button { |
|||
display: block; |
|||
box-sizing: border-box; |
|||
position: absolute; |
|||
font-size: var(--font-size-m); |
|||
line-height: 110%; |
|||
z-index: 1000; |
|||
top: 4px; |
|||
left: 50px; |
|||
margin: 0; |
|||
padding: 0; |
|||
width: 1.3rem; |
|||
height: 1.3rem; |
|||
border: 0; |
|||
color: white; |
|||
border-radius: var(--border-radius-xl); |
|||
background: black; |
|||
transition: transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1), |
|||
background 0.2s cubic-bezier(0.25, 0.1, 0.25, 1); |
|||
-webkit-appearance: none; |
|||
outline: none; |
|||
} |
|||
button:hover { |
|||
background-color: var(--grey-8); |
|||
cursor: pointer; |
|||
} |
|||
button:active { |
|||
background-color: var(--grey-9); |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.file { |
|||
position: relative; |
|||
height: 75px; |
|||
width: 75px; |
|||
border: 2px dashed var(--grey-7); |
|||
padding: var(--spacing-xs); |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
span { |
|||
width: 200px; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
</style> |
|||
@ -1,26 +0,0 @@ |
|||
<script> |
|||
import { Dropzone } from "@budibase/bbui" |
|||
import { getContext } from "svelte" |
|||
|
|||
const { API } = getContext("sdk") |
|||
const BYTES_IN_MB = 1000000 |
|||
|
|||
export let files = [] |
|||
|
|||
function handleFileTooLarge(fileSizeLimit) { |
|||
alert( |
|||
`Files cannot exceed ${fileSizeLimit / |
|||
BYTES_IN_MB}MB. Please try again with smaller files.` |
|||
) |
|||
} |
|||
|
|||
async function processFiles(fileList) { |
|||
let data = new FormData() |
|||
for (let i = 0; i < fileList.length; i++) { |
|||
data.append("file", fileList[i]) |
|||
} |
|||
return await API.uploadAttachment(data) |
|||
} |
|||
</script> |
|||
|
|||
<Dropzone bind:files {processFiles} {handleFileTooLarge} /> |
|||
@ -1,5 +0,0 @@ |
|||
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"], |
|||
} |
|||
Loading…
Reference in new issue