~breatheoutbreathein/u4u

9f911501de04be875f35f5f60f7db741a5c61c70 — Joseph Turner 2 years ago fa1af29 refactor_point
more progress
M src/components/MainPoint.tsx => src/components/MainPoint.tsx +33 -11
@@ 23,8 23,9 @@ import { ReplyFill, ArrowUp, XLg, ArrowsMove, Files } from 'react-bootstrap-icon
import './point.scss'
import './button.scss'

import { getPointIfReference, getReferenceData, isDraft } from '../dataModels/pointUtils'
import { getPointIfReference, getReferenceData, isDraft, isQuote } from '../dataModels/pointUtils'
import { useDragPoint } from '../hooks/useDragPoint'
import { ReadOnlyPoint } from './ReadOnlyPoint'
import Point from './Point'

import { useAppSelector, useAppDispatch } from '../hooks/useRedux'


@@ 66,7 67,7 @@ export const MainPoint = ({ messageURL, pointURL }: Props): ReactElement => {
  }

  const handleShapeIconClick = (e: React.MouseEvent): void => {
    setShowOptions(true)
    /*     setShowOptions(true) */
    dispatch(togglePoint({ pointURL }))
  }



@@ 160,26 161,47 @@ export const MainPoint = ({ messageURL, pointURL }: Props): ReactElement => {
    }
  }

  const readOnlyPointButtons = (
    <>
      {(isQuote(point)) && (
        <button className='button' onClick={handleViewOriginalMessageButtonClick} title='View original message'>
          <ArrowUp className='bi' />
        </button>
      )}
      <button className='button' onClick={handleReplyButtonClick} title='Reply to this point'>
        <ReplyFill className='bi' />
      </button>
      <button className='button' onClick={handleMoveOrQuotePointsClick} title='Quote points'>
        <Files className='bi' />
      </button>
    </>
  )

  let pointWrapperClassName = 'main-point'
  if (isSelected) pointWrapperClassName += ' selected'

  if (!isDraft(pointURL) || (isQuote(point))) {
    return (
      <ReadOnlyPoint
        pointURL={pointURL}
        isSelected={isSelected}
        buttons={readOnlyPointButtons}
      />
    )
  }

  return (
    <div className={pointWrapperClassName} onClick={handlePointClick}>
      <Point
        url={pointURL}
        displayPoint={{ ...point, content }}
        referenceData={referenceData}
        buttons={buttons}
        pointURL={pointURL}
        messageURL={messageURL}
        isMainPoint
        isSelected={isSelected}
        showOptions={showOptions}
        readOnlyOverride={!isDraft(pointURL)}
        handleChange={handleChange}
        handleKeyDown={handleKeyDown}
        handleBlur={handleBlur}
        expandedRegion={expandedRegion}
        handleShapeIconClick={handleShapeIconClick}
        ref={pointRef}
      />
    </div>
  )
}


M src/components/Point.tsx => src/components/Point.tsx +31 -14
@@ 31,6 31,20 @@ import './point.scss'
import './button.scss'

import { useAppDispatch, useAppSelector } from '../hooks/useRedux'
import { jumpToPrevPointThunk, jumpToNextPointThunk, clearCursorPosition } from '../slices/cursorPosition'
import {
  draftPointCreate,
  splitIntoTwoPoints,
  combinePointsThunk,
  pointsMoveWithinMessageThunk,
  draftPointUpdate,
  deletePointsThunk,
  autoDeleteEmptyPoint,
  setMainThunk
} from '../slices/drafts'
import { hoverOver } from '../slices/drag'
import { togglePoint, viewOriginalMessage } from '../slices/selectedPoints'
import { openMovePointsDraftsModal, openReplyToPointDraftsModal } from '../slices/modal'

import { Banner } from './Banner'
import { AllShapes } from './AllShapes'


@@ 38,6 52,7 @@ import { useTextareaIndent } from '../hooks/useTextareaIndent'

interface Props {
  pointURL: string
  messageURL: string
  // TODO: make not optional
  isMainPoint: boolean
  isSelected: boolean


@@ 46,10 61,11 @@ interface Props {
}

// TODO: fix ref type below
const Point = forwardRef<any, Props>((props, ref) => {
const Point = forwardRef<any>((props, ref) => {
  const dispatch = useAppDispatch()
  const { pointURL, messageURL, isMainPoint, isSelected, expandedRegion, handleShapeIconClick } = props

  const point = useAppSelector(({ drafts }) => drafts.byURL[messageURL].points[pointURL])
  const point = useAppSelector(({ drafts }) => drafts.byURL[messageURL].present.points[pointURL])

  const divRef = useRef<HTMLDivElement>(null)
  const buttonRef = useRef<HTMLButtonElement>(null)


@@ 101,8 117,6 @@ const Point = forwardRef<any, Props>((props, ref) => {
  }

  const handleKeyDown = (e: React.KeyboardEvent): void => {
    if (!isDraft(pointURL) || referenceData !== undefined) return

    const textareaRef = pointRef.current.textarea
    const isAtBeginning = textareaRef.selectionStart === 0
    const isAtEnd = textareaRef.selectionEnd === textareaRef.value.length


@@ 146,7 160,6 @@ const Point = forwardRef<any, Props>((props, ref) => {
    if (!e.currentTarget.contains(e.relatedTarget as Node)) { // Don't hide options if focus switches to another child of point-wrapper div
      setShowOptions(false)
    }
    if (!isDraft(pointURL) || referenceData !== undefined) return
    if (content === '') {
      dispatch(autoDeleteEmptyPoint({ messageURL, pointURLs: [pointURL] }))
    } else {


@@ 162,24 175,28 @@ const Point = forwardRef<any, Props>((props, ref) => {
  // The useState and useEffect are purely to cause the component to
  // re-render after it first mounts. A better solution must exist.
  const [, setCounter] = useState(0)
  const { expandedRegion } = props
  useEffect(() => {
    setCounter((c) => c + 1)
  }, [expandedRegion, showOptions])

  const handleShapeIconClick = (e: React.MouseEvent): void => {
  const handleShapeButtonClick = (e: React.MouseEvent): void => {
    setShowOptions(true)
    props.handleShapeIconClick(e)
    handleShapeIconClick(e)
  }

  function handleMoveOrQuotePointsClick (e: React.MouseEvent): void {
    dispatch(openMovePointsDraftsModal({ pointURL }))
    e.stopPropagation()
  }

  function handleXButtonClick (e: React.MouseEvent): void {
    dispatch(deletePointsThunk({ pointURLs: [pointURL], messageURL }))
    e.stopPropagation()
  }

  // TODO: Different buttons if main point
  const buttons = (
    <>
      {(referenceData !== undefined) && (
        <button className='button' onClick={handleViewOriginalMessageButtonClick} title='View original message'>
          <ArrowUp className='bi' />
        </button>
      )}
      <button className='button' onClick={handleSetMainPointButtonClick} title='Set main point'>
        <ExclamationLg className='bi' />
      </button>


@@ 204,7 221,7 @@ const Point = forwardRef<any, Props>((props, ref) => {
      <div className='button-group' ref={buttonGroupRef}>
        <button
          className='button'
          onClick={handleShapeIconClick}
          onClick={handleShapeButtonClick}
          title='Select point'
          ref={buttonRef}
        >

M src/components/ReadOnlyPoint.tsx => src/components/ReadOnlyPoint.tsx +14 -5
@@ 38,7 38,7 @@ interface Props {
  buttons?: React.ReactNode
}

export const ReadOnlyPoint = ({ pointURL, isSelected, buttons }: Props): ReactElement => {
export const ReadOnlyPoint = ({ pointURL, isSelected, handleShapeIconClick, buttons }: Props): ReactElement => {
  const point = useAppSelector(({ published, drafts }) => getPointByURL(pointURL, published, drafts))
  const { content, shape, createdAt } = useAppSelector(({ published, drafts }) => getPointIfReference(pointURL, published, drafts))
  const { authorURL, borderColor } = useAppSelector(({ published, drafts, authors }) => {


@@ 55,7 55,7 @@ export const ReadOnlyPoint = ({ pointURL, isSelected, buttons }: Props): ReactEl

  const [showOptions, setShowOptions] = useState(false)

  const handleShapeIconClick = (e: React.MouseEvent): void => {
  const handleShapeButtonClick = (e: React.MouseEvent): void => {
    setShowOptions(true)
    if (handleShapeIconClick !== undefined) handleShapeIconClick(e)
  }


@@ 63,13 63,21 @@ export const ReadOnlyPoint = ({ pointURL, isSelected, buttons }: Props): ReactEl
  function handlePointClick (e: React.MouseEvent): void {
    if (isExpanded) e.stopPropagation()
  }
  

  function handleBlur (e: React.FocusEvent<HTMLDivElement>): void {
    if (!e.currentTarget.contains(e.relatedTarget as Node)) { // Don't hide options if focus switches to another child of point-wrapper div
      setShowOptions(false)
    }
  }

  let readOnlyPointWrapperClassName = 'read-only-point-wrapper'
  if (isQuote(point)) readOnlyPointWrapperClassName += ' quote'
  if (isSelected) readOnlyPointWrapperClassName += ' selected'

  return (
    <div onClick={handlePointClick}>
    <div
      onClick={handlePointClick}
    >
      {isQuote(point) && (
        <div className='read-only-point-top-bar'>
          <TimeStamp createdAt={createdAt} />


@@ 78,12 86,13 @@ export const ReadOnlyPoint = ({ pointURL, isSelected, buttons }: Props): ReactEl
      )}
      <div
        className={readOnlyPointWrapperClassName}
        onBlur={handleBlur}
        style={{ borderColor }}
      >
        <div className='button-group'>
          <button
            className='button'
            onClick={handleShapeIconClick}
            onClick={handleShapeButtonClick}
            title='Select point'
          >
            <AllShapes shape={shape} />

M src/components/RegionPoint.tsx => src/components/RegionPoint.tsx +3 -2
@@ 155,7 155,7 @@ export const RegionPoint = ({ messageURL, pointURL, index }: Props): ReactElemen
    e.stopPropagation()
  }

  const buttonsIfReadOnlyPoint = (
  const readOnlyPointButtons = (
    <>
      {(isQuote(point)) && (
        <button className='button' onClick={handleViewOriginalMessageButtonClick} title='View original message'>


@@ 179,7 179,7 @@ export const RegionPoint = ({ messageURL, pointURL, index }: Props): ReactElemen
      <ReadOnlyPoint
        pointURL={pointURL}
        isSelected={isSelected}
        buttons={buttonsIfReadOnlyPoint}
        buttons={readOnlyPointButtons}
      />
    )
  }


@@ 188,6 188,7 @@ export const RegionPoint = ({ messageURL, pointURL, index }: Props): ReactElemen
    <div className={pointWrapperClassName} onClick={handlePointClick}>
      <Point
        pointURL={pointURL}
        messageURL={messageURL}
        isMainPoint={false}
        isSelected={isSelected}
        expandedRegion={expandedRegion}