react-beautiful-dnd#DropResult TypeScript Examples
The following examples show how to use
react-beautiful-dnd#DropResult.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: BuildOrder.tsx From sc2-planner with MIT License | 6 votes |
onDragEnd(result: DropResult): void {
// Dropped outside the list
if (!result.destination) {
return
}
const items: Array<IBuildOrderElement> = reorder(
this.props.gamelogic.bo,
result.source.index,
result.destination.index
)
this.props.rerunBuildOrder(items)
this.props.updateUrl(
this.props.gamelogic.race,
items,
this.props.gamelogic.exportSettings(),
this.props.gamelogic.exportOptimizeSettings()
)
}
Example #2
Source File: DndContextProvider.tsx From calories-in with MIT License | 6 votes |
function DndContextProvider({ children }: Props) {
const dietFormActions = useDietFormActions()
const onDragEnd = (dropResult: DropResult) => {
const { source, destination, type } = dropResult
if (!destination) {
return
}
if (type === 'variantsList') {
dietFormActions.moveVariantForm(source.index, destination.index)
} else if (type === 'mealsList') {
dietFormActions.moveMealForm(source.index, destination.index)
} else if (type === 'ingredientsList') {
dietFormActions.moveIngredientForm(
source.droppableId,
source.index,
destination.droppableId,
destination.index
)
}
}
return <DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
}
Example #3
Source File: Tree.tsx From react-beautiful-tree with Apache License 2.0 | 6 votes |
onDragEnd = (result: DropResult) => {
const { onDragEnd, tree } = this.props
const { flattenedTree } = this.state
this.expandTimer.stop()
const finalDragState: DragState = {
...this.dragState!,
source: result.source,
destination: result.destination,
combine: result.combine,
}
this.setState({
draggedItemId: undefined,
})
const { sourcePosition, destinationPosition } = calculateFinalDropPositions(
tree,
flattenedTree,
finalDragState
)
onDragEnd(sourcePosition, destinationPosition)
this.dragState = undefined
}
Example #4
Source File: FlowList.tsx From Protoman with MIT License | 6 votes |
FlowList: React.FunctionComponent<Props> = ({ collectionName }) => {
const dispatch = useDispatch();
const collection = useSelector((s: AppState) => getByKey(s.collections, collectionName));
const flowNames = useSelector((s: AppState) => collection?.flows?.map(([n]) => n));
const isCurrentCollection = useSelector((s: AppState) => s.currentCollection === collectionName);
const currentFlow = useSelector((s: AppState) => s.currentFlow);
function handleSelection(flowName: string): void {
dispatch(selectFlow(collectionName, flowName));
}
function validateFlowName(flowName: string): boolean {
return !collection?.flows?.map(([n]) => n)?.includes(flowName);
}
function handleDelete(flowName: string): void {
const flowCount = collection?.flows?.length || 0;
if (flowCount > 1) {
dispatch(deleteFlow(collectionName, flowName));
} else {
message.error("Can't delete the last request");
}
}
function handleClone(originalFlowName: string): void {
//check if this clone already exists
const tmpName = originalFlowName.concat('_clone');
let tmpNameIdx = 1;
while (!validateFlowName(`${tmpName}${tmpNameIdx}`)) tmpNameIdx++;
dispatch(cloneFlow(collectionName, originalFlowName, `${tmpName}${tmpNameIdx}`));
}
function handleDragEnd(result: DropResult): void {
console.log(result);
if (!result.destination || result.source.droppableId != result.destination.droppableId) return;
const src = result.source.index;
const dst = result.destination.index;
dispatch(reorderFlow(collectionName, src, dst));
}
return (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId={collectionName}>
{(provided): React.ReactElement => (
<div {...provided.droppableProps} ref={provided.innerRef}>
<List
dataSource={flowNames}
rowKey={(name): string => name}
renderItem={(flowName, idx): React.ReactNode => (
<FlowCell
idx={idx}
flowName={flowName}
emphasize={isCurrentCollection && currentFlow === flowName}
handleSelection={handleSelection}
handleDelete={handleDelete}
handleClone={handleClone}
/>
)}
/>
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
}
Example #5
Source File: AccountManagementPage.tsx From signer with Apache License 2.0 | 6 votes |
onDragEnd = (result: DropResult) => {
// dropped outside the list
if (!result.destination) {
return;
}
this.props.accountManager.reorderAccount(
result.source.index,
result.destination.index
);
};
Example #6
Source File: TasksBoard.tsx From projectboard with MIT License | 5 votes |
TasksBoard = () => {
const backlogTasks: Array<Task> = useSelector((state: RootState) => state.taskList?.tasks.backlog);
const todoTasks: Array<Task> = useSelector((state: RootState) => state.taskList?.tasks.todo);
const inProgressTasks: Array<Task> = useSelector(
(state: RootState) => state.taskList?.tasks?.in_progress
);
const doneTasks: Array<Task> = useSelector((state: RootState) => state.taskList?.tasks?.done);
const canceledTasks: Array<Task> = useSelector((state: RootState) => state.taskList?.tasks?.cancelled);
const todoSorted: Array<Task> = todoTasks.sort((prev: Task, next: Task) => prev.order - next.order);
const backlogSorted: Array<Task> = backlogTasks.sort((prev: Task, next: Task) => prev.order - next.order);
const inProgressSorted: Array<Task> = inProgressTasks.sort(
(prev: Task, next: Task) => prev.order - next.order
);
const doneSorted: Array<Task> = doneTasks.sort((prev: Task, next: Task) => prev.order - next.order);
const cancelledSorted: Array<Task> = canceledTasks.sort(
(prev: Task, next: Task) => prev.order - next.order
);
const match = useRouteMatch<MatchParams>();
const { getAccessTokenSilently } = useAuth0();
// dispatch
const dispatch = useDispatch<AppDispatch>();
const onDragEnd = async (
{ source, destination, draggableId }: DropResult,
provided: ResponderProvided
) => {
if (source.droppableId === destination?.droppableId) return;
if (!source || !destination) return;
const token = await getAccessTokenSilently();
dispatch(
changeStatusOfTaskBoard(
draggableId,
source.droppableId,
destination.droppableId,
source.index,
destination.index,
match.params.projectId,
token
)
);
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<div className="flex flex-1 pt-6 pl-8 overflow-scroll bg-gray-100">
<IssueCol title={'Backlog'} status={Status.BACKLOG} tasks={backlogSorted} />
<IssueCol title={'Todo'} status={Status.TODO} tasks={todoSorted} />
<IssueCol title={'In Progress'} status={Status.IN_PROGRESS} tasks={inProgressSorted} />
<IssueCol title={'Done'} status={Status.DONE} tasks={doneSorted} />
<IssueCol title={'Canceled'} status={Status.CANCELED} tasks={cancelledSorted} />
</div>
</DragDropContext>
);
}
Example #7
Source File: Todos.tsx From max-todos with MIT License | 5 votes |
Todos = () => {
const { todos, moveTodo } = useContext(MainContext)!;
const [deleteSnackOpen, setDeleteSnackOpen] = useState(false);
const [editSnackOpen, setEditSnackOpen] = useState(false);
const [dragging, setDragging] = useState(false);
const onDragEnd = (x: DropResult) => {
if (!x.destination) return console.log(x);
moveTodo(x.source.index, x.destination.index);
setTimeout(() => setDragging(false), 200);
};
return (
<>
<DragDropContext
onBeforeDragStart={() => setDragging(true)}
onDragEnd={onDragEnd}
>
<Droppable droppableId="0">
{(p) => (
<div {...p.droppableProps} ref={p.innerRef}>
<FlipMove disableAllAnimations={dragging}>
{todos.map((todo, i) => {
return (
<Todo
todo={todo}
key={todo.id}
onDelete={() => setDeleteSnackOpen(true)}
index={i}
onEdit={() => setEditSnackOpen(true)}
/>
);
})}
</FlipMove>
{p.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<Snackbar
open={deleteSnackOpen}
autoHideDuration={4000}
onClose={() => setDeleteSnackOpen(false)}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert
elevation={6}
variant="filled"
onClose={() => setDeleteSnackOpen(false)}
severity="success"
>
Successfully deleted item!
</Alert>
</Snackbar>
<Snackbar
open={editSnackOpen}
autoHideDuration={4000}
onClose={() => setEditSnackOpen(false)}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert
elevation={6}
variant="filled"
onClose={() => setEditSnackOpen(false)}
severity="success"
>
Successfully edited item!
</Alert>
</Snackbar>
</>
);
}
Example #8
Source File: useDnd.ts From TabMerger with GNU General Public License v3.0 | 5 votes |
export default function useDnd() {
const dispatch = useDispatch();
const [groupTitle] = useLocalStorage("groupTitle", DEFAULT_GROUP_TITLE);
const [groupColor] = useLocalStorage("groupColor", DEFAULT_GROUP_COLOR);
const [windowTitle] = useLocalStorage("windowTitle", DEFAULT_WINDOW_TITLE);
const { active } = useSelector((state) => state.groups.present);
const { index } = active;
const onBeforeCapture = useCallback(
({ draggableId }: BeforeCapture) => {
const windowDrag = isWindowDrag(draggableId);
const tabDrag = isTabDrag(draggableId);
if (windowDrag) {
// Hide tabs during a window drag
toggleWindowTabsVisibility(draggableId, false);
} else if (tabDrag) {
// Add window to end of group (only if not in the first group)
index > 0 && dispatch(addWindow({ index, name: windowTitle }));
}
if (windowDrag || tabDrag) {
// Add group to end of side panel on both tab and window drag types
dispatch(addGroup({ title: groupTitle, color: groupColor }));
}
},
[dispatch, index, groupTitle, groupColor, windowTitle]
);
const onDragStart = useCallback(
({ draggableId }: DragStart) => {
dispatch(updateDragOriginType(draggableId));
dispatch(updateIsDragging(true));
},
[dispatch]
);
const onDragEnd = useCallback(
({ source, destination, combine, draggableId }: DropResult) => {
const [isTab, isWindow, isGroup] = [isTabDrag, isWindowDrag, isGroupDrag].map((cb) => cb(draggableId));
const commonPayload = { index, source };
const sidePanelPayload = { ...commonPayload, combine };
const destPayload = { ...commonPayload, destination };
const isValidCombine = combine && Number(combine.draggableId.split("-")[1]) > 0;
const isValidDndWithinGroup = destination && destination.droppableId !== "sidePanel";
if (isTab) {
isValidCombine && dispatch(updateTabsFromSidePanelDnd({ ...sidePanelPayload, name: windowTitle }));
isValidDndWithinGroup && dispatch(updateTabsFromGroupDnd(destPayload));
} else if (isWindow) {
// Re-show the tabs since the drag ended
toggleWindowTabsVisibility(draggableId, true);
isValidCombine && dispatch(updateWindowsFromSidePanelDnd(sidePanelPayload));
isValidDndWithinGroup && dispatch(updateWindowsFromGroupDnd(destPayload));
} else if (isGroup && destination && destination.index > 0) {
// Only swap if the destination exists (valid) and is below the first group
dispatch(updateGroupOrder({ source, destination }));
}
dispatch(resetDnDInfo());
/**
* Must clear the windows in the current group first, then clear the group
* @note Only relevant for tab or window dragging since a group drag does not add either a (temporary) window or group
*/
if (isTab || isWindow) {
dispatch(clearEmptyWindows({ index }));
dispatch(clearEmptyGroups(active));
}
},
[dispatch, index, windowTitle, active]
);
return { onBeforeCapture, onDragStart, onDragEnd };
}
Example #9
Source File: onDragEnd.ts From taskcafe with MIT License | 5 votes |
onDragEnd = (
{ draggableId, source, destination, type }: DropResult,
task: Task,
onChecklistDrop: OnChecklistDropFn,
onChecklistItemDrop: OnChecklistItemDropFn,
) => {
if (typeof destination === 'undefined') return;
if (!isPositionChanged(source, destination)) return;
const isChecklist = type === 'checklist';
const isSameChecklist = destination.droppableId === source.droppableId;
let droppedDraggable: DraggableElement | null = null;
let beforeDropDraggables: Array<DraggableElement> | null = null;
if (!task.checklists) return;
if (isChecklist) {
const droppedGroup = task.checklists.find(taskGroup => taskGroup.id === draggableId);
if (droppedGroup) {
droppedDraggable = {
id: draggableId,
position: droppedGroup.position,
};
beforeDropDraggables = getSortedDraggables(
task.checklists.map(checklist => {
return { id: checklist.id, position: checklist.position };
}),
);
if (droppedDraggable === null || beforeDropDraggables === null) {
throw new Error('before drop draggables is null');
}
const afterDropDraggables = getAfterDropDraggableList(
beforeDropDraggables,
droppedDraggable,
isChecklist,
isSameChecklist,
destination,
);
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
onChecklistDrop({ ...droppedGroup, position: newPosition });
} else {
throw new Error('task group can not be found');
}
} else {
const targetChecklist = task.checklists.findIndex(
checklist => checklist.items.findIndex(item => item.id === draggableId) !== -1,
);
const droppedChecklistItem = task.checklists[targetChecklist].items.find(item => item.id === draggableId);
if (droppedChecklistItem) {
droppedDraggable = {
id: draggableId,
position: droppedChecklistItem.position,
};
beforeDropDraggables = getSortedDraggables(
task.checklists[targetChecklist].items.map(item => {
return { id: item.id, position: item.position };
}),
);
if (droppedDraggable === null || beforeDropDraggables === null) {
throw new Error('before drop draggables is null');
}
const afterDropDraggables = getAfterDropDraggableList(
beforeDropDraggables,
droppedDraggable,
isChecklist,
isSameChecklist,
destination,
);
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
const newItem = {
...droppedChecklistItem,
position: newPosition,
};
onChecklistItemDrop(droppedChecklistItem.taskChecklistID, destination.droppableId, newItem);
}
}
}
Example #10
Source File: BookmarksTable.tsx From flame with MIT License | 4 votes |
BookmarksTable = ({ openFormForUpdating }: Props): JSX.Element => {
const {
bookmarks: { categoryInEdit },
config: { config },
} = useSelector((state: State) => state);
const dispatch = useDispatch();
const {
deleteBookmark,
updateBookmark,
createNotification,
reorderBookmarks,
} = bindActionCreators(actionCreators, dispatch);
const [localBookmarks, setLocalBookmarks] = useState<Bookmark[]>([]);
// Copy bookmarks array
useEffect(() => {
if (categoryInEdit) {
setLocalBookmarks([...categoryInEdit.bookmarks]);
}
}, [categoryInEdit]);
// Drag and drop handler
const dragEndHanlder = (result: DropResult): void => {
if (config.useOrdering !== 'orderId') {
createNotification({
title: 'Error',
message: 'Custom order is disabled',
});
return;
}
if (!result.destination) {
return;
}
const tmpBookmarks = [...localBookmarks];
const [movedBookmark] = tmpBookmarks.splice(result.source.index, 1);
tmpBookmarks.splice(result.destination.index, 0, movedBookmark);
setLocalBookmarks(tmpBookmarks);
const categoryId = categoryInEdit?.id || -1;
reorderBookmarks(tmpBookmarks, categoryId);
};
// Action hanlders
const deleteBookmarkHandler = (id: number, name: string) => {
const categoryId = categoryInEdit?.id || -1;
const proceed = window.confirm(`Are you sure you want to delete ${name}?`);
if (proceed) {
deleteBookmark(id, categoryId);
}
};
const updateBookmarkHandler = (id: number) => {
const bookmark =
categoryInEdit?.bookmarks.find((b) => b.id === id) || bookmarkTemplate;
openFormForUpdating(bookmark);
};
const changeBookmarkVisibiltyHandler = (id: number) => {
const bookmark =
categoryInEdit?.bookmarks.find((b) => b.id === id) || bookmarkTemplate;
const categoryId = categoryInEdit?.id || -1;
const [prev, curr] = [categoryId, categoryId];
updateBookmark(
id,
{ ...bookmark, isPublic: !bookmark.isPublic },
{ prev, curr }
);
};
return (
<Fragment>
{!categoryInEdit ? (
<Message isPrimary={false}>
Switch to grid view and click on the name of category you want to edit
</Message>
) : (
<Message isPrimary={false}>
Editing bookmarks from <span>{categoryInEdit.name}</span>
category
</Message>
)}
{categoryInEdit && (
<DragDropContext onDragEnd={dragEndHanlder}>
<Droppable droppableId="bookmarks">
{(provided) => (
<Table
headers={[
'Name',
'URL',
'Icon',
'Visibility',
'Category',
'Actions',
]}
innerRef={provided.innerRef}
>
{localBookmarks.map((bookmark, index): JSX.Element => {
return (
<Draggable
key={bookmark.id}
draggableId={bookmark.id.toString()}
index={index}
>
{(provided, snapshot) => {
const style = {
border: snapshot.isDragging
? '1px solid var(--color-accent)'
: 'none',
borderRadius: '4px',
...provided.draggableProps.style,
};
return (
<tr
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
style={style}
>
<td style={{ width: '200px' }}>{bookmark.name}</td>
<td style={{ width: '200px' }}>{bookmark.url}</td>
<td style={{ width: '200px' }}>{bookmark.icon}</td>
<td style={{ width: '200px' }}>
{bookmark.isPublic ? 'Visible' : 'Hidden'}
</td>
<td style={{ width: '200px' }}>
{categoryInEdit.name}
</td>
{!snapshot.isDragging && (
<TableActions
entity={bookmark}
deleteHandler={deleteBookmarkHandler}
updateHandler={updateBookmarkHandler}
changeVisibilty={changeBookmarkVisibiltyHandler}
showPin={false}
/>
)}
</tr>
);
}}
</Draggable>
);
})}
</Table>
)}
</Droppable>
</DragDropContext>
)}
</Fragment>
);
}
Example #11
Source File: Step1Columns.tsx From firetable with Apache License 2.0 | 4 votes |
export default function Step1Columns({ config, setConfig }: IStepProps) {
const classes = useStyles();
// Get a list of fields from first 50 documents
const { tableState } = useFiretableContext();
const allFields = useMemo(() => {
const sample = tableState!.rows.slice(0, 50);
const fields_ = new Set<string>();
sample.forEach((doc) =>
Object.keys(doc).forEach((key) => {
if (key !== "ref") fields_.add(key);
})
);
return Array.from(fields_).sort();
}, [tableState?.rows]);
// Store selected fields
const [selectedFields, setSelectedFields] = useState<string[]>(
_sortBy(Object.keys(config), "index")
);
const handleSelect = (field: string) => (_, checked: boolean) => {
if (checked) {
setSelectedFields([...selectedFields, field]);
} else {
const newSelection = [...selectedFields];
newSelection.splice(newSelection.indexOf(field), 1);
setSelectedFields(newSelection);
}
};
const handleSelectAll = () => {
if (selectedFields.length !== allFields.length)
setSelectedFields(allFields);
else setSelectedFields([]);
};
const handleDragEnd = (result: DropResult) => {
const newOrder = [...selectedFields];
const [removed] = newOrder.splice(result.source.index, 1);
newOrder.splice(result.destination!.index, 0, removed);
setSelectedFields(newOrder);
};
useEffect(() => {
setConfig(
selectedFields.reduce(
(a, c, i) => ({
...a,
[c]: {
fieldName: c,
key: c,
name: config[c]?.name || _startCase(c),
type:
config[c]?.type ||
suggestType(tableState!.rows, c) ||
FieldType.shortText,
index: i,
config: {},
},
}),
{}
)
);
}, [selectedFields]);
return (
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<Typography variant="overline" gutterBottom component="h2">
Select Columns ({selectedFields.length} of {allFields.length})
</Typography>
<Divider />
<FadeList>
<li>
<FormControlLabel
control={
<Checkbox
checked={selectedFields.length === allFields.length}
indeterminate={
selectedFields.length !== 0 &&
selectedFields.length !== allFields.length
}
onChange={handleSelectAll}
color="default"
/>
}
label={
<Typography variant="subtitle2" color="textSecondary">
Select all
</Typography>
}
classes={{
root: classes.formControlLabel,
label: classes.columnLabel,
}}
/>
</li>
<li className={classes.spacer} />
{allFields.map((field) => (
<li key={field}>
<FormControlLabel
key={field}
control={
<Checkbox
checked={selectedFields.indexOf(field) > -1}
aria-label={`Select column ${field}`}
onChange={handleSelect(field)}
color="default"
/>
}
label={<Column label={field} />}
classes={{
root: classes.formControlLabel,
label: classes.columnLabel,
}}
/>
</li>
))}
</FadeList>
</Grid>
<Grid item xs={12} sm={6}>
<Typography variant="overline" gutterBottom component="h2">
Sort Firetable Columns
</Typography>
<Divider />
{selectedFields.length === 0 ? (
<FadeList>
<EmptyState Icon={AddColumnIcon} message="No columns selected" />
</FadeList>
) : (
<DragDropContext onDragEnd={handleDragEnd}>
<FadeList>
<Droppable droppableId="droppable">
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{selectedFields.map((field, i) => (
<li key={field}>
<Draggable draggableId={field} index={i}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Column
label={field}
active={snapshot.isDragging}
secondaryItem={<DragHandleIcon />}
/>
</div>
)}
</Draggable>
</li>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</FadeList>
</DragDropContext>
)}
</Grid>
</Grid>
);
}
Example #12
Source File: AccountManagementPage.tsx From clarity with Apache License 2.0 | 4 votes |
AccountManagementPage = observer((props: Props) => {
const [openDialog, setOpenDialog] = React.useState(false);
const [openKeyDialog, setOpenKeyDialog] = React.useState(false);
const [
selectedAccount,
setSelectedAccount
] = React.useState<SignKeyPairWithAlias | null>(null);
const [name, setName] = React.useState('');
const [publicKey64, setPublicKey64] = React.useState('');
const [publicKeyHex, setPublicKeyHex] = React.useState('');
/* Note: 01 prefix denotes algorithm used in key generation */
const address = '01' + publicKeyHex;
const [copyStatus, setCopyStatus] = React.useState(false);
const handleClickOpen = (account: SignKeyPairWithAlias) => {
setOpenDialog(true);
setSelectedAccount(account);
setName(account.name);
};
const handleViewKey = async (accountName: string) => {
let publicKey64 = await props.authContainer.getSelectedAccountKey(
accountName
);
let publicKeyHex = await props.authContainer.getPublicKeyHex(accountName);
setName(accountName);
setPublicKey64(publicKey64);
setPublicKeyHex(publicKeyHex);
setOpenKeyDialog(true);
};
const handleCopyMessage = (event?: React.SyntheticEvent, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setCopyStatus(false);
};
const handleClose = () => {
setOpenDialog(false);
setOpenKeyDialog(false);
setSelectedAccount(null);
};
const handleUpdateName = () => {
if (selectedAccount) {
props.authContainer.renameUserAccount(selectedAccount.name, name);
handleClose();
}
};
const onDragEnd = (result: DropResult) => {
// dropped outside the list
if (!result.destination) {
return;
}
props.authContainer.reorderAccount(
result.source.index,
result.destination.index
);
};
const handleClickRemove = (name: string) => {
confirm(
<div className="text-danger">Remove account</div>,
'Are you sure you want to remove this account?'
).then(() => props.authContainer.removeUserAccount(name));
};
return (
<React.Fragment>
<DragDropContext onDragEnd={result => onDragEnd(result)}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<Observer>
{() => (
<RootRef rootRef={provided.innerRef}>
<List>
{props.authContainer.userAccounts.map((item, index) => (
<Draggable
key={item.name}
draggableId={item.name}
index={index}
>
{(provided, snapshot) => (
<ListItem
innerRef={provided.innerRef}
ContainerProps={{
...provided.draggableProps,
...provided.dragHandleProps,
style: getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)
}}
>
<ListItemText primary={item.name} />
<ListItemSecondaryAction>
<IconButton
edge={'end'}
onClick={() => {
handleClickOpen(item);
}}
>
<EditIcon />
</IconButton>
<IconButton
edge={'end'}
onClick={() => {
handleClickRemove(item.name);
}}
>
<DeleteIcon />
</IconButton>
<IconButton
edge={'end'}
onClick={() => {
handleViewKey(item.name);
}}
>
<VpnKeyIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)}
</Draggable>
))}
{provided.placeholder}
</List>
</RootRef>
)}
</Observer>
)}
</Droppable>
</DragDropContext>
<Dialog
open={openDialog}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Rename</DialogTitle>
<DialogContent>
<Input
autoFocus
margin="dense"
id="name"
type="text"
fullWidth
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleUpdateName} color="primary">
Update
</Button>
</DialogActions>
</Dialog>
<Dialog
fullScreen
open={openKeyDialog}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Account Details</DialogTitle>
<DialogContent>
<List>
<ListSubheader>
<Typography variant={'h6'}>{name}</Typography>
</ListSubheader>
<ListItem>
<IconButton
edge={'start'}
onClick={() => {
copy(address);
setCopyStatus(true);
}}
>
<FilterNoneIcon />
</IconButton>
<ListItemText
primary={'Address: ' + address}
style={{ overflowWrap: 'break-word' }}
/>
</ListItem>
<ListItem>
<IconButton
edge={'start'}
onClick={() => {
copy(publicKey64);
setCopyStatus(true);
}}
>
<FilterNoneIcon />
</IconButton>
<ListItemText
primary={'Public Key: ' + publicKey64}
style={{ overflowWrap: 'break-word' }}
/>
</ListItem>
</List>
<Snackbar
open={copyStatus}
message="Copied!"
autoHideDuration={1500}
onClose={handleCopyMessage}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
})
Example #13
Source File: board.tsx From wikitrivia with MIT License | 4 votes |
export default function Board(props: Props) {
const { highscore, resetGame, state, setState, updateHighscore } = props;
const [isDragging, setIsDragging] = React.useState(false);
async function onDragStart() {
setIsDragging(true);
navigator.vibrate(20);
}
async function onDragEnd(result: DropResult) {
setIsDragging(false);
const { source, destination } = result;
if (
!destination ||
state.next === null ||
(source.droppableId === "next" && destination.droppableId === "next")
) {
return;
}
const item = { ...state.next };
if (source.droppableId === "next" && destination.droppableId === "played") {
const newDeck = [...state.deck];
const newPlayed = [...state.played];
const { correct, delta } = checkCorrect(
newPlayed,
item,
destination.index
);
newPlayed.splice(destination.index, 0, {
...state.next,
played: { correct },
});
const newNext = state.nextButOne;
const newNextButOne = getRandomItem(
newDeck,
newNext ? [...newPlayed, newNext] : newPlayed
);
const newImageCache = [preloadImage(newNextButOne.image)];
setState({
...state,
deck: newDeck,
imageCache: newImageCache,
next: newNext,
nextButOne: newNextButOne,
played: newPlayed,
lives: correct ? state.lives : state.lives - 1,
badlyPlaced: correct
? null
: {
index: destination.index,
rendered: false,
delta,
},
});
} else if (
source.droppableId === "played" &&
destination.droppableId === "played"
) {
const newPlayed = [...state.played];
const [item] = newPlayed.splice(source.index, 1);
newPlayed.splice(destination.index, 0, item);
setState({
...state,
played: newPlayed,
badlyPlaced: null,
});
}
}
// Ensure that newly placed items are rendered as draggables before trying to
// move them to the right place if needed.
React.useLayoutEffect(() => {
if (
state.badlyPlaced &&
state.badlyPlaced.index !== null &&
!state.badlyPlaced.rendered
) {
setState({
...state,
badlyPlaced: { ...state.badlyPlaced, rendered: true },
});
}
}, [setState, state]);
const score = React.useMemo(() => {
return state.played.filter((item) => item.played.correct).length - 1;
}, [state.played]);
React.useLayoutEffect(() => {
if (score > highscore) {
updateHighscore(score);
}
}, [score, highscore, updateHighscore]);
return (
<DragDropContext
onDragEnd={onDragEnd}
onDragStart={onDragStart}
sensors={[useAutoMoveSensor.bind(null, state)]}
>
<div className={styles.wrapper}>
<div className={styles.top}>
<Hearts lives={state.lives} />
{state.lives > 0 ? (
<>
<NextItemList next={state.next} />
</>
) : (
<GameOver
highscore={highscore}
resetGame={resetGame}
score={score}
/>
)}
</div>
<div id="bottom" className={styles.bottom}>
<PlayedItemList
badlyPlacedIndex={
state.badlyPlaced === null ? null : state.badlyPlaced.index
}
isDragging={isDragging}
items={state.played}
/>
</div>
</div>
</DragDropContext>
);
}
Example #14
Source File: modal-entry.tsx From keycaplendar with MIT License | 4 votes |
ModalEntry = ({
keyset: propsKeyset,
loading,
onClose,
onSubmit,
open,
user,
}: ModalEntryProps) => {
const device = useAppSelector(selectDevice);
const allDesigners = useAppSelector(selectAllDesigners);
const allProfiles = useAppSelector(selectAllProfiles);
const allVendors = useAppSelector(selectAllVendors);
const allVendorRegions = useAppSelector(selectAllVendorRegions);
const [keyset, updateKeyset] = useImmer<KeysetState>(
partialSet({ alias: nanoid(10) })
);
const keyedKeysetUpdate =
<K extends keyof KeysetState>(key: K, payload: KeysetState[K]) =>
(draft: KeysetState) => {
draft[key] = payload;
};
const [salesImageLoaded, setSalesImageLoaded] = useState(false);
const [focused, setFocused] = useState("");
useEffect(() => {
if (!user.isEditor && user.isDesigner) {
updateKeyset(keyedKeysetUpdate("designer", [user.nickname]));
}
if (!open) {
updateKeyset(partialSet({ alias: nanoid(10) }));
setSalesImageLoaded(false);
setFocused("");
}
}, [open]);
useEffect(() => {
if (propsKeyset) {
updateKeyset(
produce(propsKeyset, (draft) => {
draft.alias ??= nanoid(10);
draft.gbMonth ??= false;
draft.notes ??= "";
draft.sales ??= {
img: "",
thirdParty: false,
};
draft.sales.img ??= "";
draft.sales.thirdParty ??= false;
draft.vendors ??= [];
draft.vendors.forEach((vendor) => {
vendor.id ??= nanoid();
});
if (draft.gbMonth && draft.gbLaunch.length === 10) {
draft.gbLaunch = draft.gbLaunch.slice(0, 7);
}
})
);
}
}, [propsKeyset, open]);
const setImage = (image: Blob | File) =>
updateKeyset(keyedKeysetUpdate("image", image));
const handleFocus = (e: FocusEvent<HTMLInputElement>) =>
setFocused(e.target.name);
const handleBlur = () => setFocused("");
const toggleDate = () =>
updateKeyset((keyset) => {
keyset.gbMonth = !keyset.gbMonth;
});
const selectValue = (prop: string, value: string) => {
if (hasKey(keyset, prop)) {
updateKeyset(keyedKeysetUpdate(prop, value));
}
setFocused("");
};
const selectValueAppend = (prop: string, value: string) =>
updateKeyset((draft) => {
if (hasKey(draft, prop)) {
const { [prop]: original } = draft;
if (original) {
if (is<string[]>(original)) {
original[original.length - 1] = value;
setFocused("");
} else if (is<string>(original)) {
const array = original.split(", ");
array[array.length - 1] = value;
draft[prop as KeysMatching<KeysetState, string>] = array.join(", ");
setFocused("");
}
}
}
});
const selectVendor = (prop: string, value: string) => {
const property = prop.replace(/\d/g, "");
const index = parseInt(prop.replace(/\D/g, ""));
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (hasKey(vendor, property)) {
vendor[property] = value;
setFocused("");
}
});
};
const selectVendorAppend = (prop: string, value: string) => {
const property = prop.replace(/\d/g, "");
const index = parseInt(prop.replace(/\D/g, ""));
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (hasKey(vendor, property)) {
const { [property]: original } = vendor;
if (typeof original !== "undefined") {
const array = original.split(", ");
array[array.length - 1] = value;
vendor[property] = array.join(", ");
setFocused("");
}
}
});
};
const handleChange = ({
target: { checked, name, value },
}: ChangeEvent<HTMLInputElement>) => {
if (name === "designer") {
updateKeyset(keyedKeysetUpdate(name, value.split(", ")));
} else if (name === "shipped") {
updateKeyset(keyedKeysetUpdate(name, checked));
} else if (hasKey(keyset, name)) {
updateKeyset(keyedKeysetUpdate(name, value));
}
};
const handleSalesImage = ({
target: { checked, name, value },
}: ChangeEvent<HTMLInputElement>) => {
if (hasKey(keyset.sales, name)) {
if (name === "thirdParty") {
updateKeyset((keyset) => {
keyset.sales[name] = checked;
});
} else {
updateKeyset((keyset) => {
keyset.sales[name] = value;
});
}
}
};
const handleNamedChange =
<Key extends keyof KeysetState>(name: Key) =>
(value: KeysetState[Key]) =>
updateKeyset(keyedKeysetUpdate(name, value));
const handleChangeVendor = ({
target: { name, value },
}: ChangeEvent<HTMLInputElement>) => {
const property = name.replace(/\d/g, "");
const index = parseInt(name.replace(/\D/g, ""));
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (hasKey(vendor, property)) {
vendor[property] = value;
}
});
};
const handleNamedChangeVendor =
(name: keyof VendorType, index: number) => (value: string) =>
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (hasKey(vendor, name)) {
vendor[name] = value;
}
});
const handleChangeVendorEndDate = (e: ChangeEvent<HTMLInputElement>) => {
const index = parseInt(e.target.name.replace(/\D/g, ""));
updateKeyset((draft) => {
const {
vendors: { [index]: vendor },
} = draft;
if (e.target.checked) {
vendor.endDate = "";
} else {
delete vendor.endDate;
}
});
};
const addVendor = () => {
const emptyVendor = {
id: nanoid(),
name: "",
region: "",
storeLink: "",
};
updateKeyset((draft) => {
draft.vendors.push(emptyVendor);
});
};
const removeVendor = (index: number) =>
updateKeyset((draft) => {
draft.vendors.splice(index, 1);
});
const handleDragVendor = (result: DropResult) => {
if (!result.destination) return;
updateKeyset((draft) => {
arrayMove(
draft.vendors,
result.source.index,
result.destination?.index || 0
);
});
};
const result = useMemo(
() =>
gbMonthCheck(
SetSchema.extend({
id: z.string().min(propsKeyset ? 1 : 0),
image: z.union([z.string().url(), z.instanceof(Blob)]),
})
).safeParse(keyset),
[keyset]
);
const useDrawer = device !== "mobile";
const dateCard = keyset.gbMonth ? (
<Card className="date-container" outlined>
<Typography className="date-title" tag="h3" use="caption">
Month
</Typography>
<div className="date-form">
<DatePicker
allowQuarter
autoComplete="off"
icon={iconObject(<CalendarToday />)}
label="GB month"
month
name="gbLaunch"
onChange={handleNamedChange("gbLaunch")}
outlined
showNowButton
value={keyset.gbLaunch}
/>
</div>
<CardActions>
<CardActionButtons>
<CardActionButton label="Date" onClick={toggleDate} type="button" />
</CardActionButtons>
</CardActions>
</Card>
) : (
<Card className="date-container" outlined>
<Typography className="date-title" tag="h3" use="caption">
Date
</Typography>
<div className="date-form">
<DatePicker
allowQuarter
autoComplete="off"
icon={iconObject(<CalendarToday />)}
label="GB launch"
name="gbLaunch"
onChange={handleNamedChange("gbLaunch")}
outlined
showNowButton
value={keyset.gbLaunch}
/>
<DatePicker
autoComplete="off"
fallbackValue={keyset.gbLaunch}
icon={iconObject(<CalendarToday />)}
label="GB end"
name="gbEnd"
onChange={handleNamedChange("gbEnd")}
outlined
showNowButton
value={keyset.gbEnd}
/>
</div>
<CardActions>
<CardActionButtons>
<CardActionButton label="Month" onClick={toggleDate} type="button" />
</CardActionButtons>
</CardActions>
</Card>
);
return (
<BoolWrapper
condition={useDrawer}
falseWrapper={(children) => (
<FullScreenDialog className="entry-modal" {...{ onClose, open }}>
{children}
</FullScreenDialog>
)}
trueWrapper={(children) => (
<Drawer
className="drawer-right entry-modal"
modal
{...{ onClose, open }}
>
{children}
</Drawer>
)}
>
<BoolWrapper
condition={useDrawer}
falseWrapper={(children) => (
<FullScreenDialogAppBar>
<TopAppBarRow>{children}</TopAppBarRow>
<LinearProgress
closed={!loading}
progress={typeof loading === "number" ? loading : undefined}
/>
</FullScreenDialogAppBar>
)}
trueWrapper={(children) => (
<DrawerHeader>
{children}
<LinearProgress
closed={!loading}
progress={typeof loading === "number" ? loading : undefined}
/>
</DrawerHeader>
)}
>
<BoolWrapper
condition={useDrawer}
falseWrapper={(children) => (
<TopAppBarSection alignStart>
<TopAppBarNavigationIcon icon="close" onClick={onClose} />
<TopAppBarTitle>{children}</TopAppBarTitle>
</TopAppBarSection>
)}
trueWrapper={(children) => <DrawerTitle>{children}</DrawerTitle>}
>
{propsKeyset ? "Edit" : "Create"} Entry
</BoolWrapper>
<ConditionalWrapper
condition={!useDrawer}
wrapper={(children) => (
<TopAppBarSection alignEnd>{children}</TopAppBarSection>
)}
>
<Button
disabled={!result?.success || !!loading}
label="Save"
onClick={() => onSubmit(keyset)}
outlined={useDrawer}
type="button"
/>
</ConditionalWrapper>
</BoolWrapper>
<BoolWrapper
condition={useDrawer}
falseWrapper={(children) => (
<FullScreenDialogContent>{children}</FullScreenDialogContent>
)}
trueWrapper={(children) => <DrawerContent>{children}</DrawerContent>}
>
<div className="banner">
<div className="banner-text">Make sure to read the entry guide.</div>
<div className="banner-button">
<a
href="/guides?guideId=JLB4xxfx52NJmmnbvbzO"
rel="noopener noreferrer"
target="_blank"
>
<Button label="guide" />
</a>
</div>
</div>
<form className="form">
<div className="form-double">
<div className="select-container">
<MenuSurfaceAnchor>
<TextField
autoComplete="off"
label="Profile"
name="profile"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
required
value={keyset.profile}
/>
<Autocomplete
array={allProfiles}
minChars={1}
open={focused === "profile"}
prop="profile"
query={keyset.profile}
select={selectValue}
/>
</MenuSurfaceAnchor>
</div>
<div className="field-container">
<TextField
autoComplete="off"
className="field"
label="Colorway"
name="colorway"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
required
value={keyset.colorway}
/>
</div>
</div>
<MenuSurfaceAnchor>
<TextField
autoComplete="off"
disabled={user.isEditor === false && user.isDesigner}
helpText={{
children: (
<>
Separate multiple designers with{" "}
<code className="multiline">, </code>.
</>
),
persistent: true,
}}
label="Designer"
name="designer"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
required
value={keyset.designer.join(", ")}
/>
<Autocomplete
array={allDesigners}
listSplit
minChars={2}
open={focused === "designer"}
prop="designer"
query={keyset.designer.join(", ")}
select={selectValueAppend}
/>
</MenuSurfaceAnchor>
<DatePicker
autoComplete="off"
icon={iconObject(<CalendarToday />)}
label="IC date"
name="icDate"
onChange={handleNamedChange("icDate")}
outlined
pickerProps={{ disableFuture: true }}
required
showNowButton
value={keyset.icDate}
/>
<TextField
autoComplete="off"
helpText={{
children: "Must be valid link",
persistent: false,
validationMsg: true,
}}
icon="link"
label="Details"
name="details"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
pattern={validLink}
required
value={keyset.details}
/>
<TextField
autoComplete="off"
label="Notes"
name="notes"
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
outlined
rows={2}
textarea
value={keyset.notes}
/>
<ImageUpload
desktop={device === "desktop"}
image={keyset.image}
setImage={setImage}
/>
{dateCard}
<Checkbox
checked={keyset.shipped}
id="create-shipped"
label="Shipped"
name="shipped"
onChange={handleChange}
/>
<Typography className="subheader" tag="h3" use="caption">
Vendors
</Typography>
<DragDropContext onDragEnd={handleDragVendor}>
<Droppable droppableId="vendors-create">
{(provided) => (
<div
ref={provided.innerRef}
className="vendors-container"
{...provided.droppableProps}
>
{keyset.vendors.map((vendor, index) => {
const endDateField =
typeof vendor.endDate === "string" ? (
<DatePicker
autoComplete="off"
icon={iconObject(<CalendarToday />)}
label="End date"
name={`endDate${index}`}
onChange={handleNamedChangeVendor("endDate", index)}
outlined
required
showNowButton
value={vendor.endDate}
/>
) : null;
return (
<Draggable
key={vendor.id}
draggableId={vendor.id ?? index.toString()}
index={index}
>
{(provided, snapshot) => (
<Card
ref={provided.innerRef}
className={classNames("vendor-container", {
dragged: snapshot.isDragging,
})}
outlined
{...provided.draggableProps}
style={getVendorStyle(provided)}
>
<div className="title-container">
<Typography
className="vendor-title"
use="caption"
>
{`Vendor ${index + 1}`}
</Typography>
{withTooltip(
<IconButton
icon={iconObject(<Delete />)}
onClick={() => {
removeVendor(index);
}}
type="button"
/>,
"Delete"
)}
{withTooltip(
<Icon
className="drag-handle"
icon="drag_handle"
{...provided.dragHandleProps}
/>,
"Drag"
)}
</div>
<div className="vendor-form">
<MenuSurfaceAnchor>
<TextField
autoComplete="off"
icon={iconObject(<Store />)}
label="Name"
name={`name${index}`}
onBlur={handleBlur}
onChange={handleChangeVendor}
onFocus={handleFocus}
outlined
required
value={vendor.name}
/>
<Autocomplete
array={allVendors}
minChars={1}
open={focused === `name${index}`}
prop={`name${index}`}
query={vendor.name}
select={selectVendor}
/>
</MenuSurfaceAnchor>
<MenuSurfaceAnchor>
<TextField
autoComplete="off"
icon={iconObject(<Public />)}
label="Region"
name={`region${index}`}
onBlur={handleBlur}
onChange={handleChangeVendor}
onFocus={handleFocus}
outlined
required
value={vendor.region}
/>
<Autocomplete
array={allVendorRegions}
listSplit
minChars={1}
open={focused === `region${index}`}
prop={`region${index}`}
query={vendor.region}
select={selectVendorAppend}
/>
</MenuSurfaceAnchor>
<TextField
autoComplete="off"
helpText={{
children: "Must be valid link",
persistent: false,
validationMsg: true,
}}
icon="link"
label="Store link"
name={`storeLink${index}`}
onBlur={handleBlur}
onChange={handleChangeVendor}
onFocus={handleFocus}
outlined
pattern={validLink}
value={vendor.storeLink}
/>
<Checkbox
checked={
!!vendor.endDate || vendor.endDate === ""
}
className="end-date-field"
id={`editEndDate${index}`}
label="Different end date"
name={`endDate${index}`}
onChange={handleChangeVendorEndDate}
/>
{endDateField}
</div>
</Card>
)}
</Draggable>
);
})}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<div className="add-button">
<Button
label="Add vendor"
onClick={addVendor}
outlined
type="button"
/>
</div>
<Card className="sales-container" outlined>
<Typography className="sales-title" tag="h3" use="caption">
Sales
</Typography>
<div
className={classNames("sales-image", {
loaded: salesImageLoaded,
})}
>
<div className="sales-image-icon">
<Icon icon={iconObject(<AddPhotoAlternate />)} />
</div>
<img
alt=""
onError={() => {
setSalesImageLoaded(false);
}}
onLoad={() => {
setSalesImageLoaded(true);
}}
src={keyset.sales.img}
/>
</div>
<div className="sales-field">
<TextField
autoComplete="off"
helpText={{
children: "Must be direct link to image",
persistent: true,
validationMsg: true,
}}
icon="link"
label="URL"
name="img"
onBlur={handleBlur}
onChange={handleSalesImage}
onFocus={handleFocus}
outlined
pattern={validLink}
value={keyset.sales.img}
/>
<Checkbox
checked={keyset.sales.thirdParty}
className="sales-checkbox"
label="Third party graph"
name="thirdParty"
onChange={handleSalesImage}
/>
</div>
</Card>
</form>
</BoolWrapper>
</BoolWrapper>
);
}
Example #15
Source File: MediaMenu.tsx From sync-party with GNU General Public License v3.0 | 4 votes |
export default function MediaMenu({
socket,
setPlayerFocused,
emitPlayWish,
freezeUiVisible,
isPlaying,
playerState
}: Props): JSX.Element {
const party: ClientParty | null = useSelector(
(state: RootAppState) => state.globalState.party
);
const playingItem: MediaItem | null = useSelector(
(state: RootAppState) => state.globalState.playingItem
);
const uiVisible = useSelector(
(state: RootAppState) => state.globalState.uiVisible
);
const uiFocused = useSelector(
(state: RootAppState) => state.globalState.uiFocused
);
const [hoverTimestamp, setHoverTimestamp] = useState(0);
const hoverTimestampRef = useRef(hoverTimestamp);
hoverTimestampRef.current = hoverTimestamp;
const [addMediaIsActive, setAddMediaIsActive] = useState(false);
const [partyItemsSet, setPartyItemsSet] = useState<Set<string>>(new Set());
const partyItemListRef = useRef<HTMLDivElement | null>(null);
const dispatch = useDispatch();
const { t } = useTranslation();
// Collect items in active party in a set for faster checks
useEffect(() => {
if (party) {
const itemsSet = new Set<string>();
party.items.forEach((item) => {
itemsSet.add(item.id);
});
setPartyItemsSet(itemsSet);
}
}, [party]);
const handleRemoveItemFromParty = async (
item: MediaItem
): Promise<void> => {
if (socket && party) {
if (
party.items.length === 1 &&
playerState.playingItem &&
playerState.playingItem.id === item.id
) {
dispatch(
setGlobalState({
errorMessage: t(`errors.removeLastItemError`)
})
);
return Promise.reject();
}
try {
const response = await Axios.delete('/api/partyItems', {
data: { itemId: item.id, partyId: party.id },
...axiosConfig()
});
if (response.data.success) {
const currentIndex = playerState.playlistIndex;
if (
playerState.playingItem &&
playerState.playingItem.id === item.id
) {
emitPlayWish(
party.items[
currentIndex < party.items.length - 1
? currentIndex + 1
: currentIndex - 1
],
playerState.isPlaying,
null,
false,
0
);
}
socket.emit('partyUpdate', {
partyId: party.id
});
setPlayerFocused(true);
} else {
dispatch(
setGlobalState({
errorMessage: t(
`apiResponseMessages.${response.data.msg}`
)
})
);
}
} catch (error) {
dispatch(
setGlobalState({
errorMessage: t(`errors.removeItemError`)
})
);
}
}
};
const handleItemEditSave = async (item: MediaItem): Promise<void> => {
if (socket && party) {
try {
const response = await Axios.put(
'/api/mediaItem/' + item.id,
item,
axiosConfig()
);
if (response.data.success) {
await getUpdatedUserItems(dispatch, t);
socket.emit('partyUpdate', { partyId: party.id });
setPlayerFocused(true);
} else {
dispatch(
setGlobalState({
errorMessage: t(
`apiResponseMessages.${response.data.msg}`
)
})
);
}
} catch (error) {
dispatch(
setGlobalState({
errorMessage: t(`errors.itemSaveError`)
})
);
}
}
};
const handleItemClick = (chosenItem: MediaItem): void => {
if (party) {
const newSource = party.items.filter((mediaItem: MediaItem) => {
return mediaItem.id === chosenItem.id;
})[0];
emitPlayWish(
newSource,
true,
playerState.playingItem ? playerState.playingItem.id : null,
true,
0
);
}
};
const onDragEnd = async (result: DropResult): Promise<void> => {
if (socket && party) {
// Dropped outside the list
if (!result.destination) {
return;
}
const orderedItems = reorderItems(
party.items,
result.source.index,
result.destination.index
);
try {
const response = await Axios.put(
'/api/partyItems',
{ orderedItems: orderedItems, partyId: party.id },
axiosConfig()
);
if (response.data.success) {
socket.emit('partyUpdate', { partyId: party.id });
} else {
dispatch(
setGlobalState({
errorMessage: t(
`apiResponseMessages.${response.data.msg}`
)
})
);
}
} catch (error) {
dispatch(
setGlobalState({
errorMessage: t(`errors.reorderError`)
})
);
}
}
};
return (
<div
className={
'mediaMenu fixed top-0 right-0 flex flex-col mt-16 p-2 border border-gray-500 rounded m-2 shadow-md backgroundShade z-50' +
(uiVisible || !playingItem || !playingItem.url ? '' : ' hidden')
}
onMouseOver={(): void => {
const now = Date.now();
if (hoverTimestampRef.current + 10000 < now) {
freezeUiVisible(true);
setHoverTimestamp(now);
}
}}
onMouseLeave={(): void => {
if (!uiFocused.chat && !addMediaIsActive) {
freezeUiVisible(false);
setHoverTimestamp(0);
}
}}
>
{party && party.items.length ? (
<div className="partyItemList" ref={partyItemListRef}>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided: DroppableProvided): JSX.Element => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
{party.items.map(
(
mediaItem: MediaItem,
index: number
) => {
const isCurrentlyPlayingItem =
playingItem &&
playingItem.id === mediaItem.id
? true
: false;
const alreadyPlayed =
party.metadata.played &&
party.metadata.played[
mediaItem.id
]
? true
: false;
return (
<MediaMenuDraggable
key={mediaItem.id}
mediaItemId={mediaItem.id}
index={index}
>
<ItemListed
item={mediaItem}
isPlaying={isPlaying}
isCurrentlyPlayingItem={
isCurrentlyPlayingItem
}
alreadyPlayed={
alreadyPlayed
}
nameEditingAllowed={
false
}
handleItemClick={(): void =>
handleItemClick(
mediaItem
)
}
onRemoveButtonClick={(
item: MediaItem
): void => {
handleRemoveItemFromParty(
item
);
}}
handleItemSave={(
item: MediaItem
): void => {
handleItemEditSave(
item
);
}}
setPlayerFocused={
setPlayerFocused
}
partyItemListRef={
partyItemListRef
}
></ItemListed>
</MediaMenuDraggable>
);
}
)}
</div>
)}
</Droppable>
</DragDropContext>
</div>
) : (
<div className="mb-2">There's nothing.</div>
)}
<AddMedia
isActive={addMediaIsActive}
partyItemsSet={partyItemsSet}
setAddMediaIsActive={setAddMediaIsActive}
handleItemEditSave={handleItemEditSave}
setPlayerFocused={(focused: boolean): void =>
setPlayerFocused(focused)
}
socket={socket}
></AddMedia>
</div>
);
}
Example #16
Source File: index.tsx From taskcafe with MIT License | 4 votes |
SimpleLists: React.FC<SimpleProps> = ({
taskGroups,
onTaskDrop,
onChangeTaskGroupName,
onCardLabelClick,
onTaskGroupDrop,
onTaskClick,
onCreateTask,
onQuickEditorOpen,
onCreateTaskGroup,
cardLabelVariant,
onExtraMenuOpen,
onCardMemberClick,
taskStatusFilter = initTaskStatusFilter,
isPublic = false,
taskMetaFilters = initTaskMetaFilters,
taskSorting = initTaskSorting,
}) => {
const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => {
if (typeof destination === 'undefined') return;
if (!isPositionChanged(source, destination)) return;
const isList = type === 'column';
const isSameList = destination.droppableId === source.droppableId;
let droppedDraggable: DraggableElement | null = null;
let beforeDropDraggables: Array<DraggableElement> | null = null;
if (isList) {
const droppedGroup = taskGroups.find((taskGroup) => taskGroup.id === draggableId);
if (droppedGroup) {
droppedDraggable = {
id: draggableId,
position: droppedGroup.position,
};
beforeDropDraggables = getSortedDraggables(
taskGroups.map((taskGroup) => {
return { id: taskGroup.id, position: taskGroup.position };
}),
);
if (droppedDraggable === null || beforeDropDraggables === null) {
throw new Error('before drop draggables is null');
}
const afterDropDraggables = getAfterDropDraggableList(
beforeDropDraggables,
droppedDraggable,
isList,
isSameList,
destination,
);
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
onTaskGroupDrop({
...droppedGroup,
position: newPosition,
});
} else {
throw new Error('task group can not be found');
}
} else {
const curTaskGroup = taskGroups.findIndex(
(taskGroup) => taskGroup.tasks.findIndex((task) => task.id === draggableId) !== -1,
);
let targetTaskGroup = curTaskGroup;
if (!isSameList) {
targetTaskGroup = taskGroups.findIndex((taskGroup) => taskGroup.id === destination.droppableId);
}
const droppedTask = taskGroups[curTaskGroup].tasks.find((task) => task.id === draggableId);
if (droppedTask) {
droppedDraggable = {
id: draggableId,
position: droppedTask.position,
};
beforeDropDraggables = getSortedDraggables(
taskGroups[targetTaskGroup].tasks.map((task) => {
return { id: task.id, position: task.position };
}),
);
if (droppedDraggable === null || beforeDropDraggables === null) {
throw new Error('before drop draggables is null');
}
const afterDropDraggables = getAfterDropDraggableList(
beforeDropDraggables,
droppedDraggable,
isList,
isSameList,
destination,
);
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
const newTask = {
...droppedTask,
position: newPosition,
taskGroup: {
id: destination.droppableId,
},
};
log.debug(
`action=move taskId=${droppedTask.id} source=${source.droppableId} dest=${destination.droppableId} oldPos=${droppedTask.position} newPos=${newPosition}`,
);
onTaskDrop(newTask, droppedTask.taskGroup.id);
}
}
};
const [currentComposer, setCurrentComposer] = useState('');
const [toggleLabels, setToggleLabels] = useState(false);
const [toggleDirection, setToggleDirection] = useState<'shrink' | 'expand'>(
cardLabelVariant === 'large' ? 'shrink' : 'expand',
);
return (
<BoardContainer>
<BoardWrapper>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable direction="horizontal" type="column" droppableId="root">
{(provided) => (
<Container {...provided.droppableProps} ref={provided.innerRef}>
{taskGroups
.slice()
.sort((a: any, b: any) => a.position - b.position)
.map((taskGroup: TaskGroup, index: number) => {
return (
<Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
{(columnDragProvided) => (
<Droppable type="tasks" droppableId={taskGroup.id}>
{(columnDropProvided, snapshot) => (
<List
name={taskGroup.name}
onOpenComposer={(id) => setCurrentComposer(id)}
isComposerOpen={currentComposer === taskGroup.id}
onSaveName={(name) => onChangeTaskGroupName(taskGroup.id, name)}
isPublic={isPublic}
ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps}
headerProps={columnDragProvided.dragHandleProps}
onExtraMenuOpen={onExtraMenuOpen}
id={taskGroup.id}
key={taskGroup.id}
index={index}
>
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
{taskGroup.tasks
.slice()
.filter((t) => shouldStatusFilter(t, taskStatusFilter))
.filter((t) => shouldMetaFilter(t, taskMetaFilters))
.sort((a: any, b: any) => a.position - b.position)
.sort((a: any, b: any) => sortTasks(a, b, taskSorting))
.map((task: Task, taskIndex: any) => {
return (
<Draggable
key={task.id}
draggableId={task.id}
index={taskIndex}
isDragDisabled={taskSorting.type !== TaskSortingType.NONE}
>
{(taskProvided) => {
return (
<Card
toggleDirection={toggleDirection}
toggleLabels={toggleLabels}
isPublic={isPublic}
labelVariant={cardLabelVariant}
watched={task.watched}
wrapperProps={{
...taskProvided.draggableProps,
...taskProvided.dragHandleProps,
}}
setToggleLabels={setToggleLabels}
onCardLabelClick={() => {
setToggleLabels(true);
setToggleDirection(
cardLabelVariant === 'large' ? 'shrink' : 'expand',
);
if (onCardLabelClick) {
onCardLabelClick();
}
}}
ref={taskProvided.innerRef}
taskID={task.id}
complete={task.complete ?? false}
taskGroupID={taskGroup.id}
description=""
labels={task.labels.map((label) => label.projectLabel)}
dueDate={
task.dueDate.at
? {
isPastDue: false,
formattedDate: dayjs(task.dueDate.at).format('MMM D, YYYY'),
}
: undefined
}
title={task.name}
members={task.assigned}
onClick={() => {
onTaskClick(task);
}}
checklists={task.badges && task.badges.checklist}
comments={task.badges && task.badges.comments}
onCardMemberClick={onCardMemberClick}
onContextMenu={onQuickEditorOpen}
/>
);
}}
</Draggable>
);
})}
{columnDropProvided.placeholder}
{currentComposer === taskGroup.id && (
<CardComposer
onClose={() => {
setCurrentComposer('');
}}
onCreateCard={(name) => {
onCreateTask(taskGroup.id, name);
}}
isOpen
/>
)}
</ListCards>
</List>
)}
</Droppable>
)}
</Draggable>
);
})}
{provided.placeholder}
</Container>
)}
</Droppable>
</DragDropContext>
{!isPublic && (
<AddList
onSave={(listName) => {
onCreateTaskGroup(listName);
}}
/>
)}
</BoardWrapper>
</BoardContainer>
);
}
Example #17
Source File: index.tsx From nextjs-hasura-fullstack with MIT License | 4 votes |
BoardPage: React.FC = () => {
const router = useRouter()
const { setLastPosition } = useLastPositionNumber()
const { data, loading } = useBoardQuery({
variables: {
id: Number(router.query.boardId),
},
skip: !router.query.boardId,
})
const [updateList] = useUpdateListMutation()
const [updateCard] = useUpdateCardMutation()
const [moveCard] = useMoveCardMutation()
const renderLists = React.useMemo(() => {
return data?.boards_by_pk?.lists || []
}, [data])
React.useEffect(() => {
setLastPosition(renderLists[renderLists.length - 1]?.position || 1)
}, [renderLists])
const onDragEnd = async (result: DropResult, provided: ResponderProvided) => {
const { source, destination, type } = result
// dropped outside the list
if (!destination) {
return
}
if (
source.droppableId === destination.droppableId &&
source.index === destination.index
) {
return
}
// DragDrop a "List"
if (type === 'LIST') {
const updatedPosition = getUpdatePositionReorder(
renderLists,
source.index,
destination.index,
)
const sourceList = renderLists[source.index]
updateList({
variables: {
id: sourceList.id,
name: sourceList.name,
position: updatedPosition,
},
update: (cache, { data }) => {
if (data?.update_lists_by_pk) {
const cacheId = cache.identify({
__typename: 'boards',
id: sourceList.board_id,
})
if (cacheId) {
cache.modify({
id: cacheId,
fields: {
lists(existingListRefs = []) {
return reorder(
existingListRefs,
source.index,
destination.index,
)
},
},
})
}
}
},
optimisticResponse: (variables) => {
return {
__typename: 'mutation_root',
update_lists_by_pk: {
...sourceList,
position: updatedPosition,
},
}
},
})
}
if (type === 'CARD') {
/* same list: reorder card */
if (source.droppableId === destination.droppableId) {
const listCards = renderLists.find(
(item) => item.id === Number(source.droppableId),
)?.cards
if (!listCards) {
return
}
const updatedPosition = getUpdatePositionReorder(
listCards,
source.index,
destination.index,
)
const sourceCard = listCards[source.index]
await updateCard({
variables: {
id: sourceCard.id,
title: sourceCard.title,
position: updatedPosition,
},
update: (cache, { data }) => {
if (data?.update_cards_by_pk) {
const cacheId = cache.identify({
__typename: 'lists',
id: sourceCard.list_id,
})
if (cacheId) {
cache.modify({
id: cacheId,
fields: {
cards(existingCardRefs = []) {
return reorder(
existingCardRefs,
source.index,
destination.index,
)
},
},
})
}
}
},
optimisticResponse: (variables) => {
return {
__typename: 'mutation_root',
update_cards_by_pk: {
...sourceCard,
position: updatedPosition,
},
}
},
})
} else {
/**
* Diferent list: move card
*/
const sourceListCards = renderLists.find(
(item) => item.id === Number(source.droppableId),
)?.cards
const destinationListCards = renderLists.find(
(item) => item.id === Number(destination.droppableId),
)?.cards
if (!sourceListCards || !destinationListCards) {
return
}
const updatedPosition = getUpdatePositionMove(
sourceListCards,
destinationListCards,
source.index,
destination.index,
)
const sourceCard = sourceListCards[source.index]
await moveCard({
variables: {
id: sourceCard.id,
list_id: Number(destination.droppableId),
title: sourceCard.title,
position: updatedPosition,
},
update: (cache, { data }) => {
if (data?.update_cards_by_pk) {
const cardCacheId = cache.identify(data.update_cards_by_pk)
if (!cardCacheId) {
return
}
const cacheId = cache.identify({
__typename: 'lists',
id: source.droppableId,
})
if (cacheId) {
cache.modify({
id: cacheId,
fields: {
cards(existingRefs = []) {
const next = existingRefs.filter(
(listRef: { __ref: string }) =>
listRef.__ref !== cardCacheId,
)
return next
},
},
})
}
const cacheIdDestination = cache.identify({
__typename: 'lists',
id: destination.droppableId,
})
if (cacheIdDestination) {
cache.modify({
id: cacheIdDestination,
fields: {
cards(existingCardRefs = [], { toReference }) {
const moveRef = toReference(cardCacheId)
if (moveRef) {
const next = produce(
existingCardRefs as any[],
(draftState) => {
draftState.splice(destination.index, 0, moveRef)
},
)
return next
}
},
},
})
}
}
},
optimisticResponse: () => ({
__typename: 'mutation_root',
update_cards_by_pk: {
...sourceCard,
position: updatedPosition,
},
}),
})
}
}
}
let listRender
if (loading) {
listRender = (
<div className={`flex flex-no-wrap min-w-max-content`}>
{[...Array(3)].map((item, idx) => (
<div
key={idx}
className="flex flex-col max-w-sm p-4 mx-4 space-y-3 border border-gray-300 rounded-md shadow w-72"
>
<div className="flex space-x-4 animate-pulse">
<div className="flex-1 py-1 space-y-4">
<div className="w-3/4 h-4 bg-gray-400 rounded"></div>
{/* <div className="space-y-2">
<div className="h-4 bg-gray-400 rounded"></div>
<div className="w-5/6 h-4 bg-gray-400 rounded"></div>
</div> */}
</div>
</div>
{[...Array(4)].map((item, index) => (
<div
key={index}
className="flex flex-col max-w-sm p-4 border border-gray-300 rounded-md shadow"
>
<div className="flex space-x-4 animate-pulse">
<div className="flex-1 py-1 space-y-2">
<div className="h-4 bg-gray-400 rounded"></div>
<div className="w-5/6 h-4 bg-gray-400 rounded"></div>
</div>
</div>
</div>
))}
<div className="flex flex-col max-w-sm border-t border-gray-300 rounded-md shadow">
<div className="flex space-x-4 animate-pulse">
<div className="flex-1 py-1 space-y-2">
<div className="h-4 bg-gray-400 rounded"></div>
</div>
</div>
</div>
</div>
))}
{/* */}
</div>
)
} else {
listRender = (
<div className={`flex flex-no-wrap h-screen w-max-content`}>
<DragDropContext
onDragStart={(source) => {
console.log(`?? [LOG]: source`, source)
}}
// onDragUpdate={onDragUpdate}
onDragEnd={onDragEnd}
>
{/* List */}
<Droppable
droppableId="board"
type="LIST"
direction="horizontal"
// ignoreContainerClipping={true}
>
{(
{ innerRef, droppableProps, placeholder },
{ isDraggingOver },
) => {
return (
<div
ref={innerRef}
{...droppableProps}
className={`inline-flex ${isDraggingOver && ``}`}
style={{
overflowAnchor: 'none',
}}
>
{renderLists.map((list, index) => (
<ListBoard list={list} index={index} key={index} />
))}
{placeholder}
</div>
)
}}
</Droppable>
</DragDropContext>
<div className={``}>
<NewList lastId={renderLists[renderLists.length - 1]?.id || 0} />
</div>
</div>
)
}
return (
<div className={`h-screen space-x-2 -mt-14`}>
<Scrollbar noScrollY className={``}>
<div className={`pt-14`}>
<div className={`mt-4 mb-8`}>
<h2
className={`text-3xl font-semibold text-center text-cool-gray-100`}
>
{data?.boards_by_pk?.icon} {data?.boards_by_pk?.name}
</h2>
</div>
{listRender}
</div>
</Scrollbar>
</div>
)
}
Example #18
Source File: Board.tsx From knboard with MIT License | 4 votes |
Board = () => {
const detail = useSelector((state: RootState) => state.board.detail);
const error = useSelector((state: RootState) => state.board.detailError);
const columns = useSelector(columnSelectors.selectAll);
const tasksByColumn = useSelector((state: RootState) => state.task.byColumn);
const tasksById = useSelector((state: RootState) => state.task.byId);
const dispatch = useDispatch();
const { id } = useParams();
React.useEffect(() => {
if (id) {
dispatch(fetchBoardById({ boardId: id }));
}
}, [id]);
const onDragEnd = (result: DropResult) => {
// dropped nowhere
if (!result.destination) {
return;
}
const source: DraggableLocation = result.source;
const destination: DraggableLocation = result.destination;
// did not move anywhere - can bail early
if (
source.droppableId === destination.droppableId &&
source.index === destination.index
) {
return;
}
// reordering column
if (result.type === "COLUMN") {
const newOrdered: IColumn[] = reorder(
columns,
source.index,
destination.index
);
dispatch(updateColumns(newOrdered));
return;
}
const data = reorderTasks({
tasksByColumn,
source,
destination,
});
dispatch(updateTasksByColumn(data.tasksByColumn));
};
const detailDataExists = detail?.id.toString() === id;
if (error) {
return <PageError>{error}</PageError>;
}
if (!detailDataExists) {
return <Spinner loading={!detailDataExists} />;
}
return (
<>
<SEO title={detail?.name} />
{columns.length !== 0 ? (
<BoardContainer data-testid="board-container">
<ColumnsBlock>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable
droppableId="board"
type="COLUMN"
direction="horizontal"
>
{(provided: DroppableProvided) => (
<ColumnContainer
ref={provided.innerRef}
{...provided.droppableProps}
>
{columns.map((column: IColumn, index: number) => (
<Column
key={column.id}
id={column.id}
title={column.title}
index={index}
tasks={tasksByColumn[column.id].map(
(taskId) => tasksById[taskId]
)}
/>
))}
{provided.placeholder}
</ColumnContainer>
)}
</Droppable>
</DragDropContext>
</ColumnsBlock>
<RightMargin />
</BoardContainer>
) : (
<EmptyBoard>This board is empty.</EmptyBoard>
)}
</>
);
}
Example #19
Source File: index.tsx From slice-machine with Apache License 2.0 | 4 votes |
FieldZones: React.FunctionComponent<FieldZonesProps> = ({
Model,
store,
variation,
}) => {
const _onDeleteItem = (widgetArea: Models.WidgetsArea) => (key: string) => {
store
.variation(variation.id)
.deleteWidgetMockConfig(Model.mockConfig, widgetArea, key);
store.variation(variation.id).removeWidget(widgetArea, key);
};
const _getFieldMockConfig =
(widgetArea: Models.WidgetsArea) =>
({ apiId }: { apiId: string }) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return SliceMockConfig.getFieldMockConfig(
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument
Model.mockConfig,
variation.id,
widgetArea,
apiId
);
};
const _onSave =
(widgetArea: Models.WidgetsArea) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
({ apiId: previousKey, newKey, value, mockValue }: any) => {
if (mockValue) {
store
.variation(variation.id)
.updateWidgetMockConfig(
Model.mockConfig,
widgetArea,
previousKey,
newKey,
mockValue
);
} else {
store
.variation(variation.id)
.deleteWidgetMockConfig(Model.mockConfig, widgetArea, newKey);
}
store
.variation(variation.id)
.replaceWidget(widgetArea, previousKey, newKey, value);
};
const _onSaveNewField =
(widgetArea: Models.WidgetsArea) =>
({ id, widgetTypeName }: { id: string; widgetTypeName: string }) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const widget = Widgets[widgetTypeName];
if (!widget) {
console.log(
`Could not find widget with type name "${widgetTypeName}". Please contact us!`
);
}
const friendlyName = createFriendlyFieldNameWithId(id);
store
.variation(variation.id)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
.addWidget(widgetArea, id, widget.create(friendlyName));
};
const _onDragEnd =
(widgetArea: Models.WidgetsArea) => (result: DropResult) => {
if (ensureDnDDestination(result)) {
return;
}
store
.variation(variation.id)
.reorderWidget(
widgetArea,
result.source.index,
result.destination && result.destination.index
);
};
return (
<>
<Zone
tabId={undefined}
mockConfig={Model.mockConfig}
title="Non-Repeatable Zone"
dataTip={dataTipText}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
fields={variation.primary}
EditModal={EditModal}
widgetsArray={sliceBuilderWidgetsArray}
getFieldMockConfig={_getFieldMockConfig(WidgetsArea.Primary)}
onDeleteItem={_onDeleteItem(WidgetsArea.Primary)}
onSave={_onSave(WidgetsArea.Primary)}
onSaveNewField={_onSaveNewField(WidgetsArea.Primary)}
onDragEnd={_onDragEnd(WidgetsArea.Primary)}
poolOfFieldsToCheck={variation.primary || []}
renderHintBase={({ item }) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
`slice.primary${transformKeyAccessor(item.key)}`
}
renderFieldAccessor={(key) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
`slice.primary${transformKeyAccessor(key)}`
}
/>
<Box mt={4} />
<Zone
tabId={undefined}
isRepeatable
mockConfig={Model.mockConfig}
title="Repeatable Zone"
dataTip={dataTipText2}
widgetsArray={sliceBuilderWidgetsArray}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
fields={variation.items}
EditModal={EditModal}
getFieldMockConfig={_getFieldMockConfig(WidgetsArea.Items)}
onDeleteItem={_onDeleteItem(WidgetsArea.Items)}
onSave={_onSave(WidgetsArea.Items)}
onSaveNewField={_onSaveNewField(WidgetsArea.Items)}
onDragEnd={_onDragEnd(WidgetsArea.Items)}
poolOfFieldsToCheck={variation.items || []}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
renderHintBase={({ item }) => `item${transformKeyAccessor(item.key)}`}
renderFieldAccessor={(key) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
`slice.items[i]${transformKeyAccessor(key)}`
}
/>
</>
);
}
Example #20
Source File: CategoryTable.tsx From flame with MIT License | 4 votes |
CategoryTable = ({ openFormForUpdating }: Props): JSX.Element => {
const {
config: { config },
bookmarks: { categories },
} = useSelector((state: State) => state);
const dispatch = useDispatch();
const {
pinCategory,
deleteCategory,
createNotification,
reorderCategories,
updateCategory,
} = bindActionCreators(actionCreators, dispatch);
const [localCategories, setLocalCategories] = useState<Category[]>([]);
// Copy categories array
useEffect(() => {
setLocalCategories([...categories]);
}, [categories]);
// Drag and drop handler
const dragEndHanlder = (result: DropResult): void => {
if (config.useOrdering !== 'orderId') {
createNotification({
title: 'Error',
message: 'Custom order is disabled',
});
return;
}
if (!result.destination) {
return;
}
const tmpCategories = [...localCategories];
const [movedCategory] = tmpCategories.splice(result.source.index, 1);
tmpCategories.splice(result.destination.index, 0, movedCategory);
setLocalCategories(tmpCategories);
reorderCategories(tmpCategories);
};
// Action handlers
const deleteCategoryHandler = (id: number, name: string) => {
const proceed = window.confirm(
`Are you sure you want to delete ${name}? It will delete ALL assigned bookmarks`
);
if (proceed) {
deleteCategory(id);
}
};
const updateCategoryHandler = (id: number) => {
const category = categories.find((c) => c.id === id) as Category;
openFormForUpdating(category);
};
const pinCategoryHandler = (id: number) => {
const category = categories.find((c) => c.id === id) as Category;
pinCategory(category);
};
const changeCategoryVisibiltyHandler = (id: number) => {
const category = categories.find((c) => c.id === id) as Category;
updateCategory(id, { ...category, isPublic: !category.isPublic });
};
return (
<Fragment>
<Message isPrimary={false}>
{config.useOrdering === 'orderId' ? (
<p>You can drag and drop single rows to reorder categories</p>
) : (
<p>
Custom order is disabled. You can change it in the{' '}
<Link to="/settings/general">settings</Link>
</p>
)}
</Message>
<DragDropContext onDragEnd={dragEndHanlder}>
<Droppable droppableId="categories">
{(provided) => (
<Table
headers={['Name', 'Visibility', 'Actions']}
innerRef={provided.innerRef}
>
{localCategories.map((category, index): JSX.Element => {
return (
<Draggable
key={category.id}
draggableId={category.id.toString()}
index={index}
>
{(provided, snapshot) => {
const style = {
border: snapshot.isDragging
? '1px solid var(--color-accent)'
: 'none',
borderRadius: '4px',
...provided.draggableProps.style,
};
return (
<tr
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
style={style}
>
<td style={{ width: '300px' }}>{category.name}</td>
<td style={{ width: '300px' }}>
{category.isPublic ? 'Visible' : 'Hidden'}
</td>
{!snapshot.isDragging && (
<TableActions
entity={category}
deleteHandler={deleteCategoryHandler}
updateHandler={updateCategoryHandler}
pinHanlder={pinCategoryHandler}
changeVisibilty={changeCategoryVisibiltyHandler}
/>
)}
</tr>
);
}}
</Draggable>
);
})}
</Table>
)}
</Droppable>
</DragDropContext>
</Fragment>
);
}
Example #21
Source File: EntitiesDiagram.tsx From amplication with Apache License 2.0 | 4 votes |
export default function EntitiesDiagram() {
const [error, setError] = useState<Error | null>(null);
const { setFieldValue, values } = useFormikContext<EntitiesDiagramFormData>();
const [zoomLevel, setZoomLevel] = useState<number>(1);
const zoomIn = useCallback(() => {
setZoomLevel((zoomLevel) => {
if (zoomLevel < ZOOM_LEVEL_MAX) return zoomLevel + ZOOM_LEVEL_STEP;
return zoomLevel;
});
}, []);
const zoomOut = useCallback(() => {
setZoomLevel((zoomLevel) => {
if (zoomLevel > ZOOM_LEVEL_MIN) return zoomLevel - ZOOM_LEVEL_STEP;
return zoomLevel;
});
}, []);
const zoomReset = useCallback(() => {
setZoomLevel(1);
}, []);
const clearError = useCallback(() => {
setError(null);
}, [setError]);
const [editedField, setEditedField] = React.useState<FieldIdentifier | null>(
null
);
const [editedEntity, setEditedEntity] = React.useState<number | null>(0);
const resetEditableElements = useCallback(() => {
setEditedEntity(null);
setEditedField(null);
setError(null);
}, [setEditedEntity, setEditedField]);
const handleEditEntity = useCallback(
(entityIndex: number | null) => {
setEditedEntity(entityIndex);
setEditedField(null);
},
[setEditedEntity, setEditedField]
);
const handleEditField = useCallback(
(fieldIdentifier: FieldIdentifier | null) => {
setEditedField(fieldIdentifier);
setEditedEntity(null);
},
[setEditedEntity, setEditedField]
);
const onFieldDragStart = useCallback(() => {
resetEditableElements();
}, [resetEditableElements]);
const onFieldDragEnd = useCallback(
(result: DropResult) => {
// dropped outside the list
if (!result.destination) {
return;
}
const [sourceEntityIndex, sourceFieldIndex] = result.draggableId.split(
"_"
);
const [, destinationEntityIndex] = result.destination.droppableId.split(
"_"
);
const destinationFieldIndex = result.destination.index;
const sourceFields = values.entities[Number(sourceEntityIndex)].fields;
const [sourceField] = sourceFields.splice(Number(sourceFieldIndex), 1);
setFieldValue(`entities.${sourceEntityIndex}.fields`, [...sourceFields]);
const destinationFields =
values.entities[Number(destinationEntityIndex)].fields;
destinationFields.splice(destinationFieldIndex, 0, sourceField);
setFieldValue(`entities.${destinationEntityIndex}.fields`, [
...destinationFields,
]);
resetEditableElements();
},
[values, setFieldValue, resetEditableElements]
);
const [entitiesPosition, setEntitiesPosition] = useState<
EntitiesPositionData
>({
0: {
top: 0,
left: 0,
width: DEFAULT_ENTITY_WIDTH,
height: DEFAULT_ENTITY_HEIGHT,
},
});
//used to force redraw the arrows (the internal lists of fields are not updated since it used )
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const handleEntityDrag = useCallback(
(entityIndex: number, draggableData: DraggableData) => {
resetEditableElements();
setEntitiesPosition((current) => {
const position = current[entityIndex] || { top: 0, left: 0 };
const deltaX = draggableData.deltaX / zoomLevel;
const deltaY = draggableData.deltaY / zoomLevel;
const newPosition = {
top: position.top + deltaY > 0 ? position.top + deltaY : 0,
left: position.left + deltaX > 0 ? position.left + deltaX : 0,
width: draggableData.node.offsetWidth,
height: draggableData.node.offsetHeight,
};
current[entityIndex] = newPosition;
return current;
});
forceUpdate();
},
[forceUpdate, resetEditableElements, zoomLevel]
);
const handleDeleteEntity = useCallback(
(entityIndex: number) => {
setError(null);
const clonedEntities = cloneDeep(values.entities);
const relations =
clonedEntities[entityIndex].relationsToEntityIndex || [];
const currentEntity = clonedEntities[entityIndex];
const clonedPositions = cloneDeep(entitiesPosition);
if (isEmpty(currentEntity.fields) && isEmpty(relations)) {
const newPositions = Object.fromEntries(
Object.keys(clonedPositions).flatMap((key) => {
const KeyNumber = parseInt(key);
if (KeyNumber < entityIndex) {
return [[KeyNumber, clonedPositions[KeyNumber]]];
} else if (KeyNumber === entityIndex) {
return [];
} else {
return [[KeyNumber - 1, clonedPositions[KeyNumber]]];
}
})
);
clonedEntities.splice(entityIndex, 1);
clonedEntities.map((entity) => {
entity.relationsToEntityIndex = entity.relationsToEntityIndex?.flatMap(
(index) => {
return index < entityIndex
? [index]
: index === entityIndex
? []
: [index - 1];
}
);
return entity;
});
setEntitiesPosition(newPositions);
setFieldValue(`entities`, [...clonedEntities]);
} else {
setError(
new Error(
"To delete an entity, first remove all its fields and relations"
)
);
}
},
[values, entitiesPosition, setFieldValue, setEntitiesPosition]
);
const handleAddEntity = useCallback(
(entityIndex: number) => {
const entities = cloneDeep(values.entities);
const currentLength = entities.length;
const relations = entities[entityIndex].relationsToEntityIndex || [];
const currentEntity = entities[entityIndex];
currentEntity.relationsToEntityIndex = [...relations, currentLength];
const clonedPositions = cloneDeep(entitiesPosition);
const sourcePosition = clonedPositions[entityIndex];
const newLeft =
sourcePosition.left +
sourcePosition.width +
DEFAULT_ENTITY_HORIZONTAL_GAP;
const newRight = newLeft + DEFAULT_ENTITY_WIDTH;
const minMaxValues: number[] = [];
Object.entries(clonedPositions).forEach(([key, value]) => {
if (!(value.left > newRight || value.left + value.width < newLeft)) {
minMaxValues.push(value.top);
minMaxValues.push(value.top + value.height);
}
});
const min = Math.min(...minMaxValues);
const max = Math.max(...minMaxValues);
let newTop = 0;
if (min >= DEFAULT_ENTITY_HEIGHT + DEFAULT_ENTITY_VERTICAL_GAP) {
newTop = 0;
} else {
newTop = max + DEFAULT_ENTITY_VERTICAL_GAP;
}
const newPosition = {
top: newTop,
left: newLeft,
width: DEFAULT_ENTITY_WIDTH,
height: DEFAULT_ENTITY_HEIGHT,
};
clonedPositions[currentLength] = newPosition;
setEntitiesPosition(clonedPositions);
setFieldValue(`entities`, [
...entities,
{
name: "",
fields: [],
},
]);
resetEditableElements();
setEditedEntity(currentLength);
},
[setFieldValue, values.entities, resetEditableElements, entitiesPosition]
);
return (
<div className={CLASS_NAME}>
<div className={`${CLASS_NAME}__toolbar`}>
<Button
className={`${CLASS_NAME}__toolbar__button`}
buttonStyle={EnumButtonStyle.Clear}
type="button"
onClick={zoomIn}
icon="zoom_in"
/>
<Button
className={`${CLASS_NAME}__toolbar__button`}
buttonStyle={EnumButtonStyle.Clear}
type="button"
onClick={zoomOut}
icon="zoom_out"
/>
<Button
className={`${CLASS_NAME}__toolbar__button`}
buttonStyle={EnumButtonStyle.Clear}
type="button"
onClick={zoomReset}
icon="maximize_2"
/>
</div>
<div className={`${CLASS_NAME}__scroll`}>
<div className={`${CLASS_NAME}__scroll-content`}>
<EntitiesDiagramRelations entities={values.entities} />
<div
className={`${CLASS_NAME}__scale`}
style={{ transform: `scale(${zoomLevel})` }}
>
<DragDropContext
onDragEnd={onFieldDragEnd}
onDragStart={onFieldDragStart}
>
{values.entities.map((entity, index) => (
<EntitiesDiagramEntity
zoomLevel={zoomLevel}
key={`entity_${index}`}
entity={entity}
entityIndex={index}
positionData={entitiesPosition[index]}
onDrag={handleEntityDrag}
onEditField={handleEditField}
onEditEntity={handleEditEntity}
onAddEntity={handleAddEntity}
onDeleteEntity={handleDeleteEntity}
editedFieldIdentifier={editedField}
editedEntity={editedEntity}
/>
))}
</DragDropContext>
</div>
</div>
</div>
<Snackbar
open={Boolean(error)}
message={error?.message}
onClose={clearError}
/>
</div>
);
}
Example #22
Source File: AppTable.tsx From flame with MIT License | 4 votes |
AppTable = (props: Props): JSX.Element => {
const {
apps: { apps },
config: { config },
} = useSelector((state: State) => state);
const dispatch = useDispatch();
const { pinApp, deleteApp, reorderApps, createNotification, updateApp } =
bindActionCreators(actionCreators, dispatch);
const [localApps, setLocalApps] = useState<App[]>([]);
// Copy apps array
useEffect(() => {
setLocalApps([...apps]);
}, [apps]);
const dragEndHanlder = (result: DropResult): void => {
if (config.useOrdering !== 'orderId') {
createNotification({
title: 'Error',
message: 'Custom order is disabled',
});
return;
}
if (!result.destination) {
return;
}
const tmpApps = [...localApps];
const [movedApp] = tmpApps.splice(result.source.index, 1);
tmpApps.splice(result.destination.index, 0, movedApp);
setLocalApps(tmpApps);
reorderApps(tmpApps);
};
// Action handlers
const deleteAppHandler = (id: number, name: string) => {
const proceed = window.confirm(`Are you sure you want to delete ${name}?`);
if (proceed) {
deleteApp(id);
}
};
const updateAppHandler = (id: number) => {
const app = apps.find((a) => a.id === id) as App;
props.openFormForUpdating(app);
};
const pinAppHandler = (id: number) => {
const app = apps.find((a) => a.id === id) as App;
pinApp(app);
};
const changeAppVisibiltyHandler = (id: number) => {
const app = apps.find((a) => a.id === id) as App;
updateApp(id, { ...app, isPublic: !app.isPublic });
};
return (
<Fragment>
<Message isPrimary={false}>
{config.useOrdering === 'orderId' ? (
<p>You can drag and drop single rows to reorder application</p>
) : (
<p>
Custom order is disabled. You can change it in the{' '}
<Link to="/settings/general">settings</Link>
</p>
)}
</Message>
<DragDropContext onDragEnd={dragEndHanlder}>
<Droppable droppableId="apps">
{(provided) => (
<Table
headers={['Name', 'URL', 'Icon', 'Visibility', 'Actions']}
innerRef={provided.innerRef}
>
{localApps.map((app: App, index): JSX.Element => {
return (
<Draggable
key={app.id}
draggableId={app.id.toString()}
index={index}
>
{(provided, snapshot) => {
const style = {
border: snapshot.isDragging
? '1px solid var(--color-accent)'
: 'none',
borderRadius: '4px',
...provided.draggableProps.style,
};
return (
<tr
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
style={style}
>
<td style={{ width: '200px' }}>{app.name}</td>
<td style={{ width: '200px' }}>{app.url}</td>
<td style={{ width: '200px' }}>{app.icon}</td>
<td style={{ width: '200px' }}>
{app.isPublic ? 'Visible' : 'Hidden'}
</td>
{!snapshot.isDragging && (
<TableActions
entity={app}
deleteHandler={deleteAppHandler}
updateHandler={updateAppHandler}
pinHanlder={pinAppHandler}
changeVisibilty={changeAppVisibiltyHandler}
/>
)}
</tr>
);
}}
</Draggable>
);
})}
</Table>
)}
</Droppable>
</DragDropContext>
</Fragment>
);
}
Example #23
Source File: index.tsx From scorpio-h5-design with MIT License | 4 votes |
export default function() {
const [isOrdering, setIsOrdering] = useBoolean(false);
const {
isDraging, pageSchema, selectPageIndex, dragingComponentIndex, selectComponent,
onDragEnter, onDragLeave, onDrop, onSelectComponent, onSortEnd, showSelectComponentBorder,
} = useModel('bridge');
let components:any[] = [];
if (pageSchema.length > 0 && selectPageIndex !== -1) {
components = pageSchema[selectPageIndex].components;
}
return (
<DragDropContext
onDragStart={()=>{
setIsOrdering.setTrue();
}}
onDragEnd={(result: DropResult)=>{
onSortEnd(result, components);
setIsOrdering.setFalse();
}}
>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
{components.length > 0 && components.map((item: any, index: any) => (
<Draggable key={item.uuid} draggableId={item.uuid} index={index}>
{(provided, snapshot) => (
<>
<div
className={classnames(
'h5-canvas-empty-block',
{
show: isDraging,
over: dragingComponentIndex === index,
}
)}
onDragEnter={() => { onDragEnter(index); }}
onDragLeave={() => { onDragLeave(); }}
onDragOver={(event) => { event.preventDefault(); }}
onDrop={(event)=>{onDrop(event, index);}}
/>
<div
className={classnames(
'h5-canvas-block',
{
'blur': !snapshot.isDragging && isOrdering,
'isSelected': selectComponent?.uuid === item.uuid,
'border': selectComponent?.uuid === item.uuid && showSelectComponentBorder,
}
)}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={() => { onSelectComponent(item.uuid); }}
>
<DynamicComponent
id={item._id}
uuid={item.uuid}
isSelected={selectComponent?.uuid === item.uuid}
componentProps={item.props}
containerProps={item.containerProps}
/>
</div>
</>
)}
</Draggable>
))}
{provided.placeholder}
<div
className={classnames(
'h5-canvas-empty-block',
{
show: isDraging,
over: dragingComponentIndex === components.length,
}
)}
onDragEnter={() => { onDragEnter(components.length); }}
onDragLeave={() => { onDragLeave(); }}
onDragOver={(event) => { event.preventDefault(); }}
onDrop={(event)=>{onDrop(event, components.length);}}
/>
</div>
)}
</Droppable>
</DragDropContext>
);
}
Example #24
Source File: bridge.ts From scorpio-h5-design with MIT License | 4 votes |
/**
* 这份状态会在父页面和iframe中实时同步
*/
export default function bridge() {
/** 是否拖拽中 */
const [isDraging, setIsDraging] = useState(false);
/** 当前拖拽的组件 */
const [dragComponent, setDragComponent] = useState<IComponentSchema | undefined>(undefined);
/** 页面id */
const [pageId, setPageId] = useState<string>();
/** 页面结构schema */
const [pageSchema, setPageSchema] = useState<IPageSchema[]>([]);
/** TODO: 使用索引不太好(若增加页面排序) */
const [selectPageIndex, setSelectPageIndex] = useState(-1);
/** 当前拖拽元素即将插入的位置索引(从0开始,-1为初始值) */
const [dragingComponentIndex, setDragingComponentIndex] = useState(-1);
/** 当前选中的组件 */
const [selectComponentId, setSelectComponentId] = useState<string | undefined>(undefined);
/** 是否显示选中组件边界 */
const [showSelectComponentBorder, setShowSelectComponentBorder] = useState(() => window.localStorage.getItem('selectArea_borderVisible') !== 'false');
const selectPage = pageSchema[selectPageIndex];
const selectComponent = (selectPage && selectComponentId) ? pageSchema[selectPageIndex].components.find((item) => item.uuid === selectComponentId) : undefined;
/**
* 根据对象更新state
* @param state
* @param isSyncState 是否在iframe之间同步状态,默认true
*/
const setStateByObjectKeys = function(state: {
isDraging?: boolean;
dragComponent?: IComponentSchema;
pageId?: string;
pageSchema?: IPageSchema[];
selectPageIndex?: number;
selectComponentId?: string;
dragingComponentIndex?: number;
showSelectComponentBorder?: boolean;
}, isSyncState = true) {
// 遍历key值set,可以避免不必要的渲染
Object.keys(state).forEach((key) => {
if (key === 'isDraging') {
// @ts-expect-error
setIsDraging(state.isDraging);
}
if (key === 'dragComponent') {
setDragComponent(state.dragComponent);
}
if (key === 'pageId') {
setPageId(state.pageId);
}
if (key === 'pageSchema') {
// @ts-expect-error
setPageSchema(state.pageSchema);
}
if (key === 'selectPageIndex') {
// @ts-expect-error
setSelectPageIndex(state.selectPageIndex);
}
if (key === 'selectComponentId') {
setSelectComponentId(state.selectComponentId);
}
if (key === 'dragingComponentIndex') {
// @ts-expect-error
setDragingComponentIndex(state.dragingComponentIndex);
}
if (key === 'showSelectComponentBorder') {
// @ts-expect-error
setShowSelectComponentBorder(state.showSelectComponentBorder);
}
});
// iframe之间同步状态
if (isSyncState) {
syncState({
payload: state,
type: IMessageType.syncState,
});
}
// TODO:同步移动端页面快照(性能问题暂不使用)
if (!isMobile() && !isSyncState && window.postmate_mobile) {
// capture.run();
}
};
// 组件拖拽开始
const onDragStart = function(component: IComponentSchema) {
// 组件增加唯一标识
component.uuid = uuidv4();
setStateByObjectKeys({
isDraging: true,
dragComponent: component,
});
};
// 组件拖拽结束
const onDragEnd = function() {
setStateByObjectKeys({
isDraging: false,
});
};
/** 拖拽-进入 */
const onDragEnter = function(index: number) {
setStateByObjectKeys({
dragingComponentIndex: index,
});
};
/** 拖拽-离开 */
const onDragLeave = function() {
setStateByObjectKeys({
dragingComponentIndex: -1,
});
};
/** 拖拽-放置 */
const onDrop = function(ev: React.DragEvent<HTMLDivElement>, index: number) {
ev.preventDefault();
const components = pageSchema[selectPageIndex].components;
components.splice(index, 0, dragComponent as IComponentSchema);
setStateByObjectKeys({
dragingComponentIndex: -1,
dragComponent: undefined,
pageSchema,
});
};
/** 重排 */
const reorder = (components: IComponentSchema[], startIndex: number, endIndex: number) => {
const result = Array.from(components);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
/**
* 排序拖拽-放置
* @TODO 视图会闪烁
*/
const onSortEnd = function(result: DropResult, currentPageComponents: IComponentSchema[]) {
if (!result.destination) {
return;
}
const reorderedComponents = reorder(currentPageComponents, result.source.index, result.destination.index);
pageSchema[selectPageIndex].components = reorderedComponents;
setStateByObjectKeys({
pageSchema,
});
};
/** 选中组件 */
const onSelectComponent = function(id: string) {
if (selectComponentId !== id) {
setStateByObjectKeys({
selectComponentId: id,
});
}
};
const changeContainerPropsState = function(key: string, value: unknown) {
// @ts-expect-error
selectComponent.containerProps[key] = value;
setStateByObjectKeys({
pageSchema: [...pageSchema],
});
};
return {
isDraging,
dragComponent,
pageId,
pageSchema,
selectPageIndex,
dragingComponentIndex,
setStateByObjectKeys,
onDragStart,
onDragEnd,
onDragEnter,
onDragLeave,
onDrop,
onSelectComponent,
onSortEnd,
selectComponent,
changeContainerPropsState,
selectComponentId,
showSelectComponentBorder,
selectPage,
};
}
Example #25
Source File: DownloadQueue.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 4 votes |
export default function DownloadQueue() {
const [, setWsClient] = useState<WebSocket>();
const [queueState, setQueueState] = useState<IQueue>(initialQueue);
const { queue, status } = queueState;
const history = useHistory();
const theme = useTheme();
const { setTitle, setAction } = useContext(NavbarContext);
const toggleQueueStatus = () => {
if (status === 'Stopped') {
client.get('/api/v1/downloads/start');
} else {
client.get('/api/v1/downloads/stop');
}
};
useEffect(() => {
setTitle('Download Queue');
setAction(() => {
if (status === 'Stopped') {
return (
<IconButton onClick={toggleQueueStatus} size="large">
<PlayArrowIcon />
</IconButton>
);
}
return (
<IconButton onClick={toggleQueueStatus} size="large">
<PauseIcon />
</IconButton>
);
});
}, [status]);
useEffect(() => {
const wsc = new WebSocket(`${baseWebsocketUrl}/api/v1/downloads`);
wsc.onmessage = (e) => {
setQueueState(JSON.parse(e.data));
};
setWsClient(wsc);
}, []);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onDragEnd = (result: DropResult) => {
};
if (queue.length === 0) {
return <EmptyView message="No downloads" />;
}
return (
<>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided) => (
<List ref={provided.innerRef}>
{queue.map((item, index) => (
<Draggable
key={`${item.mangaId}-${item.chapterIndex}`}
draggableId={`${item.mangaId}-${item.chapterIndex}`}
index={index}
>
{(provided, snapshot) => (
<ListItem
ContainerProps={{ ref: provided.innerRef } as any}
sx={{
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'flex-start',
padding: 2,
margin: '10px',
'&:hover': {
backgroundColor: 'action.hover',
transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
},
'&:active': {
backgroundColor: 'action.selected',
transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
},
}}
onClick={() => history.push(`/manga/${item.chapter.mangaId}`)}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style,
theme.palette,
)}
ref={provided.innerRef}
>
<ListItemIcon sx={{ margin: 'auto 0' }}>
<DragHandleIcon />
</ListItemIcon>
<Box sx={{ display: 'flex' }}>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography variant="h5" component="h2">
{item.manga.title}
</Typography>
<Typography variant="caption" display="block" gutterBottom>
{`${item.chapter.name} `
+ `(${(item.progress * 100).toFixed(2)}%)`
+ ` => state: ${item.state}`}
</Typography>
</Box>
</Box>
<IconButton
sx={{ marginLeft: 'auto' }}
onClick={(e) => {
// deleteCategory(index);
// prevent parent tags from getting the event
e.stopPropagation();
}}
size="large"
>
<DeleteIcon />
</IconButton>
</ListItem>
)}
</Draggable>
))}
{provided.placeholder}
</List>
)}
</Droppable>
</DragDropContext>
</>
);
}
Example #26
Source File: index.tsx From peterportal-client with MIT License | 4 votes |
RoadmapPage: FC = () => {
const dispatch = useAppDispatch();
const showSearch = useAppSelector(state => state.roadmap.showSearch);
const onDragEnd = useCallback((result: DropResult) => {
if (result.reason === 'DROP') {
// no destination or dragging to search bar
if (!result.destination || result.destination.droppableId === 'search') {
// removing from quarter
if (result.source.droppableId != 'search') {
let [yearIndex, quarterIndex] = result.source.droppableId.split('-');
dispatch(deleteCourse({
yearIndex: parseInt(yearIndex),
quarterIndex: parseInt(quarterIndex),
courseIndex: result.source.index
}))
}
return;
}
let movePayload = {
from: {
yearIndex: -1,
quarterIndex: -1,
courseIndex: -1,
},
to: {
yearIndex: -1,
quarterIndex: -1,
courseIndex: -1
}
};
console.log(result.source.droppableId, '=>', result.destination.droppableId)
// roadmap to roadmap has source
if (result.source.droppableId != 'search') {
let [yearIndex, quarterIndex] = result.source.droppableId.split('-');
movePayload.from.yearIndex = parseInt(yearIndex);
movePayload.from.quarterIndex = parseInt(quarterIndex);
movePayload.from.courseIndex = result.source.index;
}
// search to roadmap has no source (use activeCourse in global state)
// both have destination
let [yearIndex, quarterIndex] = result.destination.droppableId.split('-');
movePayload.to.yearIndex = parseInt(yearIndex);
movePayload.to.quarterIndex = parseInt(quarterIndex);
movePayload.to.courseIndex = result.destination.index;
console.log(movePayload);
dispatch(moveCourse(movePayload));
}
}, []);
const onDragUpdate = useCallback((initial: DragUpdate) => {
console.log(initial)
}, []);
// do not conditionally renderer because it would remount planner which would discard unsaved changes
const mobileVersion = <>
<div className={`main-wrapper mobile ${showSearch ? 'hide' : ''}`}>
<Planner />
</div>
<div className={`sidebar-wrapper mobile ${!showSearch ? 'hide' : ''}`}>
<SearchSidebar />
</div>
</>
const desktopVersion = <>
<div className='main-wrapper'>
<Planner />
</div>
<div className='sidebar-wrapper'>
<SearchSidebar />
</div>
</>
return (
<>
<div className='roadmap-page'>
<AddCoursePopup />
<DragDropContext onDragEnd={onDragEnd} onDragUpdate={onDragUpdate}>
{isMobile && mobileVersion}
{!isMobile && desktopVersion}
</DragDropContext>
</div>
</>
);
}
Example #27
Source File: PipelineEditComponents.tsx From baleen3 with Apache License 2.0 | 4 votes |
PipelineEditComponents: React.FC<PipelineEditComponentsProps> = ({
id,
type,
components,
addComponents,
moveComponent,
removeComponent,
setComponentName,
setComponentSettings,
}: PipelineEditComponentsProps) => {
const handleDrag = (result: DropResult): void => {
const { source, destination } = result
if (destination === undefined) {
return
}
if (destination.index === source.index) {
return
}
moveComponent(source.index, destination.index)
}
return (
<DragDropContext onDragEnd={handleDrag}>
<Droppable droppableId={id}>
{(provided: DroppableProvided): React.ReactElement => (
<Column
alignItems="center"
{...provided.droppableProps}
// eslint-disable-next-line @typescript-eslint/unbound-method
ref={provided.innerRef}
>
{components.map((component, index) => {
return (
<Draggable
key={component.id}
draggableId={component.id}
index={index}
>
{(
provider: DraggableProvided,
snapshot: DraggableStateSnapshot
): React.ReactElement => (
<Column
width={1}
alignItems="center"
// eslint-disable-next-line @typescript-eslint/unbound-method
ref={provider.innerRef}
{...provider.draggableProps}
>
<PipelineEditComponentSeparator
onInsert={(components: ComponentInfo[]): void =>
addComponents(components, index)
}
isDragging={snapshot.isDragging}
type={type}
/>
<PipelineComponent
{...provider.dragHandleProps}
type={type}
descriptor={component}
setName={setComponentName}
setSettings={setComponentSettings}
onDelete={removeComponent}
/>
</Column>
)}
</Draggable>
)
})}
{provided.placeholder}
<PipelineEditComponentSeparator
onInsert={(components: ComponentInfo[]): void =>
addComponents(components)
}
isDragging={false}
type={type}
/>
</Column>
)}
</Droppable>
</DragDropContext>
)
}
Example #28
Source File: main.tsx From webminidisc with GNU General Public License v2.0 | 4 votes |
Main = (props: {}) => {
let dispatch = useDispatch();
const disc = useShallowEqualSelector(state => state.main.disc);
const deviceName = useShallowEqualSelector(state => state.main.deviceName);
const deviceStatus = useShallowEqualSelector(state => state.main.deviceStatus);
const { vintageMode } = useShallowEqualSelector(state => state.appState);
const [selected, setSelected] = React.useState<number[]>([]);
const [uploadedFiles, setUploadedFiles] = React.useState<File[]>([]);
const [lastClicked, setLastClicked] = useState(-1);
const [moveMenuAnchorEl, setMoveMenuAnchorEl] = React.useState<null | HTMLElement>(null);
const handleShowMoveMenu = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
setMoveMenuAnchorEl(event.currentTarget);
},
[setMoveMenuAnchorEl]
);
const handleCloseMoveMenu = useCallback(() => {
setMoveMenuAnchorEl(null);
}, [setMoveMenuAnchorEl]);
const handleMoveSelectedTrack = useCallback(
(destIndex: number) => {
dispatch(moveTrack(selected[0], destIndex));
handleCloseMoveMenu();
},
[dispatch, selected, handleCloseMoveMenu]
);
const handleDrop = useCallback(
(result: DropResult, provided: ResponderProvided) => {
if (!result.destination) return;
let sourceList = parseInt(result.source.droppableId),
sourceIndex = result.source.index,
targetList = parseInt(result.destination.droppableId),
targetIndex = result.destination.index;
dispatch(dragDropTrack(sourceList, sourceIndex, targetList, targetIndex));
},
[dispatch]
);
const handleShowDumpDialog = useCallback(() => {
dispatch(dumpDialogActions.setVisible(true));
}, [dispatch]);
useEffect(() => {
dispatch(listContent());
}, [dispatch]);
useEffect(() => {
setSelected([]); // Reset selection if disc changes
}, [disc]);
const onDrop = useCallback(
(acceptedFiles: File[], rejectedFiles: File[]) => {
setUploadedFiles(acceptedFiles);
dispatch(convertDialogActions.setVisible(true));
},
[dispatch]
);
const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
onDrop,
accept: [`audio/*`, `video/mp4`],
noClick: true,
});
const classes = useStyles();
const tracks = useMemo(() => getSortedTracks(disc), [disc]);
const groupedTracks = useMemo(() => getGroupedTracks(disc), [disc]);
// Action Handlers
const handleSelectTrackClick = useCallback(
(event: React.MouseEvent, item: number) => {
if (event.shiftKey && selected.length && lastClicked !== -1) {
let rangeBegin = Math.min(lastClicked + 1, item),
rangeEnd = Math.max(lastClicked - 1, item);
let copy = [...selected];
for (let i = rangeBegin; i <= rangeEnd; i++) {
let index = copy.indexOf(i);
if (index === -1) copy.push(i);
else copy.splice(index, 1);
}
if (!copy.includes(item)) copy.push(item);
setSelected(copy);
} else if (selected.includes(item)) {
setSelected(selected.filter(i => i !== item));
} else {
setSelected([...selected, item]);
}
setLastClicked(item);
},
[selected, setSelected, lastClicked, setLastClicked]
);
const handleSelectAllClick = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
if (selected.length < tracks.length) {
setSelected(tracks.map(t => t.index));
} else {
setSelected([]);
}
},
[selected, tracks]
);
const handleRenameTrack = useCallback(
(event: React.MouseEvent, index: number) => {
let track = tracks.find(t => t.index === index);
if (!track) {
return;
}
dispatch(
batchActions([
renameDialogActions.setVisible(true),
renameDialogActions.setGroupIndex(null),
renameDialogActions.setCurrentName(track.title),
renameDialogActions.setCurrentFullWidthName(track.fullWidthTitle),
renameDialogActions.setIndex(track.index),
])
);
},
[dispatch, tracks]
);
const handleRenameGroup = useCallback(
(event: React.MouseEvent, index: number) => {
let group = groupedTracks.find(g => g.index === index);
if (!group) {
return;
}
dispatch(
batchActions([
renameDialogActions.setVisible(true),
renameDialogActions.setGroupIndex(index),
renameDialogActions.setCurrentName(group.title ?? ''),
renameDialogActions.setCurrentFullWidthName(group.fullWidthTitle ?? ''),
renameDialogActions.setIndex(-1),
])
);
},
[dispatch, groupedTracks]
);
const handleRenameActionClick = useCallback(
(event: React.MouseEvent) => {
if (event.detail !== 1) return; //Event retriggering when hitting enter in the dialog
handleRenameTrack(event, selected[0]);
},
[handleRenameTrack, selected]
);
const handleDeleteSelected = useCallback(
(event: React.MouseEvent) => {
dispatch(deleteTracks(selected));
},
[dispatch, selected]
);
const handleGroupTracks = useCallback(
(event: React.MouseEvent) => {
dispatch(groupTracks(selected));
},
[dispatch, selected]
);
const handleDeleteGroup = useCallback(
(event: React.MouseEvent, index: number) => {
dispatch(deleteGroup(index));
},
[dispatch]
);
const handleTogglePlayPauseTrack = useCallback(
(event: React.MouseEvent, track: number) => {
if (!deviceStatus) {
return;
}
if (deviceStatus.track !== track) {
dispatch(control('goto', track));
dispatch(control('play'));
} else if (deviceStatus.state === 'playing') {
dispatch(control('pause'));
} else {
dispatch(control('play'));
}
},
[dispatch, deviceStatus]
);
const canGroup = useMemo(() => {
return (
tracks.filter(n => n.group === null && selected.includes(n.index)).length === selected.length &&
isSequential(selected.sort((a, b) => a - b))
);
}, [tracks, selected]);
const selectedCount = selected.length;
if (vintageMode) {
const p = {
disc,
deviceName,
selected,
setSelected,
selectedCount,
tracks,
uploadedFiles,
setUploadedFiles,
onDrop,
getRootProps,
getInputProps,
isDragActive,
open,
moveMenuAnchorEl,
setMoveMenuAnchorEl,
handleShowMoveMenu,
handleCloseMoveMenu,
handleMoveSelectedTrack,
handleShowDumpDialog,
handleDeleteSelected,
handleRenameActionClick,
handleRenameTrack,
handleSelectAllClick,
handleSelectTrackClick,
};
return <W95Main {...p} />;
}
return (
<React.Fragment>
<Box className={classes.headBox}>
<Typography component="h1" variant="h4">
{deviceName || `Loading...`}
</Typography>
<TopMenu />
</Box>
<Typography component="h2" variant="body2">
{disc !== null ? (
<React.Fragment>
<span>{`${formatTimeFromFrames(disc.left, false)} left of ${formatTimeFromFrames(disc.total, false)} `}</span>
<Tooltip
title={
<React.Fragment>
<span>{`${formatTimeFromFrames(disc.left * 2, false)} left in LP2 Mode`}</span>
<br />
<span>{`${formatTimeFromFrames(disc.left * 4, false)} left in LP4 Mode`}</span>
</React.Fragment>
}
arrow
>
<span className={classes.remainingTimeTooltip}>SP Mode</span>
</Tooltip>
</React.Fragment>
) : (
`Loading...`
)}
</Typography>
<Toolbar
className={clsx(classes.toolbar, {
[classes.toolbarHighlight]: selectedCount > 0,
})}
>
{selectedCount > 0 ? (
<Checkbox
indeterminate={selectedCount > 0 && selectedCount < tracks.length}
checked={selectedCount > 0}
onChange={handleSelectAllClick}
inputProps={{ 'aria-label': 'select all tracks' }}
/>
) : null}
{selectedCount > 0 ? (
<Typography className={classes.toolbarLabel} color="inherit" variant="subtitle1">
{selectedCount} selected
</Typography>
) : (
<Typography component="h3" variant="h6" className={classes.toolbarLabel}>
{disc?.fullWidthTitle && `${disc.fullWidthTitle} / `}
{disc?.title || `Untitled Disc`}
</Typography>
)}
{selectedCount > 0 ? (
<React.Fragment>
<Tooltip title="Record from MD">
<Button aria-label="Record" onClick={handleShowDumpDialog}>
Record
</Button>
</Tooltip>
</React.Fragment>
) : null}
{selectedCount > 0 ? (
<Tooltip title="Delete">
<IconButton aria-label="delete" onClick={handleDeleteSelected}>
<DeleteIcon />
</IconButton>
</Tooltip>
) : null}
{selectedCount > 0 ? (
<Tooltip title={canGroup ? 'Group' : ''}>
<IconButton aria-label="group" disabled={!canGroup} onClick={handleGroupTracks}>
<CreateNewFolderIcon />
</IconButton>
</Tooltip>
) : null}
{selectedCount > 0 ? (
<Tooltip title="Rename">
<IconButton aria-label="rename" disabled={selectedCount !== 1} onClick={handleRenameActionClick}>
<EditIcon />
</IconButton>
</Tooltip>
) : null}
</Toolbar>
<Box className={classes.main} {...getRootProps()} id="main">
<input {...getInputProps()} />
<Table size="small">
<TableHead>
<TableRow>
<TableCell className={classes.dragHandleEmpty}></TableCell>
<TableCell className={classes.indexCell}>#</TableCell>
<TableCell>Title</TableCell>
<TableCell align="right">Duration</TableCell>
</TableRow>
</TableHead>
<DragDropContext onDragEnd={handleDrop}>
<TableBody>
{groupedTracks.map((group, index) => (
<TableRow key={`${index}`}>
<TableCell colSpan={4} style={{ padding: '0' }}>
<Table size="small">
<Droppable droppableId={`${index}`} key={`${index}`}>
{(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => (
<TableBody
{...provided.droppableProps}
ref={provided.innerRef}
className={clsx({ [classes.hoveringOverGroup]: snapshot.isDraggingOver })}
>
{group.title !== null && (
<GroupRow
group={group}
onRename={handleRenameGroup}
onDelete={handleDeleteGroup}
/>
)}
{group.title === null && group.tracks.length === 0 && (
<TableRow style={{ height: '1px' }} />
)}
{group.tracks.map((t, tidx) => (
<Draggable
draggableId={`${group.index}-${t.index}`}
key={`t-${t.index}`}
index={tidx}
>
{(provided: DraggableProvided) => (
<TrackRow
track={t}
draggableProvided={provided}
inGroup={group.title !== null}
isSelected={selected.includes(t.index)}
trackStatus={getTrackStatus(t, deviceStatus)}
onSelect={handleSelectTrackClick}
onRename={handleRenameTrack}
onTogglePlayPause={handleTogglePlayPauseTrack}
/>
)}
</Draggable>
))}
{provided.placeholder}
</TableBody>
)}
</Droppable>
</Table>
</TableCell>
</TableRow>
))}
</TableBody>
</DragDropContext>
</Table>
{isDragActive ? (
<Backdrop className={classes.backdrop} open={isDragActive}>
Drop your Music to Upload
</Backdrop>
) : null}
</Box>
<Fab color="primary" aria-label="add" className={classes.add} onClick={open}>
<AddIcon />
</Fab>
<UploadDialog />
<RenameDialog />
<ErrorDialog />
<ConvertDialog files={uploadedFiles} />
<RecordDialog />
<DumpDialog trackIndexes={selected} />
<AboutDialog />
<PanicDialog />
</React.Fragment>
);
}
Example #29
Source File: Categories.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 4 votes |
export default function Categories() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => { setTitle('Categories'); setAction(<></>); }, []);
const [categories, setCategories] = useState<ICategory[]>([]);
const [categoryToEdit, setCategoryToEdit] = useState<number>(-1); // -1 means new category
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const [dialogName, setDialogName] = useState<string>('');
const [dialogDefault, setDialogDefault] = useState<boolean>(false);
const theme = useTheme();
const [updateTriggerHolder, setUpdateTriggerHolder] = useState<number>(0); // just a hack
const triggerUpdate = () => setUpdateTriggerHolder(updateTriggerHolder + 1); // just a hack
useEffect(() => {
if (!dialogOpen) {
client.get('/api/v1/category/')
.then((response) => response.data)
.then((data) => { if (data.length > 0 && data[0].name === 'Default') data.shift(); return data; })
.then((data) => setCategories(data));
}
}, [updateTriggerHolder]);
const categoryReorder = (list: ICategory[], from: number, to: number) => {
const formData = new FormData();
formData.append('from', `${from + 1}`);
formData.append('to', `${to + 1}`);
client.patch('/api/v1/category/reorder', formData)
.finally(() => triggerUpdate());
// also move it in local state to avoid jarring moving behviour...
const result = Array.from(list);
const [removed] = result.splice(from, 1);
result.splice(to, 0, removed);
return result;
};
const onDragEnd = (result: DropResult) => {
// dropped outside the list?
if (!result.destination) {
return;
}
setCategories(categoryReorder(
categories,
result.source.index,
result.destination.index,
));
};
const resetDialog = () => {
setDialogName('');
setDialogDefault(false);
setCategoryToEdit(-1);
};
const handleDialogOpen = () => {
resetDialog();
setDialogOpen(true);
};
const handleEditDialogOpen = (index:number) => {
setDialogName(categories[index].name);
setDialogDefault(categories[index].default);
setCategoryToEdit(index);
setDialogOpen(true);
};
const handleDialogCancel = () => {
setDialogOpen(false);
};
const handleDialogSubmit = () => {
setDialogOpen(false);
const formData = new FormData();
formData.append('name', dialogName);
formData.append('default', dialogDefault.toString());
if (categoryToEdit === -1) {
client.post('/api/v1/category/', formData)
.finally(() => triggerUpdate());
} else {
const category = categories[categoryToEdit];
client.patch(`/api/v1/category/${category.id}`, formData)
.finally(() => triggerUpdate());
}
};
const deleteCategory = (index:number) => {
const category = categories[index];
client.delete(`/api/v1/category/${category.id}`)
.finally(() => triggerUpdate());
};
return (
<>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided) => (
<List ref={provided.innerRef}>
{categories.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id.toString()}
index={index}
>
{(provided, snapshot) => (
<ListItem
ContainerProps={{ ref: provided.innerRef } as any}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style,
theme.palette,
)}
ref={provided.innerRef}
>
<ListItemIcon>
<DragHandleIcon />
</ListItemIcon>
<ListItemText
primary={item.name}
/>
<IconButton
onClick={() => {
handleEditDialogOpen(index);
}}
size="large"
>
<EditIcon />
</IconButton>
<IconButton
onClick={() => {
deleteCategory(index);
}}
size="large"
>
<DeleteIcon />
</IconButton>
</ListItem>
)}
</Draggable>
))}
{provided.placeholder}
</List>
)}
</Droppable>
</DragDropContext>
<Fab
color="primary"
aria-label="add"
style={{
position: 'absolute',
bottom: theme.spacing(2),
right: theme.spacing(2),
}}
onClick={handleDialogOpen}
>
<AddIcon />
</Fab>
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
<DialogTitle id="form-dialog-title">
{categoryToEdit === -1 ? 'New Catalog' : 'Edit Catalog'}
</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
id="name"
label="Category Name"
type="text"
fullWidth
value={dialogName}
onChange={(e) => setDialogName(e.target.value)}
/>
<FormControlLabel
control={(
<Checkbox
checked={dialogDefault}
onChange={(e) => setDialogDefault(e.target.checked)}
color="default"
/>
)}
label="Default category when adding new manga to library"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogCancel} color="primary">
Cancel
</Button>
<Button onClick={handleDialogSubmit} color="primary">
Submit
</Button>
</DialogActions>
</Dialog>
</>
);
}