Merge remote-tracking branch 'origin/master' into mike-fixes
This commit is contained in:
commit
74dcfef4aa
|
@ -106,10 +106,6 @@ const setPackage = (store, initial) => async pkg => {
|
||||||
initial.pages = pkg.pages
|
initial.pages = pkg.pages
|
||||||
initial.hasAppPackage = true
|
initial.hasAppPackage = true
|
||||||
initial.screens = values(pkg.screens)
|
initial.screens = values(pkg.screens)
|
||||||
initial.allScreens = [
|
|
||||||
...Object.values(main_screens),
|
|
||||||
...Object.values(unauth_screens),
|
|
||||||
]
|
|
||||||
initial.builtins = [getBuiltin("##builtin/screenslot")]
|
initial.builtins = [getBuiltin("##builtin/screenslot")]
|
||||||
initial.appInstances = pkg.application.instances
|
initial.appInstances = pkg.application.instances
|
||||||
initial.appId = pkg.application._id
|
initial.appId = pkg.application._id
|
||||||
|
@ -139,7 +135,6 @@ const _saveScreen = async (store, s, screen) => {
|
||||||
innerState.pages[pageName]._screens = screens
|
innerState.pages[pageName]._screens = screens
|
||||||
innerState.screens = screens
|
innerState.screens = screens
|
||||||
innerState.currentPreviewItem = screen
|
innerState.currentPreviewItem = screen
|
||||||
innerState.allScreens = [...innerState.allScreens, screen]
|
|
||||||
const safeProps = makePropsSafe(
|
const safeProps = makePropsSafe(
|
||||||
innerState.components[screen.props._component],
|
innerState.components[screen.props._component],
|
||||||
screen.props
|
screen.props
|
||||||
|
|
|
@ -52,6 +52,8 @@
|
||||||
.map(template => template.create())
|
.map(template => template.create())
|
||||||
|
|
||||||
for (let screen of screens) {
|
for (let screen of screens) {
|
||||||
|
// record the table that created this screen so we can link it later
|
||||||
|
screen.autoTableId = table._id
|
||||||
try {
|
try {
|
||||||
await store.createScreen(screen)
|
await store.createScreen(screen)
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
|
@ -30,8 +30,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function showModal() {
|
function showModal() {
|
||||||
const screens = $store.allScreens
|
const screens = $store.screens
|
||||||
templateScreens = screens.filter(screen => screen.props.table === table._id)
|
templateScreens = screens.filter(screen => screen.autoTableId === table._id)
|
||||||
willBeDeleted = ["All table data"].concat(
|
willBeDeleted = ["All table data"].concat(
|
||||||
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
{:else if parameter.name === 'url'}
|
{:else if parameter.name === 'url'}
|
||||||
<DataList on:change bind:value={parameter.value}>
|
<DataList on:change bind:value={parameter.value}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $store.allScreens as screen}
|
{#each $store.screens as screen}
|
||||||
<option value={screen.route}>{screen.props._instanceName}</option>
|
<option value={screen.route}>{screen.props._instanceName}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</DataList>
|
</DataList>
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<Heading medium black>Welcome to the Budibase Beta</Heading>
|
<Heading medium black>Welcome to the Budibase Beta</Heading>
|
||||||
<Button primary purple on:click={modal.show}>Create New Web App</Button>
|
<Button primary on:click={modal.show}>Create New Web App</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
|
|
|
@ -70,6 +70,7 @@ exports.authenticate = async ctx => {
|
||||||
expires,
|
expires,
|
||||||
path: "/",
|
path: "/",
|
||||||
httpOnly: false,
|
httpOnly: false,
|
||||||
|
overwrite: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
|
|
@ -2,6 +2,7 @@ const fs = require("fs")
|
||||||
const { join } = require("../../../utilities/centralPath")
|
const { join } = require("../../../utilities/centralPath")
|
||||||
const AWS = require("aws-sdk")
|
const AWS = require("aws-sdk")
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
|
const uuid = require("uuid")
|
||||||
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
||||||
const PouchDB = require("../../../db")
|
const PouchDB = require("../../../db")
|
||||||
const environment = require("../../../environment")
|
const environment = require("../../../environment")
|
||||||
|
@ -13,7 +14,7 @@ async function invalidateCDN(cfDistribution, appId) {
|
||||||
.createInvalidation({
|
.createInvalidation({
|
||||||
DistributionId: cfDistribution,
|
DistributionId: cfDistribution,
|
||||||
InvalidationBatch: {
|
InvalidationBatch: {
|
||||||
CallerReference: appId,
|
CallerReference: `${appId}-${uuid.v4()}`,
|
||||||
Paths: {
|
Paths: {
|
||||||
Quantity: 1,
|
Quantity: 1,
|
||||||
Items: [`/assets/${appId}/*`],
|
Items: [`/assets/${appId}/*`],
|
||||||
|
|
|
@ -51,6 +51,7 @@ module.exports = async (ctx, next) => {
|
||||||
ctx.auth.apiKey = jwtPayload.apiKey
|
ctx.auth.apiKey = jwtPayload.apiKey
|
||||||
ctx.user = {
|
ctx.user = {
|
||||||
...jwtPayload,
|
...jwtPayload,
|
||||||
|
instanceId: jwtPayload.instanceId,
|
||||||
accessLevel: await getAccessLevel(
|
accessLevel: await getAccessLevel(
|
||||||
jwtPayload.instanceId,
|
jwtPayload.instanceId,
|
||||||
jwtPayload.accessLevelId
|
jwtPayload.accessLevelId
|
||||||
|
|
|
@ -15,7 +15,16 @@ module.exports = (ctx, appId, instanceId) => {
|
||||||
expiresIn: "30 days",
|
expiresIn: "30 days",
|
||||||
})
|
})
|
||||||
|
|
||||||
var expiry = new Date()
|
const expiry = new Date()
|
||||||
expiry.setDate(expiry.getDate() + 30)
|
expiry.setDate(expiry.getDate() + 30)
|
||||||
ctx.cookies.set("builder:token", token, { expires: expiry, httpOnly: false })
|
// remove the app token
|
||||||
|
ctx.cookies.set("budibase:token", "", {
|
||||||
|
overwrite: true,
|
||||||
|
})
|
||||||
|
// set the builder token
|
||||||
|
ctx.cookies.set("builder:token", token, {
|
||||||
|
expires: expiry,
|
||||||
|
httpOnly: false,
|
||||||
|
overwrite: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,7 +242,10 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": "500"
|
"default": "500"
|
||||||
},
|
},
|
||||||
"pagination": "bool"
|
"pagination": {
|
||||||
|
"type": "bool",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dataform": {
|
"dataform": {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import commonjs from "@rollup/plugin-commonjs"
|
||||||
import postcss from "rollup-plugin-postcss"
|
import postcss from "rollup-plugin-postcss"
|
||||||
import { terser } from "rollup-plugin-terser"
|
import { terser } from "rollup-plugin-terser"
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH
|
||||||
const lodash_fp_exports = ["isEmpty"]
|
const lodash_fp_exports = ["isEmpty"]
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -17,7 +18,8 @@ export default {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
terser(),
|
// Only run terser in production environments
|
||||||
|
production && terser(),
|
||||||
postcss({
|
postcss({
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,30 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
import { DropdownMenu, TextButton as Button, Icon } from "@budibase/bbui"
|
|
||||||
import AttachmentList from "../../attachments/AttachmentList.svelte"
|
import AttachmentList from "../../attachments/AttachmentList.svelte"
|
||||||
// import Modal from "./Modal.svelte"
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
|
|
||||||
export let files
|
export let files
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AttachmentList {files} />
|
<AttachmentList {files} on:delete />
|
||||||
<!-- <div bind:this={anchor}>
|
|
||||||
<Button text small on:click={dropdown.show}>
|
|
||||||
<Icon name="edit" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
|
||||||
<h5>Edit Attachment(s)</h5>
|
|
||||||
<Modal
|
|
||||||
{_bb}
|
|
||||||
{table}
|
|
||||||
onClosed={dropdown.hide}
|
|
||||||
on:newRow={() => dispatch('newRow')} />
|
|
||||||
</DropdownMenu> -->
|
|
||||||
|
|
||||||
<!--<style ✂prettier:content✂="CiAgZGl2IHsKICAgIGRpc3BsYXk6IGdyaWQ7CiAgICBncmlkLXRlbXBsYXRlLWNvbHVtbnM6IGF1dG8gYXV0bzsKICAgIHBsYWNlLWl0ZW1zOiBzdGFydCBjZW50ZXI7CiAgfQogIGg1IHsKICAgIHBhZGRpbmc6IHZhcigtLXNwYWNpbmcteGwpIDAgMCB2YXIoLS1zcGFjaW5nLXhsKTsKICAgIG1hcmdpbjogMDsKICAgIGZvbnQtd2VpZ2h0OiA1MDA7CiAgfQo=" ✂prettier:content✂="" ✂prettier:content✂="" ✂prettier:content✂="" ✂prettier:content✂="" ✂prettier:content✂="" ✂prettier:content✂=""></style>-->
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
import AgGrid from "@budibase/svelte-ag-grid"
|
import AgGrid from "@budibase/svelte-ag-grid"
|
||||||
import CreateRowButton from "./CreateRow/Button.svelte"
|
import CreateRowButton from "./CreateRow/Button.svelte"
|
||||||
import { TextButton as DeleteButton, Icon } from "@budibase/bbui"
|
import { TextButton as DeleteButton, Icon, Modal, ModalContent } from "@budibase/bbui"
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let datasource = {}
|
export let datasource = {}
|
||||||
|
@ -25,6 +25,8 @@
|
||||||
let canEdit = editable && datasource && datasource.type !== "view"
|
let canEdit = editable && datasource && datasource.type !== "view"
|
||||||
let canAddDelete = editable && datasource && datasource.type === "table"
|
let canAddDelete = editable && datasource && datasource.type === "table"
|
||||||
|
|
||||||
|
let modal;
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
let dataLoaded = false
|
let dataLoaded = false
|
||||||
let data
|
let data
|
||||||
|
@ -68,7 +70,7 @@
|
||||||
field: key,
|
field: key,
|
||||||
hide: shouldHideField(key),
|
hide: shouldHideField(key),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
editable: canEdit,
|
editable: canEdit && schema[key].type !== "link",
|
||||||
cellRenderer: getRenderer(schema[key], canEdit),
|
cellRenderer: getRenderer(schema[key], canEdit),
|
||||||
autoHeight: true,
|
autoHeight: true,
|
||||||
}
|
}
|
||||||
|
@ -133,7 +135,7 @@
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<CreateRowButton {_bb} {table} on:newRow={handleNewRow} />
|
<CreateRowButton {_bb} {table} on:newRow={handleNewRow} />
|
||||||
{#if selectedRows.length > 0}
|
{#if selectedRows.length > 0}
|
||||||
<DeleteButton text small on:click={deleteRows}>
|
<DeleteButton text small on:click={modal.show()}>
|
||||||
<Icon name="addrow" />
|
<Icon name="addrow" />
|
||||||
Delete
|
Delete
|
||||||
{selectedRows.length}
|
{selectedRows.length}
|
||||||
|
@ -150,13 +152,14 @@
|
||||||
on:update={handleUpdate}
|
on:update={handleUpdate}
|
||||||
on:select={({ detail }) => (selectedRows = detail)} />
|
on:select={({ detail }) => (selectedRows = detail)} />
|
||||||
{/if}
|
{/if}
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<ModalContent title="Confirm Row Deletion" confirmText="Delete" onConfirm={deleteRows} >
|
||||||
|
<span>Are you sure you want to delete {selectedRows.length} row(s)?</span>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container :global(form) {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2);
|
|
||||||
}
|
|
||||||
.controls {
|
.controls {
|
||||||
margin-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-s);
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import api from "../../api"
|
||||||
|
import { getTable } from "./tableCache"
|
||||||
|
|
||||||
|
export let columnName
|
||||||
|
export let row
|
||||||
|
|
||||||
|
$: count =
|
||||||
|
row && columnName && Array.isArray(row[columnName])
|
||||||
|
? row[columnName].length
|
||||||
|
: 0
|
||||||
|
let linkedRows = []
|
||||||
|
let displayColumn
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
linkedRows = await fetchLinkedRowsData(row, columnName)
|
||||||
|
if (linkedRows && linkedRows.length) {
|
||||||
|
const table = await getTable(linkedRows[0].tableId)
|
||||||
|
if (table && table.primaryDisplay) {
|
||||||
|
displayColumn = table.primaryDisplay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function fetchLinkedRowsData(row, columnName) {
|
||||||
|
if (!row || !row._id) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const QUERY_URL = `/api/${row.tableId}/${row._id}/enrich`
|
||||||
|
const response = await api.get(QUERY_URL)
|
||||||
|
const enrichedRow = await response.json()
|
||||||
|
return enrichedRow[columnName]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{#if linkedRows && linkedRows.length && displayColumn}
|
||||||
|
{#each linkedRows as linkedRow}
|
||||||
|
{#if linkedRow[displayColumn] != null && linkedRow[displayColumn] !== ''}
|
||||||
|
<div class="linked-row">{linkedRow[displayColumn]}</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{:else}{count} related row(s){/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This styling is opinionated to ensure these always look consistent */
|
||||||
|
.linked-row {
|
||||||
|
color: white;
|
||||||
|
background-color: #616161;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-s) calc(var(--spacing-xs) + 1px)
|
||||||
|
var(--spacing-s);
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,20 @@
|
||||||
|
import api from "../../api"
|
||||||
|
|
||||||
|
let cache = {}
|
||||||
|
|
||||||
|
async function fetchTable(id) {
|
||||||
|
const FETCH_TABLE_URL = `/api/tables/${id}`
|
||||||
|
const response = await api.get(FETCH_TABLE_URL)
|
||||||
|
return await response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTable(tableId) {
|
||||||
|
if (!tableId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!cache[tableId]) {
|
||||||
|
cache[tableId] = fetchTable(tableId)
|
||||||
|
cache[tableId] = await cache[tableId]
|
||||||
|
}
|
||||||
|
return await cache[tableId]
|
||||||
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
import AttachmentCell from "./AttachmentCell/Button.svelte"
|
import AttachmentCell from "./AttachmentCell/Button.svelte"
|
||||||
import Select from "./Select/Wrapper.svelte"
|
import Select from "./Select/Wrapper.svelte"
|
||||||
import DatePicker from "./DateTime/Wrapper.svelte"
|
import DatePicker from "./DateTime/Wrapper.svelte"
|
||||||
|
import RelationshipDisplay from "./Relationship/RelationshipDisplay.svelte"
|
||||||
|
|
||||||
const renderers = new Map([
|
const renderers = new Map([
|
||||||
["boolean", booleanRenderer],
|
["boolean", booleanRenderer],
|
||||||
|
@ -54,6 +55,13 @@ function attachmentRenderer(constraints, editable) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const deleteFile = event => {
|
||||||
|
const newFilesArray = params.value.filter(file => file !== event.detail)
|
||||||
|
params.setValue(newFilesArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentInstance.$on("delete", deleteFile)
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +118,13 @@ function linkedRowRenderer(constraints, editable) {
|
||||||
container.style.placeItems = "center"
|
container.style.placeItems = "center"
|
||||||
container.style.height = "100%"
|
container.style.height = "100%"
|
||||||
|
|
||||||
container.innerText = params.value ? params.value.length : 0
|
new RelationshipDisplay({
|
||||||
|
target: container,
|
||||||
|
props: {
|
||||||
|
row: params.data,
|
||||||
|
columnName: params.column.colId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,53 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { Modal, ModalContent, Icon } from '@budibase/bbui'
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
import { FILE_TYPES } from "./fileTypes"
|
import { FILE_TYPES } from "./fileTypes"
|
||||||
|
|
||||||
export let files = []
|
export let files = []
|
||||||
export let height = "70"
|
export let height = "70"
|
||||||
export let width = "70"
|
export let width = "70"
|
||||||
|
|
||||||
|
let modal;
|
||||||
|
let currentFile;
|
||||||
|
|
||||||
|
const openModal = (file) => {
|
||||||
|
currentFile = file
|
||||||
|
modal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
dispatch('delete', currentFile)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="file-list">
|
<div class="file-list">
|
||||||
{#each files as file}
|
{#each files as file}
|
||||||
<a href={file.url} target="_blank">
|
<div class="file-container">
|
||||||
<div class="file">
|
<a href={file.url} target="_blank" class="file">
|
||||||
{#if FILE_TYPES.IMAGE.includes(file.extension.toLowerCase())}
|
{#if FILE_TYPES.IMAGE.includes(file.extension.toLowerCase())}
|
||||||
<img {width} {height} src={file.url} alt="preview of {file.name}" />
|
<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>
|
</a>
|
||||||
<span>{file.name}</span>
|
<span>{file.name}</span>
|
||||||
</a>
|
<div class="button-placement"><button primary on:click|stopPropagation={() => openModal(file)}>×</button></div>
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</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>
|
<style>
|
||||||
.file-list {
|
.file-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
justify-content: start;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-gap: var(--spacing-m);
|
grid-gap: var(--spacing-m);
|
||||||
grid-template-columns: repeat(10, 1fr);
|
grid-template-columns: repeat(auto-fill, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -40,6 +63,41 @@
|
||||||
text-decoration: none;
|
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 {
|
.file {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 75px;
|
height: 75px;
|
||||||
|
|
Loading…
Reference in New Issue