diff --git a/client/.env.example b/client/.env.example
index ad0e15c94..8df4edb80 100644
--- a/client/.env.example
+++ b/client/.env.example
@@ -1,4 +1,3 @@
-REACT_APP_GAME_KEY=8144b347-8437-415d-b276-aac68c83b1dc
REACT_APP_API_URL=https://latitude-game-api.herokuapp.com
#REACT_APP_API_URL=http://localhost:8000
EXTEND_ESLINT = true
\ No newline at end of file
diff --git a/client/src/contexts/EditorProvider.tsx b/client/src/contexts/EditorProvider.tsx
index d842862bb..9752703d2 100644
--- a/client/src/contexts/EditorProvider.tsx
+++ b/client/src/contexts/EditorProvider.tsx
@@ -12,7 +12,6 @@ import { useLazyGetSpellQuery, Spell } from '../state/spells'
import LoadingScreen from '../features/common/LoadingScreen/LoadingScreen'
import { MyNode } from '../features/common/Node/Node'
import gridimg from '../grid.png'
-import { useSpell } from './SpellProvider'
import { usePubSub } from './PubSubProvider'
import { useRete, ReteContext } from './ReteProvider'
// import { ThothTab } from './TabManagerProvider'
@@ -41,7 +40,7 @@ const Context = createContext({
setEditor: (editor: any) => {},
getNodeMap: () => {},
getNodes: () => {},
- loadGraph: (graph: any) => {},
+ loadChain: (chain: any) => {},
setContainer: () => {},
undo: () => {},
redo: () => {},
@@ -52,7 +51,7 @@ export const useEditor = () => useContext(Context)
const EditorProvider = ({ children }) => {
const [editor, setEditorState] = useState({
components: [],
- loadGraph: (graph: any) => {},
+ loadGraph: (chain: any) => {},
})
const editorRef = useRef({
trigger: (event: string) => {},
@@ -70,8 +69,6 @@ const EditorProvider = ({ children }) => {
}
const buildEditor = async (container, _spell, tab, thoth) => {
- // copy spell in case it is read only
- const spell = JSON.parse(JSON.stringify(_spell))
// eslint-disable-next-line no-console
const newEditor = await initEditor({
container,
@@ -86,7 +83,11 @@ const EditorProvider = ({ children }) => {
// set editor to the map
setEditor(newEditor)
- if (tab.type === 'spell') newEditor.loadGraph(spell.graph)
+ if (tab.type === 'spell') {
+ // copy spell in case it is read onl
+ const spell = JSON.parse(JSON.stringify(_spell))
+ newEditor.loadGraph(spell.chain)
+ }
if (tab.type === 'module') {
const moduleDoc = await thoth.getModule(tab.module)
@@ -118,7 +119,7 @@ const EditorProvider = ({ children }) => {
return editor && Object.fromEntries(editor.components)
}
- const loadGraph = graph => {
+ const loadChain = graph => {
editor.loadGraph(graph)
}
@@ -134,7 +135,7 @@ const EditorProvider = ({ children }) => {
buildEditor,
getNodeMap,
getNodes,
- loadGraph,
+ loadChain,
setEditor,
getEditor,
undo,
@@ -149,17 +150,17 @@ const RawEditor = ({ tab, children }) => {
const [getSpell, { data: spell, isLoading }] = useLazyGetSpellQuery()
const [loaded, setLoaded] = useState(false)
const { buildEditor } = useEditor()
- const { getCurrentGameState, updateCurrentGameState } = useSpell()
// This will be the main interface between thoth and rete
const reteInterface = useRete()
useEffect(() => {
if (!tab) return
- getSpell(tab.spell)
+ if (tab?.spell) getSpell(tab.spell)
}, [tab])
- if (isLoading || !tab || !spell) return
+ if (!tab || (tab.type === 'spell' && (isLoading || !spell)))
+ return
return (
<>
@@ -180,11 +181,7 @@ const RawEditor = ({ tab, children }) => {
{
if (el && !loaded) {
- buildEditor(el, spell, tab, {
- ...reteInterface,
- getCurrentGameState,
- updateCurrentGameState,
- })
+ buildEditor(el, spell, tab, reteInterface)
setLoaded(true)
}
}}
diff --git a/client/src/contexts/ModuleProvider.tsx b/client/src/contexts/ModuleProvider.tsx
index 37bfcde4e..db533368e 100644
--- a/client/src/contexts/ModuleProvider.tsx
+++ b/client/src/contexts/ModuleProvider.tsx
@@ -77,7 +77,7 @@ const ModuleProvider = ({ children }) => {
const getSpellModules = async spell => {
// should actually look for spells that have a data.module key set to a string
- const moduleNames = Object.values(spell.graph.nodes)
+ const moduleNames = Object.values(spell.chain.nodes)
.filter((n: any) => n.name === 'Module')
.map((n: any) => n.data.name)
diff --git a/client/src/contexts/ReteProvider.tsx b/client/src/contexts/ReteProvider.tsx
index f1b87afb7..620ec3a43 100644
--- a/client/src/contexts/ReteProvider.tsx
+++ b/client/src/contexts/ReteProvider.tsx
@@ -1,8 +1,11 @@
import { EngineContext } from '@latitudegames/thoth-core'
import { useContext, createContext } from 'react'
+import { useDispatch } from 'react-redux'
import { postEnkiCompletion } from '../services/game-api/enki'
import { completion as _completion } from '../services/game-api/text'
+import { selectGameStateBySpellId, updateGameState } from '../state/gameState'
+import { store } from '../state/store'
import { invokeInference } from '../utils/huggingfaceHelper'
import { useDB } from './DatabaseProvider'
import { usePubSub } from './PubSubProvider'
@@ -42,6 +45,8 @@ const Context = createContext({
getGameState: () => {},
setGameState: () => {},
getModules: async () => {},
+ getCurrentGameState: () => ({} as Record),
+ updateCurrentGameState: () => new Promise(() => {}) as Promise,
completion: _completion,
enkiCompletion: async (): Promise<{ outputs: string[] }> =>
await new Promise(resolve => {
@@ -57,6 +62,7 @@ export const useRete = () => useContext(Context)
const ReteProvider = ({ children, tab }) => {
const { events, publish, subscribe } = usePubSub()
+ const dispatch = useDispatch()
const {
models: { spells, modules },
} = useDB()
@@ -135,6 +141,22 @@ const ReteProvider = ({ children, tab }) => {
publish($TEXT_EDITOR_CLEAR(tab.id))
}
+ const getCurrentGameState = () => {
+ const currentGameState = selectGameStateBySpellId(
+ store.getState().gameState,
+ tab.spell
+ )
+ return currentGameState?.state
+ }
+
+ const updateCurrentGameState = update => {
+ const newState = {
+ spellId: tab.spell,
+ state: update,
+ }
+ dispatch(updateGameState(newState))
+ }
+
const publicInterface = {
onInspector,
onAddModule,
@@ -148,6 +170,8 @@ const ReteProvider = ({ children, tab }) => {
completion,
enkiCompletion,
huggingface,
+ getCurrentGameState,
+ updateCurrentGameState,
...modules,
// going to need to manuall create theses
diff --git a/client/src/database/models/moduleModel.ts b/client/src/database/models/moduleModel.ts
index 1db3ba8a9..f5f599d37 100644
--- a/client/src/database/models/moduleModel.ts
+++ b/client/src/database/models/moduleModel.ts
@@ -26,9 +26,6 @@ const loadModuleModel = db => {
const updateModule = async (moduleName: string, update: object) => {
const module = await getModule(moduleName)
- // eslint-disable-next-line
- console.log('module', module)
-
const updatedModule = await module.atomicUpdate(oldData => {
return {
...oldData,
@@ -68,6 +65,37 @@ const loadModuleModel = db => {
return await db.modules.insert(newModule)
}
+ const getNestedModules = async (moduleNames: string[]) => {
+ const moduleDocs = await Promise.all(
+ moduleNames.map(moduleName => getModule(moduleName))
+ )
+ if (moduleDocs.length === 0) return []
+ const modules = moduleDocs.filter(Boolean).map(module => module.toJSON())
+ const nestedModules = await Promise.all(
+ modules.map(async module => {
+ const nestedModuleNames = Object.values(module.data.nodes)
+ .filter((n: any) => n.data.module)
+ .map((n: any) => n.data.module)
+ if (nestedModuleNames.length === 0) {
+ return []
+ } else {
+ const nextModuleLayer = await getNestedModules(nestedModuleNames)
+ return nextModuleLayer.flat()
+ }
+ })
+ )
+ return modules.concat(nestedModules.flat())
+ }
+
+ const getSpellModules = async spell => {
+ const moduleNames = Object.values(spell.chain.nodes)
+ .filter((n: any) => n.data.module)
+ .map((n: any) => n.data.module)
+
+ const modules = await getNestedModules(moduleNames)
+ return modules
+ }
+
return {
insert,
getModules,
@@ -76,6 +104,7 @@ const loadModuleModel = db => {
updateModule,
findOneModule,
updateOrCreate,
+ getSpellModules,
}
}
export default loadModuleModel
diff --git a/client/src/features/StartScreen/components/CreateNew.jsx b/client/src/features/StartScreen/components/CreateNew.jsx
index 87c7a60aa..d30cdbc21 100644
--- a/client/src/features/StartScreen/components/CreateNew.jsx
+++ b/client/src/features/StartScreen/components/CreateNew.jsx
@@ -43,12 +43,16 @@ const CreateNew = () => {
const onCreate = async () => {
const placeholderName = uniqueNamesGenerator(customConfig)
const { data: spell } = await newSpell({
- graph: defaultGraph,
+ chain: defaultGraph,
name: placeholderName,
})
await clearTabs()
- await openTab({ name: spell.name, spellId: spell.name, type: 'spell' })
+ await openTab({
+ name: placeholderName,
+ spellId: placeholderName,
+ type: 'spell',
+ })
setLocation('/thoth')
}
diff --git a/client/src/features/Thoth/Thoth.jsx b/client/src/features/Thoth/Thoth.jsx
index 9502cdd43..5e5068142 100644
--- a/client/src/features/Thoth/Thoth.jsx
+++ b/client/src/features/Thoth/Thoth.jsx
@@ -37,7 +37,12 @@ const Thoth = ({ empty }) => {
{!empty &&
tabs.map((tab, i) => (
-
+
))}
)
diff --git a/client/src/features/Thoth/components/EditorWindow/Deployment.tsx b/client/src/features/Thoth/components/EditorWindow/Deployment.tsx
index 1ac68143a..cd0645583 100644
--- a/client/src/features/Thoth/components/EditorWindow/Deployment.tsx
+++ b/client/src/features/Thoth/components/EditorWindow/Deployment.tsx
@@ -22,9 +22,10 @@ const DeploymentView = ({ open, setOpen, spellId }) => {
const [deploySpell] = useDeploySpellMutation()
const spell = useSelector(state => selectSpellById(state, spellId))
- const { data: deployments, isLoading } = useGetDeploymentsQuery(
- spell?.name || ''
- )
+ const name = spell?.name as string
+ const { data: deployments, isLoading } = useGetDeploymentsQuery(name, {
+ skip: !spell?.name,
+ })
const deploy = message => {
if (!spell) return
@@ -32,6 +33,12 @@ const DeploymentView = ({ open, setOpen, spellId }) => {
enqueueSnackbar('Spell deployed', { variant: 'success' })
}
+ const buildUrl = version => {
+ return encodeURI(
+ `${process.env.REACT_APP_API_URL}/games/spells/${spellId}/${version}`
+ )
+ }
+
const copy = url => {
const el = document.createElement('textarea')
el.value = url
@@ -127,10 +134,12 @@ const DeploymentView = ({ open, setOpen, spellId }) => {
>
-
+
Change notes
{
const saveSpell = async () => {
const currentSpell = spellRef.current
- const graph = serialize(currentSpell)
+ const chain = serialize(currentSpell)
- await saveSpellMutation({ ...currentSpell, graph })
+ await saveSpellMutation({ ...currentSpell, chain })
}
const createStateManager = () => {
diff --git a/client/src/features/Thoth/components/StateManagerWindow.jsx b/client/src/features/Thoth/components/StateManagerWindow.tsx
similarity index 63%
rename from client/src/features/Thoth/components/StateManagerWindow.jsx
rename to client/src/features/Thoth/components/StateManagerWindow.tsx
index 3e8caea4c..7d2411a0c 100644
--- a/client/src/features/Thoth/components/StateManagerWindow.jsx
+++ b/client/src/features/Thoth/components/StateManagerWindow.tsx
@@ -2,18 +2,31 @@ import Editor from '@monaco-editor/react'
import jsonFormat from 'json-format'
import { useSnackbar } from 'notistack'
import { useState, useEffect } from 'react'
+import { useSelector } from 'react-redux'
-import { useSpell } from '../../../contexts/SpellProvider'
+import { selectSpellById, useSaveSpellMutation } from '../../../state/spells'
+import {
+ selectGameStateBySpellId,
+ // updateGameState,
+} from '../../../state/gameState'
import Window from '../../common/Window/Window'
import '../thoth.module.css'
+import { RootState } from '../../../state/store'
+import WindowMessage from './WindowMessage'
+
+const StateManager = ({ tab, ...props }) => {
+ // const dispatch = useDispatch()
+ const [saveSpell] = useSaveSpellMutation()
+ const gameState = useSelector((state: RootState) => {
+ return selectGameStateBySpellId(state.gameState, tab.spell)
+ })
+ const spell = useSelector(state => selectSpellById(state, tab.spell))
-const StateManager = props => {
- const { currentSpell, rewriteCurrentGameState } = useSpell()
const { enqueueSnackbar } = useSnackbar()
- const [typing, setTyping] = useState(null)
+ const [typing, setTyping] = useState(false)
const [code, setCode] = useState('{}')
- const [height, setHeight] = useState()
+ const [height, setHeight] = useState()
const bottomHeight = 50
@@ -41,7 +54,7 @@ const StateManager = props => {
useEffect(() => {
if (props?.node?.rect?.height)
- setHeight(props.node.rect.height - bottomHeight)
+ setHeight((props.node.rect.height - bottomHeight) as number)
// this is to dynamically set the appriopriate height so that Monaco editor doesnt break flexbox when resizing
props.node.setEventListener('resize', data => {
@@ -61,9 +74,11 @@ const StateManager = props => {
return () => clearTimeout(delayDebounceFn)
}, [code])
+ // update code when game state changes
useEffect(() => {
- if (currentSpell?.gameState) setCode(jsonFormat(currentSpell.gameState))
- }, [currentSpell])
+ if (!gameState) return
+ setCode(jsonFormat(gameState.state))
+ }, [gameState])
const onClear = () => {
const reset = `{}`
@@ -76,7 +91,13 @@ const StateManager = props => {
}
const onSave = () => {
- rewriteCurrentGameState(JSON.parse(code))
+ if (!gameState) return
+ const parsedState = JSON.parse(code)
+ const spellUpdate = {
+ ...spell,
+ gameState: parsedState,
+ }
+ saveSpell(spellUpdate)
enqueueSnackbar('State saved', {
preventDuplicate: true,
variant: 'success',
@@ -92,6 +113,9 @@ const StateManager = props => {
>
)
+ if (tab.type === 'module')
+ return
+
return (
{
+const Workspace = ({ tab, tabs, appPubSub }) => {
const [loadSpell, { data: spellData }] = useLazyGetSpellQuery()
const [saveSpell] = useSaveSpellMutation()
const { saveModule } = useModule()
@@ -29,17 +32,31 @@ const Workspace = ({ tab, appPubSub }) => {
'save nodecreated noderemoved connectioncreated connectionremoved nodetranslated',
debounce(() => {
if (tab.type === 'spell') {
- saveSpell({ ...spellData, graph: editor.toJSON() }, false)
+ saveSpell({ ...spellData, chain: editor.toJSON() }, false)
}
if (tab.type === 'module') {
saveModule(tab.module, { data: editor.toJSON() }, false)
+ // when a module is saved, we look for any open spell tabs, and check if they have the module.
+ /// if they do, we trigger a save to ensure the module change is captured to the server
+ tabs
+ .filter(tab => tab.type === 'spell')
+ .forEach(filteredTab => {
+ if (filteredTab.spell) {
+ const spell = selectSpellById(
+ store.getState(),
+ filteredTab.spell
+ )
+ if (spell?.modules.some(module => module.name === tab.module))
+ saveSpell({ ...spell })
+ }
+ })
}
}, 500)
)
}, [editor])
useEffect(() => {
- if (!tab) return
+ if (!tab || !tab.spell) return
loadSpell(tab.spell)
}, [tab])
diff --git a/client/src/features/common/MenuBar/MenuBar.js b/client/src/features/common/MenuBar/MenuBar.js
index e557509ea..c3ca0527b 100644
--- a/client/src/features/common/MenuBar/MenuBar.js
+++ b/client/src/features/common/MenuBar/MenuBar.js
@@ -220,8 +220,6 @@ const MenuBar = () => {
eval(func)
}
- console.log('MENU BAR')
-
return (