dnd functionality works.
This commit is contained in:
@ -4,7 +4,6 @@ import {NewLibraryItem} from './NewLibraryItem';
|
||||
import { Category, addItemToLibrary, categoryHasName } from '../lib/prompt';
|
||||
|
||||
jest.mock('../lib/prompt', () => ({
|
||||
Category: Category,
|
||||
addItemToLibrary: jest.fn(),
|
||||
categoryHasName: jest.fn(),
|
||||
}));
|
||||
|
@ -2,7 +2,7 @@ import { Button, FormControl, InputLabel, MenuItem, TextField } from "@material-
|
||||
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
||||
import { Category, LibraryItem, addItemToLibrary, categoryHasName } from "../lib/prompt";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import {v4 as uuidv4} from "uuid"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
|
||||
export interface NewLibraryItemProps {
|
||||
onNewCreated?: () => void;
|
||||
@ -47,8 +47,8 @@ export function NewLibraryItem(props: NewLibraryItemProps) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<FormControl>
|
||||
<FormControl onSubmit={handleCreateItem}>
|
||||
<div>
|
||||
<InputLabel htmlFor="new-prompt-category">Category</InputLabel>
|
||||
<Select
|
||||
native
|
||||
@ -61,21 +61,17 @@ export function NewLibraryItem(props: NewLibraryItemProps) {
|
||||
<option value={cat} id={cat} key={cat}>{titleCase(cat)}</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div>
|
||||
<FormControl>
|
||||
</div>
|
||||
<div>
|
||||
{categoryHasName(category) ? (<InputLabel htmlFor="name">Name</InputLabel>) : <></>}
|
||||
{categoryHasName(category) ? (<TextField aria-label="Prompt Item Name" value={name} onChange={handleNameChange} id="name" />) : <></>}
|
||||
</FormControl>
|
||||
</div>
|
||||
<div>
|
||||
<FormControl>
|
||||
</div>
|
||||
<div>
|
||||
<InputLabel htmlFor="prompt">Prompt</InputLabel>
|
||||
<TextField aria-label="Prompt Item Text" value={prompt} onChange={handlePromptChange} id="prompt" />
|
||||
<Button onClick={handleCreateItem} >Create</Button>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,34 +1,93 @@
|
||||
import { Button, ButtonGroup, Chip, Divider } from '@material-ui/core';
|
||||
import React, { Component, useState } from 'react';
|
||||
import React, { Component, DragEvent, useEffect, useState } from 'react';
|
||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||
|
||||
import { $composition, Nugget as NuggetType, decreaseNuggetScore, increaseNuggetScore } from '../lib/prompt';
|
||||
import "./Nugget.css";
|
||||
import { Nugget as NuggetType, decreaseNuggetScore, increaseNuggetScore } from '../lib/prompt';
|
||||
import "./PromptItem.css"
|
||||
import { $sourceItem, cancelDrop, completeDrop, isPromptItemDropTarget, startDrag, startHoverOver } from '../store/prompt-dnd';
|
||||
import { PromptItemProps } from './PromptItem';
|
||||
import { useStore } from '@nanostores/react';
|
||||
|
||||
export interface NuggetProps {
|
||||
nugget : NuggetType,
|
||||
export interface NuggetProps extends PromptItemProps {
|
||||
nugget: NuggetType,
|
||||
}
|
||||
|
||||
export default function Nugget(props : NuggetProps) {
|
||||
const {nugget} = props;
|
||||
export default function Nugget(props: NuggetProps) {
|
||||
|
||||
const { nugget,
|
||||
onDragStart,
|
||||
onDragOver,
|
||||
onDragEnd,
|
||||
onDrop,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
} = props;
|
||||
|
||||
const scoreDisp = nugget.score > 0 ? "+" + nugget.score : nugget.score;
|
||||
|
||||
|
||||
const sourceItem = useStore($sourceItem);
|
||||
const composition = useStore($composition)
|
||||
const thisId = `prompt-item-${nugget.id}`
|
||||
|
||||
const handleOnDragStart = () => {
|
||||
onDragStart ? onDragStart(nugget) : null;
|
||||
}
|
||||
|
||||
const handleOnMouseEnter = () => {
|
||||
if (!sourceItem) {
|
||||
return;
|
||||
}
|
||||
startHoverOver(nugget);
|
||||
}
|
||||
|
||||
const handleOnMouseLeave = () => {
|
||||
onMouseLeave ? onMouseLeave(nugget) : null;
|
||||
}
|
||||
|
||||
const handleOnDragOver = ($e: DragEvent) => {
|
||||
if (!sourceItem) return;
|
||||
// extract the prompt item's ID:
|
||||
const targetId = $e.currentTarget.getAttribute("data-promptitem-id");
|
||||
if (sourceItem.id == targetId) return;
|
||||
console.log("current target id: %s", targetId);
|
||||
const promptItem = composition.find(i => (targetId === i.id));
|
||||
if (!promptItem) {
|
||||
console.warn("Could not find promptitem with ID %s", targetId);
|
||||
console.log(composition.map(c => c.id));
|
||||
return;
|
||||
}
|
||||
startHoverOver(promptItem);
|
||||
}
|
||||
|
||||
const handleOnDragEnd = () => {
|
||||
completeDrop();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='nugget'>
|
||||
<div
|
||||
className='nugget prompt-item'
|
||||
id={thisId}
|
||||
draggable
|
||||
onDragStart={handleOnDragStart}
|
||||
onDragOver={handleOnDragOver}
|
||||
onDragEnd={handleOnDragEnd}
|
||||
onMouseEnter={handleOnMouseEnter}
|
||||
onMouseOut={handleOnMouseLeave}
|
||||
data-promptitem-id={nugget.id}
|
||||
>
|
||||
<span className='text'>{nugget.item.name || nugget.item.prompt}</span>
|
||||
<Divider orientation="vertical" variant="middle" flexItem />
|
||||
<span className='score'>{scoreDisp}</span>
|
||||
<span className='buttons'>
|
||||
<ButtonGroup size="small" orientation='vertical'>
|
||||
<Button onClick={() => increaseNuggetScore(nugget.id)} className='incScore' aria-label="incScore">
|
||||
<KeyboardArrowUpIcon />
|
||||
</Button>
|
||||
<Button onClick={() => decreaseNuggetScore(nugget.id)} className='decScore' aria-label='decScore'>
|
||||
<KeyboardArrowDownIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup size="small" orientation='vertical'>
|
||||
<Button onClick={() => increaseNuggetScore(nugget.id)} className='incScore' aria-label="incScore">
|
||||
<KeyboardArrowUpIcon />
|
||||
</Button>
|
||||
<Button onClick={() => decreaseNuggetScore(nugget.id)} className='decScore' aria-label='decScore'>
|
||||
<KeyboardArrowDownIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { Menu, MenuItem } from "@material-ui/core";
|
||||
import React, { Children, ReactNode } from 'react';
|
||||
import React, { Children, DragEvent, ReactNode, useEffect } from 'react';
|
||||
import "./Operation.css";
|
||||
import { Op } from "../lib/operator";
|
||||
import { v4 as randomUUID } from "uuid";
|
||||
import { Operation as OperationType, changeOperationOp } from "../lib/prompt";
|
||||
import { $composition, Operation as OperationType, changeOperationOp } from "../lib/prompt";
|
||||
import Nugget from "./Nugget";
|
||||
import { PromptItemProps } from "./PromptItem";
|
||||
import { useStore } from "@nanostores/react";
|
||||
import { $sourceItem, cancelDrop, completeDrop, startHoverOver } from "../store/prompt-dnd";
|
||||
|
||||
interface OperationProps {
|
||||
operation : OperationType
|
||||
interface OperationProps extends PromptItemProps {
|
||||
operation: OperationType
|
||||
}
|
||||
|
||||
function Operation(props : OperationProps) {
|
||||
const {operation} = props;
|
||||
function Operation(props: OperationProps) {
|
||||
const { operation, onDragStart, onDragOver, onDragEnd, onDrop, onMouseEnter, onMouseLeave } = props;
|
||||
|
||||
const [contextMenu, setContextMenu] = React.useState<{
|
||||
mouseX: number;
|
||||
@ -19,7 +22,44 @@ function Operation(props : OperationProps) {
|
||||
} | null>(null);
|
||||
|
||||
const [id,] = React.useState(randomUUID);
|
||||
const sourceItem = useStore($sourceItem);
|
||||
const handleOnDragStart = () => {
|
||||
onDragStart ? onDragStart(operation) : null;
|
||||
}
|
||||
|
||||
const composition = useStore($composition);
|
||||
|
||||
|
||||
const handleOnMouseEnter = () => {
|
||||
if (!sourceItem) {
|
||||
return;
|
||||
}
|
||||
startHoverOver(operation);
|
||||
}
|
||||
|
||||
|
||||
const handleOnMouseLeave = () => {
|
||||
onMouseLeave ? onMouseLeave(operation) : null;
|
||||
}
|
||||
|
||||
const handleOnDragOver = ($e: DragEvent) => {
|
||||
if (!sourceItem) return;
|
||||
// extract the prompt item's ID:
|
||||
const targetId = $e.currentTarget.getAttribute("data-promptitem-id");
|
||||
if (sourceItem.id == targetId) return;
|
||||
console.log("current target id: %s", targetId);
|
||||
const promptItem = composition.find(i => (targetId === i.id));
|
||||
if (!promptItem) {
|
||||
console.warn("Could not find promptitem with ID %s", targetId);
|
||||
console.log(composition.map(c => c.id));
|
||||
return;
|
||||
}
|
||||
startHoverOver(promptItem);
|
||||
}
|
||||
|
||||
const handleOnDragEnd = () => {
|
||||
completeDrop();
|
||||
}
|
||||
const handleContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
setContextMenu(
|
||||
@ -45,7 +85,17 @@ function Operation(props : OperationProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="operation" onContextMenu={handleContextMenu}>
|
||||
<div
|
||||
draggable
|
||||
onDragStart={handleOnDragStart}
|
||||
onDragEnd={handleOnDragEnd}
|
||||
onDragOver={handleOnDragOver}
|
||||
onMouseEnter={handleOnMouseEnter}
|
||||
onMouseOut={handleOnMouseLeave}
|
||||
className="operation"
|
||||
onContextMenu={handleContextMenu}
|
||||
data-promptitem-id={operation.id}
|
||||
>
|
||||
<div className="title">{operation.op}</div>
|
||||
<div className="nuggets">
|
||||
{
|
||||
|
@ -3,13 +3,15 @@ import Masonry from '@mui/lab/Masonry';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import "./PromptComposer.css";
|
||||
import { PromptLibrary } from './PromptLibrary';
|
||||
import React, { useState } from 'react';
|
||||
import { $slottedComposition, LibraryItem, PromptItem, insertIntoComposition } from '../lib/prompt';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { $slottedComposition, LibraryItem, PromptItem, addToOperation, insertIntoComposition, itemIsNugget, itemIsOperation, lassoNuggets } from '../lib/prompt';
|
||||
import { Category } from '@mui/icons-material';
|
||||
import { useStore } from '@nanostores/react'
|
||||
import Nugget from './Nugget';
|
||||
import { Stack } from '@mui/material';
|
||||
import { Operation } from './Operation';
|
||||
import { Op, Operation } from './Operation';
|
||||
import { PromptItemProps } from './PromptItem';
|
||||
import { $dragDropState, $dropCandidate, $isDragInProgress, $sourceItem, completeDrop, endHoverOver, startDrag, startHoverOver } from '../store/prompt-dnd';
|
||||
|
||||
export interface PromptComposerProps {
|
||||
|
||||
@ -32,8 +34,45 @@ export default function PromptComposer(props: PromptComposerProps) {
|
||||
|
||||
const slottedComposition = useStore($slottedComposition);
|
||||
|
||||
const promptItemFactory = (promptItem : PromptItem, key : string) => {
|
||||
return "op" in promptItem ? <Operation operation={promptItem} key={key} /> : <Nugget nugget={promptItem} key={key} />
|
||||
const promptItemFactory = (promptItem: PromptItem, key: string) => {
|
||||
|
||||
const callbacks = {
|
||||
onDragStart: (item: PromptItem) => {
|
||||
if (itemIsNugget(promptItem)) {
|
||||
startDrag(item);
|
||||
}
|
||||
// TODO: operation
|
||||
},
|
||||
onDrop: (item: PromptItem) => {
|
||||
const dnd = useStore($dragDropState);
|
||||
const isDragInProgress = useStore($isDragInProgress);
|
||||
const dropCandidate = useStore($dropCandidate);
|
||||
const sourceItem = useStore($sourceItem);
|
||||
if (!(sourceItem && dropCandidate)) {
|
||||
return;
|
||||
}
|
||||
if (itemIsNugget(dropCandidate) && itemIsNugget(sourceItem)) {
|
||||
// TODO: show a pop-up to select the operator.
|
||||
lassoNuggets(dropCandidate.id, sourceItem.id, Op.AND);
|
||||
}
|
||||
if (itemIsNugget(sourceItem) && itemIsOperation(dropCandidate)) {
|
||||
addToOperation(sourceItem.id, dropCandidate.id);
|
||||
}
|
||||
completeDrop();
|
||||
},
|
||||
onDragEnd: (item: PromptItem) => {
|
||||
|
||||
},
|
||||
onMouseEnter: (item: PromptItem) => {
|
||||
},
|
||||
onMouseLeave: (item: PromptItem) => {
|
||||
endHoverOver();
|
||||
},
|
||||
} as PromptItemProps;
|
||||
|
||||
return ("op" in promptItem ?
|
||||
<Operation operation={promptItem} key={key} {...callbacks} />
|
||||
: <Nugget nugget={promptItem} key={key} {...callbacks} />)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -51,10 +90,10 @@ export default function PromptComposer(props: PromptComposerProps) {
|
||||
<div>
|
||||
{
|
||||
slottedComposition.map((itemCol, i) => (
|
||||
<Stack>
|
||||
{itemCol.map((promptItem, j) => promptItemFactory(promptItem, `item-${j}-${j}`))}
|
||||
</Stack>
|
||||
))
|
||||
<Stack>
|
||||
{itemCol.map((promptItem, j) => promptItemFactory(promptItem, `item-${j}-${j}`))}
|
||||
</Stack>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
7
src/components/PromptItem.css
Normal file
7
src/components/PromptItem.css
Normal file
@ -0,0 +1,7 @@
|
||||
.prompt-item .drag-target-highlight {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
.prompt-item .drag-target-hover {
|
||||
border: 1px solid blue;
|
||||
}
|
22
src/components/PromptItem.tsx
Normal file
22
src/components/PromptItem.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import {PromptItem as PIType} from "../lib/prompt"
|
||||
|
||||
/**
|
||||
* NOTE: Drag-n-drop rules!
|
||||
*
|
||||
* - Nuggets can be dragged and dropped into...
|
||||
* - another nugget
|
||||
* - when this happens, an Operation is created.
|
||||
* - an operation
|
||||
* - when this happens, the Nugget is added to the operation.
|
||||
* - Operations can be dragged, but only to reorder.
|
||||
* - Nuggets can also be dragged to be re-ordered.
|
||||
*/
|
||||
|
||||
export interface PromptItemProps {
|
||||
onDragStart?: (item : PIType) => void,
|
||||
onDragEnd?: (item: PIType) => void,
|
||||
onDragOver?: (item: PIType) => void,
|
||||
onDrop?: (item : PIType) => void,
|
||||
onMouseEnter?: (item : PIType) => void,
|
||||
onMouseLeave?: (item : PIType) => void,
|
||||
}
|
Reference in New Issue
Block a user