Skip to content

Commit

Permalink
add some convenience stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiion committed Aug 20, 2024
1 parent 094eb2a commit 3e7740d
Show file tree
Hide file tree
Showing 21 changed files with 578 additions and 210 deletions.
39 changes: 39 additions & 0 deletions src/app/hooks/aiContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { OpenAI } from 'openai'
import React, { useEffect } from 'react'
import { useAppSelector } from './reduxHooks'
import { selectApiKey, selectOrganization } from '../../features/wizard/credentialsSlice'
import NiceModal from '@ebay/nice-modal-react'
import { OpenAIKeyModal } from '../../features/modals/OpenAIKeyModal'

type AIContextType = {
openAIInstance?: OpenAI
}

export const AIContext = React.createContext<AIContextType>({})

export const AIProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const organizationStore = useAppSelector(selectOrganization)
const apiKeyStore = useAppSelector(selectApiKey)
const organization = process.env.REACT_APP_OPENAI_ORGANIZATION || organizationStore
const apiKey = process.env.REACT_APP_OPENAI_API_KEY || apiKeyStore
const [openAIInstance, setOpenAIInstance] = React.useState<OpenAI | undefined>(undefined)
useEffect(() => {
if (organization && apiKey && organization !== '' && apiKey !== '') {
setOpenAIInstance(
new OpenAI({
dangerouslyAllowBrowser: true,
organization,
apiKey,
})
)
}
}, [organization, apiKey, setOpenAIInstance])
useEffect(() => {
if (!openAIInstance) {
NiceModal.show(OpenAIKeyModal, { onConfirm: (instance) => setOpenAIInstance(instance), onReject: () => {} })
}
}, [openAIInstance, setOpenAIInstance])
return <AIContext.Provider value={{ openAIInstance }}>{children}</AIContext.Provider>
}

export const useAI = () => React.useContext(AIContext)
39 changes: 37 additions & 2 deletions src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'
import { persistStore, persistReducer } from 'redux-persist'
import counterReducer from '../features/counter/counterSlice'
import jsonFormsEditReducer from '../features/wizard/WizardSlice'
import templateSlice from '../features/wizard/TemplateSlice'
import templateSlice, { restoreTemplates } from '../features/wizard/TemplateSlice'
import storage from 'redux-persist/lib/storage'
import { formsDataReducer } from '../features/wizard/FormDataSlice' // defaults to localStorage for web
import { formsDataReducer, initialFormState, restoreForms } from '../features/wizard/FormDataSlice'
import { credentialsReducer } from '../features/wizard/credentialsSlice' // defaults to localStorage for web
export const store = configureStore({
reducer: {
counter: counterReducer,
jsonFormsEdit: jsonFormsEditReducer,
credentials: credentialsReducer,
template: persistReducer(
{
key: 'template',
Expand All @@ -30,3 +32,36 @@ export const persistor = persistStore(store)
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, Action<string>>

export const appVersion = process.env.REACT_APP_VERSION || process.env.REACT_APP_COMMIT || new Date().toISOString()

type StoreBackup = { appVersion: string; store: { template: RootState['template']; formsData: RootState['formsData'] } }
export const downloadBackup: () => string = () => {
const template = store.getState().template
const formsData = store.getState().formsData
const appVersion = process.env.REACT_APP_VERSION || process.env.REACT_APP_COMMIT || new Date().toISOString()
const backup: StoreBackup = {
appVersion,
store: {
template,
formsData,
},
}
const blob = new Blob([JSON.stringify(backup)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
return url
}

export const uploadBackup = (backup: StoreBackup) => {
console.log(
`uploading backup from App Version: ${backup.appVersion} to current instance of version ${appVersion}`,
backup
)
store.dispatch(restoreTemplates(backup.store.template))
store.dispatch(restoreForms(backup.store.formsData))
}

export const resetStore = () => {
store.dispatch(restoreTemplates({ templates: [] }))
store.dispatch(restoreForms(initialFormState))
}
41 changes: 24 additions & 17 deletions src/features/dragAndDrop/DropTargetFormsPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,39 @@ import { JsonForms } from '@jsonforms/react'
import { materialCells, materialRenderers } from '@jsonforms/material-renderers'
import { JsonSchema7, UISchemaElement } from '@jsonforms/core'

const DropTargetFormsPreview: React.FC<{ metadata: DraggableComponent }> = ({ metadata }) => (
const DropTargetFormsPreview: React.FC<{ metadata: DraggableComponent; topLevelUISchema?: boolean }> = ({
metadata,
topLevelUISchema,
}) => (
<>
{metadata.jsonSchemaElement && (
<JsonForms
data={{}}
renderers={materialRenderers}
cells={materialCells}
uischema={
{
type: 'VerticalLayout',
elements: [
{
type: 'Control',
scope: `#/properties/${metadata.name}`,
...(metadata.uiSchema || {}),
},
],
} as UISchemaElement
topLevelUISchema && metadata.uiSchema
? metadata.uiSchema
: ({
type: 'VerticalLayout',
elements: [
{
type: 'Control',
scope: `#/properties/${metadata.name}`,
...(metadata.uiSchema || {}),
},
],
} as UISchemaElement)
}
schema={
{
type: 'object',
properties: {
[metadata?.name]: metadata.jsonSchemaElement,
},
} as JsonSchema7
topLevelUISchema && metadata.jsonSchemaElement
? metadata.jsonSchemaElement
: ({
type: 'object',
properties: {
[metadata?.name]: metadata.jsonSchemaElement,
},
} as JsonSchema7)
}
/>
)}
Expand Down
33 changes: 27 additions & 6 deletions src/features/home/ClickBox.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import { Avatar, Button, Card, CardActionArea, CardContent, CardHeader, CardMedia, Typography } from '@mui/material'
import React, { useCallback } from 'react'
import { useAppDispatch } from '../../app/hooks/reduxHooks'
import { loadForm } from '../wizard/FormDataSlice'
import { useAppDispatch, useAppSelector } from '../../app/hooks/reduxHooks'
import { listFormData, loadForm, removeForm } from '../wizard/FormDataSlice'
import { red } from '@mui/material/colors'
import { replaceSchema, replaceUISchema } from '../wizard/WizardSlice'

type ClickBoxProps = {
id: string
title: string
avatar?: string
disableActions?: boolean
}
export const ClickBox = ({ id, title, avatar }: ClickBoxProps) => {
export const ClickBox = ({ id, title, avatar, disableActions }: ClickBoxProps) => {
const dispatch = useAppDispatch()
const formList = useAppSelector(listFormData)
const handleLoad = useCallback(() => {
dispatch(loadForm({ id }))
}, [id])

const handleLoadFormData = useCallback(() => {
const { jsonSchema, uiSchema } = formList.find((f) => f.id === id) || { jsonSchema: {}, uiSchema: undefined }
dispatch(replaceSchema(jsonSchema))
dispatch(replaceUISchema(uiSchema))
dispatch(loadForm({ id }))
}, [id, formList])

const handleRemove = useCallback(() => {
dispatch(removeForm({ id: id }))
}, [id])

return (
<Card>
<CardHeader
Expand All @@ -26,9 +40,16 @@ export const ClickBox = ({ id, title, avatar }: ClickBoxProps) => {
title={title}
></CardHeader>
{avatar && <CardMedia component="img" height="194" image={avatar} />}
<CardActionArea>
<Button onClick={handleLoad}>load Form</Button>
</CardActionArea>

{!disableActions && (
<CardActionArea>
<Button onClick={handleLoadFormData} variant={'contained'}>
load Form & Data
</Button>
<Button onClick={handleLoad}>load only Data</Button>
<Button onClick={handleRemove}>Eintrag entfernen</Button>
</CardActionArea>
)}
</Card>
)
}
26 changes: 19 additions & 7 deletions src/features/home/DragBox.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react'
import React, { useCallback } from 'react'
import { useDrag } from 'react-dnd'
import { Button, Card, CardActionArea, CardActions, CardContent, Typography } from '@mui/material'
import { DraggableComponent, replaceSchema } from '../wizard/WizardSlice'
import { Button, Card, CardActionArea, CardContent, Typography } from '@mui/material'
import { DraggableComponent, replaceSchema, replaceUISchema } from '../wizard/WizardSlice'
import { useAppDispatch } from '../../app/hooks/reduxHooks'
import { newForm } from '../wizard/FormDataSlice'
import { removeTemplate } from '../wizard/TemplateSlice'

type DragBoxProps = {
name: string
img?: string
componentMeta: DraggableComponent
disableActions?: boolean
}
const DragBox = ({ name = 'Eingabefeld', img = '', componentMeta }: DragBoxProps) => {
const DragBox = ({ name = 'Eingabefeld', img = '', componentMeta, disableActions }: DragBoxProps) => {
const dispatch = useAppDispatch()
const [, dragRef] = useDrag(
() => ({
Expand All @@ -29,6 +32,12 @@ const DragBox = ({ name = 'Eingabefeld', img = '', componentMeta }: DragBoxProps

const handleReplace = useCallback(() => {
dispatch(replaceSchema(componentMeta.jsonSchemaElement))
dispatch(replaceUISchema(componentMeta.uiSchema))
dispatch(newForm({ jsonSchema: componentMeta.jsonSchemaElement, uiSchema: componentMeta.uiSchema }))
}, [dispatch, componentMeta])

const handleRemove = useCallback(() => {
dispatch(removeTemplate({ element: componentMeta }))
}, [dispatch, componentMeta])

return (
Expand All @@ -39,9 +48,12 @@ const DragBox = ({ name = 'Eingabefeld', img = '', componentMeta }: DragBoxProps
{name}
</Typography>
</CardContent>
<CardActionArea>
<Button onClick={handleReplace}>replace current</Button>
</CardActionArea>
{!disableActions && (
<CardActionArea>
<Button onClick={handleReplace}>Formular ersetzen</Button>
<Button onClick={handleRemove}>Template entfernen</Button>
</CardActionArea>
)}
</CardActionArea>
</Card>
)
Expand Down
34 changes: 28 additions & 6 deletions src/features/home/LeftDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ClickBox } from './ClickBox'
import { listFormData, newForm } from '../wizard/FormDataSlice'
import { CreateRounded } from '@mui/icons-material'

const drawerWidth = 240
const drawerWidth = 340

export const basicDraggableComponents: DraggableComponent[] = [
{
Expand Down Expand Up @@ -51,6 +51,26 @@ export const basicDraggableComponents: DraggableComponent[] = [
},
},
},
{
name: 'Auswahlfeld',
jsonSchemaElement: {
type: 'string',
enum: ['DE', 'IT', 'JP', 'US', 'RU', 'Other'],
},
},
{
name: 'Zahleneingabe',
jsonSchemaElement: {
type: 'number',
},
},
{
name: 'E-Mail',
jsonSchemaElement: {
type: 'string',
format: 'email',
},
},
]

export const advancedDraggableComponents: DraggableComponent[] = [
Expand Down Expand Up @@ -161,12 +181,12 @@ export default function LeftDrawer() {
<TabContext value={activeTab}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={handleChange} aria-label="lab API tabs example">
<Tab label="Tools" value="1" />
<Tab label="Templates" value="2" />
<Tab label="Templates" value="1" />
<Tab label="Tools" value="2" />
<Tab label="Data" value="3" />
</TabList>
</Box>
<TabPanel value="1" sx={{ p: 0 }}>
<TabPanel value="2" sx={{ p: 0 }}>
<Box
sx={{
overflow: 'auto',
Expand All @@ -176,11 +196,13 @@ export default function LeftDrawer() {
}}
>
{basicDraggableComponents.map((component, index) => {
return <DragBox name={component.name} key={component.name} componentMeta={component}></DragBox>
return (
<DragBox name={component.name} key={component.name} componentMeta={component} disableActions></DragBox>
)
})}
</Box>
</TabPanel>
<TabPanel value="2" sx={{ p: 0 }}>
<TabPanel value="1" sx={{ p: 0 }}>
<ConfirmButton>KI gestützte Formulargenerierung</ConfirmButton>
{advancedDraggableComponents.map((component, index) => {
return <DragBox name={component.name} key={component.name} componentMeta={component}></DragBox>
Expand Down
38 changes: 38 additions & 0 deletions src/features/home/RightDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useAppDispatch } from '../../app/hooks/reduxHooks'
import { pathToScope } from '../../utils/uiSchemaHelpers'
import RecursiveTreeView, { RenderTree } from '../wizard/JSONSchemaTreeView'
import { JsonSchema7 } from '@jsonforms/core'
import { selectCurrentForm, selectFormData } from '../wizard/FormDataSlice'

const drawerWidth = 240

Expand Down Expand Up @@ -112,14 +113,47 @@ const jsonSchema2RenderTreeView: (key: string, jsonSchema: JsonSchema7) => Rende
}
}

const jsonDataObj2RenderTreeView: (key: string, jsonData: object) => RenderTree = (key, jsonData) => {
return {
id: key,
name: key,
children: Object.keys(jsonData || {}).map((innerKey) =>
jsonData2RenderTreeView(`${innerKey}`, jsonData[innerKey] as any)
),
}
}

const jsonDataArray2RenderTreeView: (key: string, jsonData: any[]) => RenderTree = (key, jsonData) => {
return {
id: key,
name: key,
children: jsonData.map((item, index) => jsonData2RenderTreeView(`[${index}]`, item)),
}
}

const jsonData2RenderTreeView: (key: string, jsonData: any) => RenderTree = (key, jsonData) => {
if (Array.isArray(jsonData)) {
return jsonDataArray2RenderTreeView(key, jsonData)
} else if (typeof jsonData === 'object') {
return jsonDataObj2RenderTreeView(key, jsonData)
} else {
return {
id: key,
name: `${key} = ${String(jsonData)}`,
children: [],
}
}
}
export default function RightDrawer() {
const selectedKey = useSelector(selectSelectedElementKey)
const uiSchema = useSelector(selectUIElementFromSelection)
const formData = useSelector(selectCurrentForm)
const jsonSchema = useSelector(selectJsonSchema)
const jsonSchemaTree = useMemo<RenderTree>(
() => jsonSchema2RenderTreeView('Schema', jsonSchema as JsonSchema7),
[jsonSchema]
)
const formDataTree = useMemo<RenderTree>(() => jsonData2RenderTreeView('Data', formData), [formData])
const dispatch = useAppDispatch()
const handleLabelChange = useCallback(
(e) => {
Expand Down Expand Up @@ -164,6 +198,10 @@ export default function RightDrawer() {
<Box sx={{ overflow: 'auto', p: 0 }}>
<RecursiveTreeView key={jsonSchemaTree.id} data={jsonSchemaTree} checkboxes={false} omitString={'Schema.'} />
</Box>
<Divider />
<Box sx={{ overflow: 'auto', p: 0 }}>
<RecursiveTreeView key={formData.id} data={formDataTree} checkboxes={false} omitString={'Data.'} />
</Box>
</Drawer>
)
}
Loading

0 comments on commit 3e7740d

Please sign in to comment.