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