Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typing grid clipboard store #15250

Merged
merged 5 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import { derived, writable, get } from "svelte/store"
import { derived, writable, get, Writable, Readable } from "svelte/store"
import { Helpers } from "@budibase/bbui"
import { parseCellID, getCellID } from "../lib/utils"
import { NewRowID } from "../lib/constants"
import { Store as StoreContext } from "."

export const createStores = () => {
const clipboard = writable({
type ClipboardStoreData =
| {
value: any[][]
multiCellCopy: true
}
| {
value: any | null | undefined
multiCellCopy: false
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really cool - I didn't know you can basically conditionally type it based on the value of another field like this.

interface ClipboardStore {
clipboard: Writable<ClipboardStoreData>
}

interface ClipboardDerivedStore {
copyAllowed: Readable<boolean>
pasteAllowed: Readable<boolean>
}

interface ClipboardActions {
clipboard: ClipboardStore["clipboard"] & {
actions: {
copy: () => void
paste: (progressCallback: () => void) => Promise<void>
}
}
}

export type Store = ClipboardStore & ClipboardDerivedStore & ClipboardActions

export const createStores = (): ClipboardStore => {
const clipboard = writable<ClipboardStoreData>({
value: null,
multiCellCopy: false,
})
Expand All @@ -13,7 +44,7 @@ export const createStores = () => {
}
}

export const deriveStores = context => {
export const deriveStores = (context: StoreContext): ClipboardDerivedStore => {
const { clipboard, focusedCellAPI, selectedCellCount, config, focusedRowId } =
context

Expand Down Expand Up @@ -60,7 +91,7 @@ export const deriveStores = context => {
}
}

export const createActions = context => {
export const createActions = (context: StoreContext): ClipboardActions => {
const {
clipboard,
focusedCellAPI,
Expand Down Expand Up @@ -92,11 +123,11 @@ export const createActions = context => {
const $rowChangeCache = get(rowChangeCache)

// Extract value of each selected cell, accounting for the change cache
let value = []
for (let row of $selectedCells) {
const value = []
for (const row of $selectedCells) {
const rowValues = []
for (let cellId of row) {
const { rowId, field } = parseCellID(cellId)
for (const cellId of row) {
const { rowId = "", field = "" } = parseCellID(cellId)
const row = {
...$rowLookupMap[rowId],
...$rowChangeCache[rowId],
Expand All @@ -113,7 +144,7 @@ export const createActions = context => {
})
} else {
// Single value to copy
const value = $focusedCellAPI.getValue()
const value = $focusedCellAPI?.getValue()
clipboard.set({
value,
multiCellCopy,
Expand All @@ -130,7 +161,7 @@ export const createActions = context => {
}

// Pastes the previously copied value(s) into the selected cell(s)
const paste = async progressCallback => {
const paste = async (progressCallback: () => void) => {
if (!get(pasteAllowed)) {
return
}
Expand Down Expand Up @@ -166,8 +197,8 @@ export const createActions = context => {
const { rowId, field } = parseCellID($focusedCellId)
const $rowLookupMap = get(rowLookupMap)
const $columnLookupMap = get(columnLookupMap)
const rowIdx = $rowLookupMap[rowId].__idx
const colIdx = $columnLookupMap[field].__idx
const rowIdx = $rowLookupMap[rowId!].__idx
const colIdx = $columnLookupMap[field!].__idx || 0

// Get limits of how many rows and columns we're able to paste into
const $rows = get(rows)
Expand All @@ -187,7 +218,7 @@ export const createActions = context => {
// Paste into target cell range
if (targetCellId === $focusedCellId) {
// Single cell edge case
get(focusedCellAPI).setValue(value[0][0])
get(focusedCellAPI)?.setValue(value[0][0])
} else {
// Select the new cells to paste into, then paste
selectedCells.actions.selectRange($focusedCellId, targetCellId)
Expand All @@ -197,29 +228,34 @@ export const createActions = context => {
} else {
if (multiCellPaste) {
// Single to multi - duplicate value to all selected cells
const newValue = get(selectedCells).map(row => row.map(() => value))
const newValue = get(selectedCells).map(row => row.map(() => value!))
await pasteIntoSelectedCells(newValue, progressCallback)
} else {
// Single to single - just update the cell's value
get(focusedCellAPI).setValue(value)
get(focusedCellAPI)?.setValue(value ?? null)
}
}
}

// Paste the specified value into the currently selected cells
const pasteIntoSelectedCells = async (value, progressCallback) => {
const pasteIntoSelectedCells = async (
value: string[][],
progressCallback: () => any
) => {
const $selectedCells = get(selectedCells)

// Find the extent at which we can paste
const rowExtent = Math.min(value.length, $selectedCells.length)
const colExtent = Math.min(value[0].length, $selectedCells[0].length)

// Build change map
let changeMap = {}
let changeMap: Record<string, Record<string, string>> = {}
for (let rowIdx = 0; rowIdx < rowExtent; rowIdx++) {
for (let colIdx = 0; colIdx < colExtent; colIdx++) {
const cellId = $selectedCells[rowIdx][colIdx]
const { rowId, field } = parseCellID(cellId)
let { rowId, field } = parseCellID(cellId)
rowId = rowId!
field = field!
if (!changeMap[rowId]) {
changeMap[rowId] = {}
}
Expand Down
6 changes: 4 additions & 2 deletions packages/frontend-core/src/components/grid/stores/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Writable } from "svelte/store"
import { Readable, Writable } from "svelte/store"
import type { APIClient } from "../../../api/types"

import * as Bounds from "./bounds"
Expand Down Expand Up @@ -65,7 +65,8 @@ export type Store = BaseStore &
Users.Store &
Menu.Store &
Filter.Store &
UI.Store & {
UI.Store &
Clipboard.Store & {
// TODO while typing the rest of stores
fetch: Writable<any>
sort: Writable<any>
Expand All @@ -83,6 +84,7 @@ export type Store = BaseStore &
rowLookupMap: Writable<any>
width: Writable<number>
fixedRowHeight: Writable<number>
rowChangeCache: Readable<any>
}

export const attachStores = (context: Store): Store => {
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend-core/src/components/grid/stores/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface UIStore {
focusedCellId: Writable<string | null>
focusedCellAPI: Writable<{
isReadonly: () => boolean
getValue: () => any
setValue: (val: any) => void
} | null>
selectedRows: Writable<Record<string, boolean>>
hoveredRowId: Writable<string | null>
Expand Down
Loading