Rewrite relationship cell and update default column widths

This commit is contained in:
Andrew Kingston 2023-04-17 12:20:21 +01:00
parent b867d359f4
commit 6931436006
7 changed files with 206 additions and 96 deletions

View File

@ -164,7 +164,6 @@
user-select: none; user-select: none;
} }
.arrow { .arrow {
/*border-right: 2px solid var(--spectrum-global-color-blue-400);*/
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
@ -202,7 +201,7 @@
top: 0; top: 0;
} }
.option { .option {
flex: 0 0 var(--row-height); flex: 0 0 var(--default-row-height);
padding: 0 var(--cell-padding); padding: 0 var(--cell-padding);
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -13,7 +13,8 @@
export let invertX = false export let invertX = false
export let invertY = false export let invertY = false
const { API } = getContext("sheet") const { API, dispatch } = getContext("sheet")
const color = getColor(0)
let isOpen = false let isOpen = false
let searchResults let searchResults
@ -23,11 +24,9 @@
let primaryDisplay let primaryDisplay
let candidateIndex let candidateIndex
let lastSearchId let lastSearchId
let results
$: oneRowOnly = schema?.relationshipType === "one-to-many" $: oneRowOnly = schema?.relationshipType === "one-to-many"
$: editable = focused && !readonly $: editable = focused && !readonly
$: results = getResults(searchResults, value)
$: lookupMap = buildLookupMap(value, isOpen) $: lookupMap = buildLookupMap(value, isOpen)
$: search(searchString) $: search(searchString)
$: { $: {
@ -94,10 +93,12 @@
} }
// Sort and process results // Sort and process results
searchResults = results.rows?.map(row => ({ searchResults = sortRows(
...row, results.rows?.map(row => ({
primaryDisplay: row[primaryDisplay], ...row,
})) primaryDisplay: row[primaryDisplay],
}))
)
candidateIndex = searchResults?.length ? 0 : null candidateIndex = searchResults?.length ? 0 : null
lastSearchString = searchString lastSearchString = searchString
}, 250) }, 250)
@ -112,17 +113,9 @@
}) })
} }
// Generates the list of results to show inside the dropdown
const getResults = (searchResults, value) => {
return searchString ? sortRows(searchResults) : sortRows(value)
}
const open = async () => { const open = async () => {
isOpen = true isOpen = true
// Ensure results are properly reset
results = sortRows(value)
// Fetch definition if required // Fetch definition if required
if (!definition) { if (!definition) {
definition = await API.fetchTableDefinition(schema.tableId) definition = await API.fetchTableDefinition(schema.tableId)
@ -196,6 +189,14 @@
close() close()
} }
const showRelationship = async id => {
const relatedRow = await API.fetchRow({
tableId: schema.tableId,
rowId: id,
})
dispatch("edit-row", relatedRow)
}
onMount(() => { onMount(() => {
api = { api = {
focus: open, focus: open,
@ -205,74 +206,136 @@
}) })
</script> </script>
<div class="container" on:click={editable ? open : null} class:editable> <div class="wrapper" class:editable class:focused style="--color:{color};">
{#each value || [] as relationship, idx} <div class="container">
{#if relationship.primaryDisplay} <div class="values" on:wheel={e => (focused ? e.stopPropagation() : null)}>
<div class="badge" style="--color: {getColor(idx)}"> {#each value || [] as relationship, idx}
{relationship.primaryDisplay} {#if relationship.primaryDisplay}
</div> <div class="badge">
{/if} <span
{/each} on:click={focused
</div> ? () => showRelationship(relationship._id)
: null}
{#if isOpen} >
<div class="dropdown" class:invertX class:invertY on:wheel|stopPropagation> {relationship.primaryDisplay}
<div class="search"> </span>
<Input autofocus quiet type="text" bind:value={searchString} /> {#if focused}
</div>
{#if !lastSearchString}
{#if primaryDisplay}
<div class="info">
Search for {definition.name} rows by {primaryDisplay}
</div>
{/if}
{:else}
<div class="info">
{searchResults.length} row{searchResults.length === 1 ? "" : "s"} found
</div>
{/if}
{#if results?.length}
<div class="results">
{#each results as row, idx}
<div
class="result"
on:click={() => toggleRow(row)}
class:candidate={idx === candidateIndex}
on:mouseenter={() => (candidateIndex = idx)}
>
<div class="badge" style="--color: {getColor(idx)}">
{row.primaryDisplay}
</div>
{#if isRowSelected(row)}
<Icon <Icon
size="S" name="Close"
name="Checkmark" size="XS"
color="var(--spectrum-global-color-blue-400)" hoverable
on:click={() => toggleRow(relationship)}
/> />
{/if} {/if}
</div> </div>
{/each} {/if}
</div> {/each}
{/if} {#if focused}
<div class="add" on:click={open}>
<Icon name="Add" size="S" />
</div>
{/if}
</div>
</div> </div>
{/if}
{#if isOpen}
<div class="dropdown" class:invertX class:invertY on:wheel|stopPropagation>
<div class="search">
<Input
autofocus
quiet
type="text"
bind:value={searchString}
placeholder={primaryDisplay ? `Search by ${primaryDisplay}` : null}
/>
</div>
{#if searchResults}
<div class="info">
{searchResults.length} row{searchResults.length === 1 ? "" : "s"} found
</div>
{/if}
{#if searchResults?.length}
<div class="results">
{#each searchResults as row, idx}
<div
class="result"
on:click={() => toggleRow(row)}
class:candidate={idx === candidateIndex}
on:mouseenter={() => (candidateIndex = idx)}
>
<div class="badge">
{row.primaryDisplay}
</div>
{#if isRowSelected(row)}
<Icon
size="S"
name="Checkmark"
color="var(--spectrum-global-color-blue-400)"
/>
{/if}
</div>
{/each}
</div>
{/if}
</div>
{/if}
</div>
<style> <style>
.container { .wrapper {
align-self: stretch;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 var(--cell-padding);
flex: 1 1 auto; flex: 1 1 auto;
width: 0; align-self: flex-start;
gap: var(--cell-spacing); min-height: var(--row-height);
max-height: var(--row-height);
overflow: hidden; overflow: hidden;
--max-relationship-height: 94px;
} }
.container.editable:hover { .wrapper.focused {
cursor: pointer; position: absolute;
top: 0;
left: 0;
width: 100%;
background: var(--cell-background);
z-index: 1;
max-height: none;
overflow: visible;
} }
.container {
min-height: var(--row-height);
overflow: hidden;
}
.focused .container {
overflow-y: auto;
border-radius: 2px;
max-height: var(--max-relationship-height);
}
.focused .container:after {
content: " ";
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
border: 2px solid var(--cell-color);
pointer-events: none;
border-radius: 2px;
box-sizing: border-box;
}
.values {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
flex: 1 1 auto;
gap: var(--cell-spacing);
grid-row-gap: 7px;
overflow: hidden;
padding: 7px var(--cell-padding);
flex-wrap: wrap;
}
.badge { .badge {
flex: 0 0 auto; flex: 0 0 auto;
padding: 2px var(--cell-padding); padding: 2px var(--cell-padding);
@ -282,6 +345,23 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
display: flex;
align-items: center;
gap: var(--cell-spacing);
}
.focused .badge span:hover {
cursor: pointer;
text-decoration: underline;
}
.add {
background: var(--spectrum-global-color-gray-200);
padding: 3px 4px;
border-radius: 4px;
}
.add:hover {
background: var(--spectrum-global-color-gray-300);
cursor: pointer;
} }
.dropdown { .dropdown {
@ -290,7 +370,10 @@
left: 0; left: 0;
min-width: 100%; min-width: 100%;
max-width: calc(100% + var(--max-cell-render-width-overflow)); max-width: calc(100% + var(--max-cell-render-width-overflow));
max-height: var(--max-cell-render-height); height: calc(
var(--max-cell-render-height) + var(--row-height) -
var(--max-relationship-height)
);
background: var(--cell-background); background: var(--cell-background);
border: var(--cell-border); border: var(--cell-border);
box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.15); box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.15);
@ -301,7 +384,7 @@
} }
.dropdown.invertY { .dropdown.invertY {
transform: translateY(-100%); transform: translateY(-100%);
top: 0; top: -1px;
} }
.dropdown.invertX { .dropdown.invertX {
left: auto; left: auto;
@ -317,7 +400,7 @@
} }
.result { .result {
padding: 0 var(--cell-padding); padding: 0 var(--cell-padding);
flex: 0 0 var(--row-height); flex: 0 0 var(--default-row-height);
display: flex; display: flex;
gap: var(--cell-spacing); gap: var(--cell-spacing);
justify-content: space-between; justify-content: space-between;
@ -332,7 +415,7 @@
} }
.search { .search {
flex: 0 0 calc(var(--row-height) - 1px); flex: 0 0 calc(var(--default-row-height) - 1px);
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0 var(--cell-padding); margin: 0 var(--cell-padding);
@ -342,6 +425,9 @@
min-width: 0; min-width: 0;
width: 100%; width: 100%;
} }
.search :global(.spectrum-Textfield-input) {
font-size: 13px;
}
.search :global(.spectrum-Form-item) { .search :global(.spectrum-Form-item) {
flex: 1 1 auto; flex: 1 1 auto;
} }
@ -349,8 +435,8 @@
.info { .info {
color: var(--spectrum-global-color-gray-600); color: var(--spectrum-global-color-gray-600);
font-size: 12px; font-size: 12px;
padding: var(--cell-padding); padding: 4px var(--cell-padding);
flex: 0 0 var(--row-height); flex: 0 0 auto;
display: flex; display: flex;
align-items: center; align-items: center;
white-space: nowrap; white-space: nowrap;

View File

@ -1,20 +1,21 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { ActionButton, Popover } from "@budibase/bbui" import { ActionButton, Popover } from "@budibase/bbui"
import { DefaultRowHeight } from "../lib/constants"
const { rowHeight, loaded } = getContext("sheet") const { rowHeight, loaded } = getContext("sheet")
const sizeOptions = [ const sizeOptions = [
{ {
label: "Small", label: "Small",
size: 36, size: DefaultRowHeight,
}, },
{ {
label: "Medium", label: "Medium",
size: 54, size: 65,
}, },
{ {
label: "Large", label: "Large",
size: 72, size: 94,
}, },
] ]

View File

@ -24,7 +24,9 @@
MaxCellRenderHeight, MaxCellRenderHeight,
MaxCellRenderWidthOverflow, MaxCellRenderWidthOverflow,
GutterWidth, GutterWidth,
DefaultRowHeight,
} from "../lib/constants" } from "../lib/constants"
import RowHeightButton from "../controls/RowHeightButton.svelte"
export let API export let API
export let tableId export let tableId
@ -88,13 +90,14 @@
id="sheet-{rand}" id="sheet-{rand}"
class:is-resizing={$isResizing} class:is-resizing={$isResizing}
class:is-reordering={$isReordering} class:is-reordering={$isReordering}
style="--row-height:{$rowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px;" 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;"
> >
<div class="controls"> <div class="controls">
<div class="controls-left"> <div class="controls-left">
<AddRowButton /> <AddRowButton />
<AddColumnButton /> <AddColumnButton />
<slot name="controls" /> <slot name="controls" />
<RowHeightButton />
<HideColumnsButton /> <HideColumnsButton />
<SortButton /> <SortButton />
</div> </div>

View File

@ -1,6 +1,9 @@
export const SheetPadding = 264 export const SheetPadding = 276
export const MaxCellRenderHeight = 216 export const MaxCellRenderHeight = 252
export const MaxCellRenderWidthOverflow = 200 export const MaxCellRenderWidthOverflow = 200
export const ScrollBarSize = 8 export const ScrollBarSize = 8
export const GutterWidth = 72 export const GutterWidth = 72
export const DefaultColumnWidth = 200 export const DefaultColumnWidth = 200
export const DefaultRelationshipColumnWidth = 360
export const MinColumnWidth = 100
export const DefaultRowHeight = 36

View File

@ -1,6 +1,16 @@
import { derived, get, writable } from "svelte/store" import { derived, get, writable } from "svelte/store"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { GutterWidth, DefaultColumnWidth } from "../lib/constants" import {
GutterWidth,
DefaultColumnWidth,
DefaultRelationshipColumnWidth,
} from "../lib/constants"
export const getDefaultColumnWidth = column => {
return column?.schema?.type === "link"
? DefaultRelationshipColumnWidth
: DefaultColumnWidth
}
export const createStores = () => { export const createStores = () => {
const columns = writable([]) const columns = writable([])
@ -76,10 +86,12 @@ export const deriveStores = context => {
// Copy over metadata // Copy over metadata
if (column === $stickyColumn?.name) { if (column === $stickyColumn?.name) {
newSchema[column].visible = true newSchema[column].visible = true
newSchema[column].width = $stickyColumn.width || DefaultColumnWidth newSchema[column].width =
$stickyColumn.width || getDefaultColumnWidth($stickyColumn)
} else { } else {
newSchema[column].visible = $columns[index]?.visible ?? true newSchema[column].visible = $columns[index]?.visible ?? true
newSchema[column].width = $columns[index]?.width || DefaultColumnWidth newSchema[column].width =
$columns[index]?.width || getDefaultColumnWidth($columns[index])
} }
}) })
@ -143,7 +155,9 @@ export const initialise = context => {
.map(field => ({ .map(field => ({
name: field, name: field,
schema: schema[field], schema: schema[field],
width: schema[field].width || DefaultColumnWidth, width:
schema[field].width ||
getDefaultColumnWidth({ schema: schema[field].type }),
visible: schema[field].visible ?? true, visible: schema[field].visible ?? true,
order: schema[field].order, order: schema[field].order,
})) }))
@ -181,9 +195,12 @@ export const initialise = context => {
if (!existing && currentStickyColumn?.name === primaryDisplay) { if (!existing && currentStickyColumn?.name === primaryDisplay) {
existing = currentStickyColumn existing = currentStickyColumn
} }
const defaultWidth = getDefaultColumnWidth({
schema: schema[primaryDisplay],
})
stickyColumn.set({ stickyColumn.set({
name: primaryDisplay, name: primaryDisplay,
width: schema[primaryDisplay].width || DefaultColumnWidth, width: schema[primaryDisplay].width || defaultWidth,
left: GutterWidth, left: GutterWidth,
schema: schema[primaryDisplay], schema: schema[primaryDisplay],
idx: "sticky", idx: "sticky",

View File

@ -1,7 +1,6 @@
import { writable, get, derived } from "svelte/store" import { writable, get, derived } from "svelte/store"
import { DefaultColumnWidth } from "../lib/constants" import { getDefaultColumnWidth } from "./columns"
import { MinColumnWidth } from "../lib/constants"
export const MinColumnWidth = 100
const initialState = { const initialState = {
initialMouseX: null, initialMouseX: null,
@ -98,15 +97,17 @@ export const deriveStores = context => {
// Resets a column size back to default // Resets a column size back to default
const resetSize = async column => { const resetSize = async column => {
if (column.name === get(stickyColumn)?.name) { const $stickyColumn = get(stickyColumn)
const width = getDefaultColumnWidth(column)
if (column.name === $stickyColumn?.name) {
stickyColumn.update(state => ({ stickyColumn.update(state => ({
...state, ...state,
width: DefaultColumnWidth, width,
})) }))
} else { } else {
columns.update(state => { columns.update(state => {
const columnIdx = state.findIndex(x => x.name === column.name) const columnIdx = state.findIndex(x => x.name === column.name)
state[columnIdx].width = DefaultColumnWidth state[columnIdx].width = width
return [...state] return [...state]
}) })
} }