Merge branch 'master' of github.com:Budibase/budibase into screen-updates
This commit is contained in:
commit
3de238c66a
|
@ -10,7 +10,7 @@
|
|||
"eslint-plugin-svelte3": "^2.7.3",
|
||||
"lerna": "3.14.1",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier-plugin-svelte": "^0.7.0",
|
||||
"prettier-plugin-svelte": "^1.4.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup-plugin-replace": "^2.2.0",
|
||||
"svelte": "^3.28.0"
|
||||
|
|
|
@ -27,7 +27,6 @@ context("Create a Table", () => {
|
|||
cy.get(".actions input")
|
||||
.first()
|
||||
.type("updated")
|
||||
cy.get("select").select("Text")
|
||||
cy.contains("Save Column").click()
|
||||
cy.contains("nameupdated").should("have.text", "nameupdated")
|
||||
})
|
||||
|
|
|
@ -53,6 +53,7 @@ export const getStore = () => {
|
|||
|
||||
store.saveScreen = saveScreen(store)
|
||||
store.setCurrentScreen = setCurrentScreen(store)
|
||||
store.deleteScreens = deleteScreens(store)
|
||||
store.setCurrentPage = setCurrentPage(store)
|
||||
store.createScreen = createScreen(store)
|
||||
store.addStylesheet = addStylesheet(store)
|
||||
|
@ -185,6 +186,26 @@ const setCurrentScreen = store => screenName => {
|
|||
})
|
||||
}
|
||||
|
||||
const deleteScreens = store => (screens, pageName = null) => {
|
||||
if (!(screens instanceof Array)) {
|
||||
screens = [screens]
|
||||
}
|
||||
store.update(state => {
|
||||
if (pageName == null) {
|
||||
pageName = state.pages.main.name
|
||||
}
|
||||
for (let screen of screens) {
|
||||
state.screens = state.screens.filter(c => c.name !== screen.name)
|
||||
// Remove screen from current page as well
|
||||
state.pages[pageName]._screens = state.pages[pageName]._screens.filter(
|
||||
scr => scr.name !== screen.name
|
||||
)
|
||||
api.delete(`/_builder/api/pages/${pageName}/screens/${screen.name}`)
|
||||
}
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const savePage = store => async page => {
|
||||
store.update(state => {
|
||||
if (state.currentFrontEndType !== "page" || !state.currentPageName) {
|
||||
|
|
|
@ -23,9 +23,7 @@
|
|||
class="automation-block hoverable"
|
||||
on:click={addBlockToAutomation}
|
||||
data-cy={stepId}>
|
||||
<div>
|
||||
<i class={blockDefinition.icon} />
|
||||
</div>
|
||||
<div><i class={blockDefinition.icon} /></div>
|
||||
<div class="automation-text">
|
||||
<h4>{blockDefinition.name}</h4>
|
||||
<p>{blockDefinition.description}</p>
|
||||
|
|
|
@ -62,10 +62,7 @@
|
|||
{#if $automationStore.selectedBlock}
|
||||
<AutomationBlockSetup bind:block={$automationStore.selectedBlock} />
|
||||
{:else if $automationStore.selectedAutomation}
|
||||
<div class="block-label">
|
||||
Automation
|
||||
<b>{automation.name}</b>
|
||||
</div>
|
||||
<div class="block-label">Automation <b>{automation.name}</b></div>
|
||||
<Button secondary wide on:click={testAutomation}>Test Automation</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -108,7 +108,8 @@
|
|||
<div
|
||||
class:link={row[header] && row[header].length}
|
||||
on:click={() => selectRelationship(row, header)}>
|
||||
{row[header] ? row[header].length : 0} related row(s)
|
||||
{row[header] ? row[header].length : 0}
|
||||
related row(s)
|
||||
</div>
|
||||
{:else if schema[header].type === 'attachment'}
|
||||
<AttachmentList files={row[header] || []} />
|
||||
|
|
|
@ -49,9 +49,7 @@
|
|||
<button class:selected={currentPage === 0} on:click={() => selectPage(0)}>
|
||||
1
|
||||
</button>
|
||||
{#if currentPage > 3}
|
||||
<button disabled>...</button>
|
||||
{/if}
|
||||
{#if currentPage > 3}<button disabled>...</button>{/if}
|
||||
{#each pagesAroundCurrent as idx}
|
||||
<button
|
||||
class:selected={idx === currentPage}
|
||||
|
@ -59,9 +57,7 @@
|
|||
{idx + 1}
|
||||
</button>
|
||||
{/each}
|
||||
{#if currentPage < numPages - 4}
|
||||
<button disabled>...</button>
|
||||
{/if}
|
||||
{#if currentPage < numPages - 4}<button disabled>...</button>{/if}
|
||||
<button
|
||||
class:selected={currentPage === numPages - 1}
|
||||
on:click={() => selectPage(numPages - 1)}>
|
||||
|
@ -77,8 +73,13 @@
|
|||
|
||||
<p>
|
||||
{#if numPages > 1}
|
||||
Showing {ITEMS_PER_PAGE * currentPage + 1} - {ITEMS_PER_PAGE * currentPage + pageItemCount}
|
||||
of {data.length} rows
|
||||
Showing
|
||||
{ITEMS_PER_PAGE * currentPage + 1}
|
||||
-
|
||||
{ITEMS_PER_PAGE * currentPage + pageItemCount}
|
||||
of
|
||||
{data.length}
|
||||
rows
|
||||
{:else if numPages === 1}Showing all {data.length} row(s){/if}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -65,16 +65,18 @@
|
|||
<div class="actions">
|
||||
<Input label="Name" thin bind:value={field.name} />
|
||||
|
||||
<Select
|
||||
secondary
|
||||
thin
|
||||
label="Type"
|
||||
on:change={handleFieldConstraints}
|
||||
bind:value={field.type}>
|
||||
{#each Object.values(fieldDefinitions) as field}
|
||||
<option value={field.type}>{field.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{#if !originalName}
|
||||
<Select
|
||||
secondary
|
||||
thin
|
||||
label="Type"
|
||||
on:change={handleFieldConstraints}
|
||||
bind:value={field.type}>
|
||||
{#each Object.values(fieldDefinitions) as field}
|
||||
<option value={field.type}>{field.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{/if}
|
||||
|
||||
{#if field.type !== 'link'}
|
||||
<Toggle
|
||||
|
|
|
@ -20,10 +20,21 @@
|
|||
let modal
|
||||
let name
|
||||
let dataImport
|
||||
let error = ""
|
||||
|
||||
function resetState() {
|
||||
name = ""
|
||||
dataImport = undefined
|
||||
error = ""
|
||||
}
|
||||
|
||||
function checkValid(evt) {
|
||||
const tableName = evt.target.value
|
||||
if ($backendUiStore.models?.some(model => model.name === tableName)) {
|
||||
error = `Table with name ${tableName} already exists. Please choose another name.`
|
||||
return
|
||||
}
|
||||
error = ""
|
||||
}
|
||||
|
||||
async function saveTable() {
|
||||
|
@ -61,12 +72,14 @@
|
|||
title="Create Table"
|
||||
confirmText="Create"
|
||||
onConfirm={saveTable}
|
||||
disabled={!name || (dataImport && !dataImport.valid)}>
|
||||
disabled={error || !name || (dataImport && !dataImport.valid)}>
|
||||
<Input
|
||||
data-cy="table-name-input"
|
||||
thin
|
||||
label="Table Name"
|
||||
bind:value={name} />
|
||||
on:input={checkValid}
|
||||
bind:value={name}
|
||||
{error} />
|
||||
<div>
|
||||
<Label grey extraSmall>Create Table from CSV (Optional)</Label>
|
||||
<TableDataImport bind:dataImport />
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<script>
|
||||
import { backendUiStore } from "builderStore"
|
||||
import { backendUiStore, store } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
|
||||
import { FIELDS } from "constants/backend"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import screenTemplates from "builderStore/store/screenTemplates"
|
||||
import api from "builderStore/api"
|
||||
|
||||
export let table
|
||||
|
||||
|
@ -11,6 +13,10 @@
|
|||
let dropdown
|
||||
let editing
|
||||
let confirmDeleteDialog
|
||||
let error = ""
|
||||
let originalName = table.name
|
||||
let templateScreens
|
||||
let willBeDeleted
|
||||
|
||||
$: fields = Object.keys(table.schema)
|
||||
|
||||
|
@ -24,12 +30,18 @@
|
|||
}
|
||||
|
||||
function showModal() {
|
||||
const screens = $store.allScreens
|
||||
templateScreens = screens.filter(screen => screen.props.table === table._id)
|
||||
willBeDeleted = ["All table data"].concat(
|
||||
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
||||
)
|
||||
hideEditor()
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
async function deleteTable() {
|
||||
await backendUiStore.actions.tables.delete(table)
|
||||
store.deleteScreens(templateScreens)
|
||||
notifier.success("Table deleted")
|
||||
hideEditor()
|
||||
}
|
||||
|
@ -39,6 +51,18 @@
|
|||
notifier.success("Table renamed successfully")
|
||||
hideEditor()
|
||||
}
|
||||
|
||||
function checkValid(evt) {
|
||||
const tableName = evt.target.value
|
||||
if (
|
||||
originalName !== tableName &&
|
||||
$backendUiStore.models?.some(model => model.name === tableName)
|
||||
) {
|
||||
error = `Table with name ${tableName} already exists. Please choose another name.`
|
||||
return
|
||||
}
|
||||
error = ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={anchor} class="icon" on:click={dropdown.show}>
|
||||
|
@ -48,7 +72,12 @@
|
|||
{#if editing}
|
||||
<div class="actions">
|
||||
<h5>Edit Table</h5>
|
||||
<Input label="Table Name" thin bind:value={table.name} />
|
||||
<Input
|
||||
label="Table Name"
|
||||
thin
|
||||
bind:value={table.name}
|
||||
on:input={checkValid}
|
||||
{error} />
|
||||
<Select
|
||||
label="Primary Display Column"
|
||||
thin
|
||||
|
@ -61,7 +90,7 @@
|
|||
</Select>
|
||||
<footer>
|
||||
<Button secondary on:click={hideEditor}>Cancel</Button>
|
||||
<Button primary on:click={save}>Save</Button>
|
||||
<Button primary disabled={error} on:click={save}>Save</Button>
|
||||
</footer>
|
||||
</div>
|
||||
{:else}
|
||||
|
@ -79,10 +108,21 @@
|
|||
</DropdownMenu>
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
body={`Are you sure you wish to delete the table '${table.name}'? Your data will be deleted and this action cannot be undone.`}
|
||||
okText="Delete Table"
|
||||
onOk={deleteTable}
|
||||
title="Confirm Delete" />
|
||||
title="Confirm Delete">
|
||||
Are you sure you wish to delete the table
|
||||
<i>{table.name}?</i>
|
||||
The following will also be deleted:
|
||||
<b>
|
||||
<div class="delete-items">
|
||||
{#each willBeDeleted as item}
|
||||
<div>{item}</div>
|
||||
{/each}
|
||||
</div>
|
||||
</b>
|
||||
This action cannot be undone.
|
||||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
div.icon {
|
||||
|
@ -96,6 +136,17 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
|
||||
div.delete-items {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
div.delete-items div {
|
||||
margin-top: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: var(--spacing-xl);
|
||||
display: grid;
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
transition: 0.2s ease transform, 0.2s ease background-color,
|
||||
0.2s ease box-shadow;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,10 @@
|
|||
|
||||
<Modal bind:this={modal} on:hide={onCancel}>
|
||||
<ModalContent onConfirm={onOk} {title} confirmText={okText} {cancelText} red>
|
||||
<div class="body">{body}</div>
|
||||
<div class="body">
|
||||
{body}
|
||||
<slot />
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
|
|
|
@ -31,9 +31,7 @@
|
|||
style="background: {themes[notification.type]};"
|
||||
transition:fly={{ y: -30 }}>
|
||||
<div class="content">{notification.message}</div>
|
||||
{#if notification.icon}
|
||||
<i class={notification.icon} />
|
||||
{/if}
|
||||
{#if notification.icon}<i class={notification.icon} />{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -12,15 +12,12 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
||||
<div class="switcher">
|
||||
|
||||
{#each tabs as tab}
|
||||
<button class:selected={selected === tab} on:click={() => selectTab(tab)}>
|
||||
{tab}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
|
@ -34,7 +31,6 @@
|
|||
<slot name="3" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div class="img">
|
||||
<img src="https://picsum.photos/60/60" alt="zoom" />
|
||||
</div>
|
||||
<div class="img"><img src="https://picsum.photos/60/60" alt="zoom" /></div>
|
||||
<div class="body">
|
||||
<div class="title">Zoom</div>
|
||||
<div class="description">
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
||||
<Input
|
||||
thin
|
||||
type="password"
|
||||
bind:value={password}
|
||||
name="Password"
|
||||
placeholder="Password" />
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
<Spacer medium />
|
||||
<div class="card-footer">
|
||||
<TextButton text medium blue href="/_builder/{_id}">
|
||||
Open {name} →
|
||||
Open
|
||||
{name}
|
||||
→
|
||||
</TextButton>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,12 +28,11 @@
|
|||
<Spacer small />
|
||||
<Body medium grey>{template.category}</Body>
|
||||
<Body lh small black>{template.description}</Body>
|
||||
<div>
|
||||
<img src={template.image} width="100%" />
|
||||
</div>
|
||||
<div><img src={template.image} width="100%" /></div>
|
||||
<div class="card-footer">
|
||||
<Button secondary on:click={() => onSelect(template)}>
|
||||
Create {template.name}
|
||||
Create
|
||||
{template.name}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
{category.name}
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -99,9 +99,7 @@
|
|||
</script>
|
||||
|
||||
<div bind:this={anchor} on:click|stopPropagation={() => {}}>
|
||||
<div class="icon" on:click={dropdown.show}>
|
||||
<i class="ri-more-line" />
|
||||
</div>
|
||||
<div class="icon" on:click={dropdown.show}><i class="ri-more-line" /></div>
|
||||
</div>
|
||||
<DropdownMenu
|
||||
bind:this={dropdown}
|
||||
|
|
|
@ -1,21 +1,10 @@
|
|||
<script>
|
||||
import { setContext, onMount } from "svelte"
|
||||
|
||||
import { store } from "builderStore"
|
||||
import {
|
||||
LayoutIcon,
|
||||
PaintIcon,
|
||||
TerminalIcon,
|
||||
CircleIndicator,
|
||||
EventsIcon,
|
||||
} from "components/common/Icons/"
|
||||
import panelStructure from "./temporaryPanelStructure.js"
|
||||
import CategoryTab from "./CategoryTab.svelte"
|
||||
import DesignView from "./DesignView.svelte"
|
||||
import SettingsView from "./SettingsView.svelte"
|
||||
|
||||
let current_view = "design"
|
||||
let codeEditor
|
||||
let flattenedPanel = flattenComponents(panelStructure.categories)
|
||||
let categories = [
|
||||
{ value: "settings", name: "Settings" },
|
||||
|
@ -23,7 +12,6 @@
|
|||
]
|
||||
let selectedCategory = categories[0]
|
||||
|
||||
$: components = $store.components
|
||||
$: componentInstance =
|
||||
$store.currentView !== "component"
|
||||
? { ...$store.currentPreviewItem, ...$store.currentComponentInfo }
|
||||
|
@ -76,7 +64,6 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
||||
<CategoryTab
|
||||
onClick={category => (selectedCategory = category)}
|
||||
{categories}
|
||||
|
@ -99,9 +86,7 @@
|
|||
onScreenPropChange={store.setPageOrScreenProp}
|
||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,33 +1,13 @@
|
|||
<script>
|
||||
import { goto } from "@sveltech/routify"
|
||||
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
||||
import { store } from "builderStore"
|
||||
import components from "./temporaryPanelStructure.js"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import CategoryTab from "./CategoryTab.svelte"
|
||||
import {
|
||||
find,
|
||||
sortBy,
|
||||
groupBy,
|
||||
values,
|
||||
filter,
|
||||
map,
|
||||
uniqBy,
|
||||
flatten,
|
||||
} from "lodash/fp"
|
||||
|
||||
import { pipe } from "components/common/core"
|
||||
|
||||
import Tab from "./ItemTab/Tab.svelte"
|
||||
import { store } from "builderStore"
|
||||
|
||||
export let toggleTab
|
||||
|
||||
let selectTemplateDialog
|
||||
let selectedTemplateInstance
|
||||
let templateInstances = []
|
||||
|
||||
let selectedComponent = null
|
||||
|
||||
const categories = components.categories
|
||||
let selectedCategory = categories[0]
|
||||
|
||||
|
@ -45,7 +25,6 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
||||
<CategoryTab
|
||||
onClick={category => (selectedCategory = category)}
|
||||
{selectedCategory}
|
||||
|
|
|
@ -38,13 +38,11 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
||||
{#each screens as screen}
|
||||
<div
|
||||
class="budibase__nav-item screen-header-row"
|
||||
class:selected={$store.currentComponentInfo._id === screen.props._id}
|
||||
on:click|stopPropagation={() => changeScreen(screen)}>
|
||||
|
||||
<span
|
||||
class="icon"
|
||||
class:rotate={$store.currentPreviewItem.name !== screen.props._instanceName}>
|
||||
|
@ -69,7 +67,6 @@
|
|||
{dragDropStore} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -129,7 +129,6 @@
|
|||
<ul>
|
||||
{#each components as component, index (component._id)}
|
||||
<li on:click|stopPropagation={() => selectComponent(component)}>
|
||||
|
||||
{#if $dragDropStore && $dragDropStore.targetComponent === component && $dragDropStore.dropPosition === 'above'}
|
||||
<div
|
||||
on:drop={drop}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<div class="root">
|
||||
{#if $store.currentFrontEndType === 'page' || $store.screens.length}
|
||||
<div class="switcher">
|
||||
|
||||
<button
|
||||
class:selected={selected === COMPONENT_SELECTION_TAB}
|
||||
on:click={() => selectTab(COMPONENT_SELECTION_TAB)}>
|
||||
|
@ -32,7 +31,6 @@
|
|||
on:click={() => selectTab(PROPERTIES_TAB)}>
|
||||
Edit
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
|
@ -43,10 +41,8 @@
|
|||
{#if selected === COMPONENT_SELECTION_TAB}
|
||||
<ComponentSelectionList {toggleTab} />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
</script>
|
||||
|
||||
<div class="design-view-container">
|
||||
|
||||
<div class="design-view-state-categories">
|
||||
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} />
|
||||
</div>
|
||||
|
|
|
@ -18,9 +18,7 @@
|
|||
</script>
|
||||
|
||||
<div class="handler-option">
|
||||
{#if parameter.name === 'automation'}
|
||||
<span>{parameter.name}</span>
|
||||
{/if}
|
||||
{#if parameter.name === 'automation'}<span>{parameter.name}</span>{/if}
|
||||
{#if parameter.name === 'automation'}
|
||||
<Select on:change bind:value={parameter.value}>
|
||||
<option value="" />
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
{schemaFields}
|
||||
on:fieldschanged={onFieldsChanged} />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -109,7 +109,6 @@
|
|||
{schemaFields}
|
||||
on:fieldschanged={onFieldsChanged} />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -107,7 +107,6 @@
|
|||
{schemaFields}
|
||||
on:fieldschanged={onFieldsChanged} />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -126,9 +126,7 @@
|
|||
on:click={() => switchLetter(letter)}>
|
||||
{letter}
|
||||
</span>
|
||||
{#if idx !== alphabet.length - 1}
|
||||
<span>-</span>
|
||||
{/if}
|
||||
{#if idx !== alphabet.length - 1}<span>-</span>{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="search-input">
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
</script>
|
||||
|
||||
<div data-cy={item.name} class="item-item" on:click>
|
||||
<div class="item-icon">
|
||||
<i class={item.icon} />
|
||||
</div>
|
||||
<div class="item-icon"><i class={item.icon} /></div>
|
||||
<div class="item-text">
|
||||
<div class="item-name">{item.name}</div>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store } from "builderStore"
|
||||
import Item from "./Item.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
import Item from "./Item.svelte"
|
||||
export let list
|
||||
|
||||
let category = list
|
||||
|
@ -21,10 +23,12 @@
|
|||
</script>
|
||||
|
||||
{#if !list.isCategory}
|
||||
<button class="back-button" on:click={() => (list = category)}>Back</button>
|
||||
<button class="back-button" on:click={goBack}>Back</button>
|
||||
{/if}
|
||||
{#each list.children as item}
|
||||
<Item {item} on:click={() => handleClick(item)} />
|
||||
{#if !item.showOnPages || item.showOnPages.includes($store.currentPageName)}
|
||||
<Item {item} on:click={() => handleClick(item)} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -89,7 +89,6 @@
|
|||
</script>
|
||||
|
||||
<ModalContent title="New Screen" confirmText="Create Screen" onConfirm={save}>
|
||||
|
||||
<Select
|
||||
label="Choose a Template"
|
||||
bind:value={templateIndex}
|
||||
|
@ -109,5 +108,4 @@
|
|||
error={routeError}
|
||||
bind:value={route}
|
||||
on:change={routeChanged} />
|
||||
|
||||
</ModalContent>
|
||||
|
|
|
@ -17,26 +17,13 @@
|
|||
}
|
||||
|
||||
const deleteScreen = () => {
|
||||
store.deleteScreens(screen, $store.currentPageName)
|
||||
// update the page if required
|
||||
store.update(state => {
|
||||
// Remove screen from screens
|
||||
const screens = state.screens.filter(c => c.name !== screen.name)
|
||||
state.screens = screens
|
||||
|
||||
// Remove screen from current page as well
|
||||
const pageScreens = state.pages[state.currentPageName]._screens.filter(
|
||||
scr => scr.name !== screen.name
|
||||
)
|
||||
state.pages[state.currentPageName]._screens = pageScreens
|
||||
|
||||
if (state.currentPreviewItem.name === screen.name) {
|
||||
store.setCurrentPage($store.currentPageName)
|
||||
$goto(`./:page/page-layout`)
|
||||
}
|
||||
|
||||
api.delete(
|
||||
`/_builder/api/pages/${state.currentPageName}/screens/${screen.name}`
|
||||
)
|
||||
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1165,14 +1165,6 @@ export default {
|
|||
},
|
||||
children: [],
|
||||
},
|
||||
// {
|
||||
// name: "Map",
|
||||
// _component: "@budibase/standard-components/datamap",
|
||||
// description: "Shiny map",
|
||||
// icon: "ri-map-pin-line",
|
||||
// properties: { design: { ...all } },
|
||||
// children: [],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -1208,6 +1200,7 @@ export default {
|
|||
"A component that automatically generates a login screen for your app.",
|
||||
icon: "ri-login-box-line",
|
||||
children: [],
|
||||
showOnPages: ["unauthenticated"],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
|
|
|
@ -68,7 +68,10 @@
|
|||
<span
|
||||
class:active={false}
|
||||
class="topnavitemright"
|
||||
on:click={() => window.open(`/${application}`)}>
|
||||
on:click={() => {
|
||||
document.cookie = 'budibase:token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'
|
||||
window.open(`/${application}`)
|
||||
}}>
|
||||
<PreviewIcon />
|
||||
</span>
|
||||
</div>
|
||||
|
@ -77,7 +80,7 @@
|
|||
{#await promise}
|
||||
<!-- This should probably be some kind of loading state? -->
|
||||
<div />
|
||||
{:then _}
|
||||
{:then results}
|
||||
<slot />
|
||||
{:catch error}
|
||||
<p>Something went wrong: {error.message}</p>
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
|
||||
{#if $backendUiStore.selectedDatabase._id && selectedTable.name}
|
||||
<TableDataTable />
|
||||
{:else}
|
||||
<i>Create your first table to start building</i>
|
||||
{/if}
|
||||
{:else}<i>Create your first table to start building</i>{/if}
|
||||
|
||||
<style>
|
||||
i {
|
||||
|
|
|
@ -23,9 +23,7 @@
|
|||
|
||||
{#if $backendUiStore.tables.length === 0}
|
||||
<i>Create your first table to start building</i>
|
||||
{:else}
|
||||
<i>Select a table to edit</i>
|
||||
{/if}
|
||||
{:else}<i>Select a table to edit</i>{/if}
|
||||
|
||||
<style>
|
||||
i {
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
|
||||
{#if $backendUiStore.selectedDatabase._id && selectedView}
|
||||
<ViewDataTable view={selectedView} />
|
||||
{:else}
|
||||
<i>Create your first table to start building</i>
|
||||
{/if}
|
||||
{:else}<i>Create your first table to start building</i>{/if}
|
||||
|
||||
<style>
|
||||
i {
|
||||
|
|
|
@ -3,7 +3,7 @@ const ClientDb = require("../../db/clientDb")
|
|||
const { getPackageForBuilder, buildPage } = require("../../utilities/builder")
|
||||
const env = require("../../environment")
|
||||
const instanceController = require("./instance")
|
||||
const { copy, exists, readFile, writeFile } = require("fs-extra")
|
||||
const { copy, existsSync, readFile, writeFile } = require("fs-extra")
|
||||
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
|
||||
const sqrl = require("squirrelly")
|
||||
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
||||
|
@ -116,6 +116,12 @@ exports.delete = async function(ctx) {
|
|||
const db = new CouchDB(ClientDb.name(getClientId(ctx)))
|
||||
const app = await db.get(ctx.params.applicationId)
|
||||
const result = await db.remove(app)
|
||||
for (let instance of app.instances) {
|
||||
const instanceDb = new CouchDB(instance._id)
|
||||
await instanceDb.destroy()
|
||||
}
|
||||
|
||||
// remove top level directory
|
||||
await fs.rmdir(join(budibaseAppsDir(), ctx.params.applicationId), {
|
||||
recursive: true,
|
||||
})
|
||||
|
@ -137,7 +143,7 @@ const createEmptyAppPackage = async (ctx, app) => {
|
|||
const appsFolder = budibaseAppsDir()
|
||||
const newAppFolder = resolve(appsFolder, app._id)
|
||||
|
||||
if (await exists(newAppFolder)) {
|
||||
if (existsSync(newAppFolder)) {
|
||||
ctx.throw(400, "App folder already exists for this application")
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ exports.authenticate = async ctx => {
|
|||
dbUser = await instanceDb.get(generateUserID(username))
|
||||
} catch (_) {
|
||||
// do not want to throw a 404 - as this could be
|
||||
// used to dtermine valid usernames
|
||||
// used to determine valid usernames
|
||||
ctx.throw(401, "Invalid Credentials")
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,12 @@ exports.save = async function(ctx) {
|
|||
let row = ctx.request.body
|
||||
row.tableId = ctx.params.tableId
|
||||
|
||||
if (ctx.request.body.type === "delete") {
|
||||
await bulkDelete(ctx)
|
||||
ctx.body = ctx.request.body.rows
|
||||
return
|
||||
}
|
||||
|
||||
if (!row._rev && !row._id) {
|
||||
row._id = generateRowID(row.tableId)
|
||||
}
|
||||
|
@ -348,3 +354,25 @@ const TYPE_TRANSFORM_MAP = {
|
|||
false: false,
|
||||
},
|
||||
}
|
||||
|
||||
async function bulkDelete(ctx) {
|
||||
const instanceId = ctx.user.instanceId
|
||||
const { rows } = ctx.request.body
|
||||
const db = new CouchDB(instanceId)
|
||||
|
||||
const linkUpdates = rows.map(row =>
|
||||
linkRows.updateLinks({
|
||||
instanceId,
|
||||
eventType: linkRows.EventType.ROW_DELETE,
|
||||
row,
|
||||
tableId: row.tableId,
|
||||
})
|
||||
)
|
||||
|
||||
await db.bulkDocs(rows.map(row => ({ ...row, _deleted: true })))
|
||||
await Promise.all(linkUpdates)
|
||||
|
||||
rows.forEach(row => {
|
||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, instanceId, row)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* This controller is not currently fully implemented. Screens are
|
||||
* currently managed as part of the pages API, please look in api/routes/page.js
|
||||
* for routes and controllers.
|
||||
*/
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
ctx.throw(501)
|
||||
}
|
||||
|
||||
exports.save = async ctx => {
|
||||
ctx.throw(501)
|
||||
}
|
||||
|
||||
exports.destroy = async ctx => {
|
||||
ctx.throw(501)
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
const send = require("koa-send")
|
||||
const { resolve, join } = require("../../utilities/centralPath")
|
||||
const jwt = require("jsonwebtoken")
|
||||
const fetch = require("node-fetch")
|
||||
const fs = require("fs-extra")
|
||||
const uuid = require("uuid")
|
||||
|
@ -13,8 +12,8 @@ const {
|
|||
} = require("../../utilities/budibaseDir")
|
||||
const CouchDB = require("../../db")
|
||||
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
||||
const { ANON_LEVEL_ID } = require("../../utilities/accessLevels")
|
||||
const fileProcessor = require("../../utilities/fileProcessor")
|
||||
const { AuthTypes } = require("../../constants")
|
||||
|
||||
exports.serveBuilder = async function(ctx) {
|
||||
let builderPath = resolve(__dirname, "../../../builder")
|
||||
|
@ -136,7 +135,8 @@ exports.performLocalFileProcessing = async function(ctx) {
|
|||
}
|
||||
|
||||
exports.serveApp = async function(ctx) {
|
||||
const mainOrAuth = ctx.auth.authenticated ? "main" : "unauthenticated"
|
||||
const mainOrAuth =
|
||||
ctx.auth.authenticated === AuthTypes.APP ? "main" : "unauthenticated"
|
||||
|
||||
// default to homedir
|
||||
const appPath = resolve(
|
||||
|
@ -146,26 +146,7 @@ exports.serveApp = async function(ctx) {
|
|||
mainOrAuth
|
||||
)
|
||||
|
||||
let appId = ctx.params.appId
|
||||
if (process.env.CLOUD) {
|
||||
appId = ctx.subdomains[1]
|
||||
}
|
||||
|
||||
// only set the appId cookie for /appId .. we COULD check for valid appIds
|
||||
// but would like to avoid that DB hit
|
||||
const looksLikeAppId = /^(app_)?[0-9a-f]{32}$/.test(appId)
|
||||
if (looksLikeAppId && !ctx.auth.authenticated) {
|
||||
const anonUser = {
|
||||
userId: "ANON",
|
||||
accessLevelId: ANON_LEVEL_ID,
|
||||
appId,
|
||||
}
|
||||
const anonToken = jwt.sign(anonUser, ctx.config.jwtSecret)
|
||||
ctx.cookies.set("budibase:token", anonToken, {
|
||||
path: "/",
|
||||
httpOnly: false,
|
||||
})
|
||||
}
|
||||
const appId = ctx.user.appId
|
||||
|
||||
if (process.env.CLOUD) {
|
||||
const S3_URL = `https://${appId}.app.budi.live/assets/${appId}/${mainOrAuth}/${ctx.file ||
|
||||
|
@ -200,7 +181,8 @@ exports.serveAttachment = async function(ctx) {
|
|||
|
||||
exports.serveAppAsset = async function(ctx) {
|
||||
// default to homedir
|
||||
const mainOrAuth = ctx.auth.authenticated ? "main" : "unauthenticated"
|
||||
const mainOrAuth =
|
||||
ctx.auth.authenticated === AuthTypes.APP ? "main" : "unauthenticated"
|
||||
|
||||
const appPath = resolve(
|
||||
budibaseAppsDir(),
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
const AuthTypes = {
|
||||
APP: "app",
|
||||
BUILDER: "builder",
|
||||
EXTERNAL: "external",
|
||||
}
|
||||
|
||||
exports.AuthTypes = AuthTypes
|
|
@ -7,6 +7,8 @@ const {
|
|||
BUILDER_LEVEL_ID,
|
||||
ANON_LEVEL_ID,
|
||||
} = require("../utilities/accessLevels")
|
||||
const environment = require("../environment")
|
||||
const { AuthTypes } = require("../constants")
|
||||
|
||||
module.exports = async (ctx, next) => {
|
||||
if (ctx.path === "/_builder") {
|
||||
|
@ -17,36 +19,32 @@ module.exports = async (ctx, next) => {
|
|||
const appToken = ctx.cookies.get("budibase:token")
|
||||
const builderToken = ctx.cookies.get("builder:token")
|
||||
|
||||
if (builderToken) {
|
||||
try {
|
||||
const jwtPayload = jwt.verify(builderToken, ctx.config.jwtSecret)
|
||||
ctx.auth = {
|
||||
apiKey: jwtPayload.apiKey,
|
||||
authenticated: jwtPayload.accessLevelId === BUILDER_LEVEL_ID,
|
||||
}
|
||||
ctx.user = {
|
||||
...jwtPayload,
|
||||
accessLevel: await getAccessLevel(
|
||||
jwtPayload.instanceId,
|
||||
jwtPayload.accessLevelId
|
||||
),
|
||||
}
|
||||
} catch (_) {
|
||||
// empty: do nothing
|
||||
}
|
||||
|
||||
await next()
|
||||
return
|
||||
let token
|
||||
// if running locally in the builder itself
|
||||
if (!environment.CLOUD && !appToken) {
|
||||
token = builderToken
|
||||
ctx.auth.authenticated = AuthTypes.BUILDER
|
||||
} else {
|
||||
token = appToken
|
||||
ctx.auth.authenticated = AuthTypes.APP
|
||||
}
|
||||
|
||||
if (!appToken) {
|
||||
if (!token) {
|
||||
ctx.auth.authenticated = false
|
||||
|
||||
const appId = process.env.CLOUD ? ctx.subdomains[1] : ctx.params.appId
|
||||
|
||||
ctx.user = {
|
||||
// if appId can't be determined from path param or subdomain
|
||||
appId: appId || ctx.referer.split("/").pop(),
|
||||
}
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const jwtPayload = jwt.verify(appToken, ctx.config.jwtSecret)
|
||||
const jwtPayload = jwt.verify(token, ctx.config.jwtSecret)
|
||||
ctx.auth.apiKey = jwtPayload.apiKey
|
||||
ctx.user = {
|
||||
...jwtPayload,
|
||||
accessLevel: await getAccessLevel(
|
||||
|
@ -54,10 +52,6 @@ module.exports = async (ctx, next) => {
|
|||
jwtPayload.accessLevelId
|
||||
),
|
||||
}
|
||||
ctx.auth = {
|
||||
authenticated: ctx.user.accessLevelId !== ANON_LEVEL_ID,
|
||||
apiKey: jwtPayload.apiKey,
|
||||
}
|
||||
} catch (err) {
|
||||
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ const {
|
|||
} = require("../utilities/accessLevels")
|
||||
const environment = require("../environment")
|
||||
const { apiKeyTable } = require("../db/dynamoClient")
|
||||
const { AuthTypes } = require("../constants")
|
||||
|
||||
module.exports = (permName, getItemId) => async (ctx, next) => {
|
||||
if (
|
||||
|
@ -21,8 +22,7 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
|
|||
|
||||
if (apiKeyInfo) {
|
||||
ctx.auth = {
|
||||
authenticated: true,
|
||||
external: true,
|
||||
authenticated: AuthTypes.EXTERNAL,
|
||||
apiKey: ctx.headers["x-api-key"],
|
||||
}
|
||||
ctx.user = {
|
||||
|
@ -34,6 +34,9 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
|
|||
ctx.throw(403, "API key invalid")
|
||||
}
|
||||
|
||||
// don't expose builder endpoints in the cloud
|
||||
if (environment.CLOUD && permName === BUILDER) return
|
||||
|
||||
if (!ctx.auth.authenticated) {
|
||||
ctx.throw(403, "Session not authenticated")
|
||||
}
|
||||
|
@ -42,6 +45,10 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
|
|||
ctx.throw(403, "User not found")
|
||||
}
|
||||
|
||||
if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) {
|
||||
return next()
|
||||
}
|
||||
|
||||
if (ctx.user.accessLevel._id === BUILDER_LEVEL_ID) {
|
||||
return next()
|
||||
}
|
||||
|
@ -53,10 +60,6 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
|
|||
|
||||
const permissionId = ({ name, itemId }) => name + (itemId ? `-${itemId}` : "")
|
||||
|
||||
if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const thisPermissionId = permissionId({
|
||||
name: permName,
|
||||
itemId: getItemId && getItemId(ctx),
|
||||
|
|
|
@ -21,7 +21,6 @@ module.exports.PRETTY_ACCESS_LEVELS = {
|
|||
[module.exports.ADMIN_LEVEL_ID]: "Admin",
|
||||
[module.exports.POWERUSER_LEVEL_ID]: "Power user",
|
||||
[module.exports.BUILDER_LEVEL_ID]: "Builder",
|
||||
[module.exports.ANON_LEVEL_ID]: "Anonymous",
|
||||
}
|
||||
module.exports.adminPermissions = [
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@ const VALIDATORS = {
|
|||
}
|
||||
|
||||
const PARSERS = {
|
||||
number: attribute => Number(attribute),
|
||||
datetime: attribute => new Date(attribute).toISOString(),
|
||||
}
|
||||
|
||||
|
@ -24,7 +25,7 @@ function parse(path, parsers) {
|
|||
}
|
||||
}
|
||||
})
|
||||
result.fromFile(path).subscribe(row => {
|
||||
result.subscribe(row => {
|
||||
// For each CSV row parse all the columns that need parsed
|
||||
for (let key in parsers) {
|
||||
if (!schema[key] || schema[key].success) {
|
||||
|
|
|
@ -10,7 +10,6 @@ async function processImage(file) {
|
|||
const imgMeta = await sharp(file.path)
|
||||
.resize(300)
|
||||
.toFile(file.outputPath)
|
||||
|
||||
return {
|
||||
...file,
|
||||
...imgMeta,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const { exists, readFile, writeFile, ensureDir } = require("fs-extra")
|
||||
const { existsSync, readFile, writeFile, ensureDir } = require("fs-extra")
|
||||
const { join, resolve } = require("./centralPath")
|
||||
const Sqrl = require("squirrelly")
|
||||
const uuid = require("uuid")
|
||||
|
@ -28,7 +28,7 @@ const setCouchDbUrl = async opts => {
|
|||
|
||||
const createDevEnvFile = async opts => {
|
||||
const destConfigFile = join(opts.dir, "./.env")
|
||||
let createConfig = !(await exists(destConfigFile)) || opts.quiet
|
||||
let createConfig = !existsSync(destConfigFile) || opts.quiet
|
||||
if (createConfig) {
|
||||
const template = await readFile(
|
||||
resolve(__dirname, "..", "..", ".env.template"),
|
||||
|
|
|
@ -23,9 +23,7 @@
|
|||
</script>
|
||||
|
||||
<div use:cssVars={cssVariables} class="container">
|
||||
{#if showImage}
|
||||
<img class="image" src={imageUrl} alt="" />
|
||||
{/if}
|
||||
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
||||
<div class="content">
|
||||
<h2 class="heading">{heading}</h2>
|
||||
<h4 class="text">{description}</h4>
|
||||
|
|
|
@ -26,9 +26,7 @@
|
|||
</script>
|
||||
|
||||
<div use:cssVars={cssVariables} class="container">
|
||||
{#if showImage}
|
||||
<img class="image" src={imageUrl} alt="" />
|
||||
{/if}
|
||||
{#if showImage}<img class="image" src={imageUrl} alt="" />{/if}
|
||||
<div class="content">
|
||||
<main>
|
||||
<h2 class="heading">{heading}</h2>
|
||||
|
|
|
@ -27,15 +27,4 @@
|
|||
on:newRow={() => dispatch('newRow')} />
|
||||
</DropdownMenu> -->
|
||||
|
||||
<!-- <style>
|
||||
div {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
place-items: start center;
|
||||
}
|
||||
h5 {
|
||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style> -->
|
||||
<!--<style ✂prettier:content✂="CiAgZGl2IHsKICAgIGRpc3BsYXk6IGdyaWQ7CiAgICBncmlkLXRlbXBsYXRlLWNvbHVtbnM6IGF1dG8gYXV0bzsKICAgIHBsYWNlLWl0ZW1zOiBzdGFydCBjZW50ZXI7CiAgfQogIGg1IHsKICAgIHBhZGRpbmc6IHZhcigtLXNwYWNpbmcteGwpIDAgMCB2YXIoLS1zcGFjaW5nLXhsKTsKICAgIG1hcmdpbjogMDsKICAgIGZvbnQtd2VpZ2h0OiA1MDA7CiAgfQo=" ✂prettier:content✂="" ✂prettier:content✂="" ✂prettier:content✂="" ✂prettier:content✂=""></style>-->
|
||||
|
|
|
@ -135,7 +135,9 @@
|
|||
{#if selectedRows.length > 0}
|
||||
<DeleteButton text small on:click={deleteRows}>
|
||||
<Icon name="addrow" />
|
||||
Delete {selectedRows.length} row(s)
|
||||
Delete
|
||||
{selectedRows.length}
|
||||
row(s)
|
||||
</DeleteButton>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -42,9 +42,7 @@
|
|||
<div class="root">
|
||||
<div class="content">
|
||||
{#if logo}
|
||||
<div class="logo-container">
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
<div class="logo-container"><img src={logo} alt="logo" /></div>
|
||||
{/if}
|
||||
|
||||
{#if title}
|
||||
|
|
|
@ -33,6 +33,4 @@
|
|||
<sub class={className}>{text}</sub>
|
||||
{:else if isTag('sup')}
|
||||
<sup class={className}>{text}</sup>
|
||||
{:else}
|
||||
<span>{text}</span>
|
||||
{/if}
|
||||
{:else}<span>{text}</span>{/if}
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
<div 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}
|
||||
{:else}<i class="far fa-file" />{/if}
|
||||
</div>
|
||||
<span>{file.name}</span>
|
||||
</a>
|
||||
|
|
11
yarn.lock
11
yarn.lock
|
@ -3690,11 +3690,10 @@ prettier-linter-helpers@^1.0.0:
|
|||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier-plugin-svelte@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-0.7.0.tgz#5ac0b9f194e0450c88ff1e167cbf3b32d2642df2"
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
prettier-plugin-svelte@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-1.4.0.tgz#bb992759fb77ec2c3545d454a7c60f7a258cb745"
|
||||
integrity sha512-KXO2He7Kql0Lz4DdlzVli1j2JTDUR9jPV/DqyfnJmY1pCeSV1qZkxgdsyYma35W6OLrCAr/G6yKdmzo+75u2Ng==
|
||||
|
||||
prettier@^1.19.1:
|
||||
version "1.19.1"
|
||||
|
@ -4529,7 +4528,7 @@ trim-off-newlines@^1.0.0:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
|
||||
|
||||
tslib@^1.9.0, tslib@^1.9.3:
|
||||
tslib@^1.9.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
|
||||
|
|
Loading…
Reference in New Issue