Commit 2dc6924c authored by Filip Šenk's avatar Filip Šenk
Browse files

Feat: add inject pattern export

parent 2e07cb84
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ type Props<T extends string | number> = {
  buttonClassName?: string
  fill?: boolean
  placeholder?: string
  disabled?: boolean
}

const SharedSelect = <T extends string | number>({
@@ -66,6 +67,7 @@ const SharedSelect = <T extends string | number>({
  buttonClassName,
  fill = false,
  placeholder,
  disabled,
}: Props<T>) => (
  <Select<OptionProps<T>>
    items={items}
@@ -78,6 +80,7 @@ const SharedSelect = <T extends string | number>({
    onItemSelect={onItemSelect}
    className={selectClassname}
    fill={fill}
    disabled={disabled}
    popoverProps={{
      enforceFocus: false,
      autoFocus: false,
+149 −0
Original line number Diff line number Diff line
import { Button, Popover } from '@blueprintjs/core'
import { css } from '@emotion/css'
import { notify } from '@inject/shared'
import type { Dispatch, SetStateAction } from 'react'
import { useEffect, useState, type FC } from 'react'
import { exportInjectPattern } from '../../../importExport'
import type { TreeNode } from '../../../indexeddb/types'
import type { InjectPattern } from '../../../routes/create/tree-view'
import { getNodeName } from '../Node/utils'
import { getSelectedRec } from '../utils'

const contentWrapper = css`
  min-width: 18rem;
  max-height: 20rem;
  padding: 1rem;
  overflow-y: auto;
  max-width: 25rem;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
`
const buttonWrapper = css`
  padding-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
`

type AssignPatternsProps = {
  onClick: () => void
  selected: boolean
  injectPattern: InjectPattern
  setInjectPattern: Dispatch<SetStateAction<InjectPattern>>
  selectedNodes: TreeNode[]
  setSelectedNodes: Dispatch<SetStateAction<TreeNode[]>>
}

export const AssignPatterns: FC<AssignPatternsProps> = ({
  onClick,
  selected,
  injectPattern,
  setInjectPattern,
  setSelectedNodes,
  selectedNodes,
}) => {
  const [opened, setOpened] = useState(false)
  const handleDownload = async () => {
    if (injectPattern.begin && injectPattern.end) {
      await exportInjectPattern(selectedNodes, injectPattern.begin)
    }
  }

  useEffect(() => {
    if (!selected) {
      setOpened(false)
    }
  }, [selected])

  useEffect(() => {
    if (injectPattern.begin && injectPattern.end) {
      const children = getSelectedRec(injectPattern.begin, injectPattern.end.id)

      if (children.length <= 1) {
        notify('Wrongly selected begin and end', JSON.stringify(''), {
          intent: 'warning',
        })
      } else {
        setSelectedNodes(children)
      }
    } else {
      setSelectedNodes([])
    }
  }, [injectPattern.begin, injectPattern.end, setSelectedNodes])

  return (
    <div onClick={onClick}>
      <Popover
        fill
        minimal
        position={'bottom-left'}
        autoFocus={false}
        enforceFocus={false}
        captureDismiss={false}
        content={
          <>
            {selected && (
              <div className={contentWrapper}>
                <Button
                  text={
                    injectPattern.begin
                      ? getNodeName(injectPattern.begin)
                      : 'Select a begin'
                  }
                  fill
                  active={injectPattern.beginSelect}
                  intent={injectPattern.beginSelect ? 'success' : 'none'}
                  onClick={() =>
                    setInjectPattern({ ...injectPattern, beginSelect: true })
                  }
                />
                <Button
                  text={
                    injectPattern.end
                      ? getNodeName(injectPattern.end)
                      : 'Select a end'
                  }
                  active={!injectPattern.beginSelect}
                  intent={!injectPattern.beginSelect ? 'success' : 'none'}
                  fill
                  onClick={() =>
                    setInjectPattern({ ...injectPattern, beginSelect: false })
                  }
                />

                <div className={buttonWrapper}>
                  <Button
                    intent={'primary'}
                    disabled={!injectPattern.begin || !injectPattern.end}
                    onClick={handleDownload}
                    icon='download'
                    alignText='left'
                  >
                    Export Inject Pattern
                  </Button>
                </div>
              </div>
            )}
          </>
        }
        isOpen={opened}
      >
        <Button
          title='Download Inject Pattern'
          active={opened}
          icon='download'
          alignText='left'
          fill
          minimal={!selected}
          onClick={() => {
            setOpened(prev => !prev)
          }}
        >
          Inject Pattern
        </Button>
      </Popover>
    </div>
  )
}
+2 −23
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import {
  CONTENT_HEIGHT,
  getControlLinks,
  getFilteredQuestions,
  getNodeName,
  getQuestionLinks,
  isControlUsed,
  QUESTION_HEIGHT,
@@ -266,28 +267,6 @@ const AdditionalNodeData: FC<{
  return null
}

const getName = (node: TreeNode) => {
  switch (node.type) {
    case TreeNodeTypes.EMAIL_ADDRESS: {
      return node.data.address
    }
    case TreeNodeTypes.TOOL_RESPONSE: {
      return (
        node.data.toolName +
        (node.data.param.length > 0 ? ` - ${node.data.param}` : '')
      )
    }
    case TreeNodeTypes.INJECT:
    case TreeNodeTypes.QUESTIONNAIRE:
    case TreeNodeTypes.MILESTONE: {
      return node.data.display_name ?? node.data.name
    }
    case TreeNodeTypes.START: {
      return node.data.name
    }
  }
}

type EditorNodeProps = {
  node: HierarchyNode<TreeNode>
  nodeMap: Map<string, HierarchyNode<TreeNode>>
@@ -301,7 +280,7 @@ export const EditorNode: FC<EditorNodeProps> = ({
  selected,
  nodeMap,
}) => {
  const name = getName(node.data)
  const name = getNodeName(node.data)

  let currentY = NODE_PADDING_Y

+22 −0
Original line number Diff line number Diff line
@@ -148,3 +148,25 @@ export const getFilteredQuestions = (questionnaire: Questionnaire) => {
  })
  return result
}

export const getNodeName = (node: TreeNode) => {
  switch (node.type) {
    case TreeNodeTypes.EMAIL_ADDRESS: {
      return node.data.address
    }
    case TreeNodeTypes.TOOL_RESPONSE: {
      return (
        node.data.toolName +
        (node.data.param.length > 0 ? ` - ${node.data.param}` : '')
      )
    }
    case TreeNodeTypes.INJECT:
    case TreeNodeTypes.QUESTIONNAIRE:
    case TreeNodeTypes.MILESTONE: {
      return node.data.display_name ?? node.data.name
    }
    case TreeNodeTypes.START: {
      return node.data.name
    }
  }
}
+59 −44
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@ import { css } from '@emotion/css'
import { notify } from '@inject/shared'
import { useLiveQuery } from 'dexie-react-hooks'
import type { FC } from 'react'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import {
  assignToLearningActivity,
  removeAssigned,
@@ -38,12 +38,16 @@ type AssignLearningActivityProps = {
  selectedNodes: TreeNode[]
  onClose: () => void
  onUpdate: () => void
  onClick: () => void
  selected: boolean
}

export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({
  selectedNodes,
  onClose,
  onUpdate,
  onClick,
  selected,
}) => {
  const learningActivities = useLiveQuery(() => getAllExtendedActivities(), [])

@@ -56,6 +60,13 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({
  const [selectedLa, setSelectedLa] = useState<OptionProps<string> | null>(null)
  const [opened, setOpened] = useState(false)

  useEffect(() => {
    if (!selected) {
      setOpened(false)
      setSelectedLa(null)
    }
  }, [selected])

  const handleAssignSelected = async () => {
    try {
      if (!selectedLa) {
@@ -92,7 +103,7 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({
  }

  return (
    <div>
    <div onClick={onClick}>
      <Popover
        fill
        minimal
@@ -101,6 +112,8 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({
        enforceFocus={false}
        captureDismiss={false}
        content={
          <>
            {selected && (
              <div className={contentWrapper}>
                <div className={selectWrapper}>
                  <SharedSelect
@@ -144,6 +157,8 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({
                  </Button>
                </div>
              </div>
            )}
          </>
        }
        isOpen={opened}
      >
@@ -153,7 +168,7 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({
          icon={'swap-horizontal'}
          alignText='left'
          fill
          minimal
          minimal={!selected}
          onClick={() => {
            setOpened(prev => !prev)
            setSelectedLa(null)
Loading