feat: list reordering (#154)
This commit is contained in:
@@ -4,9 +4,9 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|||||||
import List from '@views/components/common/List/List';
|
import List from '@views/components/common/List/List';
|
||||||
import ScheduleDropdown from '@views/components/common/ScheduleDropdown/ScheduleDropdown';
|
import ScheduleDropdown from '@views/components/common/ScheduleDropdown/ScheduleDropdown';
|
||||||
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
|
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
|
||||||
import useSchedules, { switchSchedule } from '@views/hooks/useSchedules';
|
import useSchedules, { getActiveSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||||
import type { Serialized } from 'chrome-extension-toolkit';
|
import type { Serialized } from 'chrome-extension-toolkit';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import { exampleSchedule } from '../injected/mocked';
|
import { exampleSchedule } from '../injected/mocked';
|
||||||
|
|
||||||
@@ -47,27 +47,45 @@ const meta: Meta<typeof ScheduleDropdown> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
render: (args: any) => (
|
render: (args: any) => {
|
||||||
<div className='w-80'>
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
<ScheduleDropdown {...args}>
|
const [activeSchedule, schedules] = useSchedules();
|
||||||
<List
|
|
||||||
draggableElements={schedules.map((schedule, index) => {
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
const [activeSchedule] = useSchedules();
|
useEffect(() => {
|
||||||
return (
|
console.log(activeSchedule);
|
||||||
|
}, [activeSchedule]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-80'>
|
||||||
|
<ScheduleDropdown {...args}>
|
||||||
|
<List
|
||||||
|
draggables={schedules}
|
||||||
|
equalityCheck={(a, b) => a.name === b.name}
|
||||||
|
onReordered={reordered => {
|
||||||
|
const activeSchedule = getActiveSchedule();
|
||||||
|
const activeIndex = reordered.findIndex(s => s.name === activeSchedule.name);
|
||||||
|
|
||||||
|
// don't care about the promise
|
||||||
|
UserScheduleStore.set('schedules', reordered);
|
||||||
|
UserScheduleStore.set('activeIndex', activeIndex);
|
||||||
|
}}
|
||||||
|
gap={10}
|
||||||
|
>
|
||||||
|
{(schedule, handleProps) => (
|
||||||
<ScheduleListItem
|
<ScheduleListItem
|
||||||
active={activeSchedule?.name === schedule.name}
|
|
||||||
name={schedule.name}
|
name={schedule.name}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
switchSchedule(schedule.name);
|
switchSchedule(schedule.name);
|
||||||
}}
|
}}
|
||||||
|
dragHandleProps={handleProps}
|
||||||
/>
|
/>
|
||||||
);
|
)}
|
||||||
})}
|
</List>
|
||||||
gap={10}
|
</ScheduleDropdown>
|
||||||
/>
|
</div>
|
||||||
</ScheduleDropdown>
|
);
|
||||||
</div>
|
},
|
||||||
),
|
|
||||||
} satisfies Meta<typeof ScheduleDropdown>;
|
} satisfies Meta<typeof ScheduleDropdown>;
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
|
||||||
import { Course, Status } from '@shared/types/Course';
|
import { Course, Status } from '@shared/types/Course';
|
||||||
import { CourseMeeting } from '@shared/types/CourseMeeting';
|
import { CourseMeeting } from '@shared/types/CourseMeeting';
|
||||||
import Instructor from '@shared/types/Instructor';
|
import Instructor from '@shared/types/Instructor';
|
||||||
@@ -71,9 +72,10 @@ const generateCourses = (count: number): Course[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const exampleCourses = generateCourses(numberOfCourses);
|
const exampleCourses = generateCourses(numberOfCourses);
|
||||||
const generateCourseBlocks = (exampleCourses: Course[], colors: CourseColors[]) =>
|
const generateCourseBlocks = (
|
||||||
exampleCourses.map((course, i) => <PopupCourseBlock key={course.uniqueId} course={course} colors={colors[i]} />);
|
{ course, colors }: { course: Course; colors: CourseColors },
|
||||||
const ExampleCourseBlocks = generateCourseBlocks(exampleCourses, tailwindColorways);
|
dragHandleProps: DraggableProvidedDragHandleProps
|
||||||
|
) => <PopupCourseBlock key={course.uniqueId} course={course} colors={colors} dragHandleProps={dragHandleProps} />;
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Common/List',
|
title: 'Components/Common/List',
|
||||||
@@ -83,7 +85,6 @@ const meta = {
|
|||||||
},
|
},
|
||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
draggableElements: { control: 'object' },
|
|
||||||
gap: { control: 'number' },
|
gap: { control: 'number' },
|
||||||
},
|
},
|
||||||
} satisfies Meta<typeof List>;
|
} satisfies Meta<typeof List>;
|
||||||
@@ -93,7 +94,8 @@ type Story = StoryObj<typeof meta>;
|
|||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
draggableElements: ExampleCourseBlocks,
|
draggables: exampleCourses.map((course, i) => ({ course, colors: tailwindColorways[i] })),
|
||||||
|
children: generateCourseBlocks,
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
render: args => (
|
render: args => (
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
import { tailwindColorways } from '@shared/util/storybook';
|
import { tailwindColorways } from '@shared/util/storybook';
|
||||||
import Divider from '@views/components/common/Divider/Divider';
|
import Divider from '@views/components/common/Divider/Divider';
|
||||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||||
import List from '@views/components/common/List/List';
|
import List from '@views/components/common/List/List';
|
||||||
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
|
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions';
|
import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions';
|
||||||
import useSchedules, { switchSchedule } from '@views/hooks/useSchedules';
|
import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||||
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
@@ -16,6 +16,7 @@ import SettingsIcon from '~icons/material-symbols/settings';
|
|||||||
|
|
||||||
import CourseStatus from './common/CourseStatus/CourseStatus';
|
import CourseStatus from './common/CourseStatus/CourseStatus';
|
||||||
import { LogoIcon } from './common/LogoIcon';
|
import { LogoIcon } from './common/LogoIcon';
|
||||||
|
import PopupCourseBlock from './common/PopupCourseBlock/PopupCourseBlock';
|
||||||
import ScheduleDropdown from './common/ScheduleDropdown/ScheduleDropdown';
|
import ScheduleDropdown from './common/ScheduleDropdown/ScheduleDropdown';
|
||||||
import ScheduleListItem from './common/ScheduleListItem/ScheduleListItem';
|
import ScheduleListItem from './common/ScheduleListItem/ScheduleListItem';
|
||||||
|
|
||||||
@@ -61,27 +62,53 @@ export default function PopupMain(): JSX.Element {
|
|||||||
<div className='px-5 pb-2.5 pt-3.75'>
|
<div className='px-5 pb-2.5 pt-3.75'>
|
||||||
<ScheduleDropdown>
|
<ScheduleDropdown>
|
||||||
<List
|
<List
|
||||||
draggableElements={schedules.map((schedule, index) => (
|
draggables={schedules}
|
||||||
|
equalityCheck={(a, b) => a.name === b.name}
|
||||||
|
onReordered={reordered => {
|
||||||
|
const activeSchedule = getActiveSchedule();
|
||||||
|
const activeIndex = reordered.findIndex(s => s.name === activeSchedule.name);
|
||||||
|
|
||||||
|
// don't care about the promise
|
||||||
|
UserScheduleStore.set('schedules', reordered);
|
||||||
|
UserScheduleStore.set('activeIndex', activeIndex);
|
||||||
|
}}
|
||||||
|
gap={10}
|
||||||
|
>
|
||||||
|
{(schedule, handleProps) => (
|
||||||
<ScheduleListItem
|
<ScheduleListItem
|
||||||
active={false}
|
|
||||||
name={schedule.name}
|
name={schedule.name}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
switchSchedule(schedule.name);
|
switchSchedule(schedule.name);
|
||||||
}}
|
}}
|
||||||
|
dragHandleProps={handleProps}
|
||||||
/>
|
/>
|
||||||
))}
|
)}
|
||||||
gap={10}
|
</List>
|
||||||
/>
|
|
||||||
</ScheduleDropdown>
|
</ScheduleDropdown>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-1 self-stretch overflow-y-auto px-5'>
|
<div className='flex-1 self-stretch overflow-y-auto px-5'>
|
||||||
{activeSchedule?.courses?.length > 0 && (
|
{activeSchedule?.courses?.length > 0 && (
|
||||||
<List
|
<List
|
||||||
draggableElements={activeSchedule?.courses.map((course, i) => (
|
draggables={activeSchedule.courses.map((course, i) => ({
|
||||||
<PopupCourseBlock key={course.uniqueId} course={course} colors={tailwindColorways[i]} />
|
course,
|
||||||
))}
|
colors: tailwindColorways[i],
|
||||||
|
}))}
|
||||||
|
onReordered={reordered => {
|
||||||
|
activeSchedule.courses = reordered.map(c => c.course);
|
||||||
|
replaceSchedule(getActiveSchedule(), activeSchedule);
|
||||||
|
}}
|
||||||
|
equalityCheck={(a, b) => a.course.uniqueId === b.course.uniqueId}
|
||||||
gap={10}
|
gap={10}
|
||||||
/>
|
>
|
||||||
|
{({ course, colors }, handleProps) => (
|
||||||
|
<PopupCourseBlock
|
||||||
|
key={course.uniqueId}
|
||||||
|
course={course}
|
||||||
|
colors={colors}
|
||||||
|
dragHandleProps={handleProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { background } from '@shared/messages';
|
import { background } from '@shared/messages';
|
||||||
|
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
|
||||||
import type { UserSchedule } from '@shared/types/UserSchedule';
|
import type { UserSchedule } from '@shared/types/UserSchedule';
|
||||||
import List from '@views/components/common/List/List';
|
import List from '@views/components/common/List/List';
|
||||||
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
|
import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import useSchedules from '@views/hooks/useSchedules';
|
import useSchedules, { getActiveSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import AddSchedule from '~icons/material-symbols/add';
|
import AddSchedule from '~icons/material-symbols/add';
|
||||||
@@ -47,20 +48,6 @@ export function CalendarSchedules({ style, dummySchedules, dummyActiveIndex }: P
|
|||||||
setNewSchedule(e.target.value);
|
setNewSchedule(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectItem = (index: number) => {
|
|
||||||
background.switchSchedule({ scheduleName: schedules[index].name }).then(() => {
|
|
||||||
setActiveScheduleIndex(index);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const scheduleComponents = schedules.map((schedule, index) => (
|
|
||||||
<ScheduleListItem
|
|
||||||
active={index === activeScheduleIndex}
|
|
||||||
name={schedule.name}
|
|
||||||
onClick={() => selectItem(index)}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
const fixBuildError = {
|
const fixBuildError = {
|
||||||
dummySchedules,
|
dummySchedules,
|
||||||
dummyActiveIndex,
|
dummyActiveIndex,
|
||||||
@@ -78,7 +65,29 @@ export function CalendarSchedules({ style, dummySchedules, dummyActiveIndex }: P
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col space-y-2.5'>
|
<div className='flex flex-col space-y-2.5'>
|
||||||
<List gap={10} draggableElements={scheduleComponents} />
|
<List
|
||||||
|
gap={10}
|
||||||
|
draggables={schedules}
|
||||||
|
equalityCheck={(a, b) => a.name === b.name}
|
||||||
|
onReordered={reordered => {
|
||||||
|
const activeSchedule = getActiveSchedule();
|
||||||
|
const activeIndex = reordered.findIndex(s => s.name === activeSchedule.name);
|
||||||
|
|
||||||
|
// don't care about the promise
|
||||||
|
UserScheduleStore.set('schedules', reordered);
|
||||||
|
UserScheduleStore.set('activeIndex', activeIndex);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(schedule, handleProps) => (
|
||||||
|
<ScheduleListItem
|
||||||
|
name={schedule.name}
|
||||||
|
onClick={() => {
|
||||||
|
switchSchedule(schedule.name);
|
||||||
|
}}
|
||||||
|
dragHandleProps={handleProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
placeholder='Enter new schedule'
|
placeholder='Enter new schedule'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { DraggableProvided, DraggableProvidedDragHandleProps, OnDragEndResponder } from '@hello-pangea/dnd';
|
||||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
@@ -9,27 +10,30 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|||||||
/**
|
/**
|
||||||
* Props for the List component.
|
* Props for the List component.
|
||||||
*/
|
*/
|
||||||
export interface ListProps {
|
export interface ListProps<T> {
|
||||||
draggableElements: any[]; // TODO: Will later define draggableElements based on what types
|
draggables: T[];
|
||||||
// of components are draggable.
|
children: (draggable: T, handleProps: DraggableProvidedDragHandleProps) => ReactElement;
|
||||||
gap: number; // Impacts the spacing between items in the list
|
onReordered: (elements: T[]) => void;
|
||||||
|
equalityCheck?: (a: T, b: T) => boolean;
|
||||||
|
gap?: number; // Impacts the spacing between items in the list
|
||||||
}
|
}
|
||||||
|
|
||||||
function initial(count: number, draggableElements: any[] = []) {
|
function wrap<T>(draggableElements: T[]) {
|
||||||
return draggableElements.map((element, index) => ({
|
return draggableElements.map((element, index) => ({
|
||||||
id: `id:${index + count}`,
|
id: `id:${index}`,
|
||||||
content: element as ReactElement,
|
content: element,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function reorder(list: { id: string; content: ReactElement }[], startIndex: number, endIndex: number) {
|
function reorder<T>(list: T[], startIndex: number, endIndex: number) {
|
||||||
const result = Array.from(list);
|
const listCopy = [...list];
|
||||||
const [removed] = result.splice(startIndex, 1);
|
|
||||||
result.splice(endIndex, 0, removed);
|
const [removed] = listCopy.splice(startIndex, 1);
|
||||||
return result;
|
listCopy.splice(endIndex, 0, removed);
|
||||||
|
return listCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStyle({ provided, style /* , isDragging, gap */ }) {
|
function getStyle(provided: DraggableProvided, style: React.CSSProperties) {
|
||||||
const combined = {
|
const combined = {
|
||||||
...style,
|
...style,
|
||||||
...provided.draggableProps.style,
|
...provided.draggableProps.style,
|
||||||
@@ -38,15 +42,21 @@ function getStyle({ provided, style /* , isDragging, gap */ }) {
|
|||||||
return combined;
|
return combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Item({ provided, item, style, isDragging /* , gap */ }) {
|
function Item<T>(props: {
|
||||||
|
provided: DraggableProvided;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
item: T;
|
||||||
|
isDragging: boolean;
|
||||||
|
children: React.ReactElement;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...provided.draggableProps}
|
{...props.provided.draggableProps}
|
||||||
ref={provided.innerRef}
|
ref={props.provided.innerRef}
|
||||||
style={getStyle({ provided, style /* , isDragging, gap */ })}
|
style={getStyle(props.provided, props.style)}
|
||||||
className={`item ${isDragging ? 'is-dragging' : ''}`}
|
className={props.isDragging ? 'is-dragging' : ''}
|
||||||
>
|
>
|
||||||
{React.cloneElement(item.content, { dragHandleProps: provided.dragHandleProps })}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -57,30 +67,34 @@ function Item({ provided, item, style, isDragging /* , gap */ }) {
|
|||||||
* @example
|
* @example
|
||||||
* <List draggableElements={elements} />
|
* <List draggableElements={elements} />
|
||||||
*/
|
*/
|
||||||
export default function List({ draggableElements, gap = 12 }: ListProps): JSX.Element {
|
function List<T>(props: ListProps<T>): JSX.Element {
|
||||||
const [items, setItems] = useState(() => initial(0, draggableElements));
|
const [items, setItems] = useState(wrap(props.draggables));
|
||||||
|
|
||||||
|
const equalityCheck = props.equalityCheck || ((a, b) => a === b);
|
||||||
|
const transformFunction = props.children;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setItems(prevItems => {
|
// check if the draggables content has *actually* changed
|
||||||
const prevItemIds = prevItems.map(item => item.id);
|
if (props.draggables.every((element, index) => equalityCheck(element, items[index].content))) {
|
||||||
const newElements = draggableElements.filter((_, index) => !prevItemIds.includes(`id:${index}`));
|
console.log("List's draggables have not changed");
|
||||||
const newItems = initial(prevItems.length, newElements);
|
return;
|
||||||
return [...prevItems, ...newItems];
|
}
|
||||||
});
|
|
||||||
}, [draggableElements]);
|
console.log("List's draggables have changed, updating...");
|
||||||
const onDragEnd = useCallback(
|
|
||||||
|
setItems(wrap(props.draggables));
|
||||||
|
}, [props.draggables]);
|
||||||
|
|
||||||
|
const onDragEnd: OnDragEndResponder = useCallback(
|
||||||
result => {
|
result => {
|
||||||
if (!result.destination) {
|
if (!result.destination) return;
|
||||||
return;
|
if (result.source.index === result.destination.index) return;
|
||||||
}
|
|
||||||
|
|
||||||
if (result.source.index === result.destination.index) {
|
// will reorder in place
|
||||||
return;
|
const reordered = reorder(items, result.source.index, result.destination.index);
|
||||||
}
|
|
||||||
|
|
||||||
const newItems = reorder(items, result.source.index, result.destination.index);
|
setItems(reordered);
|
||||||
|
props.onReordered(reordered.map(item => item.content));
|
||||||
setItems(newItems satisfies { id: string; content: React.ReactElement }[]);
|
|
||||||
},
|
},
|
||||||
[items]
|
[items]
|
||||||
);
|
);
|
||||||
@@ -107,14 +121,20 @@ export default function List({ draggableElements, gap = 12 }: ListProps): JSX.El
|
|||||||
isDragging={snapshot.isDragging}
|
isDragging={snapshot.isDragging}
|
||||||
item={items[rubric.source.index]}
|
item={items[rubric.source.index]}
|
||||||
style={{
|
style={{
|
||||||
style,
|
...style,
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{transformFunction(items[rubric.source.index].content, provided.dragHandleProps)}
|
||||||
|
</Item>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<div {...provided.droppableProps} ref={provided.innerRef} style={{ marginBottom: `-${gap}px` }}>
|
<div
|
||||||
|
{...provided.droppableProps}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
style={{ marginBottom: `-${props.gap}px` }}
|
||||||
|
>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<Draggable key={item.id} draggableId={item.id} index={index}>
|
<Draggable key={item.id} draggableId={item.id} index={index}>
|
||||||
{draggableProvided => (
|
{draggableProvided => (
|
||||||
@@ -124,12 +144,10 @@ export default function List({ draggableElements, gap = 12 }: ListProps): JSX.El
|
|||||||
style={{
|
style={{
|
||||||
...draggableProvided.draggableProps.style,
|
...draggableProvided.draggableProps.style,
|
||||||
// if last item, don't add margin
|
// if last item, don't add margin
|
||||||
marginBottom: `${gap}px`,
|
marginBottom: `${props.gap}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{React.cloneElement(item.content, {
|
{transformFunction(item.content, draggableProvided.dragHandleProps)}
|
||||||
dragHandleProps: draggableProvided.dragHandleProps,
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
@@ -142,3 +160,5 @@ export default function List({ draggableElements, gap = 12 }: ListProps): JSX.El
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default React.memo(List) as typeof List;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import DragIndicatorIcon from '~icons/material-symbols/drag-indicator';
|
|||||||
*/
|
*/
|
||||||
export type Props = {
|
export type Props = {
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
active?: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
dragHandleProps?: Omit<React.HTMLAttributes<HTMLDivElement>, 'className'>;
|
dragHandleProps?: Omit<React.HTMLAttributes<HTMLDivElement>, 'className'>;
|
||||||
onClick?: React.DOMAttributes<HTMLDivElement>['onClick'];
|
onClick?: React.DOMAttributes<HTMLDivElement>['onClick'];
|
||||||
@@ -22,7 +21,7 @@ export type Props = {
|
|||||||
export default function ScheduleListItem({ style, name, dragHandleProps, onClick }: Props): JSX.Element {
|
export default function ScheduleListItem({ style, name, dragHandleProps, onClick }: Props): JSX.Element {
|
||||||
const [activeSchedule] = useSchedules();
|
const [activeSchedule] = useSchedules();
|
||||||
|
|
||||||
const isActive = useMemo(() => activeSchedule?.name === name, [activeSchedule, name]);
|
const isActive = useMemo(() => activeSchedule.name === name, [activeSchedule, name]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ ...style }} className='items-center rounded bg-white'>
|
<div style={{ ...style }} className='items-center rounded bg-white'>
|
||||||
|
|||||||
@@ -39,10 +39,12 @@ export default function useSchedules(): [active: UserSchedule | null, schedules:
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const l1 = UserScheduleStore.listen('schedules', ({ newValue }) => {
|
const l1 = UserScheduleStore.listen('schedules', ({ newValue }) => {
|
||||||
setSchedules(newValue.map(s => new UserSchedule(s)));
|
schedulesCache = newValue.map(s => new UserSchedule(s));
|
||||||
|
setSchedules(schedulesCache);
|
||||||
});
|
});
|
||||||
|
|
||||||
const l2 = UserScheduleStore.listen('activeIndex', ({ newValue }) => {
|
const l2 = UserScheduleStore.listen('activeIndex', ({ newValue }) => {
|
||||||
|
activeIndexCache = newValue;
|
||||||
setActiveIndex(newValue);
|
setActiveIndex(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,11 +57,23 @@ export default function useSchedules(): [active: UserSchedule | null, schedules:
|
|||||||
// recompute active schedule on a schedule/index change
|
// recompute active schedule on a schedule/index change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveSchedule(schedules[activeIndex]);
|
setActiveSchedule(schedules[activeIndex]);
|
||||||
});
|
}, [activeIndex, schedules]);
|
||||||
|
|
||||||
return [activeSchedule, schedules];
|
return [activeSchedule, schedules];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getActiveSchedule(): UserSchedule | null {
|
||||||
|
return schedulesCache[activeIndexCache] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function replaceSchedule(oldSchedule: UserSchedule, newSchedule: UserSchedule) {
|
||||||
|
const schedules = await UserScheduleStore.get('schedules');
|
||||||
|
const oldIndex = schedules.findIndex(s => s.name === oldSchedule.name);
|
||||||
|
schedules[oldIndex] = newSchedule;
|
||||||
|
await UserScheduleStore.set('schedules', schedules);
|
||||||
|
console.log('schedule replaced');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches the active schedule to the one with the specified name.
|
* Switches the active schedule to the one with the specified name.
|
||||||
* @param name - The name of the schedule to switch to.
|
* @param name - The name of the schedule to switch to.
|
||||||
|
|||||||
Reference in New Issue
Block a user