Merge branch 'feat/budi-8123-single-user' into budi-8123/single-user-column-type
This commit is contained in:
commit
70c6d56c1e
|
@ -92,8 +92,6 @@ jobs:
|
||||||
|
|
||||||
test-libraries:
|
test-libraries:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
|
||||||
REUSE_CONTAINERS: true
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
@ -150,8 +148,6 @@ jobs:
|
||||||
|
|
||||||
test-server:
|
test-server:
|
||||||
runs-on: budi-tubby-tornado-quad-core-150gb
|
runs-on: budi-tubby-tornado-quad-core-150gb
|
||||||
env:
|
|
||||||
REUSE_CONTAINERS: true
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
|
@ -1,16 +1,49 @@
|
||||||
import { GenericContainer, Wait } from "testcontainers"
|
import {
|
||||||
|
GenericContainer,
|
||||||
|
Wait,
|
||||||
|
getContainerRuntimeClient,
|
||||||
|
} from "testcontainers"
|
||||||
|
import { ContainerInfo } from "dockerode"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import lockfile from "proper-lockfile"
|
import lockfile from "proper-lockfile"
|
||||||
|
|
||||||
|
async function getBudibaseContainers() {
|
||||||
|
const client = await getContainerRuntimeClient()
|
||||||
|
const conatiners = await client.container.list()
|
||||||
|
return conatiners.filter(
|
||||||
|
container =>
|
||||||
|
container.Labels["com.budibase"] === "true" &&
|
||||||
|
container.Labels["org.testcontainers"] === "true"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function killContainers(containers: ContainerInfo[]) {
|
||||||
|
const client = await getContainerRuntimeClient()
|
||||||
|
for (const container of containers) {
|
||||||
|
const c = client.container.getById(container.Id)
|
||||||
|
await c.kill()
|
||||||
|
await c.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function setup() {
|
export default async function setup() {
|
||||||
const lockPath = path.resolve(__dirname, "globalSetup.ts")
|
const lockPath = path.resolve(__dirname, "globalSetup.ts")
|
||||||
if (process.env.REUSE_CONTAINERS) {
|
// If you run multiple tests at the same time, it's possible for the CouchDB
|
||||||
// If you run multiple tests at the same time, it's possible for the CouchDB
|
// shared container to get started multiple times despite having an
|
||||||
// shared container to get started multiple times despite having an
|
// identical reuse hash. To avoid that, we do a filesystem-based lock so
|
||||||
// identical reuse hash. To avoid that, we do a filesystem-based lock so
|
// that only one globalSetup.ts is running at a time.
|
||||||
// that only one globalSetup.ts is running at a time.
|
lockfile.lockSync(lockPath)
|
||||||
lockfile.lockSync(lockPath)
|
|
||||||
}
|
// Remove any containers that are older than 24 hours. This is to prevent
|
||||||
|
// containers getting full volumes or accruing any other problems from being
|
||||||
|
// left up for very long periods of time.
|
||||||
|
const threshold = new Date(Date.now() - 1000 * 60 * 60 * 24)
|
||||||
|
const containers = (await getBudibaseContainers()).filter(container => {
|
||||||
|
const created = new Date(container.Created * 1000)
|
||||||
|
return created < threshold
|
||||||
|
})
|
||||||
|
|
||||||
|
await killContainers(containers)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let couchdb = new GenericContainer("budibase/couchdb:v3.2.1-sqs")
|
let couchdb = new GenericContainer("budibase/couchdb:v3.2.1-sqs")
|
||||||
|
@ -28,20 +61,16 @@ export default async function setup() {
|
||||||
target: "/opt/couchdb/etc/local.d/test-couchdb.ini",
|
target: "/opt/couchdb/etc/local.d/test-couchdb.ini",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
.withLabels({ "com.budibase": "true" })
|
||||||
|
.withReuse()
|
||||||
.withWaitStrategy(
|
.withWaitStrategy(
|
||||||
Wait.forSuccessfulCommand(
|
Wait.forSuccessfulCommand(
|
||||||
"curl http://budibase:budibase@localhost:5984/_up"
|
"curl http://budibase:budibase@localhost:5984/_up"
|
||||||
).withStartupTimeout(20000)
|
).withStartupTimeout(20000)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (process.env.REUSE_CONTAINERS) {
|
|
||||||
couchdb = couchdb.withReuse()
|
|
||||||
}
|
|
||||||
|
|
||||||
await couchdb.start()
|
await couchdb.start()
|
||||||
} finally {
|
} finally {
|
||||||
if (process.env.REUSE_CONTAINERS) {
|
lockfile.unlockSync(lockPath)
|
||||||
lockfile.unlockSync(lockPath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.23.12",
|
"version": "2.24.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -59,7 +59,8 @@
|
||||||
"dev:all": "yarn run kill-all && lerna run --stream dev",
|
"dev:all": "yarn run kill-all && lerna run --stream dev",
|
||||||
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
|
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
|
||||||
"dev:docker": "./scripts/devDocker.sh",
|
"dev:docker": "./scripts/devDocker.sh",
|
||||||
"test": "REUSE_CONTAINERS=1 lerna run --concurrency 1 --stream test --stream",
|
"test": "lerna run --concurrency 1 --stream test --stream",
|
||||||
|
"test:containers:kill": "./scripts/killTestcontainers.sh",
|
||||||
"lint:eslint": "eslint packages --max-warnings=0",
|
"lint:eslint": "eslint packages --max-warnings=0",
|
||||||
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\"",
|
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\"",
|
||||||
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
||||||
|
|
|
@ -28,7 +28,11 @@ function getTestcontainers(): ContainerInfo[] {
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.filter(x => x.length > 0)
|
.filter(x => x.length > 0)
|
||||||
.map(x => JSON.parse(x) as ContainerInfo)
|
.map(x => JSON.parse(x) as ContainerInfo)
|
||||||
.filter(x => x.Labels.includes("org.testcontainers=true"))
|
.filter(
|
||||||
|
x =>
|
||||||
|
x.Labels.includes("org.testcontainers=true") &&
|
||||||
|
x.Labels.includes("com.budibase=true")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContainerByImage(image: string) {
|
export function getContainerByImage(image: string) {
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
"@spectrum-css/avatar": "3.0.2",
|
"@spectrum-css/avatar": "3.0.2",
|
||||||
"@spectrum-css/button": "3.0.1",
|
"@spectrum-css/button": "3.0.1",
|
||||||
"@spectrum-css/buttongroup": "3.0.2",
|
"@spectrum-css/buttongroup": "3.0.2",
|
||||||
|
"@spectrum-css/calendar": "3.2.7",
|
||||||
"@spectrum-css/checkbox": "3.0.2",
|
"@spectrum-css/checkbox": "3.0.2",
|
||||||
"@spectrum-css/dialog": "3.0.1",
|
"@spectrum-css/dialog": "3.0.1",
|
||||||
"@spectrum-css/divider": "1.0.3",
|
"@spectrum-css/divider": "1.0.3",
|
||||||
|
@ -82,7 +83,6 @@
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.10.8",
|
||||||
"easymde": "^2.16.1",
|
"easymde": "^2.16.1",
|
||||||
"svelte-dnd-action": "^0.9.8",
|
"svelte-dnd-action": "^0.9.8",
|
||||||
"svelte-flatpickr": "3.2.3",
|
|
||||||
"svelte-portal": "^1.0.0"
|
"svelte-portal": "^1.0.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
|
|
@ -38,7 +38,15 @@
|
||||||
<div use:getAnchor on:click={openMenu}>
|
<div use:getAnchor on:click={openMenu}>
|
||||||
<slot name="control" />
|
<slot name="control" />
|
||||||
</div>
|
</div>
|
||||||
<Popover bind:this={dropdown} {anchor} {align} {portalTarget} on:open on:close>
|
<Popover
|
||||||
|
bind:this={dropdown}
|
||||||
|
{anchor}
|
||||||
|
{align}
|
||||||
|
{portalTarget}
|
||||||
|
resizable={false}
|
||||||
|
on:open
|
||||||
|
on:close
|
||||||
|
>
|
||||||
<Menu>
|
<Menu>
|
||||||
<slot />
|
<slot />
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
const ignoredClasses = [
|
const ignoredClasses = [
|
||||||
".flatpickr-calendar",
|
|
||||||
".spectrum-Popover",
|
|
||||||
".download-js-link",
|
".download-js-link",
|
||||||
|
".spectrum-Menu",
|
||||||
|
".date-time-popover",
|
||||||
|
]
|
||||||
|
const conditionallyIgnoredClasses = [
|
||||||
|
".spectrum-Underlay",
|
||||||
|
".drawer-wrapper",
|
||||||
|
".spectrum-Popover",
|
||||||
]
|
]
|
||||||
let clickHandlers = []
|
let clickHandlers = []
|
||||||
|
|
||||||
|
@ -9,6 +14,9 @@ let clickHandlers = []
|
||||||
* Handle a body click event
|
* Handle a body click event
|
||||||
*/
|
*/
|
||||||
const handleClick = event => {
|
const handleClick = event => {
|
||||||
|
// Treat right clicks (context menu events) as normal clicks
|
||||||
|
const eventType = event.type === "contextmenu" ? "click" : event.type
|
||||||
|
|
||||||
// Ignore click if this is an ignored class
|
// Ignore click if this is an ignored class
|
||||||
if (event.target.closest('[data-ignore-click-outside="true"]')) {
|
if (event.target.closest('[data-ignore-click-outside="true"]')) {
|
||||||
return
|
return
|
||||||
|
@ -21,26 +29,23 @@ const handleClick = event => {
|
||||||
|
|
||||||
// Process handlers
|
// Process handlers
|
||||||
clickHandlers.forEach(handler => {
|
clickHandlers.forEach(handler => {
|
||||||
|
// Check that we're the right kind of click event
|
||||||
|
if (handler.allowedType && eventType !== handler.allowedType) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the click isn't inside the target
|
||||||
if (handler.element.contains(event.target)) {
|
if (handler.element.contains(event.target)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore clicks for modals, unless the handler is registered from a modal
|
// Ignore clicks for certain classes unless we're nested inside them
|
||||||
const sourceInModal = handler.anchor.closest(".spectrum-Underlay") != null
|
for (let className of conditionallyIgnoredClasses) {
|
||||||
const clickInModal = event.target.closest(".spectrum-Underlay") != null
|
const sourceInside = handler.anchor.closest(className) != null
|
||||||
if (clickInModal && !sourceInModal) {
|
const clickInside = event.target.closest(className) != null
|
||||||
return
|
if (clickInside && !sourceInside) {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
// Ignore clicks for drawers, unless the handler is registered from a drawer
|
|
||||||
const sourceInDrawer = handler.anchor.closest(".drawer-wrapper") != null
|
|
||||||
const clickInDrawer = event.target.closest(".drawer-wrapper") != null
|
|
||||||
if (clickInDrawer && !sourceInDrawer) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler.allowedType && event.type !== handler.allowedType) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.callback?.(event)
|
handler.callback?.(event)
|
||||||
|
@ -48,6 +53,7 @@ const handleClick = event => {
|
||||||
}
|
}
|
||||||
document.documentElement.addEventListener("click", handleClick, true)
|
document.documentElement.addEventListener("click", handleClick, true)
|
||||||
document.documentElement.addEventListener("mousedown", handleClick, true)
|
document.documentElement.addEventListener("mousedown", handleClick, true)
|
||||||
|
document.documentElement.addEventListener("contextmenu", handleClick, true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds or updates a click handler
|
* Adds or updates a click handler
|
||||||
|
|
|
@ -1,3 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Valid alignment options are
|
||||||
|
* - left
|
||||||
|
* - right
|
||||||
|
* - left-outside
|
||||||
|
* - right-outside
|
||||||
|
**/
|
||||||
|
|
||||||
|
// Strategies are defined as [Popover]To[Anchor].
|
||||||
|
// They can apply for both horizontal and vertical alignment.
|
||||||
|
const Strategies = {
|
||||||
|
StartToStart: "StartToStart", // e.g. left alignment
|
||||||
|
EndToEnd: "EndToEnd", // e.g. right alignment
|
||||||
|
StartToEnd: "StartToEnd", // e.g. right-outside alignment
|
||||||
|
EndToStart: "EndToStart", // e.g. left-outside alignment
|
||||||
|
MidPoint: "MidPoint", // centers relative to midpoints
|
||||||
|
ScreenEdge: "ScreenEdge", // locks to screen edge
|
||||||
|
}
|
||||||
|
|
||||||
export default function positionDropdown(element, opts) {
|
export default function positionDropdown(element, opts) {
|
||||||
let resizeObserver
|
let resizeObserver
|
||||||
let latestOpts = opts
|
let latestOpts = opts
|
||||||
|
@ -19,6 +38,8 @@ export default function positionDropdown(element, opts) {
|
||||||
useAnchorWidth,
|
useAnchorWidth,
|
||||||
offset = 5,
|
offset = 5,
|
||||||
customUpdate,
|
customUpdate,
|
||||||
|
resizable,
|
||||||
|
wrap,
|
||||||
} = opts
|
} = opts
|
||||||
if (!anchor) {
|
if (!anchor) {
|
||||||
return
|
return
|
||||||
|
@ -27,56 +48,159 @@ export default function positionDropdown(element, opts) {
|
||||||
// Compute bounds
|
// Compute bounds
|
||||||
const anchorBounds = anchor.getBoundingClientRect()
|
const anchorBounds = anchor.getBoundingClientRect()
|
||||||
const elementBounds = element.getBoundingClientRect()
|
const elementBounds = element.getBoundingClientRect()
|
||||||
|
const winWidth = window.innerWidth
|
||||||
|
const winHeight = window.innerHeight
|
||||||
|
const screenOffset = 8
|
||||||
let styles = {
|
let styles = {
|
||||||
maxHeight: null,
|
maxHeight,
|
||||||
minWidth,
|
minWidth: useAnchorWidth ? anchorBounds.width : minWidth,
|
||||||
maxWidth,
|
maxWidth: useAnchorWidth ? anchorBounds.width : maxWidth,
|
||||||
left: null,
|
left: null,
|
||||||
top: null,
|
top: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore all our logic for custom logic
|
||||||
if (typeof customUpdate === "function") {
|
if (typeof customUpdate === "function") {
|
||||||
styles = customUpdate(anchorBounds, elementBounds, {
|
styles = customUpdate(anchorBounds, elementBounds, {
|
||||||
...styles,
|
...styles,
|
||||||
offset: opts.offset,
|
offset: opts.offset,
|
||||||
})
|
})
|
||||||
} else {
|
}
|
||||||
// Determine vertical styles
|
|
||||||
if (align === "right-outside" || align === "left-outside") {
|
// Otherwise position ourselves as normal
|
||||||
styles.top =
|
else {
|
||||||
anchorBounds.top + anchorBounds.height / 2 - elementBounds.height / 2
|
// Checks if we overflow off the screen. We only report that we overflow
|
||||||
styles.maxHeight = maxHeight
|
// when the alternative dimension is larger than the one we are checking.
|
||||||
if (styles.top + elementBounds.height > window.innerHeight) {
|
const doesXOverflow = () => {
|
||||||
styles.top = window.innerHeight - elementBounds.height
|
const overflows = styles.left + elementBounds.width > winWidth
|
||||||
}
|
return overflows && anchorBounds.left > winWidth - anchorBounds.right
|
||||||
} else if (
|
}
|
||||||
window.innerHeight - anchorBounds.bottom <
|
const doesYOverflow = () => {
|
||||||
(maxHeight || 100)
|
const overflows = styles.top + elementBounds.height > winHeight
|
||||||
) {
|
return overflows && anchorBounds.top > winHeight - anchorBounds.bottom
|
||||||
styles.top = anchorBounds.top - elementBounds.height - offset
|
|
||||||
styles.maxHeight = maxHeight || 240
|
|
||||||
} else {
|
|
||||||
styles.top = anchorBounds.bottom + offset
|
|
||||||
styles.maxHeight =
|
|
||||||
maxHeight || window.innerHeight - anchorBounds.bottom - 20
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine horizontal styles
|
// Applies a dynamic max height constraint if appropriate
|
||||||
if (!maxWidth && useAnchorWidth) {
|
const applyMaxHeight = height => {
|
||||||
styles.maxWidth = anchorBounds.width
|
if (!styles.maxHeight && resizable) {
|
||||||
|
styles.maxHeight = height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (useAnchorWidth) {
|
|
||||||
styles.minWidth = anchorBounds.width
|
// Applies the X strategy to our styles
|
||||||
|
const applyXStrategy = strategy => {
|
||||||
|
switch (strategy) {
|
||||||
|
case Strategies.StartToStart:
|
||||||
|
default:
|
||||||
|
styles.left = anchorBounds.left
|
||||||
|
break
|
||||||
|
case Strategies.EndToEnd:
|
||||||
|
styles.left = anchorBounds.right - elementBounds.width
|
||||||
|
break
|
||||||
|
case Strategies.StartToEnd:
|
||||||
|
styles.left = anchorBounds.right + offset
|
||||||
|
break
|
||||||
|
case Strategies.EndToStart:
|
||||||
|
styles.left = anchorBounds.left - elementBounds.width - offset
|
||||||
|
break
|
||||||
|
case Strategies.MidPoint:
|
||||||
|
styles.left =
|
||||||
|
anchorBounds.left +
|
||||||
|
anchorBounds.width / 2 -
|
||||||
|
elementBounds.width / 2
|
||||||
|
break
|
||||||
|
case Strategies.ScreenEdge:
|
||||||
|
styles.left = winWidth - elementBounds.width - screenOffset
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Applies the Y strategy to our styles
|
||||||
|
const applyYStrategy = strategy => {
|
||||||
|
switch (strategy) {
|
||||||
|
case Strategies.StartToStart:
|
||||||
|
styles.top = anchorBounds.top
|
||||||
|
applyMaxHeight(winHeight - anchorBounds.top - screenOffset)
|
||||||
|
break
|
||||||
|
case Strategies.EndToEnd:
|
||||||
|
styles.top = anchorBounds.bottom - elementBounds.height
|
||||||
|
applyMaxHeight(anchorBounds.bottom - screenOffset)
|
||||||
|
break
|
||||||
|
case Strategies.StartToEnd:
|
||||||
|
default:
|
||||||
|
styles.top = anchorBounds.bottom + offset
|
||||||
|
applyMaxHeight(winHeight - anchorBounds.bottom - screenOffset)
|
||||||
|
break
|
||||||
|
case Strategies.EndToStart:
|
||||||
|
styles.top = anchorBounds.top - elementBounds.height - offset
|
||||||
|
applyMaxHeight(anchorBounds.top - screenOffset)
|
||||||
|
break
|
||||||
|
case Strategies.MidPoint:
|
||||||
|
styles.top =
|
||||||
|
anchorBounds.top +
|
||||||
|
anchorBounds.height / 2 -
|
||||||
|
elementBounds.height / 2
|
||||||
|
break
|
||||||
|
case Strategies.ScreenEdge:
|
||||||
|
styles.top = winHeight - elementBounds.height - screenOffset
|
||||||
|
applyMaxHeight(winHeight - 2 * screenOffset)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine X strategy
|
||||||
if (align === "right") {
|
if (align === "right") {
|
||||||
styles.left =
|
applyXStrategy(Strategies.EndToEnd)
|
||||||
anchorBounds.left + anchorBounds.width - elementBounds.width
|
|
||||||
} else if (align === "right-outside") {
|
} else if (align === "right-outside") {
|
||||||
styles.left = anchorBounds.right + offset
|
applyXStrategy(Strategies.StartToEnd)
|
||||||
} else if (align === "left-outside") {
|
} else if (align === "left-outside") {
|
||||||
styles.left = anchorBounds.left - elementBounds.width - offset
|
applyXStrategy(Strategies.EndToStart)
|
||||||
} else {
|
} else {
|
||||||
styles.left = anchorBounds.left
|
applyXStrategy(Strategies.StartToStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine Y strategy
|
||||||
|
if (align === "right-outside" || align === "left-outside") {
|
||||||
|
applyYStrategy(Strategies.MidPoint)
|
||||||
|
} else {
|
||||||
|
applyYStrategy(Strategies.StartToEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle screen overflow
|
||||||
|
if (doesXOverflow()) {
|
||||||
|
// Swap left to right
|
||||||
|
if (align === "left") {
|
||||||
|
applyXStrategy(Strategies.EndToEnd)
|
||||||
|
}
|
||||||
|
// Swap right-outside to left-outside
|
||||||
|
else if (align === "right-outside") {
|
||||||
|
applyXStrategy(Strategies.EndToStart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (doesYOverflow()) {
|
||||||
|
// If wrapping, lock to the bottom of the screen and also reposition to
|
||||||
|
// the side to not block the anchor
|
||||||
|
if (wrap) {
|
||||||
|
applyYStrategy(Strategies.MidPoint)
|
||||||
|
if (doesYOverflow()) {
|
||||||
|
applyYStrategy(Strategies.ScreenEdge)
|
||||||
|
}
|
||||||
|
applyXStrategy(Strategies.StartToEnd)
|
||||||
|
if (doesXOverflow()) {
|
||||||
|
applyXStrategy(Strategies.EndToStart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Othewise invert as normal
|
||||||
|
else {
|
||||||
|
// If using an outside strategy then lock to the bottom of the screen
|
||||||
|
if (align === "left-outside" || align === "right-outside") {
|
||||||
|
applyYStrategy(Strategies.ScreenEdge)
|
||||||
|
}
|
||||||
|
// Otherwise flip above
|
||||||
|
else {
|
||||||
|
applyYStrategy(Strategies.EndToStart)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,268 +0,0 @@
|
||||||
<script>
|
|
||||||
import Flatpickr from "svelte-flatpickr"
|
|
||||||
import "flatpickr/dist/flatpickr.css"
|
|
||||||
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
|
||||||
import "@spectrum-css/picker/dist/index-vars.css"
|
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
import { uuid } from "../../helpers"
|
|
||||||
|
|
||||||
export let id = null
|
|
||||||
export let disabled = false
|
|
||||||
export let readonly = false
|
|
||||||
export let enableTime = true
|
|
||||||
export let value = null
|
|
||||||
export let placeholder = null
|
|
||||||
export let appendTo = undefined
|
|
||||||
export let timeOnly = false
|
|
||||||
export let ignoreTimezones = false
|
|
||||||
export let time24hr = false
|
|
||||||
export let range = false
|
|
||||||
export let flatpickr
|
|
||||||
export let useKeyboardShortcuts = true
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
const flatpickrId = `${uuid()}-wrapper`
|
|
||||||
|
|
||||||
let open = false
|
|
||||||
let flatpickrOptions
|
|
||||||
|
|
||||||
// Another classic flatpickr issue. Errors were randomly being thrown due to
|
|
||||||
// flatpickr internal code. Making sure that "destroy" is a valid function
|
|
||||||
// fixes it. The sooner we remove flatpickr the better.
|
|
||||||
$: {
|
|
||||||
if (flatpickr && !flatpickr.destroy) {
|
|
||||||
flatpickr.destroy = () => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolveTimeStamp = timestamp => {
|
|
||||||
let maskedDate = new Date(`0-${timestamp}`)
|
|
||||||
|
|
||||||
if (maskedDate instanceof Date && !isNaN(maskedDate.getTime())) {
|
|
||||||
return maskedDate
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: flatpickrOptions = {
|
|
||||||
element: `#${flatpickrId}`,
|
|
||||||
enableTime: timeOnly || enableTime || false,
|
|
||||||
noCalendar: timeOnly || false,
|
|
||||||
altInput: true,
|
|
||||||
time_24hr: time24hr || false,
|
|
||||||
altFormat: timeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y",
|
|
||||||
wrap: true,
|
|
||||||
mode: range ? "range" : "single",
|
|
||||||
appendTo,
|
|
||||||
disableMobile: "true",
|
|
||||||
onReady: () => {
|
|
||||||
let timestamp = resolveTimeStamp(value)
|
|
||||||
if (timeOnly && timestamp) {
|
|
||||||
dispatch("change", timestamp.toISOString())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onOpen: () => dispatch("open"),
|
|
||||||
onClose: () => dispatch("close"),
|
|
||||||
}
|
|
||||||
|
|
||||||
$: redrawOptions = {
|
|
||||||
timeOnly,
|
|
||||||
enableTime,
|
|
||||||
time24hr,
|
|
||||||
disabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = event => {
|
|
||||||
const [dates] = event.detail
|
|
||||||
const noTimezone = enableTime && !timeOnly && ignoreTimezones
|
|
||||||
let newValue = dates[0]
|
|
||||||
if (newValue) {
|
|
||||||
newValue = newValue.toISOString()
|
|
||||||
}
|
|
||||||
// If time only set date component to 2000-01-01
|
|
||||||
if (timeOnly) {
|
|
||||||
newValue = `2000-01-01T${newValue.split("T")[1]}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For date-only fields, construct a manual timestamp string without a time
|
|
||||||
// or time zone
|
|
||||||
else if (!enableTime) {
|
|
||||||
const year = dates[0].getFullYear()
|
|
||||||
const month = `${dates[0].getMonth() + 1}`.padStart(2, "0")
|
|
||||||
const day = `${dates[0].getDate()}`.padStart(2, "0")
|
|
||||||
newValue = `${year}-${month}-${day}T00:00:00.000`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
|
|
||||||
// time picked, without timezone
|
|
||||||
else if (noTimezone) {
|
|
||||||
const offset = dates[0].getTimezoneOffset() * 60000
|
|
||||||
newValue = new Date(dates[0].getTime() - offset)
|
|
||||||
.toISOString()
|
|
||||||
.slice(0, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (range) {
|
|
||||||
dispatch("change", event.detail)
|
|
||||||
} else {
|
|
||||||
dispatch("change", newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearDateOnBackspace = event => {
|
|
||||||
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
|
||||||
dispatch("change", "")
|
|
||||||
flatpickr.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onOpen = () => {
|
|
||||||
open = true
|
|
||||||
if (useKeyboardShortcuts) {
|
|
||||||
document.addEventListener("keyup", clearDateOnBackspace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
open = false
|
|
||||||
if (useKeyboardShortcuts) {
|
|
||||||
document.removeEventListener("keyup", clearDateOnBackspace)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manually blur all input fields since flatpickr creates a second
|
|
||||||
// duplicate input field.
|
|
||||||
// We need to blur both because the focus styling does not get properly
|
|
||||||
// applied.
|
|
||||||
const els = document.querySelectorAll(`#${flatpickrId} input`)
|
|
||||||
els.forEach(el => el.blur())
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseDate = val => {
|
|
||||||
if (!val) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
let date
|
|
||||||
let time
|
|
||||||
|
|
||||||
// it is a string like 00:00:00, just time
|
|
||||||
let ts = resolveTimeStamp(val)
|
|
||||||
|
|
||||||
if (timeOnly && ts) {
|
|
||||||
date = ts
|
|
||||||
} else if (val instanceof Date) {
|
|
||||||
// Use real date obj if already parsed
|
|
||||||
date = val
|
|
||||||
} else if (isNaN(val)) {
|
|
||||||
// Treat as date string of some sort
|
|
||||||
date = new Date(val)
|
|
||||||
} else {
|
|
||||||
// Treat as numerical timestamp
|
|
||||||
date = new Date(parseInt(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
time = date.getTime()
|
|
||||||
if (isNaN(time)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// By rounding to the nearest second we avoid locking up in an endless
|
|
||||||
// loop in the builder, caused by potentially enriching {{ now }} to every
|
|
||||||
// millisecond.
|
|
||||||
return new Date(Math.floor(time / 1000) * 1000)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#key redrawOptions}
|
|
||||||
<Flatpickr
|
|
||||||
bind:flatpickr
|
|
||||||
value={range ? value : parseDate(value)}
|
|
||||||
on:open={onOpen}
|
|
||||||
on:close={onClose}
|
|
||||||
options={flatpickrOptions}
|
|
||||||
on:change={handleChange}
|
|
||||||
element={`#${flatpickrId}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
id={flatpickrId}
|
|
||||||
class:is-disabled={disabled || readonly}
|
|
||||||
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
|
|
||||||
class:is-focused={open}
|
|
||||||
aria-readonly="false"
|
|
||||||
aria-required="false"
|
|
||||||
aria-haspopup="true"
|
|
||||||
>
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div
|
|
||||||
on:click={flatpickr?.open}
|
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
{disabled}
|
|
||||||
{readonly}
|
|
||||||
data-input
|
|
||||||
type="text"
|
|
||||||
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
{placeholder}
|
|
||||||
{id}
|
|
||||||
{value}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
|
||||||
tabindex="-1"
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
on:click={flatpickr?.open}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Calendar"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Calendar" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Flatpickr>
|
|
||||||
{/key}
|
|
||||||
{#if open}
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<div class="overlay" on:mousedown|self={flatpickr?.close} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.spectrum-Textfield-input {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.spectrum-Textfield:not(.is-disabled):hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.flatpickr {
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.flatpickr .spectrum-Textfield {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
z-index: 999;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
:global(.flatpickr-calendar) {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
.is-disabled {
|
|
||||||
pointer-events: none !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,249 @@
|
||||||
|
<script>
|
||||||
|
import { cleanInput } from "./utils"
|
||||||
|
import Select from "../../Select.svelte"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
import NumberInput from "./NumberInput.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const DaysOfWeek = [
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
"Sunday",
|
||||||
|
]
|
||||||
|
const MonthsOfYear = [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
]
|
||||||
|
|
||||||
|
const now = dayjs()
|
||||||
|
let calendarDate
|
||||||
|
|
||||||
|
$: calendarDate = dayjs(value || dayjs()).startOf("month")
|
||||||
|
$: mondays = getMondays(calendarDate)
|
||||||
|
|
||||||
|
const getMondays = monthStart => {
|
||||||
|
if (!monthStart?.isValid()) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let monthEnd = monthStart.endOf("month")
|
||||||
|
let calendarStart = monthStart.startOf("week")
|
||||||
|
const numWeeks = Math.ceil((monthEnd.diff(calendarStart, "day") + 1) / 7)
|
||||||
|
|
||||||
|
let mondays = []
|
||||||
|
for (let i = 0; i < numWeeks; i++) {
|
||||||
|
mondays.push(calendarStart.add(i, "weeks"))
|
||||||
|
}
|
||||||
|
return mondays
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCalendarYearChange = e => {
|
||||||
|
calendarDate = calendarDate.year(parseInt(e.target.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDateChange = date => {
|
||||||
|
const base = value || now
|
||||||
|
dispatch(
|
||||||
|
"change",
|
||||||
|
base.year(date.year()).month(date.month()).date(date.date())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setDate = date => {
|
||||||
|
calendarDate = date
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanYear = cleanInput({ max: 9999, pad: 0, fallback: now.year() })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="spectrum-Calendar">
|
||||||
|
<div class="spectrum-Calendar-header">
|
||||||
|
<div
|
||||||
|
class="spectrum-Calendar-title"
|
||||||
|
aria-live="assertive"
|
||||||
|
aria-atomic="true"
|
||||||
|
>
|
||||||
|
<div class="month-selector">
|
||||||
|
<Select
|
||||||
|
autoWidth
|
||||||
|
placeholder={null}
|
||||||
|
options={MonthsOfYear.map((m, idx) => ({ label: m, value: idx }))}
|
||||||
|
value={calendarDate.month()}
|
||||||
|
on:change={e => (calendarDate = calendarDate.month(e.detail))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<NumberInput
|
||||||
|
value={calendarDate.year()}
|
||||||
|
min={0}
|
||||||
|
max={9999}
|
||||||
|
width={64}
|
||||||
|
on:change={handleCalendarYearChange}
|
||||||
|
on:input={cleanYear}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
aria-label="Previous"
|
||||||
|
title="Previous"
|
||||||
|
class="spectrum-ActionButton spectrum-ActionButton--quiet spectrum-Calendar-prevMonth"
|
||||||
|
on:click={() => (calendarDate = calendarDate.subtract(1, "month"))}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-ChevronLeft100"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Next"
|
||||||
|
title="Next"
|
||||||
|
class="spectrum-ActionButton spectrum-ActionButton--quiet spectrum-Calendar-nextMonth"
|
||||||
|
on:click={() => (calendarDate = calendarDate.add(1, "month"))}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-ChevronRight100"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="spectrum-Calendar-body"
|
||||||
|
aria-readonly="true"
|
||||||
|
aria-disabled="false"
|
||||||
|
>
|
||||||
|
<table role="presentation" class="spectrum-Calendar-table">
|
||||||
|
<thead role="presentation">
|
||||||
|
<tr>
|
||||||
|
{#each DaysOfWeek as day}
|
||||||
|
<th scope="col" class="spectrum-Calendar-tableCell">
|
||||||
|
<abbr class="spectrum-Calendar-dayOfWeek" title={day}>
|
||||||
|
{day[0]}
|
||||||
|
</abbr>
|
||||||
|
</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody role="presentation">
|
||||||
|
{#each mondays as monday}
|
||||||
|
<tr>
|
||||||
|
{#each [0, 1, 2, 3, 4, 5, 6] as dayOffset}
|
||||||
|
{@const date = monday.add(dayOffset, "days")}
|
||||||
|
{@const outsideMonth = date.month() !== calendarDate.month()}
|
||||||
|
<td
|
||||||
|
class="spectrum-Calendar-tableCell"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-selected="false"
|
||||||
|
aria-invalid="false"
|
||||||
|
title={date.format("dddd, MMMM D, YYYY")}
|
||||||
|
on:click={() => handleDateChange(date)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
role="presentation"
|
||||||
|
class="spectrum-Calendar-date"
|
||||||
|
class:is-outsideMonth={outsideMonth}
|
||||||
|
class:is-today={!outsideMonth && date.isSame(now, "day")}
|
||||||
|
class:is-selected={date.isSame(value, "day")}
|
||||||
|
>
|
||||||
|
{date.date()}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Calendar overrides */
|
||||||
|
.spectrum-Calendar {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-header {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-header button {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-date.is-outsideMonth {
|
||||||
|
visibility: visible;
|
||||||
|
color: var(--spectrum-global-color-gray-400);
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-date.is-today,
|
||||||
|
.spectrum-Calendar-date.is-today::before {
|
||||||
|
border-color: var(--spectrum-global-color-gray-400);
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-date.is-today.is-selected,
|
||||||
|
.spectrum-Calendar-date.is-today.is-selected::before {
|
||||||
|
border-color: var(
|
||||||
|
--primaryColorHover,
|
||||||
|
var(--spectrum-global-color-blue-700)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-date.is-selected:not(.is-range-selection) {
|
||||||
|
background: var(--primaryColor, var(--spectrum-global-color-blue-400));
|
||||||
|
}
|
||||||
|
.spectrum-Calendar tr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-tableCell {
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-nextMonth,
|
||||||
|
.spectrum-Calendar-prevMonth {
|
||||||
|
order: 1;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-date {
|
||||||
|
color: var(--spectrum-alias-text-color);
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-date.is-selected {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.spectrum-Calendar-dayOfWeek {
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style select */
|
||||||
|
.month-selector :global(.spectrum-Picker) {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
.month-selector :global(.spectrum-Picker:hover),
|
||||||
|
.month-selector :global(.spectrum-Picker.is-open) {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
|
.month-selector :global(.spectrum-Picker-label) {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,94 @@
|
||||||
|
<script>
|
||||||
|
import Icon from "../../../Icon/Icon.svelte"
|
||||||
|
import { getDateDisplayValue } from "../../../helpers"
|
||||||
|
|
||||||
|
export let anchor
|
||||||
|
export let disabled
|
||||||
|
export let readonly
|
||||||
|
export let error
|
||||||
|
export let focused
|
||||||
|
export let placeholder
|
||||||
|
export let id
|
||||||
|
export let value
|
||||||
|
export let icon
|
||||||
|
export let enableTime
|
||||||
|
export let timeOnly
|
||||||
|
|
||||||
|
$: displayValue = getDateDisplayValue(value, { enableTime, timeOnly })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div
|
||||||
|
bind:this={anchor}
|
||||||
|
class:is-disabled={disabled || readonly}
|
||||||
|
class:is-invalid={!!error}
|
||||||
|
class:is-focused={focused}
|
||||||
|
class="spectrum-InputGroup spectrum-Datepicker"
|
||||||
|
aria-readonly={readonly}
|
||||||
|
aria-required="false"
|
||||||
|
aria-haspopup="true"
|
||||||
|
on:click
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
|
class:is-disabled={disabled}
|
||||||
|
class:is-invalid={!!error}
|
||||||
|
>
|
||||||
|
{#if !!error}
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
<input
|
||||||
|
{disabled}
|
||||||
|
{readonly}
|
||||||
|
data-input
|
||||||
|
type="text"
|
||||||
|
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
||||||
|
class:is-disabled={disabled}
|
||||||
|
{placeholder}
|
||||||
|
{id}
|
||||||
|
value={displayValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if !disabled && !readonly}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
||||||
|
tabindex="-1"
|
||||||
|
class:is-invalid={!!error}
|
||||||
|
>
|
||||||
|
<Icon name={icon} />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Date label overrides */
|
||||||
|
.spectrum-Textfield-input {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.spectrum-Textfield:not(.is-disabled):hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.spectrum-Datepicker {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.spectrum-Datepicker .spectrum-Textfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.is-disabled {
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
input:read-only {
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-top-right-radius: var(--spectrum-textfield-border-radius);
|
||||||
|
border-bottom-right-radius: var(--spectrum-textfield-border-radius);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,83 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/calendar/dist/index-vars.css"
|
||||||
|
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
||||||
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
|
import Popover from "../../../Popover/Popover.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import DateInput from "./DateInput.svelte"
|
||||||
|
import { parseDate } from "../../../helpers"
|
||||||
|
import DatePickerPopoverContents from "./DatePickerPopoverContents.svelte"
|
||||||
|
|
||||||
|
export let id = null
|
||||||
|
export let disabled = false
|
||||||
|
export let readonly = false
|
||||||
|
export let error = null
|
||||||
|
export let enableTime = true
|
||||||
|
export let value = null
|
||||||
|
export let placeholder = null
|
||||||
|
export let timeOnly = false
|
||||||
|
export let ignoreTimezones = false
|
||||||
|
export let useKeyboardShortcuts = true
|
||||||
|
export let appendTo = null
|
||||||
|
export let api = null
|
||||||
|
export let align = "left"
|
||||||
|
|
||||||
|
let isOpen = false
|
||||||
|
let anchor
|
||||||
|
let popover
|
||||||
|
|
||||||
|
$: parsedValue = parseDate(value, { timeOnly, enableTime })
|
||||||
|
|
||||||
|
const onOpen = () => {
|
||||||
|
isOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
isOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
api = {
|
||||||
|
open: () => popover?.show(),
|
||||||
|
close: () => popover?.hide(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DateInput
|
||||||
|
bind:anchor
|
||||||
|
{disabled}
|
||||||
|
{readonly}
|
||||||
|
{error}
|
||||||
|
{placeholder}
|
||||||
|
{id}
|
||||||
|
{enableTime}
|
||||||
|
{timeOnly}
|
||||||
|
focused={isOpen}
|
||||||
|
value={parsedValue}
|
||||||
|
on:click={popover?.show}
|
||||||
|
icon={timeOnly ? "Clock" : "Calendar"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
bind:this={popover}
|
||||||
|
on:open
|
||||||
|
on:close
|
||||||
|
on:open={onOpen}
|
||||||
|
on:close={onClose}
|
||||||
|
portalTarget={appendTo}
|
||||||
|
{anchor}
|
||||||
|
{align}
|
||||||
|
resizable={false}
|
||||||
|
>
|
||||||
|
{#if isOpen}
|
||||||
|
<DatePickerPopoverContents
|
||||||
|
{useKeyboardShortcuts}
|
||||||
|
{ignoreTimezones}
|
||||||
|
{enableTime}
|
||||||
|
{timeOnly}
|
||||||
|
value={parsedValue}
|
||||||
|
on:change
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</Popover>
|
|
@ -0,0 +1,102 @@
|
||||||
|
<script>
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
import TimePicker from "./TimePicker.svelte"
|
||||||
|
import Calendar from "./Calendar.svelte"
|
||||||
|
import ActionButton from "../../../ActionButton/ActionButton.svelte"
|
||||||
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
|
import { stringifyDate } from "../../../helpers"
|
||||||
|
|
||||||
|
export let useKeyboardShortcuts = true
|
||||||
|
export let ignoreTimezones
|
||||||
|
export let enableTime
|
||||||
|
export let timeOnly
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
let calendar
|
||||||
|
|
||||||
|
$: showCalendar = !timeOnly
|
||||||
|
$: showTime = enableTime || timeOnly
|
||||||
|
|
||||||
|
const setToNow = () => {
|
||||||
|
const now = dayjs()
|
||||||
|
calendar?.setDate(now)
|
||||||
|
handleChange(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = date => {
|
||||||
|
dispatch(
|
||||||
|
"change",
|
||||||
|
stringifyDate(date, { enableTime, timeOnly, ignoreTimezones })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearDateOnBackspace = event => {
|
||||||
|
// Ignore if we're typing a value
|
||||||
|
if (document.activeElement?.tagName.toLowerCase() === "input") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
||||||
|
dispatch("change", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (useKeyboardShortcuts) {
|
||||||
|
document.addEventListener("keyup", clearDateOnBackspace)
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keyup", clearDateOnBackspace)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="date-time-popover">
|
||||||
|
{#if showCalendar}
|
||||||
|
<Calendar
|
||||||
|
{value}
|
||||||
|
on:change={e => handleChange(e.detail)}
|
||||||
|
bind:this={calendar}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<div class="footer" class:spaced={showCalendar}>
|
||||||
|
{#if showTime}
|
||||||
|
<TimePicker {value} on:change={e => handleChange(e.detail)} />
|
||||||
|
{/if}
|
||||||
|
<div class="actions">
|
||||||
|
<ActionButton
|
||||||
|
disabled={!value}
|
||||||
|
size="S"
|
||||||
|
on:click={() => dispatch("change", null)}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton size="S" on:click={setToNow}>
|
||||||
|
{showTime ? "Now" : "Today"}
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.date-time-popover {
|
||||||
|
padding: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 60px;
|
||||||
|
}
|
||||||
|
.footer.spaced {
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
padding: 4px 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,54 @@
|
||||||
|
<script>
|
||||||
|
export let value
|
||||||
|
export let min
|
||||||
|
export let max
|
||||||
|
export let hideArrows = false
|
||||||
|
export let width
|
||||||
|
|
||||||
|
$: style = width ? `width:${width}px;` : ""
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class:hide-arrows={hideArrows}
|
||||||
|
type="number"
|
||||||
|
{style}
|
||||||
|
{value}
|
||||||
|
{min}
|
||||||
|
{max}
|
||||||
|
onclick="this.select()"
|
||||||
|
on:change
|
||||||
|
on:input
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: var(--spectrum-alias-text-color);
|
||||||
|
padding: 4px 6px 5px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 130ms ease-out;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
box-sizing: content-box !important;
|
||||||
|
}
|
||||||
|
input:focus,
|
||||||
|
input:hover {
|
||||||
|
--space: 30px;
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide built-in arrows */
|
||||||
|
input.hide-arrows::-webkit-outer-spin-button,
|
||||||
|
input.hide-arrows::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
input.hide-arrows {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script>
|
||||||
|
import { cleanInput } from "./utils"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
import NumberInput from "./NumberInput.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: displayValue = value || dayjs()
|
||||||
|
|
||||||
|
const handleHourChange = e => {
|
||||||
|
dispatch("change", displayValue.hour(parseInt(e.target.value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMinuteChange = e => {
|
||||||
|
dispatch("change", displayValue.minute(parseInt(e.target.value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanHour = cleanInput({ max: 23, pad: 2, fallback: "00" })
|
||||||
|
const cleanMinute = cleanInput({ max: 59, pad: 2, fallback: "00" })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="time-picker">
|
||||||
|
<NumberInput
|
||||||
|
hideArrows
|
||||||
|
value={displayValue.hour().toString().padStart(2, "0")}
|
||||||
|
min={0}
|
||||||
|
max={23}
|
||||||
|
width={20}
|
||||||
|
on:input={cleanHour}
|
||||||
|
on:change={handleHourChange}
|
||||||
|
/>
|
||||||
|
<span>:</span>
|
||||||
|
<NumberInput
|
||||||
|
hideArrows
|
||||||
|
value={displayValue.minute().toString().padStart(2, "0")}
|
||||||
|
min={0}
|
||||||
|
max={59}
|
||||||
|
width={20}
|
||||||
|
on:input={cleanMinute}
|
||||||
|
on:change={handleMinuteChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.time-picker {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.time-picker span {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
z-index: 0;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,14 @@
|
||||||
|
export const cleanInput = ({ max, pad, fallback }) => {
|
||||||
|
return e => {
|
||||||
|
if (e.target.value) {
|
||||||
|
const value = parseInt(e.target.value)
|
||||||
|
if (isNaN(value)) {
|
||||||
|
e.target.value = fallback
|
||||||
|
} else {
|
||||||
|
e.target.value = Math.min(max, value).toString().padStart(pad, "0")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e.target.value = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script>
|
||||||
|
import CoreDatePicker from "./DatePicker/DatePicker.svelte"
|
||||||
|
import Icon from "../../Icon/Icon.svelte"
|
||||||
|
|
||||||
|
export let value = null
|
||||||
|
export let disabled = false
|
||||||
|
export let readonly = false
|
||||||
|
export let error = null
|
||||||
|
export let appendTo = undefined
|
||||||
|
export let ignoreTimezones = false
|
||||||
|
|
||||||
|
let fromDate
|
||||||
|
let toDate
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="date-range">
|
||||||
|
<CoreDatePicker
|
||||||
|
value={fromDate}
|
||||||
|
on:change={e => (fromDate = e.detail)}
|
||||||
|
enableTime={false}
|
||||||
|
/>
|
||||||
|
<div class="arrow">
|
||||||
|
<Icon name="ChevronRight" />
|
||||||
|
</div>
|
||||||
|
<CoreDatePicker
|
||||||
|
value={toDate}
|
||||||
|
on:change={e => (toDate = e.detail)}
|
||||||
|
enableTime={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.date-range {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
border: 1px solid var(--spectrum-alias-border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.date-range :global(.spectrum-InputGroup),
|
||||||
|
.date-range :global(.spectrum-Textfield),
|
||||||
|
.date-range :global(input) {
|
||||||
|
min-width: 0 !important;
|
||||||
|
width: 150px !important;
|
||||||
|
}
|
||||||
|
.date-range :global(input) {
|
||||||
|
border: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.date-range :global(button) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.date-range :global(> :first-child input),
|
||||||
|
.date-range :global(> :first-child) {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
.date-range :global(> :last-child input),
|
||||||
|
.date-range :global(> :last-child) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
.arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(-50%);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -155,6 +155,7 @@
|
||||||
useAnchorWidth={!autoWidth}
|
useAnchorWidth={!autoWidth}
|
||||||
maxWidth={autoWidth ? 400 : null}
|
maxWidth={autoWidth ? 400 : null}
|
||||||
customHeight={customPopoverHeight}
|
customHeight={customPopoverHeight}
|
||||||
|
maxHeight={240}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="popover-content"
|
class="popover-content"
|
||||||
|
|
|
@ -8,7 +8,9 @@ export { default as CoreTextArea } from "./TextArea.svelte"
|
||||||
export { default as CoreCombobox } from "./Combobox.svelte"
|
export { default as CoreCombobox } from "./Combobox.svelte"
|
||||||
export { default as CoreSwitch } from "./Switch.svelte"
|
export { default as CoreSwitch } from "./Switch.svelte"
|
||||||
export { default as CoreSearch } from "./Search.svelte"
|
export { default as CoreSearch } from "./Search.svelte"
|
||||||
export { default as CoreDatePicker } from "./DatePicker.svelte"
|
export { default as CoreDatePicker } from "./DatePicker/DatePicker.svelte"
|
||||||
|
export { default as CoreDatePickerPopoverContents } from "./DatePicker/DatePickerPopoverContents.svelte"
|
||||||
|
export { default as CoreDateRangePicker } from "./DateRangePicker.svelte"
|
||||||
export { default as CoreDropzone } from "./Dropzone.svelte"
|
export { default as CoreDropzone } from "./Dropzone.svelte"
|
||||||
export { default as CoreStepper } from "./Stepper.svelte"
|
export { default as CoreStepper } from "./Stepper.svelte"
|
||||||
export { default as CoreRichTextField } from "./RichTextField.svelte"
|
export { default as CoreRichTextField } from "./RichTextField.svelte"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import DatePicker from "./Core/DatePicker.svelte"
|
import DatePicker from "./Core/DatePicker/DatePicker.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let value = null
|
export let value = null
|
||||||
|
@ -11,22 +11,15 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let enableTime = true
|
export let enableTime = true
|
||||||
export let timeOnly = false
|
export let timeOnly = false
|
||||||
export let time24hr = false
|
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let appendTo = undefined
|
export let appendTo = undefined
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
export let range = false
|
|
||||||
export let helpText = null
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
if (range) {
|
value = e.detail
|
||||||
// Flatpickr cant take two dates and work out what to display, needs to be provided a string.
|
|
||||||
// Like - "Date1 to Date2". Hence passing in that specifically from the array
|
|
||||||
value = e?.detail[1]
|
|
||||||
} else {
|
|
||||||
value = e.detail
|
|
||||||
}
|
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -40,10 +33,8 @@
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{enableTime}
|
{enableTime}
|
||||||
{timeOnly}
|
{timeOnly}
|
||||||
{time24hr}
|
|
||||||
{appendTo}
|
{appendTo}
|
||||||
{ignoreTimezones}
|
{ignoreTimezones}
|
||||||
{range}
|
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<script>
|
||||||
|
import Field from "./Field.svelte"
|
||||||
|
import DateRangePicker from "./Core/DateRangePicker.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let value = null
|
||||||
|
export let label = null
|
||||||
|
export let labelPosition = "above"
|
||||||
|
export let disabled = false
|
||||||
|
export let readonly = false
|
||||||
|
export let error = null
|
||||||
|
export let helpText = null
|
||||||
|
export let appendTo = undefined
|
||||||
|
export let ignoreTimezones = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const onChange = e => {
|
||||||
|
value = e.detail
|
||||||
|
dispatch("change", e.detail)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
|
<DateRangePicker
|
||||||
|
{error}
|
||||||
|
{disabled}
|
||||||
|
{readonly}
|
||||||
|
{value}
|
||||||
|
{appendTo}
|
||||||
|
{ignoreTimezones}
|
||||||
|
on:change={onChange}
|
||||||
|
/>
|
||||||
|
</Field>
|
|
@ -7,11 +7,11 @@
|
||||||
export let narrower = false
|
export let narrower = false
|
||||||
export let noPadding = false
|
export let noPadding = false
|
||||||
|
|
||||||
let sidePanelVisble = false
|
let sidePanelVisible = false
|
||||||
|
|
||||||
setContext("side-panel", {
|
setContext("side-panel", {
|
||||||
open: () => (sidePanelVisble = true),
|
open: () => (sidePanelVisible = true),
|
||||||
close: () => (sidePanelVisble = false),
|
close: () => (sidePanelVisible = false),
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="side-panel"
|
id="side-panel"
|
||||||
class:visible={sidePanelVisble}
|
class:visible={sidePanelVisible}
|
||||||
use:clickOutside={() => {
|
use:clickOutside={() => {
|
||||||
sidePanelVisble = false
|
sidePanelVisible = false
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<slot name="side-panel" />
|
<slot name="side-panel" />
|
||||||
|
|
|
@ -18,13 +18,15 @@
|
||||||
export let open = false
|
export let open = false
|
||||||
export let useAnchorWidth = false
|
export let useAnchorWidth = false
|
||||||
export let dismissible = true
|
export let dismissible = true
|
||||||
export let offset = 5
|
export let offset = 4
|
||||||
export let customHeight
|
export let customHeight
|
||||||
export let animate = true
|
export let animate = true
|
||||||
export let customZindex
|
export let customZindex
|
||||||
export let handlePostionUpdate
|
export let handlePostionUpdate
|
||||||
export let showPopover = true
|
export let showPopover = true
|
||||||
export let clickOutsideOverride = false
|
export let clickOutsideOverride = false
|
||||||
|
export let resizable = true
|
||||||
|
export let wrap = false
|
||||||
|
|
||||||
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
||||||
|
|
||||||
|
@ -91,6 +93,8 @@
|
||||||
useAnchorWidth,
|
useAnchorWidth,
|
||||||
offset,
|
offset,
|
||||||
customUpdate: handlePostionUpdate,
|
customUpdate: handlePostionUpdate,
|
||||||
|
resizable,
|
||||||
|
wrap,
|
||||||
}}
|
}}
|
||||||
use:clickOutside={{
|
use:clickOutside={{
|
||||||
callback: dismissible ? handleOutsideClick : () => {},
|
callback: dismissible ? handleOutsideClick : () => {},
|
||||||
|
@ -116,12 +120,11 @@
|
||||||
min-width: var(--spectrum-global-dimension-size-2000);
|
min-width: var(--spectrum-global-dimension-size-2000);
|
||||||
border-color: var(--spectrum-global-color-gray-300);
|
border-color: var(--spectrum-global-color-gray-300);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
transition: opacity 260ms ease-out, transform 260ms ease-out;
|
transition: opacity 260ms ease-out;
|
||||||
}
|
}
|
||||||
.hidden {
|
.hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transform: translateY(-20px);
|
|
||||||
}
|
}
|
||||||
.customZindex {
|
.customZindex {
|
||||||
z-index: var(--customZindex) !important;
|
z-index: var(--customZindex) !important;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
|
||||||
export const deepGet = helpers.deepGet
|
export const deepGet = helpers.deepGet
|
||||||
|
|
||||||
|
@ -115,3 +116,110 @@ export const copyToClipboard = value => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parsed a date value. This is usually an ISO string, but can be a
|
||||||
|
// bunch of different formats and shapes depending on schema flags.
|
||||||
|
export const parseDate = (value, { enableTime = true }) => {
|
||||||
|
// If empty then invalid
|
||||||
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certain string values need transformed
|
||||||
|
if (typeof value === "string") {
|
||||||
|
// Check for time only values
|
||||||
|
if (!isNaN(new Date(`0-${value}`))) {
|
||||||
|
value = `0-${value}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// If date only, check for cases where we received a UTC string
|
||||||
|
else if (!enableTime && value.endsWith("Z")) {
|
||||||
|
value = value.split("Z")[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse value and check for validity
|
||||||
|
const parsedDate = dayjs(value)
|
||||||
|
if (!parsedDate.isValid()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// By rounding to the nearest second we avoid locking up in an endless
|
||||||
|
// loop in the builder, caused by potentially enriching {{ now }} to every
|
||||||
|
// millisecond.
|
||||||
|
return dayjs(Math.floor(parsedDate.valueOf() / 1000) * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stringifies a dayjs object to create an ISO string that respects the various
|
||||||
|
// schema flags
|
||||||
|
export const stringifyDate = (
|
||||||
|
value,
|
||||||
|
{ enableTime = true, timeOnly = false, ignoreTimezones = false } = {}
|
||||||
|
) => {
|
||||||
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time only fields always ignore timezones, otherwise they make no sense.
|
||||||
|
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
|
||||||
|
// time picked, without timezone
|
||||||
|
const offsetForTimezone = (enableTime && ignoreTimezones) || timeOnly
|
||||||
|
if (offsetForTimezone) {
|
||||||
|
// Ensure we use the correct offset for the date
|
||||||
|
const referenceDate = timeOnly ? new Date() : value.toDate()
|
||||||
|
const offset = referenceDate.getTimezoneOffset() * 60000
|
||||||
|
return new Date(value.valueOf() - offset).toISOString().slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For date-only fields, construct a manual timestamp string without a time
|
||||||
|
// or time zone
|
||||||
|
else if (!enableTime) {
|
||||||
|
const year = value.year()
|
||||||
|
const month = `${value.month() + 1}`.padStart(2, "0")
|
||||||
|
const day = `${value.date()}`.padStart(2, "0")
|
||||||
|
return `${year}-${month}-${day}T00:00:00.000`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise use a normal ISO string with time and timezone
|
||||||
|
else {
|
||||||
|
return value.toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the dayjs-compatible format of the browser's default locale
|
||||||
|
const getPatternForPart = part => {
|
||||||
|
switch (part.type) {
|
||||||
|
case "day":
|
||||||
|
return "D".repeat(part.value.length)
|
||||||
|
case "month":
|
||||||
|
return "M".repeat(part.value.length)
|
||||||
|
case "year":
|
||||||
|
return "Y".repeat(part.value.length)
|
||||||
|
case "literal":
|
||||||
|
return part.value
|
||||||
|
default:
|
||||||
|
console.log("Unsupported date part", part)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const localeDateFormat = new Intl.DateTimeFormat()
|
||||||
|
.formatToParts(new Date("2021-01-01"))
|
||||||
|
.map(getPatternForPart)
|
||||||
|
.join("")
|
||||||
|
|
||||||
|
// Formats a dayjs date according to schema flags
|
||||||
|
export const getDateDisplayValue = (
|
||||||
|
value,
|
||||||
|
{ enableTime = true, timeOnly = false } = {}
|
||||||
|
) => {
|
||||||
|
if (!value?.isValid()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if (timeOnly) {
|
||||||
|
return value.format("HH:mm")
|
||||||
|
} else if (!enableTime) {
|
||||||
|
return value.format(localeDateFormat)
|
||||||
|
} else {
|
||||||
|
return value.format(`${localeDateFormat} HH:mm`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,13 +3,34 @@ import "./bbui.css"
|
||||||
// Spectrum icons
|
// Spectrum icons
|
||||||
import "@spectrum-css/icon/dist/index-vars.css"
|
import "@spectrum-css/icon/dist/index-vars.css"
|
||||||
|
|
||||||
// Components
|
// Form components
|
||||||
export { default as Input } from "./Form/Input.svelte"
|
export { default as Input } from "./Form/Input.svelte"
|
||||||
export { default as Stepper } from "./Form/Stepper.svelte"
|
export { default as Stepper } from "./Form/Stepper.svelte"
|
||||||
export { default as TextArea } from "./Form/TextArea.svelte"
|
export { default as TextArea } from "./Form/TextArea.svelte"
|
||||||
export { default as Select } from "./Form/Select.svelte"
|
export { default as Select } from "./Form/Select.svelte"
|
||||||
export { default as Combobox } from "./Form/Combobox.svelte"
|
export { default as Combobox } from "./Form/Combobox.svelte"
|
||||||
export { default as Dropzone } from "./Form/Dropzone.svelte"
|
export { default as Dropzone } from "./Form/Dropzone.svelte"
|
||||||
|
export { default as DatePicker } from "./Form/DatePicker.svelte"
|
||||||
|
export { default as DateRangePicker } from "./Form/DateRangePicker.svelte"
|
||||||
|
export { default as Toggle } from "./Form/Toggle.svelte"
|
||||||
|
export { default as RadioGroup } from "./Form/RadioGroup.svelte"
|
||||||
|
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
||||||
|
export { default as InputDropdown } from "./Form/InputDropdown.svelte"
|
||||||
|
export { default as PickerDropdown } from "./Form/PickerDropdown.svelte"
|
||||||
|
export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
|
||||||
|
export { default as Multiselect } from "./Form/Multiselect.svelte"
|
||||||
|
export { default as Search } from "./Form/Search.svelte"
|
||||||
|
export { default as RichTextField } from "./Form/RichTextField.svelte"
|
||||||
|
export { default as Slider } from "./Form/Slider.svelte"
|
||||||
|
export { default as File } from "./Form/File.svelte"
|
||||||
|
|
||||||
|
// Core form components to be used elsewhere (standard components)
|
||||||
|
export * from "./Form/Core"
|
||||||
|
|
||||||
|
// Fancy form components
|
||||||
|
export * from "./FancyForm"
|
||||||
|
|
||||||
|
// Components
|
||||||
export { default as Drawer } from "./Drawer/Drawer.svelte"
|
export { default as Drawer } from "./Drawer/Drawer.svelte"
|
||||||
export { default as DrawerContent } from "./Drawer/DrawerContent.svelte"
|
export { default as DrawerContent } from "./Drawer/DrawerContent.svelte"
|
||||||
export { default as Avatar } from "./Avatar/Avatar.svelte"
|
export { default as Avatar } from "./Avatar/Avatar.svelte"
|
||||||
|
@ -21,12 +42,6 @@ export { default as ButtonGroup } from "./ButtonGroup/ButtonGroup.svelte"
|
||||||
export { default as ClearButton } from "./ClearButton/ClearButton.svelte"
|
export { default as ClearButton } from "./ClearButton/ClearButton.svelte"
|
||||||
export { default as Icon } from "./Icon/Icon.svelte"
|
export { default as Icon } from "./Icon/Icon.svelte"
|
||||||
export { default as IconAvatar } from "./Icon/IconAvatar.svelte"
|
export { default as IconAvatar } from "./Icon/IconAvatar.svelte"
|
||||||
export { default as Toggle } from "./Form/Toggle.svelte"
|
|
||||||
export { default as RadioGroup } from "./Form/RadioGroup.svelte"
|
|
||||||
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
|
||||||
export { default as InputDropdown } from "./Form/InputDropdown.svelte"
|
|
||||||
export { default as PickerDropdown } from "./Form/PickerDropdown.svelte"
|
|
||||||
export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
|
|
||||||
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
||||||
export { default as Popover } from "./Popover/Popover.svelte"
|
export { default as Popover } from "./Popover/Popover.svelte"
|
||||||
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
||||||
|
@ -37,11 +52,6 @@ export { default as Page } from "./Layout/Page.svelte"
|
||||||
export { default as Link } from "./Link/Link.svelte"
|
export { default as Link } from "./Link/Link.svelte"
|
||||||
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
|
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
|
||||||
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
|
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
|
||||||
export {
|
|
||||||
default as AbsTooltip,
|
|
||||||
TooltipPosition,
|
|
||||||
TooltipType,
|
|
||||||
} from "./Tooltip/AbsTooltip.svelte"
|
|
||||||
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
|
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
|
||||||
export { default as Menu } from "./Menu/Menu.svelte"
|
export { default as Menu } from "./Menu/Menu.svelte"
|
||||||
export { default as MenuSection } from "./Menu/Section.svelte"
|
export { default as MenuSection } from "./Menu/Section.svelte"
|
||||||
|
@ -53,8 +63,6 @@ export { default as NotificationDisplay } from "./Notification/NotificationDispl
|
||||||
export { default as Notification } from "./Notification/Notification.svelte"
|
export { default as Notification } from "./Notification/Notification.svelte"
|
||||||
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
|
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
|
||||||
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
|
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
|
||||||
export { default as DatePicker } from "./Form/DatePicker.svelte"
|
|
||||||
export { default as Multiselect } from "./Form/Multiselect.svelte"
|
|
||||||
export { default as Context } from "./context"
|
export { default as Context } from "./context"
|
||||||
export { default as Table } from "./Table/Table.svelte"
|
export { default as Table } from "./Table/Table.svelte"
|
||||||
export { default as Tabs } from "./Tabs/Tabs.svelte"
|
export { default as Tabs } from "./Tabs/Tabs.svelte"
|
||||||
|
@ -64,7 +72,6 @@ export { default as Tag } from "./Tags/Tag.svelte"
|
||||||
export { default as TreeView } from "./TreeView/Tree.svelte"
|
export { default as TreeView } from "./TreeView/Tree.svelte"
|
||||||
export { default as TreeItem } from "./TreeView/Item.svelte"
|
export { default as TreeItem } from "./TreeView/Item.svelte"
|
||||||
export { default as Divider } from "./Divider/Divider.svelte"
|
export { default as Divider } from "./Divider/Divider.svelte"
|
||||||
export { default as Search } from "./Form/Search.svelte"
|
|
||||||
export { default as Pagination } from "./Pagination/Pagination.svelte"
|
export { default as Pagination } from "./Pagination/Pagination.svelte"
|
||||||
export { default as Badge } from "./Badge/Badge.svelte"
|
export { default as Badge } from "./Badge/Badge.svelte"
|
||||||
export { default as StatusLight } from "./StatusLight/StatusLight.svelte"
|
export { default as StatusLight } from "./StatusLight/StatusLight.svelte"
|
||||||
|
@ -76,15 +83,15 @@ export { default as CopyInput } from "./Input/CopyInput.svelte"
|
||||||
export { default as BannerDisplay } from "./Banner/BannerDisplay.svelte"
|
export { default as BannerDisplay } from "./Banner/BannerDisplay.svelte"
|
||||||
export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte"
|
export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte"
|
||||||
export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte"
|
export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte"
|
||||||
export { default as RichTextField } from "./Form/RichTextField.svelte"
|
|
||||||
export { default as List } from "./List/List.svelte"
|
export { default as List } from "./List/List.svelte"
|
||||||
export { default as ListItem } from "./List/ListItem.svelte"
|
export { default as ListItem } from "./List/ListItem.svelte"
|
||||||
export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
|
export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
|
||||||
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
|
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
|
||||||
export { default as Slider } from "./Form/Slider.svelte"
|
|
||||||
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
||||||
export { default as File } from "./Form/File.svelte"
|
|
||||||
export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte"
|
export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte"
|
||||||
|
export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"
|
||||||
|
export { TooltipPosition, TooltipType } from "./Tooltip/AbsTooltip.svelte"
|
||||||
|
|
||||||
// Renderers
|
// Renderers
|
||||||
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
||||||
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
|
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
|
||||||
|
@ -96,9 +103,6 @@ export { default as Heading } from "./Typography/Heading.svelte"
|
||||||
export { default as Detail } from "./Typography/Detail.svelte"
|
export { default as Detail } from "./Typography/Detail.svelte"
|
||||||
export { default as Code } from "./Typography/Code.svelte"
|
export { default as Code } from "./Typography/Code.svelte"
|
||||||
|
|
||||||
// Core form components to be used elsewhere (standard components)
|
|
||||||
export * from "./Form/Core"
|
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
export { default as autoResizeTextArea } from "./Actions/autoresize_textarea"
|
export { default as autoResizeTextArea } from "./Actions/autoresize_textarea"
|
||||||
export { default as positionDropdown } from "./Actions/position_dropdown"
|
export { default as positionDropdown } from "./Actions/position_dropdown"
|
||||||
|
@ -110,6 +114,3 @@ export { banner, BANNER_TYPES } from "./Stores/banner"
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
export * as Helpers from "./helpers"
|
export * as Helpers from "./helpers"
|
||||||
|
|
||||||
// Fancy form components
|
|
||||||
export * from "./FancyForm"
|
|
||||||
|
|
|
@ -106,6 +106,5 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -498,6 +498,7 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<Select
|
<Select
|
||||||
|
placeholder={null}
|
||||||
disabled={!typeEnabled}
|
disabled={!typeEnabled}
|
||||||
bind:value={editableColumn.fieldId}
|
bind:value={editableColumn.fieldId}
|
||||||
on:change={onHandleTypeChange}
|
on:change={onHandleTypeChange}
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<script>
|
||||||
|
import { DatePicker } from "@budibase/bbui"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { memo } from "@budibase/frontend-core"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const valueStore = memo(value)
|
||||||
|
|
||||||
|
let date1
|
||||||
|
let date2
|
||||||
|
|
||||||
|
$: valueStore.set(value)
|
||||||
|
$: parseValue($valueStore)
|
||||||
|
|
||||||
|
const parseValue = value => {
|
||||||
|
if (!Array.isArray(value) || !value[0] || !value[1]) {
|
||||||
|
date1 = null
|
||||||
|
date2 = null
|
||||||
|
} else {
|
||||||
|
date1 = value[0]
|
||||||
|
date2 = value[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangeDate1 = e => {
|
||||||
|
date1 = e.detail ? dayjs(e.detail).startOf("day") : null
|
||||||
|
if (date1 && (!date2 || date1.isAfter(date2))) {
|
||||||
|
date2 = date1.endOf("day")
|
||||||
|
} else if (!date1) {
|
||||||
|
date2 = null
|
||||||
|
}
|
||||||
|
broadcastChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangeDate2 = e => {
|
||||||
|
date2 = e.detail ? dayjs(e.detail).endOf("day") : null
|
||||||
|
if (date2 && (!date1 || date2.isBefore(date1))) {
|
||||||
|
date1 = date2.startOf("day")
|
||||||
|
} else if (!date2) {
|
||||||
|
date1 = null
|
||||||
|
}
|
||||||
|
broadcastChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const broadcastChange = () => {
|
||||||
|
dispatch("change", [date1, date2])
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="date-range-picker">
|
||||||
|
<DatePicker
|
||||||
|
value={date1}
|
||||||
|
label="Date range"
|
||||||
|
enableTime={false}
|
||||||
|
on:change={onChangeDate1}
|
||||||
|
/>
|
||||||
|
<DatePicker value={date2} enableTime={false} on:change={onChangeDate2} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.date-range-picker {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlap date pickers to remove double border, but put the focused one on top */
|
||||||
|
.date-range-picker :global(.spectrum-InputGroup.is-focused) {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.date-range-picker :global(> :last-child) {
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove border radius at the join */
|
||||||
|
.date-range-picker :global(> :first-child .spectrum-InputGroup),
|
||||||
|
.date-range-picker :global(> :first-child .spectrum-Picker) {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
.date-range-picker :global(> :last-child .spectrum-InputGroup),
|
||||||
|
.date-range-picker :global(> :last-child .spectrum-Textfield-input) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -75,14 +75,12 @@
|
||||||
.relationship-container {
|
.relationship-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.relationship-part {
|
.relationship-part {
|
||||||
flex-basis: 70%;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.relationship-type {
|
.relationship-type {
|
||||||
flex-basis: 30%;
|
flex: 0 0 128px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -25,6 +25,6 @@
|
||||||
name="field"
|
name="field"
|
||||||
headings
|
headings
|
||||||
options={SchemaTypeOptionsExpanded}
|
options={SchemaTypeOptionsExpanded}
|
||||||
compare={(option, value) => option.type === value.type}
|
compare={(option, value) => option.type === value?.type}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
|
@ -695,7 +695,7 @@
|
||||||
menuItems={schemaMenuItems}
|
menuItems={schemaMenuItems}
|
||||||
showMenu={!schemaReadOnly}
|
showMenu={!schemaReadOnly}
|
||||||
readOnly={schemaReadOnly}
|
readOnly={schemaReadOnly}
|
||||||
compare={(option, value) => option.type === value.type}
|
compare={(option, value) => option.type === value?.type}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -264,6 +264,7 @@ export const SchemaTypeOptions = [
|
||||||
{ label: "Number", value: FieldType.NUMBER },
|
{ label: "Number", value: FieldType.NUMBER },
|
||||||
{ label: "Boolean", value: FieldType.BOOLEAN },
|
{ label: "Boolean", value: FieldType.BOOLEAN },
|
||||||
{ label: "Datetime", value: FieldType.DATETIME },
|
{ label: "Datetime", value: FieldType.DATETIME },
|
||||||
|
{ label: "JSON", value: FieldType.JSON },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const SchemaTypeOptionsExpanded = SchemaTypeOptions.map(el => ({
|
export const SchemaTypeOptionsExpanded = SchemaTypeOptions.map(el => ({
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DatePicker,
|
|
||||||
Divider,
|
Divider,
|
||||||
Layout,
|
Layout,
|
||||||
notifications,
|
notifications,
|
||||||
|
@ -25,13 +24,13 @@
|
||||||
import BackupsDefault from "assets/backups-default.png"
|
import BackupsDefault from "assets/backups-default.png"
|
||||||
import { BackupTrigger, BackupType } from "constants/backend/backups"
|
import { BackupTrigger, BackupType } from "constants/backend/backups"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import DateRangePicker from "components/common/DateRangePicker.svelte"
|
||||||
|
|
||||||
let loading = true
|
let loading = true
|
||||||
let backupData = null
|
let backupData = null
|
||||||
let pageInfo = createPaginationStore()
|
let pageInfo = createPaginationStore()
|
||||||
let filterOpt = null
|
let filterOpt = null
|
||||||
let startDate = null
|
let dateRange = []
|
||||||
let endDate = null
|
|
||||||
let filters = [
|
let filters = [
|
||||||
{
|
{
|
||||||
label: "Manual backup",
|
label: "Manual backup",
|
||||||
|
@ -52,7 +51,7 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
$: page = $pageInfo.page
|
$: page = $pageInfo.page
|
||||||
$: fetchBackups(filterOpt, page, startDate, endDate)
|
$: fetchBackups(filterOpt, page, dateRange)
|
||||||
|
|
||||||
let schema = {
|
let schema = {
|
||||||
type: {
|
type: {
|
||||||
|
@ -99,13 +98,13 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchBackups(filters, page, startDate, endDate) {
|
async function fetchBackups(filters, page, dateRange) {
|
||||||
const response = await backups.searchBackups({
|
const response = await backups.searchBackups({
|
||||||
appId: $appStore.appId,
|
appId: $appStore.appId,
|
||||||
...filters,
|
...filters,
|
||||||
page,
|
page,
|
||||||
startDate,
|
startDate: dateRange[0],
|
||||||
endDate,
|
endDate: dateRange[1],
|
||||||
})
|
})
|
||||||
pageInfo.fetched(response.hasNextPage, response.nextPage)
|
pageInfo.fetched(response.hasNextPage, response.nextPage)
|
||||||
|
|
||||||
|
@ -165,7 +164,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await fetchBackups(filterOpt, page, startDate, endDate)
|
await fetchBackups(filterOpt, page, dateRange)
|
||||||
loading = false
|
loading = false
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -207,7 +206,7 @@
|
||||||
View plans
|
View plans
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{:else if !backupData?.length && !loading && !filterOpt && !startDate}
|
{:else if !backupData?.length && !loading && !filterOpt && !dateRange?.length}
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<Layout noPadding gap="S" justifyItems="center">
|
<Layout noPadding gap="S" justifyItems="center">
|
||||||
<img height="130px" src={BackupsDefault} alt="BackupsDefault" />
|
<img height="130px" src={BackupsDefault} alt="BackupsDefault" />
|
||||||
|
@ -236,21 +235,15 @@
|
||||||
bind:value={filterOpt}
|
bind:value={filterOpt}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DateRangePicker
|
||||||
range={true}
|
value={dateRange}
|
||||||
label="Date Range"
|
on:change={e => (dateRange = e.detail)}
|
||||||
on:change={e => {
|
|
||||||
if (e.detail[0].length > 1) {
|
|
||||||
startDate = e.detail[0][0].toISOString()
|
|
||||||
endDate = e.detail[0][1].toISOString()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button cta disabled={loading} on:click={createManualBackup}
|
<Button cta disabled={loading} on:click={createManualBackup}>
|
||||||
>Create new backup</Button
|
Create new backup
|
||||||
>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table">
|
<div class="table">
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
Icon,
|
Icon,
|
||||||
clickOutside,
|
clickOutside,
|
||||||
CoreTextArea,
|
CoreTextArea,
|
||||||
DatePicker,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
Helpers,
|
Helpers,
|
||||||
Divider,
|
Divider,
|
||||||
|
@ -27,6 +26,8 @@
|
||||||
import TimeRenderer from "./_components/TimeRenderer.svelte"
|
import TimeRenderer from "./_components/TimeRenderer.svelte"
|
||||||
import AppColumnRenderer from "./_components/AppColumnRenderer.svelte"
|
import AppColumnRenderer from "./_components/AppColumnRenderer.svelte"
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
|
import DateRangePicker from "components/common/DateRangePicker.svelte"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
date: { width: "0.8fr" },
|
date: { width: "0.8fr" },
|
||||||
|
@ -69,16 +70,13 @@
|
||||||
let sidePanelVisible = false
|
let sidePanelVisible = false
|
||||||
let wideSidePanel = false
|
let wideSidePanel = false
|
||||||
let timer
|
let timer
|
||||||
let startDate = new Date()
|
let dateRange = [dayjs().subtract(30, "days"), dayjs()]
|
||||||
startDate.setDate(startDate.getDate() - 30)
|
|
||||||
let endDate = new Date()
|
|
||||||
|
|
||||||
$: fetchUsers(userPage, userSearchTerm)
|
$: fetchUsers(userPage, userSearchTerm)
|
||||||
$: fetchLogs({
|
$: fetchLogs({
|
||||||
logsPage,
|
logsPage,
|
||||||
logSearchTerm,
|
logSearchTerm,
|
||||||
startDate,
|
dateRange,
|
||||||
endDate,
|
|
||||||
selectedUsers,
|
selectedUsers,
|
||||||
selectedApps,
|
selectedApps,
|
||||||
selectedEvents,
|
selectedEvents,
|
||||||
|
@ -136,8 +134,7 @@
|
||||||
const fetchLogs = async ({
|
const fetchLogs = async ({
|
||||||
logsPage,
|
logsPage,
|
||||||
logSearchTerm,
|
logSearchTerm,
|
||||||
startDate,
|
dateRange,
|
||||||
endDate,
|
|
||||||
selectedUsers,
|
selectedUsers,
|
||||||
selectedApps,
|
selectedApps,
|
||||||
selectedEvents,
|
selectedEvents,
|
||||||
|
@ -155,8 +152,8 @@
|
||||||
logsPageInfo.loading()
|
logsPageInfo.loading()
|
||||||
await auditLogs.search({
|
await auditLogs.search({
|
||||||
bookmark: logsPage,
|
bookmark: logsPage,
|
||||||
startDate,
|
startDate: dateRange[0],
|
||||||
endDate,
|
endDate: dateRange[1],
|
||||||
fullSearch: logSearchTerm,
|
fullSearch: logSearchTerm,
|
||||||
userIds: selectedUsers,
|
userIds: selectedUsers,
|
||||||
appIds: selectedApps,
|
appIds: selectedApps,
|
||||||
|
@ -214,8 +211,8 @@
|
||||||
const downloadLogs = async () => {
|
const downloadLogs = async () => {
|
||||||
try {
|
try {
|
||||||
window.location = auditLogs.getDownloadUrl({
|
window.location = auditLogs.getDownloadUrl({
|
||||||
startDate,
|
startDate: dateRange[0],
|
||||||
endDate,
|
endDate: dateRange[1],
|
||||||
fullSearch: logSearchTerm,
|
fullSearch: logSearchTerm,
|
||||||
userIds: selectedUsers,
|
userIds: selectedUsers,
|
||||||
appIds: selectedApps,
|
appIds: selectedApps,
|
||||||
|
@ -302,22 +299,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="date-picker">
|
<div class="date-picker">
|
||||||
<DatePicker
|
<DateRangePicker
|
||||||
value={[startDate, endDate]}
|
value={dateRange}
|
||||||
placeholder="Choose date range"
|
on:change={e => (dateRange = e.detail)}
|
||||||
range={true}
|
|
||||||
on:change={e => {
|
|
||||||
if (e.detail[0]?.length === 1) {
|
|
||||||
startDate = e.detail[0][0].toISOString()
|
|
||||||
endDate = ""
|
|
||||||
} else if (e.detail[0]?.length > 1) {
|
|
||||||
startDate = e.detail[0][0].toISOString()
|
|
||||||
endDate = e.detail[0][1].toISOString()
|
|
||||||
} else {
|
|
||||||
startDate = ""
|
|
||||||
endDate = ""
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="freeSearch">
|
<div class="freeSearch">
|
||||||
|
@ -488,7 +472,7 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-l);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-panel-icons {
|
.side-panel-icons {
|
||||||
|
@ -505,6 +489,13 @@
|
||||||
.date-picker {
|
.date-picker {
|
||||||
flex-basis: calc(70% - 32px);
|
flex-basis: calc(70% - 32px);
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.date-picker :global(.date-range-picker),
|
||||||
|
.date-picker :global(.spectrum-Form-item) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.freeSearch {
|
.freeSearch {
|
||||||
|
|
|
@ -3869,12 +3869,6 @@
|
||||||
"key": "timeOnly",
|
"key": "timeOnly",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "24-hour time",
|
|
||||||
"key": "time24hr",
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Ignore time zones",
|
"label": "Ignore time zones",
|
||||||
|
@ -6723,7 +6717,20 @@
|
||||||
"illegalChildren": ["section", "sidepanel"],
|
"illegalChildren": ["section", "sidepanel"],
|
||||||
"showEmptyState": false,
|
"showEmptyState": false,
|
||||||
"draggable": false,
|
"draggable": false,
|
||||||
"info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action."
|
"info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action.",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "ignoreClicksOutside",
|
||||||
|
"label": "Ignore clicks outside",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"key": "onClose",
|
||||||
|
"label": "On close"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"rowexplorer": {
|
"rowexplorer": {
|
||||||
"block": true,
|
"block": true,
|
||||||
|
|
|
@ -24,14 +24,7 @@
|
||||||
"@budibase/shared-core": "0.0.0",
|
"@budibase/shared-core": "0.0.0",
|
||||||
"@budibase/string-templates": "0.0.0",
|
"@budibase/string-templates": "0.0.0",
|
||||||
"@budibase/types": "0.0.0",
|
"@budibase/types": "0.0.0",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/card": "3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
|
||||||
"@spectrum-css/link": "^3.1.3",
|
|
||||||
"@spectrum-css/page": "^3.0.1",
|
|
||||||
"@spectrum-css/tag": "^3.1.4",
|
|
||||||
"@spectrum-css/typography": "^3.0.2",
|
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
|
||||||
"apexcharts": "^3.22.1",
|
"apexcharts": "^3.22.1",
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.10.8",
|
||||||
"downloadjs": "1.4.7",
|
"downloadjs": "1.4.7",
|
||||||
|
@ -41,7 +34,6 @@
|
||||||
"screenfull": "^6.0.1",
|
"screenfull": "^6.0.1",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-apexcharts": "^1.0.2",
|
"svelte-apexcharts": "^1.0.2",
|
||||||
"svelte-flatpickr": "^3.3.4",
|
|
||||||
"svelte-spa-router": "^4.0.1"
|
"svelte-spa-router": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -206,13 +206,6 @@
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<!--
|
|
||||||
Flatpickr needs to be inside the theme wrapper.
|
|
||||||
It also needs its own container because otherwise it hijacks
|
|
||||||
key events on the whole page. It is painful to work with.
|
|
||||||
-->
|
|
||||||
<div id="flatpickr-root" />
|
|
||||||
|
|
||||||
<!-- Modal container to ensure they sit on top -->
|
<!-- Modal container to ensure they sit on top -->
|
||||||
<div class="modal-container" />
|
<div class="modal-container" />
|
||||||
|
|
||||||
|
|
|
@ -60,16 +60,6 @@
|
||||||
--spectrum-link-primary-m-text-color-hover: var(--primaryColorHover);
|
--spectrum-link-primary-m-text-color-hover: var(--primaryColorHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Theme flatpickr */
|
|
||||||
:global(.flatpickr-day.selected) {
|
|
||||||
background: var(--primaryColor);
|
|
||||||
border-color: var(--primaryColor);
|
|
||||||
}
|
|
||||||
:global(.flatpickr-day.selected:hover) {
|
|
||||||
background: var(--primaryColorHover);
|
|
||||||
border-color: var(--primaryColorHover);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom scrollbars */
|
/* Custom scrollbars */
|
||||||
:global(::-webkit-scrollbar) {
|
:global(::-webkit-scrollbar) {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
|
|
@ -38,10 +38,8 @@
|
||||||
if (!field || !value) {
|
if (!field || !value) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let low = dayjs.utc().subtract(1, "year")
|
let low = dayjs.utc().subtract(1, "year")
|
||||||
let high = dayjs.utc().add(1, "day")
|
let high = dayjs.utc().add(1, "day")
|
||||||
|
|
||||||
if (value === "Last 1 day") {
|
if (value === "Last 1 day") {
|
||||||
low = dayjs.utc().subtract(1, "day")
|
low = dayjs.utc().subtract(1, "day")
|
||||||
} else if (value === "Last 7 days") {
|
} else if (value === "Last 7 days") {
|
||||||
|
@ -53,7 +51,6 @@
|
||||||
} else if (value === "Last 6 months") {
|
} else if (value === "Last 6 months") {
|
||||||
low = dayjs.utc().subtract(6, "months")
|
low = dayjs.utc().subtract(6, "months")
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
range: {
|
range: {
|
||||||
[field]: {
|
[field]: {
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
metadata: { dataSource: table },
|
metadata: { dataSource: table },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
$: height = $component.styles?.normal?.height || "408px"
|
||||||
|
$: styles = getSanitisedStyles($component.styles)
|
||||||
|
|
||||||
// Provide additional data context for live binding eval
|
// Provide additional data context for live binding eval
|
||||||
export const getAdditionalDataContext = () => {
|
export const getAdditionalDataContext = () => {
|
||||||
|
@ -106,39 +108,48 @@
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSanitisedStyles = styles => {
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
normal: {
|
||||||
|
...styles?.normal,
|
||||||
|
height: undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div use:styleable={styles} class:in-builder={$builderStore.inBuilder}>
|
||||||
use:styleable={$component.styles}
|
<span style="--height:{height};">
|
||||||
class:in-builder={$builderStore.inBuilder}
|
<Provider {actions}>
|
||||||
>
|
<Grid
|
||||||
<Provider {actions}>
|
bind:this={grid}
|
||||||
<Grid
|
datasource={table}
|
||||||
bind:this={grid}
|
{API}
|
||||||
datasource={table}
|
{stripeRows}
|
||||||
{API}
|
{quiet}
|
||||||
{stripeRows}
|
{initialFilter}
|
||||||
{quiet}
|
{initialSortColumn}
|
||||||
{initialFilter}
|
{initialSortOrder}
|
||||||
{initialSortColumn}
|
{fixedRowHeight}
|
||||||
{initialSortOrder}
|
{columnWhitelist}
|
||||||
{fixedRowHeight}
|
{schemaOverrides}
|
||||||
{columnWhitelist}
|
{repeat}
|
||||||
{schemaOverrides}
|
canAddRows={allowAddRows}
|
||||||
{repeat}
|
canEditRows={allowEditRows}
|
||||||
canAddRows={allowAddRows}
|
canDeleteRows={allowDeleteRows}
|
||||||
canEditRows={allowEditRows}
|
canEditColumns={false}
|
||||||
canDeleteRows={allowDeleteRows}
|
canExpandRows={false}
|
||||||
canEditColumns={false}
|
canSaveSchema={false}
|
||||||
canExpandRows={false}
|
showControls={false}
|
||||||
canSaveSchema={false}
|
notifySuccess={notificationStore.actions.success}
|
||||||
showControls={false}
|
notifyError={notificationStore.actions.error}
|
||||||
notifySuccess={notificationStore.actions.success}
|
buttons={enrichedButtons}
|
||||||
notifyError={notificationStore.actions.error}
|
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
||||||
buttons={enrichedButtons}
|
/>
|
||||||
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
</Provider>
|
||||||
/>
|
</span>
|
||||||
</Provider>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -149,10 +160,14 @@
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 230px;
|
|
||||||
height: 410px;
|
|
||||||
}
|
}
|
||||||
div.in-builder :global(*) {
|
div.in-builder :global(*) {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
span {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
span :global(.grid) {
|
||||||
|
height: var(--height);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -73,7 +73,10 @@
|
||||||
$context.device.width,
|
$context.device.width,
|
||||||
$context.device.height
|
$context.device.height
|
||||||
)
|
)
|
||||||
$: autoCloseSidePanel = !$builderStore.inBuilder && $sidePanelStore.open
|
$: autoCloseSidePanel =
|
||||||
|
!$builderStore.inBuilder &&
|
||||||
|
$sidePanelStore.open &&
|
||||||
|
!$sidePanelStore.ignoreClicksOutside
|
||||||
$: screenId = $builderStore.inBuilder
|
$: screenId = $builderStore.inBuilder
|
||||||
? `${$builderStore.screen?._id}-screen`
|
? `${$builderStore.screen?._id}-screen`
|
||||||
: "screen"
|
: "screen"
|
||||||
|
@ -191,6 +194,11 @@
|
||||||
}
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClickLink = () => {
|
||||||
|
mobileOpen = false
|
||||||
|
sidePanelStore.actions.close()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
@ -281,7 +289,7 @@
|
||||||
url={navItem.url}
|
url={navItem.url}
|
||||||
subLinks={navItem.subLinks}
|
subLinks={navItem.subLinks}
|
||||||
internalLink={navItem.internalLink}
|
internalLink={navItem.internalLink}
|
||||||
on:clickLink={() => (mobileOpen = false)}
|
on:clickLink={handleClickLink}
|
||||||
leftNav={navigation === "Left"}
|
leftNav={navigation === "Left"}
|
||||||
{mobile}
|
{mobile}
|
||||||
{navStateStore}
|
{navStateStore}
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
const { styleable, sidePanelStore, builderStore, dndIsDragging } =
|
const { styleable, sidePanelStore, builderStore, dndIsDragging } =
|
||||||
getContext("sdk")
|
getContext("sdk")
|
||||||
|
|
||||||
|
export let onClose
|
||||||
|
export let ignoreClicksOutside
|
||||||
|
|
||||||
// Automatically show and hide the side panel when inside the builder.
|
// Automatically show and hide the side panel when inside the builder.
|
||||||
// For some unknown reason, svelte reactivity breaks if we reference the
|
// For some unknown reason, svelte reactivity breaks if we reference the
|
||||||
// reactive variable "open" inside the following expression, or if we define
|
// reactive variable "open" inside the following expression, or if we define
|
||||||
|
@ -26,6 +29,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $: {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
// Derive visibility
|
// Derive visibility
|
||||||
$: open = $sidePanelStore.contentId === $component.id
|
$: open = $sidePanelStore.contentId === $component.id
|
||||||
|
|
||||||
|
@ -36,10 +43,17 @@
|
||||||
let renderKey = null
|
let renderKey = null
|
||||||
$: {
|
$: {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
sidePanelStore.actions.setIgnoreClicksOutside(ignoreClicksOutside)
|
||||||
renderKey = Math.random()
|
renderKey = Math.random()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSidePanelClose = async () => {
|
||||||
|
if (onClose) {
|
||||||
|
await onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const showInSidePanel = (el, visible) => {
|
const showInSidePanel = (el, visible) => {
|
||||||
const update = visible => {
|
const update = visible => {
|
||||||
const target = document.getElementById("side-panel-container")
|
const target = document.getElementById("side-panel-container")
|
||||||
|
@ -51,6 +65,7 @@
|
||||||
} else {
|
} else {
|
||||||
if (target.contains(node)) {
|
if (target.contains(node)) {
|
||||||
target.removeChild(node)
|
target.removeChild(node)
|
||||||
|
handleSidePanelClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
readonly={fieldState.readonly}
|
readonly={fieldState.readonly}
|
||||||
error={fieldState.error}
|
error={fieldState.error}
|
||||||
id={fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
appendTo={document.getElementById("flatpickr-root")}
|
|
||||||
{enableTime}
|
{enableTime}
|
||||||
{timeOnly}
|
{timeOnly}
|
||||||
{time24hr}
|
{time24hr}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import flatpickr from "flatpickr"
|
import dayjs from "dayjs"
|
||||||
import { FieldTypes } from "../../../constants"
|
import { FieldTypes } from "../../../constants"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a validation function from a combination of schema-level constraints
|
* Creates a validation function from a combination of schema-level constraints
|
||||||
|
@ -81,7 +82,7 @@ export const createValidatorFromConstraints = (
|
||||||
// Date constraint
|
// Date constraint
|
||||||
if (exists(schemaConstraints.datetime?.earliest)) {
|
if (exists(schemaConstraints.datetime?.earliest)) {
|
||||||
const limit = schemaConstraints.datetime.earliest
|
const limit = schemaConstraints.datetime.earliest
|
||||||
const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
|
const limitString = Helpers.getDateDisplayValue(dayjs(limit))
|
||||||
rules.push({
|
rules.push({
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
constraint: "minValue",
|
constraint: "minValue",
|
||||||
|
@ -91,7 +92,7 @@ export const createValidatorFromConstraints = (
|
||||||
}
|
}
|
||||||
if (exists(schemaConstraints.datetime?.latest)) {
|
if (exists(schemaConstraints.datetime?.latest)) {
|
||||||
const limit = schemaConstraints.datetime.latest
|
const limit = schemaConstraints.datetime.latest
|
||||||
const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
|
const limitString = Helpers.getDateDisplayValue(dayjs(limit))
|
||||||
rules.push({
|
rules.push({
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
constraint: "maxValue",
|
constraint: "maxValue",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { writable, derived } from "svelte/store"
|
||||||
export const createSidePanelStore = () => {
|
export const createSidePanelStore = () => {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
contentId: null,
|
contentId: null,
|
||||||
|
ignoreClicksOutside: true,
|
||||||
}
|
}
|
||||||
const store = writable(initialState)
|
const store = writable(initialState)
|
||||||
const derivedStore = derived(store, $store => {
|
const derivedStore = derived(store, $store => {
|
||||||
|
@ -32,11 +33,18 @@ export const createSidePanelStore = () => {
|
||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setIgnoreClicksOutside = bool => {
|
||||||
|
store.update(state => {
|
||||||
|
state.ignoreClicksOutside = bool
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
subscribe: derivedStore.subscribe,
|
subscribe: derivedStore.subscribe,
|
||||||
actions: {
|
actions: {
|
||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
|
setIgnoreClicksOutside,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,6 +240,7 @@ const triggerAutomationHandler = async action => {
|
||||||
const navigationHandler = action => {
|
const navigationHandler = action => {
|
||||||
const { url, peek, externalNewTab } = action.parameters
|
const { url, peek, externalNewTab } = action.parameters
|
||||||
routeStore.actions.navigate(url, peek, externalNewTab)
|
routeStore.actions.navigate(url, peek, externalNewTab)
|
||||||
|
closeSidePanelHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryExecutionHandler = async action => {
|
const queryExecutionHandler = async action => {
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
export const buildBackupsEndpoints = API => ({
|
export const buildBackupsEndpoints = API => ({
|
||||||
/**
|
|
||||||
* Gets a list of users in the current tenant.
|
|
||||||
*/
|
|
||||||
searchBackups: async ({ appId, trigger, type, page, startDate, endDate }) => {
|
searchBackups: async ({ appId, trigger, type, page, startDate, endDate }) => {
|
||||||
const opts = {}
|
const opts = {}
|
||||||
if (page) {
|
if (page) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { Dropzone } from "@budibase/bbui"
|
import { Dropzone } from "@budibase/bbui"
|
||||||
|
import GridPopover from "../overlays/GridPopover.svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let focused = false
|
export let focused = false
|
||||||
|
@ -8,7 +9,6 @@
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let api
|
export let api
|
||||||
export let invertX = false
|
export let invertX = false
|
||||||
export let invertY = false
|
|
||||||
export let schema
|
export let schema
|
||||||
export let maximum
|
export let maximum
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
||||||
|
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
|
let anchor
|
||||||
|
|
||||||
$: editable = focused && !readonly
|
$: editable = focused && !readonly
|
||||||
$: {
|
$: {
|
||||||
|
@ -73,7 +74,12 @@
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="attachment-cell" class:editable on:click={editable ? open : null}>
|
<div
|
||||||
|
class="attachment-cell"
|
||||||
|
class:editable
|
||||||
|
on:click={editable ? open : null}
|
||||||
|
bind:this={anchor}
|
||||||
|
>
|
||||||
{#each value || [] as attachment}
|
{#each value || [] as attachment}
|
||||||
{#if isImage(attachment.extension)}
|
{#if isImage(attachment.extension)}
|
||||||
<img src={attachment.url} alt={attachment.extension} />
|
<img src={attachment.url} alt={attachment.extension} />
|
||||||
|
@ -86,16 +92,24 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div class="dropzone" class:invertX class:invertY>
|
<GridPopover
|
||||||
<Dropzone
|
open={isOpen}
|
||||||
{value}
|
{anchor}
|
||||||
compact
|
{invertX}
|
||||||
on:change={e => onChange(e.detail)}
|
maxHeight={null}
|
||||||
maximum={maximum || schema.constraints?.length?.maximum}
|
on:close={close}
|
||||||
{processFiles}
|
>
|
||||||
{handleFileTooLarge}
|
<div class="dropzone">
|
||||||
/>
|
<Dropzone
|
||||||
</div>
|
{value}
|
||||||
|
compact
|
||||||
|
on:change={e => onChange(e.detail)}
|
||||||
|
maximum={maximum || schema.constraints?.length?.maximum}
|
||||||
|
{processFiles}
|
||||||
|
{handleFileTooLarge}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</GridPopover>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -129,23 +143,8 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.dropzone {
|
.dropzone {
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
width: 320px;
|
|
||||||
background: var(--grid-background-alt);
|
background: var(--grid-background-alt);
|
||||||
border: var(--cell-border);
|
width: 320px;
|
||||||
padding: var(--cell-padding);
|
padding: var(--cell-padding);
|
||||||
box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
}
|
|
||||||
.dropzone.invertX {
|
|
||||||
left: auto;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
.dropzone.invertY {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import dayjs from "dayjs"
|
import { CoreDatePickerPopoverContents, Icon, Helpers } from "@budibase/bbui"
|
||||||
import { CoreDatePicker, Icon } from "@budibase/bbui"
|
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
import GridPopover from "../overlays/GridPopover.svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let schema
|
export let schema
|
||||||
|
@ -9,83 +10,117 @@
|
||||||
export let focused = false
|
export let focused = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let api
|
export let api
|
||||||
|
export let invertX = false
|
||||||
|
|
||||||
let flatpickr
|
|
||||||
let isOpen
|
let isOpen
|
||||||
|
let anchor
|
||||||
|
|
||||||
// Adding the 0- will turn a string like 00:00:00 into a valid ISO
|
$: timeOnly = schema?.timeOnly
|
||||||
// date, but will make actual ISO dates invalid
|
$: enableTime = !schema?.dateOnly
|
||||||
$: isTimeValue = !isNaN(new Date(`0-${value}`))
|
$: ignoreTimezones = schema?.ignoreTimezones
|
||||||
$: timeOnly = isTimeValue || schema?.timeOnly
|
|
||||||
$: dateOnly = schema?.dateOnly
|
|
||||||
$: format = timeOnly
|
|
||||||
? "HH:mm:ss"
|
|
||||||
: dateOnly
|
|
||||||
? "MMM D YYYY"
|
|
||||||
: "MMM D YYYY, HH:mm"
|
|
||||||
$: editable = focused && !readonly
|
$: editable = focused && !readonly
|
||||||
$: displayValue = getDisplayValue(value, format, timeOnly, isTimeValue)
|
$: parsedValue = Helpers.parseDate(value, {
|
||||||
|
timeOnly,
|
||||||
const getDisplayValue = (value, format, timeOnly, isTimeValue) => {
|
enableTime,
|
||||||
if (!value) {
|
ignoreTimezones,
|
||||||
return ""
|
})
|
||||||
}
|
$: displayValue = getDisplayValue(parsedValue, timeOnly, enableTime)
|
||||||
// Parse full date strings
|
// Ensure open state matches desired state
|
||||||
if (!timeOnly || !isTimeValue) {
|
|
||||||
return dayjs(value).format(format)
|
|
||||||
}
|
|
||||||
// Otherwise must be a time string
|
|
||||||
return dayjs(`0-${value}`).format(format)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we close flatpickr when unselected
|
|
||||||
$: {
|
$: {
|
||||||
if (!focused) {
|
if (!focused && isOpen) {
|
||||||
flatpickr?.close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onKeyDown = () => {
|
const getDisplayValue = (value, timeOnly, enableTime) => {
|
||||||
return isOpen
|
return Helpers.getDateDisplayValue(value, {
|
||||||
|
enableTime,
|
||||||
|
timeOnly,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
isOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
isOpen = false
|
||||||
|
|
||||||
|
// Only save the changed value when closing. If the value is unchanged then
|
||||||
|
// this is handled upstream and no action is taken.
|
||||||
|
onChange(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onKeyDown = e => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
changeDate(-1, "week")
|
||||||
|
} else if (e.key === "ArrowDown") {
|
||||||
|
changeDate(1, "week")
|
||||||
|
} else if (e.key === "ArrowLeft") {
|
||||||
|
changeDate(-1, "day")
|
||||||
|
} else if (e.key === "ArrowRight") {
|
||||||
|
changeDate(1, "day")
|
||||||
|
} else if (e.key === "Enter") {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeDate = (quantity, unit) => {
|
||||||
|
let newValue
|
||||||
|
if (!value) {
|
||||||
|
newValue = dayjs()
|
||||||
|
} else {
|
||||||
|
newValue = dayjs(value).add(quantity, unit)
|
||||||
|
}
|
||||||
|
value = Helpers.stringifyDate(newValue, {
|
||||||
|
enableTime,
|
||||||
|
timeOnly,
|
||||||
|
ignoreTimezones,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
api = {
|
api = {
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
focus: () => flatpickr?.open(),
|
focus: open,
|
||||||
blur: () => flatpickr?.close(),
|
blur: close,
|
||||||
isActive: () => isOpen,
|
isActive: () => isOpen,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
class:editable
|
||||||
|
on:click={editable ? open : null}
|
||||||
|
bind:this={anchor}
|
||||||
|
>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{#if value}
|
{displayValue}
|
||||||
{displayValue}
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{#if editable}
|
{#if editable}
|
||||||
<Icon name="Calendar" />
|
<Icon name="Calendar" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if editable}
|
{#if isOpen}
|
||||||
<div class="picker">
|
<GridPopover {anchor} {invertX} maxHeight={null} on:close={close}>
|
||||||
<CoreDatePicker
|
<CoreDatePickerPopoverContents
|
||||||
{value}
|
value={parsedValue}
|
||||||
on:change={e => onChange(e.detail)}
|
|
||||||
appendTo={document.documentElement}
|
|
||||||
enableTime={!dateOnly}
|
|
||||||
{timeOnly}
|
|
||||||
time24hr
|
|
||||||
ignoreTimezones={schema.ignoreTimezones}
|
|
||||||
bind:flatpickr
|
|
||||||
on:open={() => (isOpen = true)}
|
|
||||||
on:close={() => (isOpen = false)}
|
|
||||||
useKeyboardShortcuts={false}
|
useKeyboardShortcuts={false}
|
||||||
|
on:change={e => (value = e.detail)}
|
||||||
|
{enableTime}
|
||||||
|
{timeOnly}
|
||||||
|
{ignoreTimezones}
|
||||||
/>
|
/>
|
||||||
</div>
|
</GridPopover>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -97,6 +132,10 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
gap: var(--cell-spacing);
|
gap: var(--cell-spacing);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.container.editable:hover {
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.value {
|
.value {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
@ -105,15 +144,6 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
height: 20px;
|
||||||
.picker {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.picker :global(.flatpickr) {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.picker :global(.spectrum-Textfield-input) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount, tick } from "svelte"
|
import { getContext, onMount, tick } from "svelte"
|
||||||
import { canBeDisplayColumn, canBeSortColumn } from "@budibase/shared-core"
|
import { canBeDisplayColumn, canBeSortColumn } from "@budibase/shared-core"
|
||||||
import {
|
import { Icon, Menu, MenuItem, Modal } from "@budibase/bbui"
|
||||||
Icon,
|
|
||||||
Popover,
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
clickOutside,
|
|
||||||
Modal,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import GridCell from "./GridCell.svelte"
|
import GridCell from "./GridCell.svelte"
|
||||||
import { getColumnIcon } from "../lib/utils"
|
import { getColumnIcon } from "../lib/utils"
|
||||||
import MigrationModal from "../controls/MigrationModal.svelte"
|
import MigrationModal from "../controls/MigrationModal.svelte"
|
||||||
import { debounce } from "../../../utils/utils"
|
import { debounce } from "../../../utils/utils"
|
||||||
import { FieldType, FormulaType } from "@budibase/types"
|
import { FieldType, FormulaType } from "@budibase/types"
|
||||||
import { TableNames } from "../../../constants"
|
import { TableNames } from "../../../constants"
|
||||||
|
import GridPopover from "../overlays/GridPopover.svelte"
|
||||||
|
|
||||||
export let column
|
export let column
|
||||||
export let idx
|
export let idx
|
||||||
|
@ -23,7 +17,6 @@
|
||||||
reorder,
|
reorder,
|
||||||
isReordering,
|
isReordering,
|
||||||
isResizing,
|
isResizing,
|
||||||
rand,
|
|
||||||
sort,
|
sort,
|
||||||
visibleColumns,
|
visibleColumns,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
@ -52,7 +45,6 @@
|
||||||
let open = false
|
let open = false
|
||||||
let editIsOpen = false
|
let editIsOpen = false
|
||||||
let timeout
|
let timeout
|
||||||
let popover
|
|
||||||
let migrationModal
|
let migrationModal
|
||||||
let searchValue
|
let searchValue
|
||||||
let input
|
let input
|
||||||
|
@ -67,6 +59,11 @@
|
||||||
$: debouncedUpdateFilter(searchValue)
|
$: debouncedUpdateFilter(searchValue)
|
||||||
$: orderable = !column.primaryDisplay
|
$: orderable = !column.primaryDisplay
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
open = false
|
||||||
|
editIsOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
const getSortingLabels = type => {
|
const getSortingLabels = type => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case FieldType.NUMBER:
|
case FieldType.NUMBER:
|
||||||
|
@ -106,12 +103,8 @@
|
||||||
dispatch("edit-column", column.schema)
|
dispatch("edit-column", column.schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelEdit = () => {
|
|
||||||
popover.hide()
|
|
||||||
editIsOpen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const onMouseDown = e => {
|
const onMouseDown = e => {
|
||||||
|
ui.actions.blur()
|
||||||
if ((e.touches?.length || e.button === 0) && orderable) {
|
if ((e.touches?.length || e.button === 0) && orderable) {
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
reorder.actions.startReordering(column.name, e)
|
reorder.actions.startReordering(column.name, e)
|
||||||
|
@ -237,7 +230,7 @@
|
||||||
}
|
}
|
||||||
const debouncedUpdateFilter = debounce(updateFilter, 250)
|
const debouncedUpdateFilter = debounce(updateFilter, 250)
|
||||||
|
|
||||||
onMount(() => subscribe("close-edit-column", cancelEdit))
|
onMount(() => subscribe("close-edit-column", close))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={migrationModal}>
|
<Modal bind:this={migrationModal}>
|
||||||
|
@ -314,89 +307,88 @@
|
||||||
</GridCell>
|
</GridCell>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Popover
|
{#if open}
|
||||||
bind:open
|
<GridPopover
|
||||||
bind:this={popover}
|
{anchor}
|
||||||
{anchor}
|
align="right"
|
||||||
align="right"
|
on:close={close}
|
||||||
offset={0}
|
maxHeight={null}
|
||||||
popoverTarget={document.getElementById(`grid-${rand}`)}
|
resizable
|
||||||
customZindex={50}
|
>
|
||||||
>
|
{#if editIsOpen}
|
||||||
{#if editIsOpen}
|
<div class="content">
|
||||||
<div
|
<slot />
|
||||||
use:clickOutside={() => {
|
</div>
|
||||||
editIsOpen = false
|
{:else}
|
||||||
}}
|
<Menu>
|
||||||
class="content"
|
<MenuItem
|
||||||
>
|
icon="Edit"
|
||||||
<slot />
|
on:click={editColumn}
|
||||||
</div>
|
disabled={!$config.canEditColumns || column.schema.disabled}
|
||||||
{:else}
|
>
|
||||||
<Menu>
|
Edit column
|
||||||
<MenuItem
|
|
||||||
icon="Edit"
|
|
||||||
on:click={editColumn}
|
|
||||||
disabled={!$config.canEditColumns || column.schema.disabled}
|
|
||||||
>
|
|
||||||
Edit column
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon="Duplicate"
|
|
||||||
on:click={duplicateColumn}
|
|
||||||
disabled={!$config.canEditColumns}
|
|
||||||
>
|
|
||||||
Duplicate column
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon="Label"
|
|
||||||
on:click={makeDisplayColumn}
|
|
||||||
disabled={column.primaryDisplay ||
|
|
||||||
!canBeDisplayColumn(column.schema.type)}
|
|
||||||
>
|
|
||||||
Use as display column
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon="SortOrderUp"
|
|
||||||
on:click={sortAscending}
|
|
||||||
disabled={!canBeSortColumn(column.schema.type) ||
|
|
||||||
(column.name === $sort.column && $sort.order === "ascending")}
|
|
||||||
>
|
|
||||||
Sort {sortingLabels.ascending}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon="SortOrderDown"
|
|
||||||
on:click={sortDescending}
|
|
||||||
disabled={!canBeSortColumn(column.schema.type) ||
|
|
||||||
(column.name === $sort.column && $sort.order === "descending")}
|
|
||||||
>
|
|
||||||
Sort {sortingLabels.descending}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
|
||||||
Move left
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
disabled={!canMoveRight}
|
|
||||||
icon="ChevronRight"
|
|
||||||
on:click={moveRight}
|
|
||||||
>
|
|
||||||
Move right
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
disabled={column.primaryDisplay || !$config.showControls}
|
|
||||||
icon="VisibilityOff"
|
|
||||||
on:click={hideColumn}
|
|
||||||
>
|
|
||||||
Hide column
|
|
||||||
</MenuItem>
|
|
||||||
{#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS}
|
|
||||||
<MenuItem icon="User" on:click={openMigrationModal}>
|
|
||||||
Migrate to user column
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/if}
|
<MenuItem
|
||||||
</Menu>
|
icon="Duplicate"
|
||||||
{/if}
|
on:click={duplicateColumn}
|
||||||
</Popover>
|
disabled={!$config.canEditColumns}
|
||||||
|
>
|
||||||
|
Duplicate column
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon="Label"
|
||||||
|
on:click={makeDisplayColumn}
|
||||||
|
disabled={column.primaryDisplay ||
|
||||||
|
!canBeDisplayColumn(column.schema.type)}
|
||||||
|
>
|
||||||
|
Use as display column
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon="SortOrderUp"
|
||||||
|
on:click={sortAscending}
|
||||||
|
disabled={!canBeSortColumn(column.schema.type) ||
|
||||||
|
(column.name === $sort.column && $sort.order === "ascending")}
|
||||||
|
>
|
||||||
|
Sort {sortingLabels.ascending}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon="SortOrderDown"
|
||||||
|
on:click={sortDescending}
|
||||||
|
disabled={!canBeSortColumn(column.schema.type) ||
|
||||||
|
(column.name === $sort.column && $sort.order === "descending")}
|
||||||
|
>
|
||||||
|
Sort {sortingLabels.descending}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
disabled={!canMoveLeft}
|
||||||
|
icon="ChevronLeft"
|
||||||
|
on:click={moveLeft}
|
||||||
|
>
|
||||||
|
Move left
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
disabled={!canMoveRight}
|
||||||
|
icon="ChevronRight"
|
||||||
|
on:click={moveRight}
|
||||||
|
>
|
||||||
|
Move right
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
disabled={column.primaryDisplay || !$config.showControls}
|
||||||
|
icon="VisibilityOff"
|
||||||
|
on:click={hideColumn}
|
||||||
|
>
|
||||||
|
Hide column
|
||||||
|
</MenuItem>
|
||||||
|
{#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS}
|
||||||
|
<MenuItem icon="User" on:click={openMigrationModal}>
|
||||||
|
Migrate to user column
|
||||||
|
</MenuItem>
|
||||||
|
{/if}
|
||||||
|
</Menu>
|
||||||
|
{/if}
|
||||||
|
</GridPopover>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.header-cell {
|
.header-cell {
|
||||||
|
@ -490,7 +482,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
width: 300px;
|
width: 360px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, tick } from "svelte"
|
import { onMount, tick } from "svelte"
|
||||||
import { clickOutside } from "@budibase/bbui"
|
import { clickOutside } from "@budibase/bbui"
|
||||||
|
import GridPopover from "../overlays/GridPopover.svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let focused = false
|
export let focused = false
|
||||||
|
@ -8,10 +9,10 @@
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let api
|
export let api
|
||||||
export let invertX = false
|
export let invertX = false
|
||||||
export let invertY = false
|
|
||||||
|
|
||||||
let textarea
|
let textarea
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
|
let anchor
|
||||||
|
|
||||||
$: editable = focused && !readonly
|
$: editable = focused && !readonly
|
||||||
$: {
|
$: {
|
||||||
|
@ -52,25 +53,30 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isOpen}
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<textarea
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
class:invertX
|
<div
|
||||||
class:invertY
|
class="long-form-cell"
|
||||||
bind:this={textarea}
|
on:click={editable ? open : null}
|
||||||
value={value || ""}
|
class:editable
|
||||||
on:change={handleChange}
|
bind:this={anchor}
|
||||||
on:wheel|stopPropagation
|
>
|
||||||
spellcheck="false"
|
<div class="value">
|
||||||
use:clickOutside={close}
|
{value || ""}
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div class="long-form-cell" on:click={editable ? open : null} class:editable>
|
|
||||||
<div class="value">
|
|
||||||
{value || ""}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if isOpen}
|
||||||
|
<GridPopover {anchor} {invertX} on:close={close}>
|
||||||
|
<textarea
|
||||||
|
bind:this={textarea}
|
||||||
|
value={value || ""}
|
||||||
|
on:change={handleChange}
|
||||||
|
on:wheel|stopPropagation
|
||||||
|
spellcheck="false"
|
||||||
|
use:clickOutside={close}
|
||||||
|
/>
|
||||||
|
</GridPopover>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -93,30 +99,20 @@
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
textarea {
|
textarea {
|
||||||
|
border: none;
|
||||||
|
width: 320px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: var(--max-cell-render-overflow);
|
||||||
padding: var(--cell-padding);
|
padding: var(--cell-padding);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: 2px solid var(--cell-color);
|
|
||||||
background: var(--cell-background);
|
background: var(--cell-background);
|
||||||
font-size: var(--cell-font-size);
|
font-size: var(--cell-font-size);
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: calc(100% + var(--max-cell-render-width-overflow));
|
|
||||||
height: calc(var(--row-height) + var(--max-cell-render-height));
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-radius: 2px;
|
|
||||||
resize: none;
|
resize: none;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
overflow: auto;
|
||||||
textarea.invertX {
|
|
||||||
left: auto;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
textarea.invertY {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
top: calc(100% + 1px);
|
|
||||||
}
|
}
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon, clickOutside } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { getColor } from "../lib/utils"
|
import { getColor } from "../lib/utils"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import GridPopover from "../overlays/GridPopover.svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let schema
|
export let schema
|
||||||
|
@ -10,12 +11,12 @@
|
||||||
export let multi = false
|
export let multi = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let api
|
export let api
|
||||||
export let invertX = false
|
export let invertX
|
||||||
export let invertY = false
|
|
||||||
export let contentLines = 1
|
export let contentLines = 1
|
||||||
|
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
let focusedOptionIdx = null
|
let focusedOptionIdx = null
|
||||||
|
let anchor
|
||||||
|
|
||||||
$: options = schema?.constraints?.inclusion || []
|
$: options = schema?.constraints?.inclusion || []
|
||||||
$: optionColors = schema?.optionColors || {}
|
$: optionColors = schema?.optionColors || {}
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
$: values = Array.isArray(value) ? value : [value].filter(x => x != null)
|
$: values = Array.isArray(value) ? value : [value].filter(x => x != null)
|
||||||
$: {
|
$: {
|
||||||
// Close when deselected
|
// Close when deselected
|
||||||
if (!focused) {
|
if (!focused && isOpen) {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +90,7 @@
|
||||||
class:editable
|
class:editable
|
||||||
class:open
|
class:open
|
||||||
on:click|self={editable ? open : null}
|
on:click|self={editable ? open : null}
|
||||||
|
bind:this={anchor}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="values"
|
class="values"
|
||||||
|
@ -115,16 +117,15 @@
|
||||||
<Icon name="ChevronDown" />
|
<Icon name="ChevronDown" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if isOpen}
|
</div>
|
||||||
<div
|
|
||||||
class="options"
|
{#if isOpen}
|
||||||
class:invertX
|
<GridPopover {anchor} {invertX} on:close={close}>
|
||||||
class:invertY
|
<div class="options">
|
||||||
on:wheel={e => e.stopPropagation()}
|
|
||||||
use:clickOutside={close}
|
|
||||||
>
|
|
||||||
{#each options as option, idx}
|
{#each options as option, idx}
|
||||||
{@const color = optionColors[option] || getOptionColor(option)}
|
{@const color = optionColors[option] || getOptionColor(option)}
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="option"
|
class="option"
|
||||||
on:click={() => toggleOption(option)}
|
on:click={() => toggleOption(option)}
|
||||||
|
@ -132,7 +133,9 @@
|
||||||
on:mouseenter={() => (focusedOptionIdx = idx)}
|
on:mouseenter={() => (focusedOptionIdx = idx)}
|
||||||
>
|
>
|
||||||
<div class="badge text" style="--color: {color}">
|
<div class="badge text" style="--color: {color}">
|
||||||
{option}
|
<span>
|
||||||
|
{option}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{#if values.includes(option)}
|
{#if values.includes(option)}
|
||||||
<Icon name="Checkmark" color="var(--accent-color)" />
|
<Icon name="Checkmark" color="var(--accent-color)" />
|
||||||
|
@ -140,8 +143,8 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</GridPopover>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
|
@ -211,28 +214,10 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
.options {
|
.options {
|
||||||
min-width: calc(100% + 2px);
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: -1px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
max-height: var(--max-cell-render-height);
|
|
||||||
overflow-y: auto;
|
|
||||||
border: var(--cell-border);
|
|
||||||
box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
}
|
|
||||||
.options.invertX {
|
|
||||||
left: auto;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
.options.invertY {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
.option {
|
.option {
|
||||||
flex: 0 0 var(--default-row-height);
|
flex: 0 0 var(--default-row-height);
|
||||||
|
@ -242,10 +227,10 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--cell-spacing);
|
gap: var(--cell-spacing);
|
||||||
background-color: var(--grid-background-alt);
|
|
||||||
}
|
}
|
||||||
.option:hover,
|
.option:hover,
|
||||||
.option.focused {
|
.option.focused {
|
||||||
background-color: var(--spectrum-global-color-gray-200);
|
background-color: var(--grid-background-alt);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { getColor } from "../lib/utils"
|
import { getColor } from "../lib/utils"
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { Icon, Input, ProgressCircle, clickOutside } from "@budibase/bbui"
|
import { Icon, Input, ProgressCircle } from "@budibase/bbui"
|
||||||
import { debounce } from "../../../utils/utils"
|
import { debounce } from "../../../utils/utils"
|
||||||
|
import GridPopover from "../overlays/GridPopover.svelte"
|
||||||
|
|
||||||
const { API, dispatch, cache } = getContext("grid")
|
const { API, cache } = getContext("grid")
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let api
|
export let api
|
||||||
|
@ -13,7 +14,6 @@
|
||||||
export let schema
|
export let schema
|
||||||
export let onChange
|
export let onChange
|
||||||
export let invertX = false
|
export let invertX = false
|
||||||
export let invertY = false
|
|
||||||
export let contentLines = 1
|
export let contentLines = 1
|
||||||
export let searchFunction = API.searchTable
|
export let searchFunction = API.searchTable
|
||||||
export let primaryDisplay
|
export let primaryDisplay
|
||||||
|
@ -28,15 +28,15 @@
|
||||||
let candidateIndex
|
let candidateIndex
|
||||||
let lastSearchId
|
let lastSearchId
|
||||||
let searching = false
|
let searching = false
|
||||||
let valuesHeight = 0
|
|
||||||
let container
|
let container
|
||||||
|
let anchor
|
||||||
|
|
||||||
$: oneRowOnly = schema?.relationshipType === "one-to-many"
|
$: oneRowOnly = schema?.relationshipType === "one-to-many"
|
||||||
$: editable = focused && !readonly
|
$: editable = focused && !readonly
|
||||||
$: lookupMap = buildLookupMap(value, isOpen)
|
$: lookupMap = buildLookupMap(value, isOpen)
|
||||||
$: debouncedSearch(searchString)
|
$: debouncedSearch(searchString)
|
||||||
$: {
|
$: {
|
||||||
if (!focused) {
|
if (!focused && isOpen) {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,6 @@
|
||||||
|
|
||||||
const open = async () => {
|
const open = async () => {
|
||||||
isOpen = true
|
isOpen = true
|
||||||
valuesHeight = container.getBoundingClientRect().height
|
|
||||||
|
|
||||||
// Find the primary display for the related table
|
// Find the primary display for the related table
|
||||||
if (!primaryDisplay) {
|
if (!primaryDisplay) {
|
||||||
|
@ -205,14 +204,6 @@
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
const showRelationship = async id => {
|
|
||||||
const relatedRow = await API.fetchRow({
|
|
||||||
tableId: schema.tableId,
|
|
||||||
rowId: id,
|
|
||||||
})
|
|
||||||
dispatch("edit-row", relatedRow)
|
|
||||||
}
|
|
||||||
|
|
||||||
const readable = value => {
|
const readable = value => {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return ""
|
return ""
|
||||||
|
@ -239,8 +230,8 @@
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:editable
|
class:editable
|
||||||
class:focused
|
class:focused
|
||||||
class:invertY
|
|
||||||
style="--color:{color};"
|
style="--color:{color};"
|
||||||
|
bind:this={anchor}
|
||||||
>
|
>
|
||||||
<div class="container" bind:this={container}>
|
<div class="container" bind:this={container}>
|
||||||
<div
|
<div
|
||||||
|
@ -251,11 +242,7 @@
|
||||||
{#each value || [] as relationship}
|
{#each value || [] as relationship}
|
||||||
{#if relationship[primaryDisplay] || relationship.primaryDisplay}
|
{#if relationship[primaryDisplay] || relationship.primaryDisplay}
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
<span
|
<span>
|
||||||
on:click={editable
|
|
||||||
? () => showRelationship(relationship._id)
|
|
||||||
: null}
|
|
||||||
>
|
|
||||||
{readable(
|
{readable(
|
||||||
relationship[primaryDisplay] || relationship.primaryDisplay
|
relationship[primaryDisplay] || relationship.primaryDisplay
|
||||||
)}
|
)}
|
||||||
|
@ -283,16 +270,13 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if isOpen}
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
class="dropdown"
|
{#if isOpen}
|
||||||
class:invertX
|
<GridPopover open={isOpen} {anchor} {invertX} on:close={close}>
|
||||||
class:invertY
|
<div class="dropdown" on:wheel|stopPropagation>
|
||||||
on:wheel|stopPropagation
|
|
||||||
use:clickOutside={close}
|
|
||||||
style="--values-height:{valuesHeight}px;"
|
|
||||||
>
|
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<Input
|
<Input
|
||||||
autofocus
|
autofocus
|
||||||
|
@ -328,8 +312,8 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</GridPopover>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.wrapper {
|
.wrapper {
|
||||||
|
@ -338,7 +322,6 @@
|
||||||
min-height: var(--row-height);
|
min-height: var(--row-height);
|
||||||
max-height: var(--row-height);
|
max-height: var(--row-height);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
--max-relationship-height: 96px;
|
|
||||||
}
|
}
|
||||||
.wrapper.focused {
|
.wrapper.focused {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -350,10 +333,6 @@
|
||||||
max-height: none;
|
max-height: none;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
.wrapper.invertY {
|
|
||||||
top: auto;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
min-height: var(--row-height);
|
min-height: var(--row-height);
|
||||||
|
@ -364,7 +343,6 @@
|
||||||
.focused .container {
|
.focused .container {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
max-height: var(--max-relationship-height);
|
|
||||||
}
|
}
|
||||||
.focused .container:after {
|
.focused .container:after {
|
||||||
content: " ";
|
content: " ";
|
||||||
|
@ -427,10 +405,6 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.editable .values .badge span:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add {
|
.add {
|
||||||
background: var(--spectrum-global-color-gray-200);
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
@ -447,30 +421,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown {
|
.dropdown {
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
max-height: calc(
|
|
||||||
var(--max-cell-render-height) + var(--row-height) - var(--values-height)
|
|
||||||
);
|
|
||||||
background: var(--grid-background-alt);
|
|
||||||
border: var(--cell-border);
|
|
||||||
box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: 0 0 8px 0;
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
}
|
|
||||||
.dropdown.invertY {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
top: -1px;
|
|
||||||
}
|
|
||||||
.dropdown.invertX {
|
|
||||||
left: auto;
|
|
||||||
right: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.searching {
|
.searching {
|
||||||
|
@ -498,7 +451,8 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.result .badge {
|
.result .badge {
|
||||||
max-width: calc(100% - 30px);
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
|
@ -506,7 +460,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 4px var(--cell-padding);
|
margin: 4px var(--cell-padding);
|
||||||
width: calc(100% - 2 * var(--cell-padding));
|
|
||||||
}
|
}
|
||||||
.search :global(.spectrum-Textfield) {
|
.search :global(.spectrum-Textfield) {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
import GridBody from "./GridBody.svelte"
|
import GridBody from "./GridBody.svelte"
|
||||||
import ResizeOverlay from "../overlays/ResizeOverlay.svelte"
|
import ResizeOverlay from "../overlays/ResizeOverlay.svelte"
|
||||||
import ReorderOverlay from "../overlays/ReorderOverlay.svelte"
|
import ReorderOverlay from "../overlays/ReorderOverlay.svelte"
|
||||||
|
import PopoverOverlay from "../overlays/PopoverOverlay.svelte"
|
||||||
import HeaderRow from "./HeaderRow.svelte"
|
import HeaderRow from "./HeaderRow.svelte"
|
||||||
import ScrollOverlay from "../overlays/ScrollOverlay.svelte"
|
import ScrollOverlay from "../overlays/ScrollOverlay.svelte"
|
||||||
import MenuOverlay from "../overlays/MenuOverlay.svelte"
|
import MenuOverlay from "../overlays/MenuOverlay.svelte"
|
||||||
|
@ -22,10 +23,12 @@
|
||||||
import NewRow from "./NewRow.svelte"
|
import NewRow from "./NewRow.svelte"
|
||||||
import { createGridWebsocket } from "../lib/websocket"
|
import { createGridWebsocket } from "../lib/websocket"
|
||||||
import {
|
import {
|
||||||
MaxCellRenderHeight,
|
MaxCellRenderOverflow,
|
||||||
MaxCellRenderWidthOverflow,
|
|
||||||
GutterWidth,
|
GutterWidth,
|
||||||
DefaultRowHeight,
|
DefaultRowHeight,
|
||||||
|
Padding,
|
||||||
|
SmallRowHeight,
|
||||||
|
ControlsHeight,
|
||||||
} from "../lib/constants"
|
} from "../lib/constants"
|
||||||
|
|
||||||
export let API = null
|
export let API = null
|
||||||
|
@ -52,7 +55,7 @@
|
||||||
export let buttons = null
|
export let buttons = null
|
||||||
|
|
||||||
// Unique identifier for DOM nodes inside this instance
|
// Unique identifier for DOM nodes inside this instance
|
||||||
const rand = Math.random()
|
const gridID = `grid-${Math.random().toString().slice(2)}`
|
||||||
|
|
||||||
// Store props in a store for reference in other stores
|
// Store props in a store for reference in other stores
|
||||||
const props = writable($$props)
|
const props = writable($$props)
|
||||||
|
@ -60,7 +63,7 @@
|
||||||
// Build up context
|
// Build up context
|
||||||
let context = {
|
let context = {
|
||||||
API: API || createAPIClient(),
|
API: API || createAPIClient(),
|
||||||
rand,
|
gridID,
|
||||||
props,
|
props,
|
||||||
}
|
}
|
||||||
context = { ...context, ...createEventManagers() }
|
context = { ...context, ...createEventManagers() }
|
||||||
|
@ -104,6 +107,8 @@
|
||||||
notifyError,
|
notifyError,
|
||||||
buttons,
|
buttons,
|
||||||
})
|
})
|
||||||
|
$: minHeight =
|
||||||
|
Padding + SmallRowHeight + $rowHeight + (showControls ? ControlsHeight : 0)
|
||||||
|
|
||||||
// Set context for children to consume
|
// Set context for children to consume
|
||||||
setContext("grid", context)
|
setContext("grid", context)
|
||||||
|
@ -122,14 +127,14 @@
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div
|
||||||
class="grid"
|
class="grid"
|
||||||
id="grid-{rand}"
|
id={gridID}
|
||||||
class:is-resizing={$isResizing}
|
class:is-resizing={$isResizing}
|
||||||
class:is-reordering={$isReordering}
|
class:is-reordering={$isReordering}
|
||||||
class:stripe={stripeRows}
|
class:stripe={stripeRows}
|
||||||
class:quiet
|
class:quiet
|
||||||
on:mouseenter={() => gridFocused.set(true)}
|
on:mouseenter={() => gridFocused.set(true)}
|
||||||
on:mouseleave={() => gridFocused.set(false)}
|
on:mouseleave={() => gridFocused.set(false)}
|
||||||
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px; --content-lines:{$contentLines};"
|
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-overflow:{MaxCellRenderOverflow}px; --content-lines:{$contentLines}; --min-height:{minHeight}px; --controls-height:{ControlsHeight}px;"
|
||||||
>
|
>
|
||||||
{#if showControls}
|
{#if showControls}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
@ -181,6 +186,7 @@
|
||||||
<ReorderOverlay />
|
<ReorderOverlay />
|
||||||
<ScrollOverlay />
|
<ScrollOverlay />
|
||||||
<MenuOverlay />
|
<MenuOverlay />
|
||||||
|
<PopoverOverlay />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -210,7 +216,6 @@
|
||||||
--cell-spacing: 4px;
|
--cell-spacing: 4px;
|
||||||
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
||||||
--cell-font-size: 14px;
|
--cell-font-size: 14px;
|
||||||
--controls-height: 50px;
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -219,6 +224,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--grid-background);
|
background: var(--grid-background);
|
||||||
|
min-height: var(--min-height);
|
||||||
}
|
}
|
||||||
.grid,
|
.grid,
|
||||||
.grid :global(*) {
|
.grid :global(*) {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
bounds,
|
bounds,
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
menu,
|
menu,
|
||||||
|
focusedCellAPI,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
export let scrollVertically = false
|
export let scrollVertically = false
|
||||||
|
@ -35,6 +36,9 @@
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
updateScroll(e.deltaX, e.deltaY, e.clientY)
|
updateScroll(e.deltaX, e.deltaY, e.clientY)
|
||||||
|
|
||||||
|
// Close any open popovers when scrolling
|
||||||
|
$focusedCellAPI?.blur()
|
||||||
|
|
||||||
// If a context menu was visible, hide it
|
// If a context menu was visible, hide it
|
||||||
if ($menu.visible) {
|
if ($menu.visible) {
|
||||||
menu.actions.close()
|
menu.actions.close()
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import { Icon, Popover, clickOutside } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
|
import GridPopover from "../overlays/GridPopover.svelte"
|
||||||
|
|
||||||
const { visibleColumns, scroll, width, subscribe } = getContext("grid")
|
const { visibleColumns, scroll, width, subscribe, ui } = getContext("grid")
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let open = false
|
let isOpen = false
|
||||||
|
|
||||||
$: columnsWidth = $visibleColumns.reduce(
|
$: columnsWidth = $visibleColumns.reduce(
|
||||||
(total, col) => (total += col.width),
|
(total, col) => (total += col.width),
|
||||||
|
@ -14,8 +15,13 @@
|
||||||
$: end = columnsWidth - 1 - $scroll.left
|
$: end = columnsWidth - 1 - $scroll.left
|
||||||
$: left = Math.min($width - 40, end)
|
$: left = Math.min($width - 40, end)
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
ui.actions.blur()
|
||||||
|
isOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
open = false
|
isOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => subscribe("close-edit-column", close))
|
onMount(() => subscribe("close-edit-column", close))
|
||||||
|
@ -28,27 +34,23 @@
|
||||||
bind:this={anchor}
|
bind:this={anchor}
|
||||||
class="add"
|
class="add"
|
||||||
style="left:{left}px"
|
style="left:{left}px"
|
||||||
on:click={() => (open = true)}
|
on:click={open}
|
||||||
>
|
>
|
||||||
<Icon name="Add" />
|
<Icon name="Add" />
|
||||||
</div>
|
</div>
|
||||||
<Popover
|
{#if isOpen}
|
||||||
bind:open
|
<GridPopover
|
||||||
{anchor}
|
{anchor}
|
||||||
align={$visibleColumns.length ? "right" : "left"}
|
align={$visibleColumns.length ? "right" : "left"}
|
||||||
offset={0}
|
on:close={close}
|
||||||
popoverTarget={document.getElementById(`add-column-button`)}
|
maxHeight={null}
|
||||||
customZindex={50}
|
resizable
|
||||||
>
|
|
||||||
<div
|
|
||||||
use:clickOutside={() => {
|
|
||||||
open = false
|
|
||||||
}}
|
|
||||||
class="content"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<div class="content">
|
||||||
</div>
|
<slot />
|
||||||
</Popover>
|
</div>
|
||||||
|
</GridPopover>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.add {
|
.add {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
export const Padding = 246
|
export const Padding = 100
|
||||||
export const MaxCellRenderHeight = 222
|
|
||||||
export const ScrollBarSize = 8
|
export const ScrollBarSize = 8
|
||||||
export const GutterWidth = 72
|
export const GutterWidth = 72
|
||||||
export const DefaultColumnWidth = 200
|
export const DefaultColumnWidth = 200
|
||||||
|
@ -11,5 +10,11 @@ export const DefaultRowHeight = SmallRowHeight
|
||||||
export const NewRowID = "new"
|
export const NewRowID = "new"
|
||||||
export const BlankRowID = "blank"
|
export const BlankRowID = "blank"
|
||||||
export const RowPageSize = 100
|
export const RowPageSize = 100
|
||||||
export const FocusedCellMinOffset = 48
|
export const FocusedCellMinOffset = ScrollBarSize * 3
|
||||||
export const MaxCellRenderWidthOverflow = Padding - 3 * ScrollBarSize
|
export const ControlsHeight = 50
|
||||||
|
|
||||||
|
// Popovers
|
||||||
|
export const PopoverMinWidth = 200
|
||||||
|
export const PopoverMaxWidth = 400
|
||||||
|
export const PopoverMaxHeight = 236
|
||||||
|
export const MaxCellRenderOverflow = 222
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script>
|
||||||
|
import { Popover, clickOutside } from "@budibase/bbui"
|
||||||
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
|
import {
|
||||||
|
PopoverMinWidth,
|
||||||
|
PopoverMaxWidth,
|
||||||
|
PopoverMaxHeight,
|
||||||
|
} from "../lib/constants"
|
||||||
|
|
||||||
|
export let anchor
|
||||||
|
export let minWidth = PopoverMinWidth
|
||||||
|
export let maxWidth = PopoverMaxWidth
|
||||||
|
export let maxHeight = PopoverMaxHeight
|
||||||
|
export let align = "left"
|
||||||
|
export let open = true
|
||||||
|
export let resizable = false
|
||||||
|
export let wrap = true
|
||||||
|
|
||||||
|
const { gridID } = getContext("grid")
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: style = buildStyles(minWidth, maxWidth, maxHeight)
|
||||||
|
|
||||||
|
const buildStyles = (minWidth, maxWidth, maxHeight) => {
|
||||||
|
let style = ""
|
||||||
|
if (minWidth != null) {
|
||||||
|
style += `min-width: ${minWidth}px;`
|
||||||
|
}
|
||||||
|
if (maxWidth != null) {
|
||||||
|
style += `max-width: ${maxWidth}px;`
|
||||||
|
}
|
||||||
|
if (maxHeight != null) {
|
||||||
|
style += `max-height: ${maxHeight}px;`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
{open}
|
||||||
|
{anchor}
|
||||||
|
{align}
|
||||||
|
{resizable}
|
||||||
|
{wrap}
|
||||||
|
portalTarget="#{gridID} .grid-popover-container"
|
||||||
|
offset={0}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="grid-popover-contents"
|
||||||
|
{style}
|
||||||
|
use:clickOutside={() => dispatch("close")}
|
||||||
|
on:wheel={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.grid-popover-container .spectrum-Popover) {
|
||||||
|
background: var(--grid-background);
|
||||||
|
min-width: none;
|
||||||
|
max-width: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.grid-popover-contents {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
const ignoredOriginSelectors = [
|
const ignoredOriginSelectors = [
|
||||||
".spectrum-Modal",
|
".spectrum-Modal",
|
||||||
|
".date-time-popover",
|
||||||
"#builder-side-panel-container",
|
"#builder-side-panel-container",
|
||||||
"[data-grid-ignore]",
|
"[data-grid-ignore]",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { clickOutside, Menu, MenuItem, Helpers } from "@budibase/bbui"
|
import { Menu, MenuItem, Helpers } from "@budibase/bbui"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { NewRowID } from "../lib/constants"
|
import { NewRowID } from "../lib/constants"
|
||||||
|
import GridPopover from "./GridPopover.svelte"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
focusedRow,
|
focusedRow,
|
||||||
|
@ -20,6 +21,8 @@
|
||||||
isDatasourcePlus,
|
isDatasourcePlus,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
|
||||||
$: style = makeStyle($menu)
|
$: style = makeStyle($menu)
|
||||||
$: isNewRow = $focusedRowId === NewRowID
|
$: isNewRow = $focusedRowId === NewRowID
|
||||||
|
|
||||||
|
@ -48,75 +51,74 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor} {style} class="menu-anchor" />
|
||||||
|
|
||||||
{#if $menu.visible}
|
{#if $menu.visible}
|
||||||
<div class="menu" {style} use:clickOutside={() => menu.actions.close()}>
|
{#key style}
|
||||||
<Menu>
|
<GridPopover {anchor} on:close={menu.actions.close} maxHeight={null}>
|
||||||
<MenuItem
|
<Menu>
|
||||||
icon="Copy"
|
<MenuItem
|
||||||
on:click={clipboard.actions.copy}
|
icon="Copy"
|
||||||
on:click={menu.actions.close}
|
on:click={clipboard.actions.copy}
|
||||||
>
|
on:click={menu.actions.close}
|
||||||
Copy
|
>
|
||||||
</MenuItem>
|
Copy
|
||||||
<MenuItem
|
</MenuItem>
|
||||||
icon="Paste"
|
<MenuItem
|
||||||
disabled={$copiedCell == null || $focusedCellAPI?.isReadonly()}
|
icon="Paste"
|
||||||
on:click={clipboard.actions.paste}
|
disabled={$copiedCell == null || $focusedCellAPI?.isReadonly()}
|
||||||
on:click={menu.actions.close}
|
on:click={clipboard.actions.paste}
|
||||||
>
|
on:click={menu.actions.close}
|
||||||
Paste
|
>
|
||||||
</MenuItem>
|
Paste
|
||||||
<MenuItem
|
</MenuItem>
|
||||||
icon="Maximize"
|
<MenuItem
|
||||||
disabled={isNewRow || !$config.canEditRows || !$config.canExpandRows}
|
icon="Maximize"
|
||||||
on:click={() => dispatch("edit-row", $focusedRow)}
|
disabled={isNewRow || !$config.canEditRows || !$config.canExpandRows}
|
||||||
on:click={menu.actions.close}
|
on:click={() => dispatch("edit-row", $focusedRow)}
|
||||||
>
|
on:click={menu.actions.close}
|
||||||
Edit row in modal
|
>
|
||||||
</MenuItem>
|
Edit row in modal
|
||||||
<MenuItem
|
</MenuItem>
|
||||||
icon="Copy"
|
<MenuItem
|
||||||
disabled={isNewRow || !$focusedRow?._id || !$isDatasourcePlus}
|
icon="Copy"
|
||||||
on:click={() => copyToClipboard($focusedRow?._id)}
|
disabled={isNewRow || !$focusedRow?._id || !$isDatasourcePlus}
|
||||||
on:click={menu.actions.close}
|
on:click={() => copyToClipboard($focusedRow?._id)}
|
||||||
>
|
on:click={menu.actions.close}
|
||||||
Copy row _id
|
>
|
||||||
</MenuItem>
|
Copy row _id
|
||||||
<MenuItem
|
</MenuItem>
|
||||||
icon="Copy"
|
<MenuItem
|
||||||
disabled={isNewRow || !$focusedRow?._rev}
|
icon="Copy"
|
||||||
on:click={() => copyToClipboard($focusedRow?._rev)}
|
disabled={isNewRow || !$focusedRow?._rev}
|
||||||
on:click={menu.actions.close}
|
on:click={() => copyToClipboard($focusedRow?._rev)}
|
||||||
>
|
on:click={menu.actions.close}
|
||||||
Copy row _rev
|
>
|
||||||
</MenuItem>
|
Copy row _rev
|
||||||
<MenuItem
|
</MenuItem>
|
||||||
icon="Duplicate"
|
<MenuItem
|
||||||
disabled={isNewRow || !$config.canAddRows}
|
icon="Duplicate"
|
||||||
on:click={duplicate}
|
disabled={isNewRow || !$config.canAddRows}
|
||||||
>
|
on:click={duplicate}
|
||||||
Duplicate row
|
>
|
||||||
</MenuItem>
|
Duplicate row
|
||||||
<MenuItem
|
</MenuItem>
|
||||||
icon="Delete"
|
<MenuItem
|
||||||
disabled={isNewRow || !$config.canDeleteRows}
|
icon="Delete"
|
||||||
on:click={deleteRow}
|
disabled={isNewRow || !$config.canDeleteRows}
|
||||||
>
|
on:click={deleteRow}
|
||||||
Delete row
|
>
|
||||||
</MenuItem>
|
Delete row
|
||||||
</Menu>
|
</MenuItem>
|
||||||
</div>
|
</Menu>
|
||||||
|
</GridPopover>
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.menu {
|
.menu-anchor {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: var(--cell-background);
|
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
width: 180px;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="grid-popover-container" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.grid-popover-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -18,6 +18,7 @@
|
||||||
height,
|
height,
|
||||||
isDragging,
|
isDragging,
|
||||||
menu,
|
menu,
|
||||||
|
focusedCellAPI,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
// State for dragging bars
|
// State for dragging bars
|
||||||
|
@ -48,10 +49,11 @@
|
||||||
$: barLeft = ScrollBarSize + availWidth * ($scrollLeft / $maxScrollLeft)
|
$: barLeft = ScrollBarSize + availWidth * ($scrollLeft / $maxScrollLeft)
|
||||||
|
|
||||||
// Helper to close the context menu if it's open
|
// Helper to close the context menu if it's open
|
||||||
const closeMenu = () => {
|
const closePopovers = () => {
|
||||||
if ($menu.visible) {
|
if ($menu.visible) {
|
||||||
menu.actions.close()
|
menu.actions.close()
|
||||||
}
|
}
|
||||||
|
$focusedCellAPI?.blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
// V scrollbar drag handlers
|
// V scrollbar drag handlers
|
||||||
|
@ -64,7 +66,7 @@
|
||||||
document.addEventListener("mouseup", stopVDragging)
|
document.addEventListener("mouseup", stopVDragging)
|
||||||
document.addEventListener("touchend", stopVDragging)
|
document.addEventListener("touchend", stopVDragging)
|
||||||
isDraggingV = true
|
isDraggingV = true
|
||||||
closeMenu()
|
closePopovers()
|
||||||
}
|
}
|
||||||
const moveVDragging = domDebounce(e => {
|
const moveVDragging = domDebounce(e => {
|
||||||
const delta = parseEventLocation(e).y - initialMouse
|
const delta = parseEventLocation(e).y - initialMouse
|
||||||
|
@ -93,7 +95,7 @@
|
||||||
document.addEventListener("mouseup", stopHDragging)
|
document.addEventListener("mouseup", stopHDragging)
|
||||||
document.addEventListener("touchend", stopHDragging)
|
document.addEventListener("touchend", stopHDragging)
|
||||||
isDraggingH = true
|
isDraggingH = true
|
||||||
closeMenu()
|
closePopovers()
|
||||||
}
|
}
|
||||||
const moveHDragging = domDebounce(e => {
|
const moveHDragging = domDebounce(e => {
|
||||||
const delta = parseEventLocation(e).x - initialMouse
|
const delta = parseEventLocation(e).x - initialMouse
|
||||||
|
|
|
@ -13,13 +13,13 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = context => {
|
||||||
const { menu, focusedCellId, rand } = context
|
const { menu, focusedCellId, gridID } = context
|
||||||
|
|
||||||
const open = (cellId, e) => {
|
const open = (cellId, e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
// Get DOM node for grid data wrapper to compute relative position to
|
// Get DOM node for grid data wrapper to compute relative position to
|
||||||
const gridNode = document.getElementById(`grid-${rand}`)
|
const gridNode = document.getElementById(gridID)
|
||||||
const dataNode = gridNode?.getElementsByClassName("grid-data-outer")?.[0]
|
const dataNode = gridNode?.getElementsByClassName("grid-data-outer")?.[0]
|
||||||
if (!dataNode) {
|
if (!dataNode) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -32,7 +32,6 @@ export const createActions = context => {
|
||||||
scroll,
|
scroll,
|
||||||
bounds,
|
bounds,
|
||||||
stickyColumn,
|
stickyColumn,
|
||||||
ui,
|
|
||||||
maxScrollLeft,
|
maxScrollLeft,
|
||||||
width,
|
width,
|
||||||
} = context
|
} = context
|
||||||
|
@ -45,7 +44,6 @@ export const createActions = context => {
|
||||||
const $visibleColumns = get(visibleColumns)
|
const $visibleColumns = get(visibleColumns)
|
||||||
const $bounds = get(bounds)
|
const $bounds = get(bounds)
|
||||||
const $stickyColumn = get(stickyColumn)
|
const $stickyColumn = get(stickyColumn)
|
||||||
ui.actions.blur()
|
|
||||||
|
|
||||||
// Generate new breakpoints for the current columns
|
// Generate new breakpoints for the current columns
|
||||||
let breakpoints = $visibleColumns.map(col => ({
|
let breakpoints = $visibleColumns.map(col => ({
|
||||||
|
@ -97,7 +95,7 @@ export const createActions = context => {
|
||||||
// Check if we need to start auto-scrolling
|
// Check if we need to start auto-scrolling
|
||||||
const $reorder = get(reorder)
|
const $reorder = get(reorder)
|
||||||
const proximityCutoff = Math.min(140, get(width) / 6)
|
const proximityCutoff = Math.min(140, get(width) / 6)
|
||||||
const speedFactor = 8
|
const speedFactor = 16
|
||||||
const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
|
const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
|
||||||
const leftProximity = Math.max(0, x - $reorder.gridLeft)
|
const leftProximity = Math.max(0, x - $reorder.gridLeft)
|
||||||
if (rightProximity < proximityCutoff) {
|
if (rightProximity < proximityCutoff) {
|
||||||
|
|
|
@ -196,6 +196,20 @@ export const createActions = context => {
|
||||||
// Handles validation errors from the rows API and updates local validation
|
// Handles validation errors from the rows API and updates local validation
|
||||||
// state, storing error messages against relevant cells
|
// state, storing error messages against relevant cells
|
||||||
const handleValidationError = (rowId, error) => {
|
const handleValidationError = (rowId, error) => {
|
||||||
|
// If the server doesn't reply with a valid error, assume that the source
|
||||||
|
// of the error is the focused cell's column
|
||||||
|
if (!error?.json?.validationErrors && error?.message) {
|
||||||
|
const focusedColumn = get(focusedCellId)?.split("-")[1]
|
||||||
|
if (focusedColumn) {
|
||||||
|
error = {
|
||||||
|
json: {
|
||||||
|
validationErrors: {
|
||||||
|
[focusedColumn]: error.message,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (error?.json?.validationErrors) {
|
if (error?.json?.validationErrors) {
|
||||||
// Normal validation errors
|
// Normal validation errors
|
||||||
const keys = Object.keys(error.json.validationErrors)
|
const keys = Object.keys(error.json.validationErrors)
|
||||||
|
@ -214,11 +228,19 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Process errors for columns that we have
|
// Process errors for columns that we have
|
||||||
for (let column of erroredColumns) {
|
for (let column of erroredColumns) {
|
||||||
|
// Ensure we have a valid error to display
|
||||||
|
let err = error.json.validationErrors[column]
|
||||||
|
if (Array.isArray(err)) {
|
||||||
|
err = err[0]
|
||||||
|
}
|
||||||
|
if (typeof err !== "string" || !err.length) {
|
||||||
|
error = "Something went wrong"
|
||||||
|
}
|
||||||
|
// Set error against the cell
|
||||||
validation.actions.setError(
|
validation.actions.setError(
|
||||||
`${rowId}-${column}`,
|
`${rowId}-${column}`,
|
||||||
`${column} ${error.json.validationErrors[column]}`
|
Helpers.capitalise(err)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the column is visible
|
// Ensure the column is visible
|
||||||
const index = $columns.findIndex(x => x.name === column)
|
const index = $columns.findIndex(x => x.name === column)
|
||||||
if (index !== -1 && !$columns[index].visible) {
|
if (index !== -1 && !$columns[index].visible) {
|
||||||
|
@ -523,6 +545,7 @@ export const initialise = context => {
|
||||||
previousFocusedCellId,
|
previousFocusedCellId,
|
||||||
rows,
|
rows,
|
||||||
validation,
|
validation,
|
||||||
|
focusedCellId,
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
// Wipe the row change cache when changing row
|
// Wipe the row change cache when changing row
|
||||||
|
@ -537,12 +560,22 @@ export const initialise = context => {
|
||||||
|
|
||||||
// Ensure any unsaved changes are saved when changing cell
|
// Ensure any unsaved changes are saved when changing cell
|
||||||
previousFocusedCellId.subscribe(async id => {
|
previousFocusedCellId.subscribe(async id => {
|
||||||
const rowId = id?.split("-")[0]
|
if (!id) {
|
||||||
const hasErrors = validation.actions.rowHasErrors(rowId)
|
return
|
||||||
const hasChanges = Object.keys(get(rowChangeCache)[rowId] || {}).length > 0
|
}
|
||||||
const isSavingChanges = get(inProgressChanges)[rowId]
|
// Stop if we changed row
|
||||||
if (rowId && !hasErrors && hasChanges && !isSavingChanges) {
|
const oldRowId = id.split("-")[0]
|
||||||
await rows.actions.applyRowChanges(rowId)
|
const oldColumn = id.split("-")[1]
|
||||||
|
const newRowId = get(focusedCellId)?.split("-")[0]
|
||||||
|
if (oldRowId !== newRowId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Otherwise we just changed cell in the same row
|
||||||
|
const hasChanges = oldColumn in (get(rowChangeCache)[oldRowId] || {})
|
||||||
|
const hasErrors = validation.actions.rowHasErrors(oldRowId)
|
||||||
|
const isSavingChanges = get(inProgressChanges)[oldRowId]
|
||||||
|
if (oldRowId && !hasErrors && hasChanges && !isSavingChanges) {
|
||||||
|
await rows.actions.applyRowChanges(oldRowId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import {
|
import {
|
||||||
MaxCellRenderHeight,
|
MaxCellRenderOverflow,
|
||||||
MaxCellRenderWidthOverflow,
|
|
||||||
MinColumnWidth,
|
MinColumnWidth,
|
||||||
ScrollBarSize,
|
ScrollBarSize,
|
||||||
} from "../lib/constants"
|
} from "../lib/constants"
|
||||||
|
@ -95,11 +94,11 @@ export const deriveStores = context => {
|
||||||
|
|
||||||
// Compute the last row index with space to render popovers below it
|
// Compute the last row index with space to render popovers below it
|
||||||
const minBottom =
|
const minBottom =
|
||||||
$height - ScrollBarSize * 3 - MaxCellRenderHeight + offset
|
$height - ScrollBarSize * 3 - MaxCellRenderOverflow + offset
|
||||||
const lastIdx = Math.floor(minBottom / $rowHeight)
|
const lastIdx = Math.floor(minBottom / $rowHeight)
|
||||||
|
|
||||||
// Compute the first row index with space to render popovers above it
|
// Compute the first row index with space to render popovers above it
|
||||||
const minTop = MaxCellRenderHeight + offset
|
const minTop = MaxCellRenderOverflow + offset
|
||||||
const firstIdx = Math.ceil(minTop / $rowHeight)
|
const firstIdx = Math.ceil(minTop / $rowHeight)
|
||||||
|
|
||||||
// Use the greater of the two indices so that we prefer content below,
|
// Use the greater of the two indices so that we prefer content below,
|
||||||
|
@ -117,7 +116,7 @@ export const deriveStores = context => {
|
||||||
let inversionIdx = $visibleColumns.length
|
let inversionIdx = $visibleColumns.length
|
||||||
for (let i = $visibleColumns.length - 1; i >= 0; i--, inversionIdx--) {
|
for (let i = $visibleColumns.length - 1; i >= 0; i--, inversionIdx--) {
|
||||||
const rightEdge = $visibleColumns[i].left + $visibleColumns[i].width
|
const rightEdge = $visibleColumns[i].left + $visibleColumns[i].width
|
||||||
if (rightEdge + MaxCellRenderWidthOverflow <= cutoff) {
|
if (rightEdge + MaxCellRenderOverflow <= cutoff) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ const isBetterSample = (newValue, oldValue) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldValue != null && newValue == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Don't change type
|
// Don't change type
|
||||||
const oldType = typeof oldValue
|
const oldType = typeof oldValue
|
||||||
const newType = typeof newValue
|
const newType = typeof newValue
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 01bec5657e0c3c3bb29e883e6ac71258fee8710b
|
Subproject commit 479879246aac5dd3073cc695945c62c41fae5b0e
|
|
@ -128,6 +128,7 @@ export async function importToRows(
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
let row = data[i]
|
let row = data[i]
|
||||||
row._id = generateRowID(table._id!)
|
row._id = generateRowID(table._id!)
|
||||||
|
row.type = "row"
|
||||||
row.tableId = table._id
|
row.tableId = table._id
|
||||||
|
|
||||||
// We use a reference to table here and update it after input processing,
|
// We use a reference to table here and update it after input processing,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
|
||||||
|
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
import {
|
import {
|
||||||
|
AutoFieldSubType,
|
||||||
Datasource,
|
Datasource,
|
||||||
EmptyFilterOption,
|
EmptyFilterOption,
|
||||||
FieldType,
|
FieldType,
|
||||||
|
@ -18,15 +19,16 @@ import _ from "lodash"
|
||||||
jest.unmock("mssql")
|
jest.unmock("mssql")
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
["internal", undefined],
|
["lucene", undefined],
|
||||||
["internal-sqs", undefined],
|
["sqs", undefined],
|
||||||
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
|
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
|
||||||
[DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
|
[DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
|
||||||
[DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
|
[DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
|
||||||
[DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
|
[DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
|
||||||
])("/api/:sourceId/search (%s)", (name, dsProvider) => {
|
])("/api/:sourceId/search (%s)", (name, dsProvider) => {
|
||||||
const isSqs = name === "internal-sqs"
|
const isSqs = name === "sqs"
|
||||||
const isInternal = name === "internal"
|
const isLucene = name === "lucene"
|
||||||
|
const isInternal = isSqs || isLucene
|
||||||
const config = setup.getConfig()
|
const config = setup.getConfig()
|
||||||
|
|
||||||
let envCleanup: (() => void) | undefined
|
let envCleanup: (() => void) | undefined
|
||||||
|
@ -59,7 +61,7 @@ describe.each([
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createRows(rows: Record<string, any>[]) {
|
async function createRows(rows: Record<string, any>[]) {
|
||||||
await Promise.all(rows.map(r => config.api.row.save(table._id!, r)))
|
await config.api.row.bulkImport(table._id!, { rows })
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchAssertion {
|
class SearchAssertion {
|
||||||
|
@ -339,14 +341,14 @@ describe.each([
|
||||||
}).toFindNothing())
|
}).toFindNothing())
|
||||||
|
|
||||||
// We never implemented half-open ranges in Lucene.
|
// We never implemented half-open ranges in Lucene.
|
||||||
!isInternal &&
|
!isLucene &&
|
||||||
it("can search using just a low value", () =>
|
it("can search using just a low value", () =>
|
||||||
expectQuery({
|
expectQuery({
|
||||||
range: { age: { low: 5 } },
|
range: { age: { low: 5 } },
|
||||||
}).toContainExactly([{ age: 10 }]))
|
}).toContainExactly([{ age: 10 }]))
|
||||||
|
|
||||||
// We never implemented half-open ranges in Lucene.
|
// We never implemented half-open ranges in Lucene.
|
||||||
!isInternal &&
|
!isLucene &&
|
||||||
it("can search using just a high value", () =>
|
it("can search using just a high value", () =>
|
||||||
expectQuery({
|
expectQuery({
|
||||||
range: { age: { high: 5 } },
|
range: { age: { high: 5 } },
|
||||||
|
@ -457,14 +459,14 @@ describe.each([
|
||||||
}).toFindNothing())
|
}).toFindNothing())
|
||||||
|
|
||||||
// We never implemented half-open ranges in Lucene.
|
// We never implemented half-open ranges in Lucene.
|
||||||
!isInternal &&
|
!isLucene &&
|
||||||
it("can search using just a low value", () =>
|
it("can search using just a low value", () =>
|
||||||
expectQuery({
|
expectQuery({
|
||||||
range: { dob: { low: JAN_5TH } },
|
range: { dob: { low: JAN_5TH } },
|
||||||
}).toContainExactly([{ dob: JAN_10TH }]))
|
}).toContainExactly([{ dob: JAN_10TH }]))
|
||||||
|
|
||||||
// We never implemented half-open ranges in Lucene.
|
// We never implemented half-open ranges in Lucene.
|
||||||
!isInternal &&
|
!isLucene &&
|
||||||
it("can search using just a high value", () =>
|
it("can search using just a high value", () =>
|
||||||
expectQuery({
|
expectQuery({
|
||||||
range: { dob: { high: JAN_5TH } },
|
range: { dob: { high: JAN_5TH } },
|
||||||
|
@ -642,7 +644,7 @@ describe.each([
|
||||||
// Range searches against bigints don't seem to work at all in Lucene, and I
|
// Range searches against bigints don't seem to work at all in Lucene, and I
|
||||||
// couldn't figure out why. Given that we're replacing Lucene with SQS,
|
// couldn't figure out why. Given that we're replacing Lucene with SQS,
|
||||||
// we've decided not to spend time on it.
|
// we've decided not to spend time on it.
|
||||||
!isInternal &&
|
!isLucene &&
|
||||||
describe("range", () => {
|
describe("range", () => {
|
||||||
it("successfully finds a row", () =>
|
it("successfully finds a row", () =>
|
||||||
expectQuery({
|
expectQuery({
|
||||||
|
@ -675,4 +677,137 @@ describe.each([
|
||||||
}).toContainExactly([{ num: SMALL }, { num: MEDIUM }]))
|
}).toContainExactly([{ num: SMALL }, { num: MEDIUM }]))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
isInternal &&
|
||||||
|
describe("auto", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await createTable({
|
||||||
|
auto: {
|
||||||
|
name: "auto",
|
||||||
|
type: FieldType.AUTO,
|
||||||
|
autocolumn: true,
|
||||||
|
subtype: AutoFieldSubType.AUTO_ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await createRows(new Array(10).fill({}))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("equal", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({ equal: { auto: 1 } }).toContainExactly([{ auto: 1 }]))
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", () =>
|
||||||
|
expectQuery({ equal: { auto: 0 } }).toFindNothing())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("not equal", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({ notEqual: { auto: 1 } }).toContainExactly([
|
||||||
|
{ auto: 2 },
|
||||||
|
{ auto: 3 },
|
||||||
|
{ auto: 4 },
|
||||||
|
{ auto: 5 },
|
||||||
|
{ auto: 6 },
|
||||||
|
{ auto: 7 },
|
||||||
|
{ auto: 8 },
|
||||||
|
{ auto: 9 },
|
||||||
|
{ auto: 10 },
|
||||||
|
]))
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", () =>
|
||||||
|
expectQuery({ notEqual: { auto: 0 } }).toContainExactly([
|
||||||
|
{ auto: 1 },
|
||||||
|
{ auto: 2 },
|
||||||
|
{ auto: 3 },
|
||||||
|
{ auto: 4 },
|
||||||
|
{ auto: 5 },
|
||||||
|
{ auto: 6 },
|
||||||
|
{ auto: 7 },
|
||||||
|
{ auto: 8 },
|
||||||
|
{ auto: 9 },
|
||||||
|
{ auto: 10 },
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("oneOf", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({ oneOf: { auto: [1] } }).toContainExactly([{ auto: 1 }]))
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", () =>
|
||||||
|
expectQuery({ oneOf: { auto: [0] } }).toFindNothing())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("range", () => {
|
||||||
|
it("successfully finds a row", () =>
|
||||||
|
expectQuery({
|
||||||
|
range: { auto: { low: 1, high: 1 } },
|
||||||
|
}).toContainExactly([{ auto: 1 }]))
|
||||||
|
|
||||||
|
it("successfully finds multiple rows", () =>
|
||||||
|
expectQuery({
|
||||||
|
range: { auto: { low: 1, high: 2 } },
|
||||||
|
}).toContainExactly([{ auto: 1 }, { auto: 2 }]))
|
||||||
|
|
||||||
|
it("successfully finds a row with a high bound", () =>
|
||||||
|
expectQuery({
|
||||||
|
range: { auto: { low: 2, high: 2 } },
|
||||||
|
}).toContainExactly([{ auto: 2 }]))
|
||||||
|
|
||||||
|
it("successfully finds no rows", () =>
|
||||||
|
expectQuery({
|
||||||
|
range: { auto: { low: 0, high: 0 } },
|
||||||
|
}).toFindNothing())
|
||||||
|
|
||||||
|
isSqs &&
|
||||||
|
it("can search using just a low value", () =>
|
||||||
|
expectQuery({
|
||||||
|
range: { auto: { low: 9 } },
|
||||||
|
}).toContainExactly([{ auto: 9 }, { auto: 10 }]))
|
||||||
|
|
||||||
|
isSqs &&
|
||||||
|
it("can search using just a high value", () =>
|
||||||
|
expectQuery({
|
||||||
|
range: { auto: { high: 2 } },
|
||||||
|
}).toContainExactly([{ auto: 1 }, { auto: 2 }]))
|
||||||
|
})
|
||||||
|
|
||||||
|
isSqs &&
|
||||||
|
describe("sort", () => {
|
||||||
|
it("sorts ascending", () =>
|
||||||
|
expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "auto",
|
||||||
|
sortOrder: SortOrder.ASCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ auto: 1 },
|
||||||
|
{ auto: 2 },
|
||||||
|
{ auto: 3 },
|
||||||
|
{ auto: 4 },
|
||||||
|
{ auto: 5 },
|
||||||
|
{ auto: 6 },
|
||||||
|
{ auto: 7 },
|
||||||
|
{ auto: 8 },
|
||||||
|
{ auto: 9 },
|
||||||
|
{ auto: 10 },
|
||||||
|
]))
|
||||||
|
|
||||||
|
it("sorts descending", () =>
|
||||||
|
expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "auto",
|
||||||
|
sortOrder: SortOrder.DESCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ auto: 10 },
|
||||||
|
{ auto: 9 },
|
||||||
|
{ auto: 8 },
|
||||||
|
{ auto: 7 },
|
||||||
|
{ auto: 6 },
|
||||||
|
{ auto: 5 },
|
||||||
|
{ auto: 4 },
|
||||||
|
{ auto: 3 },
|
||||||
|
{ auto: 2 },
|
||||||
|
{ auto: 1 },
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -65,9 +65,7 @@ export async function rawQuery(ds: Datasource, sql: string): Promise<any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startContainer(container: GenericContainer) {
|
export async function startContainer(container: GenericContainer) {
|
||||||
if (process.env.REUSE_CONTAINERS) {
|
container = container.withReuse().withLabels({ "com.budibase": "true" })
|
||||||
container = container.withReuse()
|
|
||||||
}
|
|
||||||
|
|
||||||
const startedContainer = await container.start()
|
const startedContainer = await container.start()
|
||||||
|
|
||||||
|
|
|
@ -131,11 +131,6 @@ export async function search(
|
||||||
},
|
},
|
||||||
relationships,
|
relationships,
|
||||||
}
|
}
|
||||||
// make sure only rows returned
|
|
||||||
request.filters!.equal = {
|
|
||||||
...request.filters?.equal,
|
|
||||||
type: "row",
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.sort) {
|
if (params.sort) {
|
||||||
const sortField = table.schema[params.sort]
|
const sortField = table.schema[params.sort]
|
||||||
|
|
|
@ -33,7 +33,7 @@ const FieldTypeMap: Record<FieldType, SQLiteType> = {
|
||||||
[FieldType.LONGFORM]: SQLiteType.TEXT,
|
[FieldType.LONGFORM]: SQLiteType.TEXT,
|
||||||
[FieldType.NUMBER]: SQLiteType.REAL,
|
[FieldType.NUMBER]: SQLiteType.REAL,
|
||||||
[FieldType.STRING]: SQLiteType.TEXT,
|
[FieldType.STRING]: SQLiteType.TEXT,
|
||||||
[FieldType.AUTO]: SQLiteType.TEXT,
|
[FieldType.AUTO]: SQLiteType.REAL,
|
||||||
[FieldType.OPTIONS]: SQLiteType.TEXT,
|
[FieldType.OPTIONS]: SQLiteType.TEXT,
|
||||||
[FieldType.JSON]: SQLiteType.BLOB,
|
[FieldType.JSON]: SQLiteType.BLOB,
|
||||||
[FieldType.INTERNAL]: SQLiteType.BLOB,
|
[FieldType.INTERNAL]: SQLiteType.BLOB,
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Find all Docker containers with the label "org.testcontainers=true"
|
||||||
|
containers=$(docker ps -q -f "label=org.testcontainers=true")
|
||||||
|
|
||||||
|
# Check if there are any containers to stop
|
||||||
|
if [ -z "$containers" ]; then
|
||||||
|
echo "No containers with label 'org.testcontainers=true' found."
|
||||||
|
else
|
||||||
|
# Stop the containers
|
||||||
|
echo "Stopping containers..."
|
||||||
|
docker stop $containers
|
||||||
|
|
||||||
|
# Remove the containers
|
||||||
|
echo "Removing containers..."
|
||||||
|
docker rm $containers
|
||||||
|
|
||||||
|
echo "Containers have been stopped and removed."
|
||||||
|
fi
|
236
yarn.lock
236
yarn.lock
|
@ -1988,7 +1988,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||||
|
|
||||||
"@babel/runtime@^7.10.5", "@babel/runtime@^7.13.10":
|
"@babel/runtime@^7.10.5":
|
||||||
version "7.23.9"
|
version "7.23.9"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
||||||
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
||||||
|
@ -2002,6 +2002,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.14.0"
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.13.10":
|
||||||
|
version "7.24.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd"
|
||||||
|
integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
"@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3":
|
"@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3":
|
||||||
version "7.22.15"
|
version "7.22.15"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
|
||||||
|
@ -2751,7 +2758,7 @@
|
||||||
"@grpc/proto-loader" "^0.7.0"
|
"@grpc/proto-loader" "^0.7.0"
|
||||||
"@types/node" ">=12.12.47"
|
"@types/node" ">=12.12.47"
|
||||||
|
|
||||||
"@grpc/proto-loader@0.7.10", "@grpc/proto-loader@^0.7.0", "@grpc/proto-loader@^0.7.8":
|
"@grpc/proto-loader@0.7.10", "@grpc/proto-loader@^0.7.0":
|
||||||
version "0.7.10"
|
version "0.7.10"
|
||||||
resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720"
|
resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720"
|
||||||
integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==
|
integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==
|
||||||
|
@ -2761,6 +2768,16 @@
|
||||||
protobufjs "^7.2.4"
|
protobufjs "^7.2.4"
|
||||||
yargs "^17.7.2"
|
yargs "^17.7.2"
|
||||||
|
|
||||||
|
"@grpc/proto-loader@^0.7.8":
|
||||||
|
version "0.7.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.12.tgz#787b58e3e3771df30b1567c057b6ab89e3a42911"
|
||||||
|
integrity sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q==
|
||||||
|
dependencies:
|
||||||
|
lodash.camelcase "^4.3.0"
|
||||||
|
long "^5.0.0"
|
||||||
|
protobufjs "^7.2.4"
|
||||||
|
yargs "^17.7.2"
|
||||||
|
|
||||||
"@hapi/hoek@^9.0.0":
|
"@hapi/hoek@^9.0.0":
|
||||||
version "9.3.0"
|
version "9.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
||||||
|
@ -4776,17 +4793,17 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.1.tgz#6db8c3e851baecd0f1c2d88fef37d49d01c6e643"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.1.tgz#6db8c3e851baecd0f1c2d88fef37d49d01c6e643"
|
||||||
integrity sha512-YXrBtjIYisk4Vaxnp0RiE4gdElQX04P2mc4Pi2GlQ27dJKlHmufYcF+kAqGdtiyK5yjdN/vKRcC8y13aA4rusA==
|
integrity sha512-YXrBtjIYisk4Vaxnp0RiE4gdElQX04P2mc4Pi2GlQ27dJKlHmufYcF+kAqGdtiyK5yjdN/vKRcC8y13aA4rusA==
|
||||||
|
|
||||||
"@spectrum-css/button@^3.0.3":
|
|
||||||
version "3.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84"
|
|
||||||
integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg==
|
|
||||||
|
|
||||||
"@spectrum-css/buttongroup@3.0.2":
|
"@spectrum-css/buttongroup@3.0.2":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.2.tgz#fd3387973ca3131609e32112de42a1c0400a48d8"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.2.tgz#fd3387973ca3131609e32112de42a1c0400a48d8"
|
||||||
integrity sha512-Wu7B4GJ/SAeVHz9SUGAkeIH8pLaZh4t+w2ykSKOPQIRuK2jCBoudkEClVxviNVwqekccf5XLFXg9GpYF1a3Uaw==
|
integrity sha512-Wu7B4GJ/SAeVHz9SUGAkeIH8pLaZh4t+w2ykSKOPQIRuK2jCBoudkEClVxviNVwqekccf5XLFXg9GpYF1a3Uaw==
|
||||||
|
|
||||||
"@spectrum-css/card@^3.0.3":
|
"@spectrum-css/calendar@3.2.7":
|
||||||
|
version "3.2.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@spectrum-css/calendar/-/calendar-3.2.7.tgz#10fd44176b6afbdf5baf29ce16728baa98b0d844"
|
||||||
|
integrity sha512-e2BGyuXzP+VOv0q855EIgrR+ne7e/EP8AMMuSAWazgq2fPZ4CoJIeLYP3tnniKnj2dlb3Gr1LH+6MPlUXS74RA==
|
||||||
|
|
||||||
|
"@spectrum-css/card@3.0.3":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-3.0.3.tgz#56b2e2da6b80c1583228baa279de7407383bfb6b"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-3.0.3.tgz#56b2e2da6b80c1583228baa279de7407383bfb6b"
|
||||||
integrity sha512-+oKLUI2a0QmQP9EzySeq/G4FpUkkdaDNbuEbqCj2IkPMc/2v/nwzsPhh1fj2UIghGAiiUwXfPpzax1e8fyhQUg==
|
integrity sha512-+oKLUI2a0QmQP9EzySeq/G4FpUkkdaDNbuEbqCj2IkPMc/2v/nwzsPhh1fj2UIghGAiiUwXfPpzax1e8fyhQUg==
|
||||||
|
@ -4808,13 +4825,6 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@spectrum-css/vars" "^3.0.2"
|
"@spectrum-css/vars" "^3.0.2"
|
||||||
|
|
||||||
"@spectrum-css/divider@^1.0.3":
|
|
||||||
version "1.0.27"
|
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.27.tgz#435bf738a65b4eb15c899edf5c536bea22f2d679"
|
|
||||||
integrity sha512-hWKPHOEo9lkOGN5zecpVVwVxE3x0SJHQJKDNx1g0xs/P/AthAboK+L1c9Rq29czNfcQ2kUjumi4igzQzcqABMQ==
|
|
||||||
dependencies:
|
|
||||||
"@spectrum-css/vars" "^8.0.0"
|
|
||||||
|
|
||||||
"@spectrum-css/dropzone@3.0.2":
|
"@spectrum-css/dropzone@3.0.2":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.2.tgz#34f137851054442b219fed7f32006b93fc5e0bcf"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.2.tgz#34f137851054442b219fed7f32006b93fc5e0bcf"
|
||||||
|
@ -4860,11 +4870,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.1.tgz#cb526a2e10b50ef5a7ae29cca7272e2610d597eb"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.1.tgz#cb526a2e10b50ef5a7ae29cca7272e2610d597eb"
|
||||||
integrity sha512-Bi88lRhTY7g6nM/ryW1yY4Cji211ZYNtRxkxbV7n2lPvwMAAQtyx0qVD3ru4kTGj/FFVvmPR3XiOE10K13HSNA==
|
integrity sha512-Bi88lRhTY7g6nM/ryW1yY4Cji211ZYNtRxkxbV7n2lPvwMAAQtyx0qVD3ru4kTGj/FFVvmPR3XiOE10K13HSNA==
|
||||||
|
|
||||||
"@spectrum-css/link@^3.1.3":
|
|
||||||
version "3.1.23"
|
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.23.tgz#9d9ff64c41366edbfdb19d04a5deec88bf2ea8fd"
|
|
||||||
integrity sha512-CAJQGnGTrTtR4tF1L94ou9Y+c4vnx9d5rWhb3AMzKb2Focqz02xSkTyaCCH7OM/3CwD8TCLOMANon8LcRpGAjA==
|
|
||||||
|
|
||||||
"@spectrum-css/menu@3.0.1":
|
"@spectrum-css/menu@3.0.1":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.1.tgz#2a376f991acc24e12ec892bb6b9db2650fc41fbe"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.1.tgz#2a376f991acc24e12ec892bb6b9db2650fc41fbe"
|
||||||
|
@ -4952,11 +4957,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.2.12.tgz#9b08f23d5aa881b3441af7757800c7173e5685ff"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.2.12.tgz#9b08f23d5aa881b3441af7757800c7173e5685ff"
|
||||||
integrity sha512-rPFUW9SSW4+3/UJ3UrtY2/l3sQvlqB1fqxHLPDjgykvbfrnMejcCTNV4ZrFNHXpE/6+kGnk+yVViSPtWGwJzkA==
|
integrity sha512-rPFUW9SSW4+3/UJ3UrtY2/l3sQvlqB1fqxHLPDjgykvbfrnMejcCTNV4ZrFNHXpE/6+kGnk+yVViSPtWGwJzkA==
|
||||||
|
|
||||||
"@spectrum-css/tag@^3.1.4":
|
|
||||||
version "3.3.15"
|
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/tag/-/tag-3.3.15.tgz#971184fd8cb977b85a529f808313851863123278"
|
|
||||||
integrity sha512-pF6Wh61Z7hmAy20twIlpjdDuivYj6UPtWIzK7giyJKr/qcn20BjVN2ChIeFB1N+vBamJdLsuQOewv4AJ3+LZ2Q==
|
|
||||||
|
|
||||||
"@spectrum-css/tags@3.0.2":
|
"@spectrum-css/tags@3.0.2":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.2.tgz#5bf35fb79c97cd9344de485bd4626ad5b9f07757"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.2.tgz#5bf35fb79c97cd9344de485bd4626ad5b9f07757"
|
||||||
|
@ -4987,11 +4987,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.1.tgz#957dafd9b18c314fa37a88b549042ba2175f5b3f"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.1.tgz#957dafd9b18c314fa37a88b549042ba2175f5b3f"
|
||||||
integrity sha512-XyR68K2rIZX3u4j7HhMLOqLVHDJZcapp3XUqgYMzMWccBFleA0qPxKpfRWqVIA5DzTMSIw0wEcZPYKWFZ2e6dA==
|
integrity sha512-XyR68K2rIZX3u4j7HhMLOqLVHDJZcapp3XUqgYMzMWccBFleA0qPxKpfRWqVIA5DzTMSIw0wEcZPYKWFZ2e6dA==
|
||||||
|
|
||||||
"@spectrum-css/typography@^3.0.2":
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38"
|
|
||||||
integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA==
|
|
||||||
|
|
||||||
"@spectrum-css/underlay@2.0.9":
|
"@spectrum-css/underlay@2.0.9":
|
||||||
version "2.0.9"
|
version "2.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.9.tgz#fc10f971d1325cc844b727e6260f7217844060e8"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.9.tgz#fc10f971d1325cc844b727e6260f7217844060e8"
|
||||||
|
@ -5012,11 +5007,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-4.3.1.tgz#d333fa41909f691c8750b5c15ad9ba029df2248e"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-4.3.1.tgz#d333fa41909f691c8750b5c15ad9ba029df2248e"
|
||||||
integrity sha512-rX6Iasu9BsFMVgEN0vGRPm9dmSxva+IK/uqQAa9HM0lliwqUiFrJxrFXHHpiAgNuux/U4srEJwbSpGzfF+CegQ==
|
integrity sha512-rX6Iasu9BsFMVgEN0vGRPm9dmSxva+IK/uqQAa9HM0lliwqUiFrJxrFXHHpiAgNuux/U4srEJwbSpGzfF+CegQ==
|
||||||
|
|
||||||
"@spectrum-css/vars@^8.0.0":
|
|
||||||
version "8.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-8.0.4.tgz#dcf115551f240b25ba629a3b6c4d3eb1429bee15"
|
|
||||||
integrity sha512-3jYj5HYxbVfkR4jLV9l+L3g6jS4R09m0lV+gupqnXWpwcThlP0EOjkCkevu195imoS4pZ/i2iLpd98l4qcTc2Q==
|
|
||||||
|
|
||||||
"@sveltejs/vite-plugin-svelte@1.4.0":
|
"@sveltejs/vite-plugin-svelte@1.4.0":
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.4.0.tgz#412a735de489ca731d0c780c2b410f45dd95b392"
|
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.4.0.tgz#412a735de489ca731d0c780c2b410f45dd95b392"
|
||||||
|
@ -5756,9 +5746,9 @@
|
||||||
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
|
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
|
||||||
|
|
||||||
"@types/node@>=8.1.0":
|
"@types/node@>=8.1.0":
|
||||||
version "20.11.10"
|
version "20.12.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.10.tgz#6c3de8974d65c362f82ee29db6b5adf4205462f9"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.4.tgz#af5921bd75ccdf3a3d8b3fa75bf3d3359268cd11"
|
||||||
integrity sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==
|
integrity sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
|
@ -8202,6 +8192,17 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5:
|
||||||
get-intrinsic "^1.2.1"
|
get-intrinsic "^1.2.1"
|
||||||
set-function-length "^1.1.1"
|
set-function-length "^1.1.1"
|
||||||
|
|
||||||
|
call-bind@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
|
||||||
|
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
|
||||||
|
dependencies:
|
||||||
|
es-define-property "^1.0.0"
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
function-bind "^1.1.2"
|
||||||
|
get-intrinsic "^1.2.4"
|
||||||
|
set-function-length "^1.2.1"
|
||||||
|
|
||||||
call-me-maybe@^1.0.1:
|
call-me-maybe@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
|
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
|
||||||
|
@ -9660,6 +9661,15 @@ define-data-property@^1.0.1, define-data-property@^1.1.0, define-data-property@^
|
||||||
gopd "^1.0.1"
|
gopd "^1.0.1"
|
||||||
has-property-descriptors "^1.0.0"
|
has-property-descriptors "^1.0.0"
|
||||||
|
|
||||||
|
define-data-property@^1.1.4:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
|
||||||
|
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
|
||||||
|
dependencies:
|
||||||
|
es-define-property "^1.0.0"
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
gopd "^1.0.1"
|
||||||
|
|
||||||
define-lazy-prop@^2.0.0:
|
define-lazy-prop@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
|
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
|
||||||
|
@ -10138,9 +10148,9 @@ dotenv@8.6.0, dotenv@^8.2.0:
|
||||||
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
||||||
|
|
||||||
dotenv@^16.3.1:
|
dotenv@^16.3.1:
|
||||||
version "16.4.1"
|
version "16.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.1.tgz#1d9931f1d3e5d2959350d1250efab299561f7f11"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
|
||||||
integrity sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==
|
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
|
||||||
|
|
||||||
dotenv@~10.0.0:
|
dotenv@~10.0.0:
|
||||||
version "10.0.0"
|
version "10.0.0"
|
||||||
|
@ -10509,6 +10519,18 @@ es-aggregate-error@^1.0.9:
|
||||||
has-property-descriptors "^1.0.0"
|
has-property-descriptors "^1.0.0"
|
||||||
set-function-name "^2.0.1"
|
set-function-name "^2.0.1"
|
||||||
|
|
||||||
|
es-define-property@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
|
||||||
|
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
|
||||||
|
dependencies:
|
||||||
|
get-intrinsic "^1.2.4"
|
||||||
|
|
||||||
|
es-errors@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||||
|
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||||
|
|
||||||
es-get-iterator@^1.1.2:
|
es-get-iterator@^1.1.2:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6"
|
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6"
|
||||||
|
@ -11189,9 +11211,9 @@ fast-xml-parser@4.2.5:
|
||||||
strnum "^1.0.5"
|
strnum "^1.0.5"
|
||||||
|
|
||||||
fast-xml-parser@^4.1.3:
|
fast-xml-parser@^4.1.3:
|
||||||
version "4.3.3"
|
version "4.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz#aeaf5778392329f17168c40c51bcbfec8ff965be"
|
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz#190f9d99097f0c8f2d3a0e681a10404afca052ff"
|
||||||
integrity sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==
|
integrity sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==
|
||||||
dependencies:
|
dependencies:
|
||||||
strnum "^1.0.5"
|
strnum "^1.0.5"
|
||||||
|
|
||||||
|
@ -11419,11 +11441,6 @@ flat@^5.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
|
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
|
||||||
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
|
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
|
||||||
|
|
||||||
flatpickr@^4.5.2:
|
|
||||||
version "4.6.13"
|
|
||||||
resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.13.tgz#8a029548187fd6e0d670908471e43abe9ad18d94"
|
|
||||||
integrity sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==
|
|
||||||
|
|
||||||
flatted@^3.1.0:
|
flatted@^3.1.0:
|
||||||
version "3.2.5"
|
version "3.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
|
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
|
||||||
|
@ -11521,9 +11538,9 @@ formidable@^2.1.2:
|
||||||
qs "^6.11.0"
|
qs "^6.11.0"
|
||||||
|
|
||||||
fp-ts@^2.5.1:
|
fp-ts@^2.5.1:
|
||||||
version "2.16.2"
|
version "2.16.5"
|
||||||
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.2.tgz#7faa90f6fc2e8cf84c711d2c4e606afe2be9e342"
|
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.5.tgz#d79b97168aeafcf9612f18bbc017f513ecb20ac9"
|
||||||
integrity sha512-CkqAjnIKFqvo3sCyoBTqgJvF+bHrSik584S9nhTjtBESLx26cbtVMR/T9a6ApChOcSDAaM3JydDmWDUn4EEXng==
|
integrity sha512-N8T8PwMSeTKKtkm9lkj/zSTAnPC/aJIIrQhnHxxkL0KLsRCNUPANksJOlMXxcKKCo7H1ORP3No9EMD+fP0tsdA==
|
||||||
|
|
||||||
fresh@^0.5.2, fresh@~0.5.2:
|
fresh@^0.5.2, fresh@~0.5.2:
|
||||||
version "0.5.2"
|
version "0.5.2"
|
||||||
|
@ -11590,7 +11607,7 @@ fs.realpath@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||||
|
|
||||||
fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2:
|
fsevents@^2.3.2, fsevents@~2.3.2:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||||
|
@ -11753,6 +11770,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@
|
||||||
has-symbols "^1.0.3"
|
has-symbols "^1.0.3"
|
||||||
hasown "^2.0.0"
|
hasown "^2.0.0"
|
||||||
|
|
||||||
|
get-intrinsic@^1.2.4:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
|
||||||
|
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
|
||||||
|
dependencies:
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
function-bind "^1.1.2"
|
||||||
|
has-proto "^1.0.1"
|
||||||
|
has-symbols "^1.0.3"
|
||||||
|
hasown "^2.0.0"
|
||||||
|
|
||||||
get-object@^0.2.0:
|
get-object@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c"
|
resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c"
|
||||||
|
@ -12373,6 +12401,13 @@ has-property-descriptors@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
get-intrinsic "^1.1.1"
|
get-intrinsic "^1.1.1"
|
||||||
|
|
||||||
|
has-property-descriptors@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
|
||||||
|
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
|
||||||
|
dependencies:
|
||||||
|
es-define-property "^1.0.0"
|
||||||
|
|
||||||
has-proto@^1.0.1:
|
has-proto@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
|
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
|
||||||
|
@ -18520,9 +18555,9 @@ pprof-format@^2.0.7:
|
||||||
integrity sha512-1qWaGAzwMpaXJP9opRa23nPnt2Egi7RMNoNBptEE/XwHbcn4fC2b/4U4bKc5arkGkIh2ZabpF2bEb+c5GNHEKA==
|
integrity sha512-1qWaGAzwMpaXJP9opRa23nPnt2Egi7RMNoNBptEE/XwHbcn4fC2b/4U4bKc5arkGkIh2ZabpF2bEb+c5GNHEKA==
|
||||||
|
|
||||||
preact@^10.19.3:
|
preact@^10.19.3:
|
||||||
version "10.19.3"
|
version "10.20.1"
|
||||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899"
|
resolved "https://registry.yarnpkg.com/preact/-/preact-10.20.1.tgz#1bc598ab630d8612978f7533da45809a8298542b"
|
||||||
integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==
|
integrity sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==
|
||||||
|
|
||||||
prebuild-install@^7.1.1:
|
prebuild-install@^7.1.1:
|
||||||
version "7.1.1"
|
version "7.1.1"
|
||||||
|
@ -18918,7 +18953,14 @@ q@^1.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||||
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
|
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
|
||||||
|
|
||||||
qs@^6.10.3, qs@^6.11.0, qs@^6.4.0:
|
qs@^6.10.3:
|
||||||
|
version "6.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.0.tgz#edd40c3b823995946a8a0b1f208669c7a200db77"
|
||||||
|
integrity sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.6"
|
||||||
|
|
||||||
|
qs@^6.11.0, qs@^6.4.0:
|
||||||
version "6.11.2"
|
version "6.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
|
||||||
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
|
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
|
||||||
|
@ -19714,13 +19756,6 @@ rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1,
|
||||||
dependencies:
|
dependencies:
|
||||||
estree-walker "^0.6.1"
|
estree-walker "^0.6.1"
|
||||||
|
|
||||||
rollup@2.45.2:
|
|
||||||
version "2.45.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.45.2.tgz#8fb85917c9f35605720e92328f3ccbfba6f78b48"
|
|
||||||
integrity sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ==
|
|
||||||
optionalDependencies:
|
|
||||||
fsevents "~2.3.1"
|
|
||||||
|
|
||||||
rollup@^2.36.2, rollup@^2.45.2:
|
rollup@^2.36.2, rollup@^2.45.2:
|
||||||
version "2.79.1"
|
version "2.79.1"
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
|
||||||
|
@ -20000,6 +20035,18 @@ set-function-length@^1.1.1:
|
||||||
gopd "^1.0.1"
|
gopd "^1.0.1"
|
||||||
has-property-descriptors "^1.0.0"
|
has-property-descriptors "^1.0.0"
|
||||||
|
|
||||||
|
set-function-length@^1.2.1:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
|
||||||
|
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
|
||||||
|
dependencies:
|
||||||
|
define-data-property "^1.1.4"
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
function-bind "^1.1.2"
|
||||||
|
get-intrinsic "^1.2.4"
|
||||||
|
gopd "^1.0.1"
|
||||||
|
has-property-descriptors "^1.0.2"
|
||||||
|
|
||||||
set-function-name@^2.0.0, set-function-name@^2.0.1:
|
set-function-name@^2.0.0, set-function-name@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a"
|
resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a"
|
||||||
|
@ -20091,6 +20138,16 @@ side-channel@^1.0.4:
|
||||||
get-intrinsic "^1.0.2"
|
get-intrinsic "^1.0.2"
|
||||||
object-inspect "^1.9.0"
|
object-inspect "^1.9.0"
|
||||||
|
|
||||||
|
side-channel@^1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
|
||||||
|
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.7"
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
get-intrinsic "^1.2.4"
|
||||||
|
object-inspect "^1.13.1"
|
||||||
|
|
||||||
siginfo@^2.0.0:
|
siginfo@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
|
resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
|
||||||
|
@ -20678,7 +20735,16 @@ string-similarity@^4.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b"
|
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b"
|
||||||
integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==
|
integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
@ -20768,7 +20834,7 @@ stringify-object@^3.2.1:
|
||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
@ -20782,6 +20848,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^4.1.0"
|
ansi-regex "^4.1.0"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
strip-ansi@^7.0.1:
|
strip-ansi@^7.0.1:
|
||||||
version "7.0.1"
|
version "7.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
|
||||||
|
@ -21020,20 +21093,6 @@ svelte-dnd-action@^0.9.8:
|
||||||
postcss "^8.4.29"
|
postcss "^8.4.29"
|
||||||
postcss-scss "^4.0.8"
|
postcss-scss "^4.0.8"
|
||||||
|
|
||||||
svelte-flatpickr@3.2.3:
|
|
||||||
version "3.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.2.3.tgz#db5dd7ad832ef83262b45e09737955ad3d591fc8"
|
|
||||||
integrity sha512-PNkqK4Napx8nTvCwkaUXdnKo8dISThaxEOK+szTUXcY6H0dQM0TSyuoMaVWY2yX7pM+PN5cpCQCcVe8YvTRFSw==
|
|
||||||
dependencies:
|
|
||||||
flatpickr "^4.5.2"
|
|
||||||
|
|
||||||
svelte-flatpickr@^3.3.4:
|
|
||||||
version "3.3.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.3.4.tgz#80b1ed2d6bc37df78b1660404e9326bfc0a206f4"
|
|
||||||
integrity sha512-i+QqJRs8zPRKsxv8r2GIk1fsb8cI3ozn3/aHXtViAoNKLy0j4PV7OSWavgEZC1wlAa34qi2hMkUh+vg6qt2DRA==
|
|
||||||
dependencies:
|
|
||||||
flatpickr "^4.5.2"
|
|
||||||
|
|
||||||
svelte-hmr@^0.15.1:
|
svelte-hmr@^0.15.1:
|
||||||
version "0.15.3"
|
version "0.15.3"
|
||||||
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.3.tgz#df54ccde9be3f091bf5f18fc4ef7b8eb6405fbe6"
|
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.3.tgz#df54ccde9be3f091bf5f18fc4ef7b8eb6405fbe6"
|
||||||
|
@ -22718,7 +22777,7 @@ worker-farm@1.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
errno "~0.1.7"
|
errno "~0.1.7"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
@ -22736,6 +22795,15 @@ wrap-ansi@^5.1.0:
|
||||||
string-width "^3.0.0"
|
string-width "^3.0.0"
|
||||||
strip-ansi "^5.0.0"
|
strip-ansi "^5.0.0"
|
||||||
|
|
||||||
|
wrap-ansi@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
string-width "^4.1.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
@ -23117,9 +23185,9 @@ z-schema@^5.0.1:
|
||||||
commander "^9.4.1"
|
commander "^9.4.1"
|
||||||
|
|
||||||
zeebe-node@^8.2.5:
|
zeebe-node@^8.2.5:
|
||||||
version "8.3.1"
|
version "8.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/zeebe-node/-/zeebe-node-8.3.1.tgz#e100bf3708464e305305b4efa1ffde53f9786c45"
|
resolved "https://registry.yarnpkg.com/zeebe-node/-/zeebe-node-8.3.2.tgz#64d156b715f03f8637054aeedb3d3024b4a09db4"
|
||||||
integrity sha512-68ascWO3g7g+9WwDzvfa3I9TkLKHku5auEgSINP+k5ktNfsfGW68ELDmEJA+XHZgzvGsdGILZqGRzVd5SC8aaQ==
|
integrity sha512-3/xbiTvhaa668JHtMEwELv5dN6HR7Qw8gzmCdjp3Brj6ekdhROVx8x/0JWKSV3Mx64ac3+eEc+9nB5+ZXcO/bg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@grpc/grpc-js" "1.9.7"
|
"@grpc/grpc-js" "1.9.7"
|
||||||
"@grpc/proto-loader" "0.7.10"
|
"@grpc/proto-loader" "0.7.10"
|
||||||
|
|
Loading…
Reference in New Issue