|
1 | 1 | import { useRef, useEffect, useMemo, useState, useCallback } from 'react' |
| 2 | +import { ReactSortable } from 'react-sortablejs' |
2 | 3 | import styles from './PromptField.module.scss' |
3 | 4 | import cn from 'classnames' |
4 | 5 | import { Icon } from '../../../common/Icon' |
@@ -76,6 +77,7 @@ export type PromptFieldProps = { |
76 | 77 | on_tab_change: (index: number) => void |
77 | 78 | on_new_tab: () => void |
78 | 79 | on_tab_delete: (index: number) => void |
| 80 | + on_tabs_reorder?: (new_order: number[]) => void |
79 | 81 | warning?: string |
80 | 82 | voice_input_push_to_talk?: boolean |
81 | 83 | translations: { |
@@ -107,6 +109,23 @@ export const PromptField: React.FC<PromptFieldProps> = (props) => { |
107 | 109 | 'at' | 'hash' | 'curly' | null |
108 | 110 | >(null) |
109 | 111 |
|
| 112 | + const [tab_items, set_tab_items] = useState<{ id: string }[]>([]) |
| 113 | + |
| 114 | + useEffect(() => { |
| 115 | + set_tab_items((prev) => { |
| 116 | + if (prev.length === props.tabs_count) return prev |
| 117 | + if (prev.length < props.tabs_count) { |
| 118 | + return [ |
| 119 | + ...prev, |
| 120 | + ...Array.from({ length: props.tabs_count - prev.length }).map(() => ({ |
| 121 | + id: Math.random().toString(36).substring(7) |
| 122 | + })) |
| 123 | + ] |
| 124 | + } |
| 125 | + return prev.slice(0, props.tabs_count) |
| 126 | + }) |
| 127 | + }, [props.tabs_count]) |
| 128 | + |
110 | 129 | const invocation_button_ref = useRef<HTMLButtonElement>(null) |
111 | 130 | const chevron_button_ref = useRef<HTMLButtonElement>(null) |
112 | 131 | const disconnected_chevron_button_ref = useRef<HTMLButtonElement>(null) |
@@ -359,6 +378,70 @@ export const PromptField: React.FC<PromptFieldProps> = (props) => { |
359 | 378 | onClick={() => input_ref.current?.focus()} |
360 | 379 | > |
361 | 380 | <div className={styles['input-wrapper']}> |
| 381 | + {props.tabs_count > 1 ? ( |
| 382 | + <ReactSortable |
| 383 | + list={tab_items} |
| 384 | + setList={(new_list) => { |
| 385 | + const has_changed = new_list.some( |
| 386 | + (item, i) => item.id !== tab_items[i]?.id |
| 387 | + ) |
| 388 | + if (has_changed) { |
| 389 | + const new_order = new_list.map((item) => |
| 390 | + tab_items.findIndex((t) => t.id === item.id) |
| 391 | + ) |
| 392 | + set_tab_items(new_list) |
| 393 | + props.on_tabs_reorder?.(new_order) |
| 394 | + } |
| 395 | + }} |
| 396 | + className={styles.tabs} |
| 397 | + style={{ |
| 398 | + position: 'relative', |
| 399 | + zIndex: 2, |
| 400 | + margin: '7px 1px 0 5px' |
| 401 | + }} |
| 402 | + animation={150} |
| 403 | + filter={`.${styles['tabs__tab--new']}`} |
| 404 | + > |
| 405 | + {tab_items.map((item, i) => ( |
| 406 | + <div |
| 407 | + key={item.id} |
| 408 | + className={cn(styles.tabs__tab, { |
| 409 | + [styles['tabs__tab--active']]: i === props.active_tab_index |
| 410 | + })} |
| 411 | + onClick={(e) => { |
| 412 | + e.stopPropagation() |
| 413 | + props.on_tab_change(i) |
| 414 | + }} |
| 415 | + > |
| 416 | + <div className={styles['tabs__tab-icon']} /> |
| 417 | + </div> |
| 418 | + ))} |
| 419 | + <div |
| 420 | + className={cn(styles.tabs__tab, styles['tabs__tab--new'])} |
| 421 | + onClick={(e) => { |
| 422 | + e.stopPropagation() |
| 423 | + props.on_new_tab() |
| 424 | + }} |
| 425 | + /> |
| 426 | + </ReactSortable> |
| 427 | + ) : props.tabs_count === 1 ? ( |
| 428 | + <div |
| 429 | + className={styles.tabs} |
| 430 | + style={{ |
| 431 | + position: 'relative', |
| 432 | + zIndex: 2, |
| 433 | + margin: '7px 1px 0 5px' |
| 434 | + }} |
| 435 | + > |
| 436 | + <div |
| 437 | + className={cn(styles.tabs__tab, styles['tabs__tab--new'])} |
| 438 | + onClick={(e) => { |
| 439 | + e.stopPropagation() |
| 440 | + props.on_new_tab() |
| 441 | + }} |
| 442 | + /> |
| 443 | + </div> |
| 444 | + ) : null} |
362 | 445 | <div |
363 | 446 | ref={input_ref} |
364 | 447 | contentEditable={true} |
|
0 commit comments