/* eslint no-underscore-dangle:0 */ import React, { useEffect, useLayoutEffect, useState, useRef, useMemo } from 'react'; import { useNavigate } from 'react-router'; import { embed } from '@nebula.js/stardust'; import { createTheme, ThemeProvider, StyledEngineProvider } from '@nebula.js/ui/theme'; import { WbSunny, Brightness3, ColorLens, Language, Home } from '@nebula.js/ui/icons'; import InstanceContext from '@nebula.js/nucleus/src/contexts/InstanceContext'; import initModelStore from '@nebula.js/nucleus/src/stores/new-model-store'; import initSelectionStore from '@nebula.js/nucleus/src/stores/new-selections-store'; import { Grid, Toolbar, Button, IconButton, ToggleButtonGroup, ToggleButton, Typography, Tab, Tabs, Menu, MenuItem, CircularProgress, } from '@mui/material'; import initiateWatch from '../../hot'; import Properties from './Properties'; import Stage from './Stage'; import Collection from './Collection'; import AppContext from '../../contexts/AppContext'; import NebulaContext from '../../contexts/NebulaContext'; import VizContext from '../../contexts/VizContext'; import storageFn from '../../storage'; import { useInfo, useOpenApp } from '../../hooks'; const SPACING = 2; const languages = [ 'en-US', 'it-IT', 'zh-CN', 'zh-TW', 'ko-KR', 'de-DE', 'sv-SE', 'es-ES', 'pt-BR', 'ja-JP', 'fr-FR', 'nl-NL', 'tr-TR', 'pl-PL', 'ru-RU', ]; const interactions = ['select', 'active', 'passive']; function getInteractions(arr) { return { select: arr.indexOf('select') !== -1, active: arr.indexOf('active') !== -1, passive: arr.indexOf('passive') !== -1, edit: arr.indexOf('edit') !== -1, }; } export default function Visualize() { const uid = useRef(); const navigate = useNavigate(); const { info } = useInfo(); const { waiting, app } = useOpenApp({ info }); const storage = useMemo(() => storageFn(app), [app]); const [activeViz, setActiveViz] = useState(null); const [expandedObject, setExpandedObject] = useState(null); const [sn, setSupernova] = useState(null); const [currentThemeName, setCurrentThemeName] = useState(storage.get('themeName')); const [currentLanguage, setCurrentLanguage] = useState(storage.get('language') || 'en-US'); const [currentMuiThemeName, setCurrentMuiThemeName] = useState('light'); const [currentInteractions, setCurrentInteractions] = useState(interactions); const [objectListMode, setObjectListMode] = useState(storage.get('objectListMode') === true); const currentSelectionsRef = useRef(null); const [currentId, setCurrentId] = useState(); const [themeChooserAnchorEl, setThemeChooserAnchorEl] = React.useState(null); const [languageChooserAnchorEl, setLanguageChooserAnchorEl] = React.useState(null); const [initialized, setInit] = useState(false); const [nebbie, setNebbie] = useState(null); const customThemes = Array.isArray(info?.themes) && info.themes.length ? ['light', 'dark', ...info.themes] : []; const [context, setContext] = useState(); const theme = useMemo(() => createTheme(currentMuiThemeName), [currentMuiThemeName]); const vizContext = useMemo( () => ({ currentThemeName, activeViz, setActiveViz, expandedObject, setExpandedObject, }), [activeViz, expandedObject, currentThemeName] ); useEffect(() => { (async () => { if (info) { await initiateWatch(info); setInit(true); } })(); }, [info]); useEffect(() => { if (waiting || !initialized) return; const n = embed(app, { context: { theme: currentThemeName, language: currentLanguage, interactions: getInteractions(currentInteractions), keyboardNavigation: info?.keyboardNavigation, }, load: (type) => Promise.resolve(window[type.name]), flags: info?.flags, themes: info?.themes ? info?.themes.map((t) => ({ id: t, load: () => fetch(`/theme/${t}`) .then((response) => response.json()) .then((raw) => { setCurrentMuiThemeName(raw.type === 'dark' ? 'dark' : 'light'); return raw; }), })) : null, }); n.__DO_NOT_USE__.types.register(info?.supernova); if (info?.types) { info?.types.forEach((t) => { n.__DO_NOT_USE__.types.register(t); }); } setNebbie(n); }, [app, info, waiting, initialized]); useLayoutEffect(() => { if (!nebbie) return; nebbie.context({ theme: currentThemeName }); if (currentThemeName === 'light' || currentThemeName === 'dark') { setCurrentMuiThemeName(currentThemeName); } }, [nebbie, currentThemeName]); const create = () => { if (window[info?.supernova.name]) { uid.current = String(Date.now()); setCurrentId(uid.current); } }; useEffect(() => { if (!nebbie) return; nebbie.selections().then((s) => s.mount(currentSelectionsRef.current)); window.onHotChange(info?.supernova.name, () => { nebbie.__DO_NOT_USE__.types.clearFromCache(info?.supernova.name); nebbie.__DO_NOT_USE__.types.register(info?.supernova); create(); nebbie.__DO_NOT_USE__.types .get({ name: info?.supernova.name, }) .supernova() .then(setSupernova); }); create(); }, [nebbie, info]); useEffect(() => { const unload = () => { if (app) app.destroySessionObject(uid.current); }; window.addEventListener('beforeunload', unload); if (app) { const modelStore = initModelStore(app.id); const selectionStore = initSelectionStore(app.id); setContext({ modelStore, selectionStore, myKey: 'this is the key here', }); } return () => { window.removeEventListener('beforeunload', unload); }; }, [app]); const handleThemeChange = (t) => { setThemeChooserAnchorEl(null); storage.save('themeName', t); setCurrentThemeName(t); }; const handleLanguageChange = (lang) => { setLanguageChooserAnchorEl(null); storage.save('language', lang); setCurrentLanguage(lang); nebbie && nebbie.context({ language: lang }); }; const handleInteractionsChange = (e, newValue) => { setCurrentInteractions(newValue); nebbie && nebbie.context({ interactions: getInteractions(newValue) }); }; const toggleDarkMode = () => { const v = currentThemeName === 'dark' ? 'light' : 'dark'; storage.save('themeName', v); setCurrentThemeName(v); }; const handleCreateEditChange = (e, newValue) => { const listMode = newValue === 1; storage.save('objectListMode', listMode); if (listMode) { setActiveViz(null); setExpandedObject(null); } setObjectListMode(listMode); }; // For another time: refactor away the usage of internal Nebula hooks so we don't need the InstanceContext here. return ( nebula.js logo navigate('/')} size="large"> Create} value={0} /> Edit} value={1} /> State: Select Active Passive Edit {customThemes.length ? ( <> setThemeChooserAnchorEl(e.currentTarget)} size="large" > setThemeChooserAnchorEl(null)} > {customThemes.map((t) => ( handleThemeChange(t)} > {t} ))} ) : ( {currentThemeName === 'dark' ? ( ) : ( )} )} setLanguageChooserAnchorEl(null)} > {languages.map((t) => ( handleLanguageChange(t)} > {t} ))}
{sn ? ( {objectListMode ? ( ) : ( )} {activeViz && ( )} ) : ( )} ); }