feat(admin-sdk,admin-bundler,admin-shared,medusa): Restructure admin packages (#8988)

**What**
- Renames /admin-next -> /admin
- Renames @medusajs/admin-sdk -> @medusajs/admin-bundler
- Creates a new package called @medusajs/admin-sdk that will hold all tooling relevant to creating admin extensions. This is currently `defineRouteConfig` and `defineWidgetConfig`, but will eventually also export methods for adding custom fields, register translation, etc. 
  - cc: @shahednasser we should update the examples in the docs so these functions are imported from `@medusajs/admin-sdk`. People will also need to install the package in their project, as it's no longer a transient dependency.
  - cc: @olivermrbl we might want to publish a changelog when this is merged, as it is a breaking change, and will require people to import the `defineXConfig` from the new package instead of `@medusajs/admin-shared`.
- Updates CODEOWNERS so /admin packages does not require a review from the UI team.
This commit is contained in:
Kasper Fabricius Kristensen
2024-09-04 21:00:25 +02:00
committed by GitHub
parent beaa851302
commit 0fe1201435
1440 changed files with 122 additions and 86 deletions

View File

@@ -0,0 +1,34 @@
import { Command } from "../../../hooks/use-command-history"
export type DataGridBulkUpdateCommandArgs = {
fields: string[]
next: any[]
prev: any[]
setter: (fields: string[], values: any[]) => void
}
export class DataGridBulkUpdateCommand implements Command {
private _fields: string[]
private _prev: any[]
private _next: any[]
private _setter: (fields: string[], any: string[]) => void
constructor({ fields, prev, next, setter }: DataGridBulkUpdateCommandArgs) {
this._fields = fields
this._prev = prev
this._next = next
this._setter = setter
}
execute(): void {
this._setter(this._fields, this._next)
}
undo(): void {
this._setter(this._fields, this._prev)
}
redo(): void {
this.execute()
}
}

View File

@@ -0,0 +1,388 @@
import { ColumnDef, Row } from "@tanstack/react-table"
import { FieldValues } from "react-hook-form"
import { DataGridColumnType, DataGridCoordinates, Grid, GridCell, InternalColumnMeta } from "../types"
export class DataGridMatrix<TData, TFieldValues extends FieldValues> {
private cells: Grid<TFieldValues>
public rowAccessors: (string | null)[] = []
public columnAccessors: (string | null)[] = []
constructor(data: Row<TData>[], columns: ColumnDef<TData>[]) {
this.cells = this._populateCells(data, columns)
this.rowAccessors = this._computeRowAccessors()
this.columnAccessors = this._computeColumnAccessors()
}
private _computeRowAccessors(): (string | null)[] {
return this.cells.map((_, rowIndex) => this.getRowAccessor(rowIndex))
}
private _computeColumnAccessors(): (string | null)[] {
if (this.cells.length === 0) {
return []
}
return this.cells[0].map((_, colIndex) => this.getColumnAccessor(colIndex))
}
getFirstNavigableCell(): DataGridCoordinates | null {
for (let row = 0; row < this.cells.length; row++) {
for (let col = 0; col < this.cells[0].length; col++) {
if (this.cells[row][col] !== null) {
return { row, col }
}
}
}
return null
}
getFieldsInRow(row: number): string[] {
const keys: string[] = []
if (row < 0 || row >= this.cells.length) {
return keys
}
this.cells[row].forEach((cell) => {
if (cell !== null) {
keys.push(cell.field)
}
})
return keys
}
getFieldsInSelection(
start: DataGridCoordinates | null,
end: DataGridCoordinates | null
): string[] {
const keys: string[] = []
if (!start || !end) {
return keys
}
if (start.col !== end.col) {
throw new Error("Selection must be in the same column")
}
const startRow = Math.min(start.row, end.row)
const endRow = Math.max(start.row, end.row)
const col = start.col
for (let row = startRow; row <= endRow; row++) {
if (this._isValidPosition(row, col) && this.cells[row][col] !== null) {
keys.push(this.cells[row][col]?.field as string)
}
}
return keys
}
getCellField(cell: DataGridCoordinates): string | null {
if (this._isValidPosition(cell.row, cell.col)) {
return this.cells[cell.row][cell.col]?.field || null
}
return null
}
getCellType(cell: DataGridCoordinates): DataGridColumnType | null {
if (this._isValidPosition(cell.row, cell.col)) {
return this.cells[cell.row][cell.col]?.type || null
}
return null
}
getIsCellSelected(
cell: DataGridCoordinates | null,
start: DataGridCoordinates | null,
end: DataGridCoordinates | null
): boolean {
if (!cell || !start || !end) {
return false
}
if (start.col !== end.col) {
throw new Error("Selection must be in the same column")
}
const startRow = Math.min(start.row, end.row)
const endRow = Math.max(start.row, end.row)
const col = start.col
return cell.col === col && cell.row >= startRow && cell.row <= endRow
}
toggleColumn(col: number, enabled: boolean) {
if (col < 0 || col >= this.cells[0].length) {
return
}
this.cells.forEach((row, index) => {
const cell = row[col]
if (cell) {
this.cells[index][col] = {
...cell,
enabled,
}
}
})
}
toggleRow(row: number, enabled: boolean) {
if (row < 0 || row >= this.cells.length) {
return
}
this.cells[row].forEach((cell, index) => {
if (cell) {
this.cells[row][index] = {
...cell,
enabled,
}
}
})
}
getCoordinatesByField(field: string): DataGridCoordinates | null {
if (this.rowAccessors.length === 1) {
const col = this.columnAccessors.indexOf(field)
if (col === -1) {
return null
}
return { row: 0, col }
}
for (let row = 0; row < this.rowAccessors.length; row++) {
const rowAccessor = this.rowAccessors[row]
if (rowAccessor === null) {
continue
}
if (!field.startsWith(rowAccessor)) {
continue
}
for (let column = 0; column < this.columnAccessors.length; column++) {
const columnAccessor = this.columnAccessors[column]
if (columnAccessor === null) {
continue
}
const fullFieldPath = `${rowAccessor}.${columnAccessor}`
if (fullFieldPath === field) {
return { row, col: column }
}
}
}
return null
}
getRowAccessor(row: number): string | null {
if (row < 0 || row >= this.cells.length) {
return null
}
const cells = this.cells[row]
const nonNullFields = cells
.filter((cell): cell is GridCell<TFieldValues> => cell !== null)
.map((cell) => cell.field.split("."))
if (nonNullFields.length === 0) {
return null
}
let commonParts = nonNullFields[0]
for (const segments of nonNullFields) {
commonParts = commonParts.filter(
(part, index) => segments[index] === part
)
if (commonParts.length === 0) {
break
}
}
const accessor = commonParts.join(".")
if (!accessor) {
return null
}
return accessor
}
public getColumnAccessor(column: number): string | null {
if (column < 0 || column >= this.cells[0].length) {
return null
}
// Extract the unique part of the field name for each row in the specified column
const uniqueParts = this.cells
.map((row, rowIndex) => {
const cell = row[column]
if (!cell) {
return null
}
// Get the row accessor for the current row
const rowAccessor = this.getRowAccessor(rowIndex)
// Remove the row accessor part from the field name
if (rowAccessor && cell.field.startsWith(rowAccessor + ".")) {
return cell.field.slice(rowAccessor.length + 1) // Extract the part after the row accessor
}
return null
})
.filter((part) => part !== null) // Filter out null values
if (uniqueParts.length === 0) {
return null
}
// Ensure all unique parts are the same (this should be true for well-formed data)
const firstPart = uniqueParts[0]
const isConsistent = uniqueParts.every((part) => part === firstPart)
return isConsistent ? firstPart : null
}
getValidMovement(
row: number,
col: number,
direction: string,
metaKey: boolean = false
): DataGridCoordinates {
const [dRow, dCol] = this._getDirectionDeltas(direction)
if (metaKey) {
return this._getLastValidCellInDirection(row, col, dRow, dCol)
} else {
let newRow = row + dRow
let newCol = col + dCol
while (this._isValidPosition(newRow, newCol)) {
if (
this.cells[newRow][newCol] !== null &&
this.cells[newRow][newCol]?.enabled !== false
) {
return { row: newRow, col: newCol }
}
newRow += dRow
newCol += dCol
}
return { row, col }
}
}
private _isValidPosition(
row: number,
col: number,
cells?: Grid<TFieldValues>
): boolean {
if (!cells) {
cells = this.cells
}
return row >= 0 && row < cells.length && col >= 0 && col < cells[0].length
}
private _getDirectionDeltas(direction: string): [number, number] {
switch (direction) {
case "ArrowUp":
return [-1, 0]
case "ArrowDown":
return [1, 0]
case "ArrowLeft":
return [0, -1]
case "ArrowRight":
return [0, 1]
default:
return [0, 0]
}
}
private _getLastValidCellInDirection(
row: number,
col: number,
dRow: number,
dCol: number
): DataGridCoordinates {
let newRow = row
let newCol = col
let lastValidRow = row
let lastValidCol = col
while (this._isValidPosition(newRow + dRow, newCol + dCol)) {
newRow += dRow
newCol += dCol
if (this.cells[newRow][newCol] !== null) {
lastValidRow = newRow
lastValidCol = newCol
}
}
return {
row: lastValidRow,
col: lastValidCol,
}
}
private _populateCells(rows: Row<TData>[], columns: ColumnDef<TData>[]) {
const cells = Array.from({ length: rows.length }, () =>
Array(columns.length).fill(null)
) as Grid<TFieldValues>
rows.forEach((row, rowIndex) => {
columns.forEach((column, colIndex) => {
if (!this._isValidPosition(rowIndex, colIndex, cells)) {
return
}
const {
name: _,
field,
type,
...rest
} = column.meta as InternalColumnMeta<TData, TFieldValues>
const context = {
row,
column: {
...column,
meta: rest,
},
}
const fieldValue = field ? field(context) : null
if (!fieldValue || !type) {
return
}
cells[rowIndex][colIndex] = {
field: fieldValue,
type,
enabled: true,
}
})
})
return cells
}
}

View File

@@ -0,0 +1,74 @@
import { DataGridCoordinates } from "../types"
import { generateCellId } from "../utils"
export class DataGridQueryTool {
private container: HTMLElement | null
constructor(container: HTMLElement | null) {
this.container = container
}
getInput(cell: DataGridCoordinates) {
const id = this._getCellId(cell)
const input = this.container?.querySelector(`[data-cell-id="${id}"]`)
if (!input) {
return null
}
return input as HTMLElement
}
getInputByField(field: string) {
const input = this.container?.querySelector(`[data-field="${field}"]`)
if (!input) {
return null
}
return input as HTMLElement
}
getCoordinatesByField(field: string): DataGridCoordinates | null {
const cell = this.container?.querySelector(
`[data-field="${field}"][data-cell-id]`
)
if (!cell) {
return null
}
const cellId = cell.getAttribute("data-cell-id")
if (!cellId) {
return null
}
const [row, col] = cellId.split(":").map((n) => parseInt(n, 10))
if (isNaN(row) || isNaN(col)) {
return null
}
return { row, col }
}
getContainer(cell: DataGridCoordinates) {
const id = this._getCellId(cell)
const container = this.container?.querySelector(
`[data-container-id="${id}"]`
)
if (!container) {
return null
}
return container as HTMLElement
}
private _getCellId(cell: DataGridCoordinates): string {
return generateCellId(cell)
}
}

View File

@@ -0,0 +1,33 @@
import { Command } from "../../../hooks/use-command-history"
export type DataGridUpdateCommandArgs = {
prev: any
next: any
setter: (value: any) => void
}
export class DataGridUpdateCommand implements Command {
private _prev: any
private _next: any
private _setter: (value: any) => void
constructor({ prev, next, setter }: DataGridUpdateCommandArgs) {
this._prev = prev
this._next = next
this._setter = setter
}
execute(): void {
this._setter(this._next)
}
undo(): void {
this._setter(this._prev)
}
redo(): void {
this.execute()
}
}

View File

@@ -0,0 +1,5 @@
export * from "./data-grid-bulk-update-command"
export * from "./data-grid-matrix"
export * from "./data-grid-query-tool"
export * from "./data-grid-update-command"