mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-24 00:47:50 +01:00
WIP
This commit is contained in:
parent
3060b29d3e
commit
cf20c434ae
@ -0,0 +1,62 @@
|
||||
import { UniqueIdentifier } from '@dnd-kit/core'
|
||||
import { Meta, StoryFn, StoryObj } from '@storybook/react'
|
||||
|
||||
import { VerticalNestedDND, VerticalNestedDNDProps } from './VerticalNestedDND'
|
||||
|
||||
type Story = StoryObj<typeof VerticalNestedDND>
|
||||
const meta: Meta<typeof VerticalNestedDND> = {
|
||||
title: 'Lemon UI/VerticalNestedDND',
|
||||
component: VerticalNestedDND,
|
||||
parameters: {
|
||||
testOptions: {
|
||||
waitForLoadersToDisappear: false,
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
export default meta
|
||||
|
||||
interface ExampleSubItem {
|
||||
id: UniqueIdentifier
|
||||
}
|
||||
interface ExampleItem {
|
||||
id: UniqueIdentifier
|
||||
items: ExampleSubItem[]
|
||||
}
|
||||
|
||||
const Template: StoryFn<typeof VerticalNestedDND> = (props: VerticalNestedDNDProps<ExampleSubItem, ExampleItem>) => {
|
||||
const starterData: ExampleItem[] = [
|
||||
{
|
||||
id: 'A',
|
||||
items: [
|
||||
{
|
||||
id: 'A1',
|
||||
},
|
||||
{
|
||||
id: 'A2',
|
||||
},
|
||||
{
|
||||
id: 'A3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'B',
|
||||
items: [
|
||||
{
|
||||
id: 'B1',
|
||||
},
|
||||
{
|
||||
id: 'B2',
|
||||
},
|
||||
{
|
||||
id: 'B3',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return <VerticalNestedDND {...props} initialItems={starterData} />
|
||||
}
|
||||
|
||||
export const Base: Story = Template.bind({})
|
@ -0,0 +1,680 @@
|
||||
import './styles.scss'
|
||||
|
||||
import {
|
||||
closestCenter,
|
||||
CollisionDetection,
|
||||
defaultDropAnimationSideEffects,
|
||||
DndContext,
|
||||
DraggableSyntheticListeners,
|
||||
DragOverlay,
|
||||
DropAnimation,
|
||||
getFirstCollision,
|
||||
MeasuringStrategy,
|
||||
MouseSensor,
|
||||
pointerWithin,
|
||||
rectIntersection,
|
||||
TouchSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core'
|
||||
import type { UniqueIdentifier } from '@dnd-kit/core/dist/types'
|
||||
import {
|
||||
AnimateLayoutChanges,
|
||||
arrayMove,
|
||||
defaultAnimateLayoutChanges,
|
||||
SortableContext,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable'
|
||||
import type { Transform } from '@dnd-kit/utilities'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { IconBuilding, IconTrash } from '@posthog/icons'
|
||||
import { LemonButton, LemonButtonProps } from 'lib/lemon-ui/LemonButton'
|
||||
import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { createPortal, unstable_batchedUpdates } from 'react-dom'
|
||||
export interface VDNDChildItem {
|
||||
id: UniqueIdentifier
|
||||
}
|
||||
|
||||
export interface VNDNDContainerItem<T extends VDNDChildItem> {
|
||||
items: T[]
|
||||
id: UniqueIdentifier
|
||||
}
|
||||
|
||||
export interface VerticalNestedDNDProps<SubItem extends VDNDChildItem, Item extends VNDNDContainerItem<SubItem>> {
|
||||
initialItems: Item[]
|
||||
}
|
||||
const PLACEHOLDER_ID = 'placeholder'
|
||||
|
||||
export function VerticalNestedDND<SubItem extends VDNDChildItem, Item extends VNDNDContainerItem<SubItem>>({
|
||||
initialItems,
|
||||
}: VerticalNestedDNDProps<SubItem, Item>): JSX.Element {
|
||||
const [items, setItems] = useState(() => {
|
||||
const items: Record<UniqueIdentifier, Item> = {}
|
||||
initialItems.forEach((item) => {
|
||||
items[item.id] = item
|
||||
})
|
||||
return items
|
||||
})
|
||||
const [clonedItems, setClonedItems] = useState<Record<UniqueIdentifier, Item> | null>(null)
|
||||
|
||||
const handle = true
|
||||
|
||||
const [containers, setContainers] = useState(Object.keys(items) as UniqueIdentifier[])
|
||||
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null)
|
||||
const lastOverId = useRef<UniqueIdentifier | null>(null)
|
||||
const recentlyMovedToNewContainer = useRef(false)
|
||||
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor))
|
||||
const isSortingContainer = activeId ? containers.includes(activeId) : false
|
||||
|
||||
const collisionDetectionStrategy: CollisionDetection = useCallback(
|
||||
(args) => {
|
||||
if (activeId && activeId in items) {
|
||||
return closestCenter({
|
||||
...args,
|
||||
droppableContainers: args.droppableContainers.filter((container) => container.id in items),
|
||||
})
|
||||
}
|
||||
|
||||
// Start by finding any intersecting droppable
|
||||
const pointerIntersections = pointerWithin(args)
|
||||
const intersections =
|
||||
pointerIntersections.length > 0
|
||||
? // If there are droppables intersecting with the pointer, return those
|
||||
pointerIntersections
|
||||
: rectIntersection(args)
|
||||
let overId = getFirstCollision(intersections, 'id')
|
||||
|
||||
if (overId != null) {
|
||||
if (overId in items) {
|
||||
const containerItems = items[overId].items
|
||||
|
||||
// If a container is matched and it contains items (columns 'A', 'B', 'C')
|
||||
if (containerItems.length > 0) {
|
||||
// Return the closest droppable within that container
|
||||
overId = closestCenter({
|
||||
...args,
|
||||
droppableContainers: args.droppableContainers.filter(
|
||||
(container) =>
|
||||
container.id !== overId &&
|
||||
containerItems.some((subItem) => subItem.id === container.id)
|
||||
),
|
||||
})[0]?.id
|
||||
}
|
||||
}
|
||||
|
||||
lastOverId.current = overId
|
||||
|
||||
return [{ id: overId }]
|
||||
}
|
||||
|
||||
// When a draggable item moves to a new container, the layout may shift
|
||||
// and the `overId` may become `null`. We manually set the cached `lastOverId`
|
||||
// to the id of the draggable item that was moved to the new container, otherwise
|
||||
// the previous `overId` will be returned which can cause items to incorrectly shift positions
|
||||
if (recentlyMovedToNewContainer.current) {
|
||||
lastOverId.current = activeId
|
||||
}
|
||||
|
||||
// If no droppable is matched, return the last match
|
||||
return lastOverId.current ? [{ id: lastOverId.current }] : []
|
||||
},
|
||||
[activeId, items]
|
||||
)
|
||||
const findContainer = (id: UniqueIdentifier): UniqueIdentifier | undefined => {
|
||||
if (id in items) {
|
||||
return id
|
||||
}
|
||||
|
||||
return Object.keys(items).find((key) => items[key].items.some((item) => item.id === id))
|
||||
}
|
||||
|
||||
const getIndex = (id: UniqueIdentifier): number => {
|
||||
const container = findContainer(id)
|
||||
|
||||
if (!container) {
|
||||
return -1
|
||||
}
|
||||
|
||||
const index = items[container].items.findIndex((subItem) => subItem.id === id)
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
const onDragCancel = (): void => {
|
||||
if (clonedItems) {
|
||||
// Reset items to their original state in case items have been
|
||||
// Dragged across containers
|
||||
setItems(clonedItems)
|
||||
}
|
||||
|
||||
setActiveId(null)
|
||||
setClonedItems(null)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
recentlyMovedToNewContainer.current = false
|
||||
})
|
||||
}, [items])
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={collisionDetectionStrategy}
|
||||
measuring={{
|
||||
droppable: {
|
||||
strategy: MeasuringStrategy.Always,
|
||||
},
|
||||
}}
|
||||
onDragStart={({ active }) => {
|
||||
setActiveId(active.id)
|
||||
setClonedItems(items)
|
||||
}}
|
||||
onDragOver={({ active, over }) => {
|
||||
const overId = over?.id
|
||||
const activeIsContainer = active.id in items
|
||||
|
||||
if (overId == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (activeIsContainer) {
|
||||
const overContainerId = findContainer(overId)
|
||||
if (!overContainerId) {
|
||||
return
|
||||
}
|
||||
if (activeId !== overContainerId) {
|
||||
setContainers((containers) => {
|
||||
const activeIndex = containers.indexOf(active.id)
|
||||
const overIndex = containers.indexOf(overContainerId)
|
||||
|
||||
return arrayMove(containers, activeIndex, overIndex)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const overContainerId = findContainer(overId)
|
||||
const activeContainerId = findContainer(active.id)
|
||||
|
||||
if (!overContainerId || !activeContainerId) {
|
||||
return
|
||||
}
|
||||
const activeContainer = items[activeContainerId]
|
||||
const overContainer = items[overContainerId]
|
||||
|
||||
if (activeContainerId !== overContainerId) {
|
||||
setItems((items) => {
|
||||
const activeItems = items[activeContainerId].items
|
||||
const overItems = items[overContainerId].items
|
||||
const overIndex = overItems.findIndex((subItem) => subItem.id === overId)
|
||||
const activeIndex = activeItems.findIndex((subItem) => subItem.id === active.id)
|
||||
|
||||
let newIndex: number
|
||||
if (overId in items) {
|
||||
newIndex = overItems.length + 1
|
||||
} else {
|
||||
const isBelowOverItem =
|
||||
over &&
|
||||
active.rect.current.translated &&
|
||||
active.rect.current.translated.top > over.rect.top + over.rect.height
|
||||
|
||||
const modifier = isBelowOverItem ? 1 : 0
|
||||
|
||||
newIndex = overIndex >= 0 ? overIndex + modifier : overItems.length + 1
|
||||
}
|
||||
|
||||
recentlyMovedToNewContainer.current = true
|
||||
|
||||
const newActiveContainer = {
|
||||
...activeContainer,
|
||||
items: activeItems.filter((item) => item.id !== active.id),
|
||||
}
|
||||
const newOverContainer = {
|
||||
...overContainer,
|
||||
items: [
|
||||
...overItems.slice(0, newIndex),
|
||||
activeContainer.items[activeIndex],
|
||||
...overItems.slice(newIndex, overItems.length),
|
||||
],
|
||||
}
|
||||
|
||||
return {
|
||||
...items,
|
||||
[activeContainerId]: newActiveContainer,
|
||||
[overContainerId]: newOverContainer,
|
||||
}
|
||||
})
|
||||
} else if (overId !== active.id) {
|
||||
setItems((items) => {
|
||||
const overItems = items[overContainerId].items
|
||||
const overIndex = overItems.findIndex((subItem) => subItem.id === overId)
|
||||
const activeIndex = overItems.findIndex((subItem) => subItem.id === active.id)
|
||||
|
||||
const isBelowOverItem =
|
||||
over &&
|
||||
active.rect.current.translated &&
|
||||
active.rect.current.translated.top > over.rect.top + over.rect.height
|
||||
|
||||
const modifier = isBelowOverItem ? 1 : 0
|
||||
|
||||
const newItems = arrayMove(overItems, activeIndex, overIndex + modifier)
|
||||
const newOverContainer = {
|
||||
...overContainer,
|
||||
items: newItems,
|
||||
}
|
||||
return {
|
||||
...items,
|
||||
[overContainerId]: newOverContainer,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}}
|
||||
onDragEnd={({ active, over }) => {
|
||||
if (active.id in items && over?.id) {
|
||||
setContainers((containers) => {
|
||||
const activeIndex = containers.indexOf(active.id)
|
||||
const overIndex = containers.indexOf(over.id)
|
||||
|
||||
return arrayMove(containers, activeIndex, overIndex)
|
||||
})
|
||||
}
|
||||
|
||||
const activeContainerId = findContainer(active.id)
|
||||
|
||||
if (!activeContainerId) {
|
||||
setActiveId(null)
|
||||
return
|
||||
}
|
||||
|
||||
const overId = over?.id
|
||||
|
||||
if (overId == null) {
|
||||
setActiveId(null)
|
||||
return
|
||||
}
|
||||
|
||||
const overContainerId = findContainer(overId)
|
||||
|
||||
if (overContainerId) {
|
||||
const activeIndex = items[activeContainerId].items.findIndex((subItem) => subItem.id === active.id)
|
||||
const overIndex = items[overContainerId].items.findIndex((subItem) => subItem.id === overId)
|
||||
|
||||
if (activeIndex !== overIndex) {
|
||||
setItems((items) => {
|
||||
const newOverContainer = {
|
||||
...items[overContainerId],
|
||||
items: arrayMove(items[overContainerId].items, activeIndex, overIndex),
|
||||
}
|
||||
return {
|
||||
...items,
|
||||
[overContainerId]: newOverContainer,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setActiveId(null)
|
||||
}}
|
||||
onDragCancel={onDragCancel}
|
||||
>
|
||||
<div>
|
||||
<SortableContext items={containers} strategy={verticalListSortingStrategy}>
|
||||
{containers.map((containerId) => (
|
||||
<DroppableContainer
|
||||
key={containerId}
|
||||
id={containerId}
|
||||
items={items[containerId].items}
|
||||
onRemove={() => handleRemove(containerId)}
|
||||
>
|
||||
<SortableContext items={items[containerId].items} strategy={verticalListSortingStrategy}>
|
||||
{items[containerId].items.map((value, index) => {
|
||||
return (
|
||||
<SortableItem
|
||||
disabled={isSortingContainer}
|
||||
key={value.id}
|
||||
id={value.id}
|
||||
index={index}
|
||||
handle={handle}
|
||||
containerId={containerId}
|
||||
getIndex={getIndex}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</SortableContext>
|
||||
</DroppableContainer>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
{createPortal(
|
||||
<DragOverlay dropAnimation={dropAnimation}>
|
||||
{activeId
|
||||
? containers.includes(activeId)
|
||||
? renderContainerDragOverlay(activeId)
|
||||
: renderSortableItemDragOverlay(activeId)
|
||||
: null}
|
||||
</DragOverlay>,
|
||||
document.body
|
||||
)}
|
||||
</DndContext>
|
||||
)
|
||||
|
||||
function renderSortableItemDragOverlay(id: UniqueIdentifier): JSX.Element {
|
||||
return <Item value={id} dragOverlay />
|
||||
}
|
||||
|
||||
function renderContainerDragOverlay(containerId: UniqueIdentifier): JSX.Element {
|
||||
return (
|
||||
<Container
|
||||
label={`Column ${containerId}`}
|
||||
style={{
|
||||
height: '100%',
|
||||
}}
|
||||
shadow
|
||||
unstyled={false}
|
||||
>
|
||||
{items[containerId].items.map((item, index) => (
|
||||
<Item key={item.id} value={item.id} />
|
||||
))}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
function handleRemove(containerID: UniqueIdentifier): void {
|
||||
setContainers((containers) => containers.filter((id) => id !== containerID))
|
||||
}
|
||||
|
||||
function handleAddColumn(): void {
|
||||
const newContainerId = getNextContainerId()
|
||||
|
||||
unstable_batchedUpdates(() => {
|
||||
setContainers((containers) => [...containers, newContainerId])
|
||||
const newItem: Item = {
|
||||
id: newContainerId,
|
||||
items: [],
|
||||
} as any
|
||||
setItems((items) => ({
|
||||
...items,
|
||||
[newContainerId]: newItem,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function getNextContainerId(): string {
|
||||
const containerIds = Object.keys(items)
|
||||
const lastContainerId = containerIds[containerIds.length - 1]
|
||||
|
||||
return String.fromCharCode(lastContainerId.charCodeAt(0) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const dropAnimation: DropAnimation = {
|
||||
sideEffects: defaultDropAnimationSideEffects({
|
||||
styles: {
|
||||
active: {
|
||||
opacity: '0.4',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
const animateLayoutChanges: AnimateLayoutChanges = (args) => defaultAnimateLayoutChanges({ ...args, wasDragging: true })
|
||||
|
||||
interface SortableItemProps {
|
||||
containerId: UniqueIdentifier
|
||||
id: UniqueIdentifier
|
||||
index: number
|
||||
handle: boolean
|
||||
disabled?: boolean
|
||||
getIndex(id: UniqueIdentifier): number
|
||||
}
|
||||
|
||||
function SortableItem({ disabled, id, index, handle }: SortableItemProps): JSX.Element {
|
||||
const {
|
||||
setNodeRef,
|
||||
setActivatorNodeRef,
|
||||
listeners,
|
||||
isDragging,
|
||||
isSorting,
|
||||
over,
|
||||
overIndex,
|
||||
transform,
|
||||
transition,
|
||||
} = useSortable({
|
||||
id,
|
||||
})
|
||||
const mounted = useMountStatus()
|
||||
const mountedWhileDragging = isDragging && !mounted
|
||||
|
||||
return (
|
||||
<Item
|
||||
ref={disabled ? undefined : setNodeRef}
|
||||
value={id}
|
||||
dragging={isDragging}
|
||||
sorting={isSorting}
|
||||
handleProps={handle ? { ref: setActivatorNodeRef } : undefined}
|
||||
index={index}
|
||||
transition={transition}
|
||||
transform={transform}
|
||||
fadeIn={mountedWhileDragging}
|
||||
listeners={listeners}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function useMountStatus(): boolean {
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => setIsMounted(true), 500)
|
||||
|
||||
return () => clearTimeout(timeout)
|
||||
}, [])
|
||||
|
||||
return isMounted
|
||||
}
|
||||
|
||||
function DroppableContainer<SubItem extends VDNDChildItem>({
|
||||
children,
|
||||
columns = 1,
|
||||
disabled,
|
||||
id,
|
||||
items,
|
||||
style,
|
||||
...props
|
||||
}: ContainerProps & {
|
||||
disabled?: boolean
|
||||
id: UniqueIdentifier
|
||||
items: SubItem[]
|
||||
style?: React.CSSProperties
|
||||
}): JSX.Element {
|
||||
const { active, attributes, isDragging, listeners, over, setNodeRef, transition, transform } = useSortable({
|
||||
id,
|
||||
data: {
|
||||
type: 'container',
|
||||
children: items,
|
||||
},
|
||||
animateLayoutChanges,
|
||||
})
|
||||
const isOverContainer = over
|
||||
? (id === over.id && active?.data.current?.type !== 'container') || items.some((item) => item.id === over.id)
|
||||
: false
|
||||
|
||||
return (
|
||||
<Container
|
||||
ref={disabled ? undefined : setNodeRef}
|
||||
isDragging={isDragging}
|
||||
hover={isOverContainer}
|
||||
transform={CSS.Translate.toString(transform)}
|
||||
transition={transition}
|
||||
handleProps={{
|
||||
...attributes,
|
||||
...listeners,
|
||||
}}
|
||||
columns={columns}
|
||||
label={`Column ${id}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export interface ContainerProps {
|
||||
children: React.ReactNode
|
||||
columns?: number
|
||||
label?: string
|
||||
style?: React.CSSProperties
|
||||
horizontal?: boolean
|
||||
hover?: boolean
|
||||
handleProps?: React.HTMLAttributes<any>
|
||||
scrollable?: boolean
|
||||
shadow?: boolean
|
||||
placeholder?: boolean
|
||||
unstyled?: boolean
|
||||
onClick?(): void
|
||||
onRemove?(): void
|
||||
isDragging?: boolean
|
||||
transition?: string
|
||||
transform?: string
|
||||
}
|
||||
|
||||
export const Container = forwardRef<HTMLDivElement, ContainerProps>(function Container_(
|
||||
{
|
||||
children,
|
||||
handleProps,
|
||||
horizontal,
|
||||
hover,
|
||||
onClick,
|
||||
onRemove,
|
||||
label,
|
||||
placeholder,
|
||||
style,
|
||||
scrollable,
|
||||
shadow,
|
||||
unstyled,
|
||||
isDragging,
|
||||
transform,
|
||||
transition,
|
||||
...props
|
||||
}: ContainerProps,
|
||||
ref
|
||||
) {
|
||||
const Component = onClick ? 'button' : 'div'
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
className={`flex flex-col p-4 bg-bg-light border rounded overflow-hidden ${isDragging ? 'opacity-40' : ''}`}
|
||||
style={{
|
||||
transform,
|
||||
transition,
|
||||
}}
|
||||
// @ts-expect-error
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
tabIndex={onClick ? 0 : undefined}
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
{label ? <span>{label}</span> : null}
|
||||
<span className="flex flex-row space-x-1">
|
||||
<Remove onClick={onRemove} />
|
||||
<Handle {...handleProps} />
|
||||
</span>
|
||||
</div>
|
||||
{placeholder ? children : <ul>{children}</ul>}
|
||||
</Component>
|
||||
)
|
||||
})
|
||||
|
||||
export interface ItemProps {
|
||||
dragOverlay?: boolean
|
||||
color?: string
|
||||
disabled?: boolean
|
||||
dragging?: boolean
|
||||
handleProps?: any
|
||||
height?: number
|
||||
index?: number
|
||||
fadeIn?: boolean
|
||||
transform?: Transform | null
|
||||
listeners?: DraggableSyntheticListeners
|
||||
sorting?: boolean
|
||||
style?: React.CSSProperties
|
||||
transition?: string | null
|
||||
wrapperStyle?: React.CSSProperties
|
||||
value: UniqueIdentifier
|
||||
onRemove?(): void
|
||||
}
|
||||
|
||||
export const Item = React.memo(
|
||||
React.forwardRef<HTMLLIElement, ItemProps>(
|
||||
(
|
||||
{
|
||||
color,
|
||||
dragOverlay,
|
||||
dragging,
|
||||
disabled,
|
||||
fadeIn,
|
||||
handleProps,
|
||||
height,
|
||||
index,
|
||||
listeners,
|
||||
onRemove,
|
||||
sorting,
|
||||
style,
|
||||
transition,
|
||||
transform,
|
||||
value,
|
||||
wrapperStyle,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const handle = true
|
||||
useEffect(() => {
|
||||
if (!dragOverlay) {
|
||||
return
|
||||
}
|
||||
|
||||
document.body.style.cursor = 'grabbing'
|
||||
|
||||
return () => {
|
||||
document.body.style.cursor = ''
|
||||
}
|
||||
}, [dragOverlay])
|
||||
|
||||
return (
|
||||
<li ref={ref} className={`${dragging ? 'opacity-40' : ''}`}>
|
||||
<div
|
||||
data-cypress="draggable-item"
|
||||
{...(!handle ? listeners : undefined)}
|
||||
{...props}
|
||||
tabIndex={!handle ? 0 : undefined}
|
||||
className="VerticalNestedDNDItem"
|
||||
>
|
||||
Item {value}
|
||||
<span className="flex flex-row space-x-1">
|
||||
{onRemove ? <Remove onClick={onRemove} /> : null}
|
||||
{handle ? <Handle {...handleProps} {...listeners} /> : null}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
export function Remove(props: LemonButtonProps): JSX.Element {
|
||||
return (
|
||||
<LemonButton type="secondary" fullWidth={false} {...props}>
|
||||
<IconTrash />
|
||||
</LemonButton>
|
||||
)
|
||||
}
|
||||
|
||||
export const Handle = forwardRef<HTMLButtonElement, LemonButtonProps>(function Handle_(props, ref) {
|
||||
return (
|
||||
<LemonButton type="secondary" fullWidth={false} ref={ref} {...props}>
|
||||
<IconBuilding />
|
||||
</LemonButton>
|
||||
)
|
||||
})
|
21
frontend/src/lib/lemon-ui/VerticalNestedDND/styles.scss
Normal file
21
frontend/src/lib/lemon-ui/VerticalNestedDND/styles.scss
Normal file
@ -0,0 +1,21 @@
|
||||
.VerticalNestedDNDContainer {
|
||||
box-sizing: initial;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--bg-light);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.VerticalNestedDNDItem {
|
||||
box-sizing: initial;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--bg-light);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
@ -1,48 +1,614 @@
|
||||
import { useActions, useValues } from 'kea'
|
||||
import { LemonButton } from 'lib/lemon-ui/LemonButton'
|
||||
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
|
||||
import { useState } from 'react'
|
||||
import { teamLogic } from 'scenes/teamLogic'
|
||||
|
||||
import { CustomChannelRule } from '~/queries/schema'
|
||||
import { LemonInput } from 'lib/lemon-ui/LemonInput'
|
||||
|
||||
// /* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
// import { useActions, useValues } from 'kea'
|
||||
// import { LemonButton } from 'lib/lemon-ui/LemonButton'
|
||||
// import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
|
||||
// import React, { useEffect, useRef, useState } from 'react'
|
||||
// import { teamLogic } from 'scenes/teamLogic'
|
||||
//
|
||||
// import {
|
||||
// Active,
|
||||
// Announcements,
|
||||
// closestCenter,
|
||||
// CollisionDetection,
|
||||
// defaultDropAnimationSideEffects,
|
||||
// DndContext,
|
||||
// DragOverlay,
|
||||
// DropAnimation,
|
||||
// KeyboardCoordinateGetter,
|
||||
// KeyboardSensor,
|
||||
// MeasuringConfiguration,
|
||||
// Modifiers,
|
||||
// MouseSensor,
|
||||
// PointerActivationConstraint,
|
||||
// ScreenReaderInstructions,
|
||||
// TouchSensor,
|
||||
// UniqueIdentifier,
|
||||
// useSensor,
|
||||
// useSensors,
|
||||
// } from '@dnd-kit/core'
|
||||
// import {
|
||||
// AnimateLayoutChanges,
|
||||
// arrayMove,
|
||||
// NewIndexGetter,
|
||||
// rectSortingStrategy,
|
||||
// SortableContext,
|
||||
// sortableKeyboardCoordinates,
|
||||
// SortingStrategy,
|
||||
// useSortable,
|
||||
// verticalListSortingStrategy,
|
||||
// } from '@dnd-kit/sortable'
|
||||
//
|
||||
// import { CustomChannelRule } from '~/queries/schema'
|
||||
// import { LemonInput } from 'lib/lemon-ui/LemonInput'
|
||||
// import { createPortal } from 'react-dom'
|
||||
//
|
||||
// import { Item, List, Wrapper } from '../../components'
|
||||
// import React, { forwardRef } from 'react'
|
||||
//
|
||||
// export interface Props {
|
||||
// children: React.ReactNode
|
||||
// columns?: number
|
||||
// style?: React.CSSProperties
|
||||
// horizontal?: boolean
|
||||
// }
|
||||
//
|
||||
// export const List = forwardRef<HTMLUListElement, Props>(({ children, columns = 1, horizontal, style }: Props, ref) => {
|
||||
// return <ul ref={ref}>{children}</ul>
|
||||
// })
|
||||
//
|
||||
// export function createRange<T = number>(length: number, initializer: (index: number) => any = defaultInitializer): T[] {
|
||||
// return [...new Array(length)].map((_, index) => initializer(index))
|
||||
// }
|
||||
//
|
||||
export function ChannelType(): JSX.Element {
|
||||
const { updateCurrentTeam } = useActions(teamLogic)
|
||||
const { currentTeam } = useValues(teamLogic)
|
||||
const { reportCustomChannelTypeRulesUpdated } = useActions(eventUsageLogic)
|
||||
|
||||
const savedCustomChannelTypeRules =
|
||||
currentTeam?.modifiers?.customChannelTypeRules ?? currentTeam?.default_modifiers?.customChannelTypeRules ?? null
|
||||
const [customChannelTypeRules, setCustomChannelTypeRules] = useState<string>(
|
||||
savedCustomChannelTypeRules ? JSON.stringify(savedCustomChannelTypeRules) : ''
|
||||
)
|
||||
|
||||
const handleChange = (rules: string): void => {
|
||||
let parsed: CustomChannelRule[] = []
|
||||
try {
|
||||
parsed = JSON.parse(rules)
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
updateCurrentTeam({ modifiers: { ...currentTeam?.modifiers, customChannelTypeRules: parsed } })
|
||||
reportCustomChannelTypeRulesUpdated(parsed.length)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>Set your custom channel type</p>
|
||||
<LemonInput
|
||||
value={customChannelTypeRules}
|
||||
onChange={setCustomChannelTypeRules}
|
||||
placeholder="Enter JSON array of custom channel type rules"
|
||||
/>
|
||||
<div className="mt-4">
|
||||
<LemonButton type="primary" onClick={() => handleChange(customChannelTypeRules)}>
|
||||
Save
|
||||
</LemonButton>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
return <div />
|
||||
// const { updateCurrentTeam } = useActions(teamLogic)
|
||||
// const { currentTeam } = useValues(teamLogic)
|
||||
// const { reportCustomChannelTypeRulesUpdated } = useActions(eventUsageLogic)
|
||||
//
|
||||
// const savedCustomChannelTypeRules =
|
||||
// currentTeam?.modifiers?.customChannelTypeRules ?? currentTeam?.default_modifiers?.customChannelTypeRules ?? null
|
||||
// const [customChannelTypeRules, setCustomChannelTypeRules] = useState<string>(
|
||||
// savedCustomChannelTypeRules ? JSON.stringify(savedCustomChannelTypeRules) : ''
|
||||
// )
|
||||
//
|
||||
// const handleChange = (rules: string): void => {
|
||||
// let parsed: CustomChannelRule[] = []
|
||||
// try {
|
||||
// parsed = JSON.parse(rules)
|
||||
// } catch (e) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// updateCurrentTeam({ modifiers: { ...currentTeam?.modifiers, customChannelTypeRules: parsed } })
|
||||
// reportCustomChannelTypeRulesUpdated(parsed.length)
|
||||
// }
|
||||
//
|
||||
// return (
|
||||
// <>
|
||||
// <p>Set your custom channel type</p>
|
||||
// <LemonInput
|
||||
// value={customChannelTypeRules}
|
||||
// onChange={setCustomChannelTypeRules}
|
||||
// placeholder="Enter JSON array of custom channel type rules"
|
||||
// />
|
||||
// <div className="mt-4">
|
||||
// <LemonButton type="primary" onClick={() => handleChange(customChannelTypeRules)}>
|
||||
// Save
|
||||
// </LemonButton>
|
||||
// </div>
|
||||
// </>
|
||||
// )
|
||||
}
|
||||
//
|
||||
// export interface ChannelTypeCustomRulesProps {
|
||||
// customRules?: CustomChannelRule[] | null
|
||||
// setCustomRules: (customRules: CustomChannelRule[]) => void
|
||||
// }
|
||||
//
|
||||
// export function ChannelTypeCustomRules({
|
||||
// customRules: _customRules,
|
||||
// setCustomRules: _setCustomRules,
|
||||
// }: ChannelTypeCustomRulesProps): JSX.Element {
|
||||
// const customRules = _customRules != null ? _customRules : []
|
||||
// const [localCustomRules, setLocalCustomRules] = useState<CustomChannelRule[]>(customRules)
|
||||
//
|
||||
// const updateCustomRules = (customRules: CustomChannelRule[]): void => {
|
||||
// setLocalCustomRules(customRules)
|
||||
// _setCustomRules(customRules)
|
||||
// }
|
||||
//
|
||||
// const onAddFilter = (filter: CustomChannelRule): void => {
|
||||
// updateCustomRules([...customRules, filter])
|
||||
// }
|
||||
// const onEditFilter = (index: number, filter: CustomChannelRule): void => {
|
||||
// const newCustomRules = customRules.map((f, i) => {
|
||||
// if (i === index) {
|
||||
// return filter
|
||||
// }
|
||||
// return f
|
||||
// })
|
||||
// updateCustomRules(newCustomRules)
|
||||
// }
|
||||
// const onRemoveFilter = (index: number): void => {
|
||||
// updateCustomRules(customRules.filter((_, i) => i !== index))
|
||||
// }
|
||||
//
|
||||
// function onSortEnd({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }): void {
|
||||
// function move(arr: CustomChannelRule[], from: number, to: number): CustomChannelRule[] {
|
||||
// const clone = [...arr]
|
||||
// Array.prototype.splice.call(clone, to, 0, Array.prototype.splice.call(clone, from, 1)[0])
|
||||
// return clone.map((child, order) => ({ ...child, order }))
|
||||
// }
|
||||
// updateCustomRules(move(customRules, oldIndex, newIndex))
|
||||
// }
|
||||
//
|
||||
// return (
|
||||
// <div className="flex flex-col gap-2">
|
||||
// <div className="flex items-center gap-2 flex-wrap">
|
||||
// <Sortable strategy={verticalListSortingStrategy} />
|
||||
// </div>
|
||||
// <div>
|
||||
// <button>Add</button>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// export interface SortableProps {
|
||||
// activationConstraint?: PointerActivationConstraint
|
||||
// animateLayoutChanges?: AnimateLayoutChanges
|
||||
// adjustScale?: boolean
|
||||
// collisionDetection?: CollisionDetection
|
||||
// coordinateGetter?: KeyboardCoordinateGetter
|
||||
// Container?: any // To-do: Fix me
|
||||
// dropAnimation?: DropAnimation | null
|
||||
// getNewIndex?: NewIndexGetter
|
||||
// handle?: boolean
|
||||
// itemCount?: number
|
||||
// items?: UniqueIdentifier[]
|
||||
// measuring?: MeasuringConfiguration
|
||||
// modifiers?: Modifiers
|
||||
// renderItem?: any
|
||||
// removable?: boolean
|
||||
// reorderItems?: typeof arrayMove
|
||||
// strategy?: SortingStrategy
|
||||
// style?: React.CSSProperties
|
||||
// useDragOverlay?: boolean
|
||||
// getItemStyles?(args: {
|
||||
// id: UniqueIdentifier
|
||||
// index: number
|
||||
// isSorting: boolean
|
||||
// isDragOverlay: boolean
|
||||
// overIndex: number
|
||||
// isDragging: boolean
|
||||
// }): React.CSSProperties
|
||||
// wrapperStyle?(args: {
|
||||
// active: Pick<Active, 'id'> | null
|
||||
// index: number
|
||||
// isDragging: boolean
|
||||
// id: UniqueIdentifier
|
||||
// }): React.CSSProperties
|
||||
// isDisabled?(id: UniqueIdentifier): boolean
|
||||
// }
|
||||
//
|
||||
// const dropAnimationConfig: DropAnimation = {
|
||||
// sideEffects: defaultDropAnimationSideEffects({
|
||||
// styles: {
|
||||
// active: {
|
||||
// opacity: '0.5',
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// }
|
||||
//
|
||||
// const screenReaderInstructions: ScreenReaderInstructions = {
|
||||
// draggable: `
|
||||
// To pick up a sortable item, press the space bar.
|
||||
// While sorting, use the arrow keys to move the item.
|
||||
// Press space again to drop the item in its new position, or press escape to cancel.
|
||||
// `,
|
||||
// }
|
||||
//
|
||||
// export function Sortable({
|
||||
// activationConstraint,
|
||||
// animateLayoutChanges,
|
||||
// adjustScale = false,
|
||||
// Container = List,
|
||||
// collisionDetection = closestCenter,
|
||||
// coordinateGetter = sortableKeyboardCoordinates,
|
||||
// dropAnimation = dropAnimationConfig,
|
||||
// getItemStyles = () => ({}),
|
||||
// getNewIndex,
|
||||
// handle = false,
|
||||
// itemCount = 16,
|
||||
// items: initialItems,
|
||||
// isDisabled = () => false,
|
||||
// measuring,
|
||||
// modifiers,
|
||||
// removable,
|
||||
// renderItem,
|
||||
// reorderItems = arrayMove,
|
||||
// strategy = rectSortingStrategy,
|
||||
// style,
|
||||
// useDragOverlay = true,
|
||||
// wrapperStyle = () => ({}),
|
||||
// }: SortableProps): JSX.Element {
|
||||
// const [items, setItems] = useState<UniqueIdentifier[]>(
|
||||
// // @ts-expect-error
|
||||
// () => initialItems ?? createRange<UniqueIdentifier>(itemCount, (index) => index + 1)
|
||||
// )
|
||||
// const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null)
|
||||
// const sensors = useSensors(
|
||||
// useSensor(MouseSensor, {
|
||||
// activationConstraint,
|
||||
// }),
|
||||
// useSensor(TouchSensor, {
|
||||
// activationConstraint,
|
||||
// }),
|
||||
// useSensor(KeyboardSensor, {
|
||||
// // Disable smooth scrolling in Cypress automated tests
|
||||
// scrollBehavior: 'Cypress' in window ? 'auto' : undefined,
|
||||
// coordinateGetter,
|
||||
// })
|
||||
// )
|
||||
// const isFirstAnnouncement = useRef(true)
|
||||
// const getIndex = (id: UniqueIdentifier) => items.indexOf(id)
|
||||
// const getPosition = (id: UniqueIdentifier) => getIndex(id) + 1
|
||||
// const activeIndex = activeId ? getIndex(activeId) : -1
|
||||
// const handleRemove = removable
|
||||
// ? (id: UniqueIdentifier) => setItems((items) => items.filter((item) => item !== id))
|
||||
// : undefined
|
||||
// const announcements: Announcements = {
|
||||
// onDragStart({ active: { id } }) {
|
||||
// return `Picked up sortable item ${String(id)}. Sortable item ${id} is in position ${getPosition(id)} of ${
|
||||
// items.length
|
||||
// }`
|
||||
// },
|
||||
// onDragOver({ active, over }) {
|
||||
// // In this specific use-case, the picked up item's `id` is always the same as the first `over` id.
|
||||
// // The first `onDragOver` event therefore doesn't need to be announced, because it is called
|
||||
// // immediately after the `onDragStart` announcement and is redundant.
|
||||
// if (isFirstAnnouncement.current === true) {
|
||||
// isFirstAnnouncement.current = false
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if (over) {
|
||||
// return `Sortable item ${active.id} was moved into position ${getPosition(over.id)} of ${items.length}`
|
||||
// }
|
||||
//
|
||||
// return
|
||||
// },
|
||||
// onDragEnd({ active, over }) {
|
||||
// if (over) {
|
||||
// return `Sortable item ${active.id} was dropped at position ${getPosition(over.id)} of ${items.length}`
|
||||
// }
|
||||
//
|
||||
// return
|
||||
// },
|
||||
// onDragCancel({ active: { id } }) {
|
||||
// return `Sorting was cancelled. Sortable item ${id} was dropped and returned to position ${getPosition(
|
||||
// id
|
||||
// )} of ${items.length}.`
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// useEffect(() => {
|
||||
// if (!activeId) {
|
||||
// isFirstAnnouncement.current = true
|
||||
// }
|
||||
// }, [activeId])
|
||||
//
|
||||
// return (
|
||||
// <DndContext
|
||||
// accessibility={{
|
||||
// announcements,
|
||||
// screenReaderInstructions,
|
||||
// }}
|
||||
// sensors={sensors}
|
||||
// collisionDetection={collisionDetection}
|
||||
// onDragStart={({ active }) => {
|
||||
// if (!active) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// setActiveId(active.id)
|
||||
// }}
|
||||
// onDragEnd={({ over }) => {
|
||||
// setActiveId(null)
|
||||
//
|
||||
// if (over) {
|
||||
// const overIndex = getIndex(over.id)
|
||||
// if (activeIndex !== overIndex) {
|
||||
// setItems((items) => reorderItems(items, activeIndex, overIndex))
|
||||
// }
|
||||
// }
|
||||
// }}
|
||||
// onDragCancel={() => setActiveId(null)}
|
||||
// measuring={measuring}
|
||||
// modifiers={modifiers}
|
||||
// >
|
||||
// <Wrapper style={style} center>
|
||||
// <SortableContext items={items} strategy={strategy}>
|
||||
// <Container>
|
||||
// {items.map((value, index) => (
|
||||
// <SortableItem
|
||||
// key={value}
|
||||
// id={value}
|
||||
// handle={handle}
|
||||
// index={index}
|
||||
// style={getItemStyles}
|
||||
// wrapperStyle={wrapperStyle}
|
||||
// disabled={isDisabled(value)}
|
||||
// renderItem={renderItem}
|
||||
// onRemove={handleRemove}
|
||||
// animateLayoutChanges={animateLayoutChanges}
|
||||
// useDragOverlay={useDragOverlay}
|
||||
// getNewIndex={getNewIndex}
|
||||
// />
|
||||
// ))}
|
||||
// </Container>
|
||||
// </SortableContext>
|
||||
// </Wrapper>
|
||||
// {useDragOverlay
|
||||
// ? createPortal(
|
||||
// <DragOverlay adjustScale={adjustScale} dropAnimation={dropAnimation}>
|
||||
// {activeId ? (
|
||||
// <Item
|
||||
// value={items[activeIndex]}
|
||||
// handle={handle}
|
||||
// renderItem={renderItem}
|
||||
// wrapperStyle={wrapperStyle({
|
||||
// active: { id: activeId },
|
||||
// index: activeIndex,
|
||||
// isDragging: true,
|
||||
// id: items[activeIndex],
|
||||
// })}
|
||||
// style={getItemStyles({
|
||||
// id: items[activeIndex],
|
||||
// index: activeIndex,
|
||||
// isSorting: activeId !== null,
|
||||
// isDragging: true,
|
||||
// overIndex: -1,
|
||||
// isDragOverlay: true,
|
||||
// })}
|
||||
// dragOverlay
|
||||
// />
|
||||
// ) : null}
|
||||
// </DragOverlay>,
|
||||
// document.body
|
||||
// )
|
||||
// : null}
|
||||
// </DndContext>
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// interface SortableItemProps {
|
||||
// animateLayoutChanges?: AnimateLayoutChanges
|
||||
// disabled?: boolean
|
||||
// getNewIndex?: NewIndexGetter
|
||||
// id: UniqueIdentifier
|
||||
// index: number
|
||||
// handle: boolean
|
||||
// useDragOverlay?: boolean
|
||||
// onRemove?(id: UniqueIdentifier): void
|
||||
// style(values: any): React.CSSProperties
|
||||
// renderItem?(args: any): React.ReactElement
|
||||
// wrapperStyle: SortableProps['wrapperStyle']
|
||||
// }
|
||||
//
|
||||
// export function SortableItem({
|
||||
// disabled,
|
||||
// animateLayoutChanges,
|
||||
// getNewIndex,
|
||||
// handle,
|
||||
// id,
|
||||
// index,
|
||||
// onRemove,
|
||||
// style,
|
||||
// renderItem,
|
||||
// useDragOverlay,
|
||||
// wrapperStyle,
|
||||
// }: SortableItemProps): JSX.Element {
|
||||
// const {
|
||||
// active,
|
||||
// attributes,
|
||||
// isDragging,
|
||||
// isSorting,
|
||||
// listeners,
|
||||
// overIndex,
|
||||
// setNodeRef,
|
||||
// setActivatorNodeRef,
|
||||
// transform,
|
||||
// transition,
|
||||
// } = useSortable({
|
||||
// id,
|
||||
// animateLayoutChanges,
|
||||
// disabled,
|
||||
// getNewIndex,
|
||||
// })
|
||||
//
|
||||
// return (
|
||||
// <Item
|
||||
// ref={setNodeRef}
|
||||
// value={id}
|
||||
// disabled={disabled}
|
||||
// dragging={isDragging}
|
||||
// sorting={isSorting}
|
||||
// handle={handle}
|
||||
// handleProps={
|
||||
// handle
|
||||
// ? {
|
||||
// ref: setActivatorNodeRef,
|
||||
// }
|
||||
// : undefined
|
||||
// }
|
||||
// renderItem={renderItem}
|
||||
// index={index}
|
||||
// style={style({
|
||||
// index,
|
||||
// id,
|
||||
// isDragging,
|
||||
// isSorting,
|
||||
// overIndex,
|
||||
// })}
|
||||
// onRemove={onRemove ? () => onRemove(id) : undefined}
|
||||
// transform={transform}
|
||||
// transition={transition}
|
||||
// wrapperStyle={wrapperStyle?.({ index, isDragging, active, id })}
|
||||
// listeners={listeners}
|
||||
// data-index={index}
|
||||
// data-id={id}
|
||||
// dragOverlay={!useDragOverlay && isDragging}
|
||||
// {...attributes}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// export interface Props {
|
||||
// dragOverlay?: boolean;
|
||||
// color?: string;
|
||||
// disabled?: boolean;
|
||||
// dragging?: boolean;
|
||||
// handle?: boolean;
|
||||
// handleProps?: any;
|
||||
// height?: number;
|
||||
// index?: number;
|
||||
// fadeIn?: boolean;
|
||||
// transform?: Transform | null;
|
||||
// listeners?: DraggableSyntheticListeners;
|
||||
// sorting?: boolean;
|
||||
// style?: React.CSSProperties;
|
||||
// transition?: string | null;
|
||||
// wrapperStyle?: React.CSSProperties;
|
||||
// value: React.ReactNode;
|
||||
// onRemove?(): void;
|
||||
// renderItem?(args: {
|
||||
// dragOverlay: boolean;
|
||||
// dragging: boolean;
|
||||
// sorting: boolean;
|
||||
// index: number | undefined;
|
||||
// fadeIn: boolean;
|
||||
// listeners: DraggableSyntheticListeners;
|
||||
// ref: React.Ref<HTMLElement>;
|
||||
// style: React.CSSProperties | undefined;
|
||||
// transform: Props['transform'];
|
||||
// transition: Props['transition'];
|
||||
// value: Props['value'];
|
||||
// }): React.ReactElement;
|
||||
// }
|
||||
//
|
||||
// export const Item = React.memo(
|
||||
// React.forwardRef<HTMLLIElement, Props>(
|
||||
// (
|
||||
// {
|
||||
// color,
|
||||
// dragOverlay,
|
||||
// dragging,
|
||||
// disabled,
|
||||
// fadeIn,
|
||||
// handle,
|
||||
// handleProps,
|
||||
// height,
|
||||
// index,
|
||||
// listeners,
|
||||
// onRemove,
|
||||
// renderItem,
|
||||
// sorting,
|
||||
// style,
|
||||
// transition,
|
||||
// transform,
|
||||
// value,
|
||||
// wrapperStyle,
|
||||
// ...props
|
||||
// },
|
||||
// ref
|
||||
// ) => {
|
||||
// useEffect(() => {
|
||||
// if (!dragOverlay) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// document.body.style.cursor = 'grabbing';
|
||||
//
|
||||
// return () => {
|
||||
// document.body.style.cursor = '';
|
||||
// };
|
||||
// }, [dragOverlay]);
|
||||
//
|
||||
// return renderItem ? (
|
||||
// renderItem({
|
||||
// dragOverlay: Boolean(dragOverlay),
|
||||
// dragging: Boolean(dragging),
|
||||
// sorting: Boolean(sorting),
|
||||
// index,
|
||||
// fadeIn: Boolean(fadeIn),
|
||||
// listeners,
|
||||
// ref,
|
||||
// style,
|
||||
// transform,
|
||||
// transition,
|
||||
// value,
|
||||
// })
|
||||
// ) : (
|
||||
// <li
|
||||
// className={classNames(
|
||||
// styles.Wrapper,
|
||||
// fadeIn && styles.fadeIn,
|
||||
// sorting && styles.sorting,
|
||||
// dragOverlay && styles.dragOverlay
|
||||
// )}
|
||||
// style={
|
||||
// {
|
||||
// ...wrapperStyle,
|
||||
// transition: [transition, wrapperStyle?.transition]
|
||||
// .filter(Boolean)
|
||||
// .join(', '),
|
||||
// '--translate-x': transform
|
||||
// ? `${Math.round(transform.x)}px`
|
||||
// : undefined,
|
||||
// '--translate-y': transform
|
||||
// ? `${Math.round(transform.y)}px`
|
||||
// : undefined,
|
||||
// '--scale-x': transform?.scaleX
|
||||
// ? `${transform.scaleX}`
|
||||
// : undefined,
|
||||
// '--scale-y': transform?.scaleY
|
||||
// ? `${transform.scaleY}`
|
||||
// : undefined,
|
||||
// '--index': index,
|
||||
// '--color': color,
|
||||
// } as React.CSSProperties
|
||||
// }
|
||||
// ref={ref}
|
||||
// >
|
||||
// <div
|
||||
// className={classNames(
|
||||
// styles.Item,
|
||||
// dragging && styles.dragging,
|
||||
// handle && styles.withHandle,
|
||||
// dragOverlay && styles.dragOverlay,
|
||||
// disabled && styles.disabled,
|
||||
// color && styles.color
|
||||
// )}
|
||||
// style={style}
|
||||
// data-cypress="draggable-item"
|
||||
// {...(!handle ? listeners : undefined)}
|
||||
// {...props}
|
||||
// tabIndex={!handle ? 0 : undefined}
|
||||
// >
|
||||
// {value}
|
||||
// <span className={styles.Actions}>
|
||||
// {onRemove ? (
|
||||
// <Remove className={styles.Remove} onClick={onRemove} />
|
||||
// ) : null}
|
||||
// {handle ? <Handle {...handleProps} {...listeners} /> : null}
|
||||
// </span>
|
||||
// </div>
|
||||
// </li>
|
||||
// );
|
||||
// }
|
||||
// )
|
||||
// );
|
||||
|
@ -111,6 +111,7 @@
|
||||
"chartjs-plugin-stacked100": "^1.4.0",
|
||||
"chartjs-plugin-trendline": "^2.1.2",
|
||||
"chokidar": "^3.5.3",
|
||||
"classnames": "^2.5.1",
|
||||
"clsx": "^1.1.1",
|
||||
"core-js": "^3.32.0",
|
||||
"cors": "^2.8.5",
|
||||
|
@ -154,6 +154,9 @@ dependencies:
|
||||
chokidar:
|
||||
specifier: ^3.5.3
|
||||
version: 3.5.3
|
||||
classnames:
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1
|
||||
clsx:
|
||||
specifier: ^1.1.1
|
||||
version: 1.2.1
|
||||
@ -10313,8 +10316,8 @@ packages:
|
||||
resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==}
|
||||
dev: true
|
||||
|
||||
/classnames@2.3.2:
|
||||
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
|
||||
/classnames@2.5.1:
|
||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||
dev: false
|
||||
|
||||
/clean-css@5.3.2:
|
||||
@ -18239,7 +18242,7 @@ packages:
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.0
|
||||
classnames: 2.3.2
|
||||
classnames: 2.5.1
|
||||
dom-align: 1.12.3
|
||||
lodash: 4.17.21
|
||||
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -18255,7 +18258,7 @@ packages:
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.0
|
||||
classnames: 2.3.2
|
||||
classnames: 2.5.1
|
||||
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@ -18269,7 +18272,7 @@ packages:
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.0
|
||||
classnames: 2.3.2
|
||||
classnames: 2.5.1
|
||||
rc-align: 4.0.12(react-dom@18.2.0)(react@18.2.0)
|
||||
rc-motion: 2.6.2(react-dom@18.2.0)(react@18.2.0)
|
||||
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
|
||||
|
@ -1,6 +1,7 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
import posthoganalytics
|
||||
from pydantic import ValidationError
|
||||
|
||||
from posthog.cloud_utils import is_cloud
|
||||
from posthog.schema import (
|
||||
@ -51,9 +52,16 @@ def create_default_modifiers_for_team(
|
||||
if isinstance(team.modifiers, dict):
|
||||
for key, value in team.modifiers.items():
|
||||
if getattr(modifiers, key) is None:
|
||||
if key == "customChannelTypeRules" and isinstance(value, list):
|
||||
value = [CustomChannelRule(**rule) if isinstance(rule, dict) else rule for rule in value]
|
||||
setattr(modifiers, key, value)
|
||||
if key == "customChannelTypeRules":
|
||||
# don't break all queries if customChannelTypeRules are invalid
|
||||
try:
|
||||
if isinstance(value, list):
|
||||
value = [CustomChannelRule(**rule) if isinstance(rule, dict) else rule for rule in value]
|
||||
setattr(modifiers, key, value)
|
||||
except ValidationError:
|
||||
pass
|
||||
else:
|
||||
setattr(modifiers, key, value)
|
||||
|
||||
set_default_modifier_values(modifiers, team)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user