diff --git a/web/src/App.js b/web/src/App.js
index 1cb4014..f3370b0 100644
--- a/web/src/App.js
+++ b/web/src/App.js
@@ -1,9 +1,9 @@
-import React, { useState } from 'react'
+import React from 'react'
import { QueryClient, QueryClientProvider } from 'react-query'
import './App.css'
-import Fractal from './ui/Fractal'
-import { Puzzle } from './ui/Puzzle'
+import Fractal from './ui/viewer/Fractal'
+import { Puzzle } from './ui/editor/Puzzle'
import { Editor } from './ui/editor/Editor'
import { useRoutes } from 'raviger'
diff --git a/web/src/lib/convertToAntdTreeData.js b/web/src/lib/convertToAntdTreeData.js
new file mode 100644
index 0000000..1e4b0c9
--- /dev/null
+++ b/web/src/lib/convertToAntdTreeData.js
@@ -0,0 +1,97 @@
+import ReactMarkdown from 'react-markdown'
+import React from 'react'
+import { postProcessTitle } from './position'
+
+export const generateExpandedKeys = (path) => {
+ const segments = path.split('-')
+ const keys = []
+
+ for (let i = 1; i <= segments.length; i++) {
+ keys.push(segments.slice(0, i).join('-'))
+ }
+
+ return keys
+}
+const customSort = (a, b) => {
+ if (a === '.text') return -1
+ if (b === '.text') return 1
+ if (a === '_text') return 1
+ if (b === '_text') return -1
+ return Number(a) - Number(b)
+}
+export const convertToAntdTreeData = (node, prefix = '') => {
+ const result = []
+
+ if (!node) return []
+ const sortedKeys = Object.keys(node).sort(customSort)
+ for (let key of sortedKeys) {
+ if (key === '.' || key === '_') continue
+ const childNode = node[key]
+ const currentKey = prefix ? `${prefix}-${key}` : key
+ const isObject = typeof childNode === 'object'
+
+ let title = key
+ if (key.includes('text')) {
+ title = (
+
+ {' '}
+ {postProcessTitle(node[key])}
+
+ )
+ }
+ if (isObject) {
+ title = postProcessTitle(childNode?.['.']) ?? ''
+ }
+
+ let treeNode = {
+ title: title,
+ key: currentKey,
+ icon: isObject ? key : key === 'text' ? '◬' : '▰',
+ }
+
+ // Check if the node has children (ignoring special keys like "." and "_")
+ if (
+ typeof childNode === 'object' &&
+ key !== '.' &&
+ key !== '_' &&
+ !key.includes('text')
+ ) {
+ treeNode.children = convertToAntdTreeData(childNode, currentKey)
+ }
+
+ result.push(treeNode)
+ }
+
+ return result
+}
+
+export const nestTexts = (path, texts) => {
+ if (!texts) return {}
+ if (!path) return texts
+
+ const keys = path.split('/').filter(Boolean) // Split by '/' and filter out empty strings
+ let currentObject = {}
+
+ keys.reduce((obj, key, index) => {
+ if (index === keys.length - 1) {
+ // If we are at the last key in the path
+ obj[key] = Object.fromEntries(
+ Object.entries(texts).map(([key, value]) => [
+ ['.', '_'].includes(key) ? key + 'text' : key,
+ ['.', '_'].includes(key) ? value : { text: value },
+ ]),
+ )
+ } else {
+ obj[key] = {} // Else create an empty object for the next level
+ }
+ return obj[key] // Return the nested object for the next iteration
+ }, currentObject)
+
+ return currentObject
+}
diff --git a/web/src/lib/useMuuriGrid.js b/web/src/lib/useMuuriGrid.js
index c876361..0ffc2b0 100644
--- a/web/src/lib/useMuuriGrid.js
+++ b/web/src/lib/useMuuriGrid.js
@@ -1,6 +1,6 @@
import { useEffect } from 'react'
import Muuri from 'muuri'
-import { idToSize, idToZIndex, positionToId } from '../ui/Puzzle'
+import { idToSize, idToZIndex, positionToId } from '../ui/editor/Puzzle'
const useMuuriGrid = (gridRef, options, size, props) => {
useEffect(() => {
diff --git a/web/src/ui/ControlContainer.jsx b/web/src/ui/ControlContainer.jsx
new file mode 100644
index 0000000..5af2c22
--- /dev/null
+++ b/web/src/ui/ControlContainer.jsx
@@ -0,0 +1,91 @@
+import React, { useEffect, useRef } from 'react'
+import Muuri from 'muuri'
+import '../../muuri.css'
+
+const areas = {
+ leftTriangle: {
+ vertices: [
+ { x: 0, y: window.innerHeight }, // Bottom left
+ { x: 0, y: 0 }, // Top left
+ { x: window.innerWidth / 2, y: 0 }, // Middle top
+ ],
+ },
+ rightTriangle: {
+ vertices: [
+ { x: window.innerWidth, y: window.innerHeight }, // right left
+ { x: window.innerWidth, y: 0 }, // Top right
+ { x: window.innerWidth / 2, y: 0 }, // Middle top
+ ],
+ },
+}
+
+function calculatePositionInArea(item, area) {
+ if (area.vertices) {
+ // Assuming area is a triangle
+ const [v1, v2, v3] = area.vertices
+
+ // Randomly choose a point inside the triangle
+ const r1 = Math.random()
+ const r2 = Math.random()
+ const sqrtR1 = Math.sqrt(r1)
+
+ const x =
+ (1 - sqrtR1) * v1.x + sqrtR1 * (1 - r2) * v2.x + sqrtR1 * r2 * v3.x
+ const y =
+ (1 - sqrtR1) * v1.y + sqrtR1 * (1 - r2) * v2.y + sqrtR1 * r2 * v3.y
+
+ return { x, y }
+ }
+}
+
+function determineAreaForItem(item, areas, index, totalItems) {
+ // Example: Alternate between left and right triangles
+ if (index % 2 === 0) {
+ return areas.leftTriangle
+ } else {
+ return areas.rightTriangle
+ }
+}
+
+function layout(grid) {
+ const items = grid.getItems()
+
+ items.forEach((item) => {
+ // Decide in which area the item should be
+ const area = determineAreaForItem(item, areas)
+
+ // Calculate position based on the area
+ const position = calculatePositionInArea(item, area)
+
+ // Set the item's left and top CSS properties
+ item.getElement().style.left = `${position.x}px`
+ item.getElement().style.top = `${position.y}px`
+ })
+
+ // Update the grid after positioning items
+ grid.refreshItems().layout()
+}
+
+const ControlContainer = ({ children }) => {
+ const gridRef = useRef(null)
+
+ useEffect(() => {
+ if (!gridRef.current) return
+ const grid = new Muuri(gridRef.current, {
+ dragEnabled: true,
+ rounding: true,
+ width: 1000,
+ layout,
+ })
+
+ return () => grid.destroy()
+ }, [gridRef])
+
+ return (
+
+ {children}
+
+ )
+}
+
+export { ControlContainer }
diff --git a/web/src/ui/ShareModal.jsx b/web/src/ui/ShareModal.jsx
index 7d04b20..27b2b6f 100644
--- a/web/src/ui/ShareModal.jsx
+++ b/web/src/ui/ShareModal.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react'
+import React, { useState } from 'react'
import { Modal, Button, Input } from 'antd'
import { ShareAltOutlined, CopyOutlined } from '@ant-design/icons'
import { removeMultipleSlashes } from '../lib/nesting'
diff --git a/web/src/ui/BibtexViewer.js b/web/src/ui/editor/BibtexViewer.js
similarity index 100%
rename from web/src/ui/BibtexViewer.js
rename to web/src/ui/editor/BibtexViewer.js
diff --git a/web/src/ui/editor/Controls.jsx b/web/src/ui/editor/Controls.jsx
index 5cf6019..b60ce4e 100644
--- a/web/src/ui/editor/Controls.jsx
+++ b/web/src/ui/editor/Controls.jsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import { Button, Menu, Popconfirm, Slider, Space } from 'antd'
+import { Button, Menu, Popconfirm, Slider } from 'antd'
import { DeleteOutlined } from '@ant-design/icons'
const ControlBar = {
@@ -146,27 +146,30 @@ const UserInteractionMenu = ({ params, onDeleteAction }) => {
position: 'fixed',
left: 0,
top: '10vh',
- height: '30vh',
- overflowY: "scroll"
+ height: '30vh',
+ overflowY: 'scroll',
}}
>
)
diff --git a/web/src/ui/editor/Editor.jsx b/web/src/ui/editor/Editor.jsx
index 9edc220..5dc4535 100644
--- a/web/src/ui/editor/Editor.jsx
+++ b/web/src/ui/editor/Editor.jsx
@@ -11,7 +11,6 @@ import { ExperimentsView } from './ExperimentsView'
import { PuzzleView } from './PuzzleView'
import TextModal from './TextModal'
import MetaModal from './MetaModal'
-var debounce = require('lodash.debounce')
let taskId = null
const setTaskId = (tI) => (taskId = tI)
diff --git a/web/src/ui/editor/ExperimentsView.jsx b/web/src/ui/editor/ExperimentsView.jsx
index e31a7ae..a4aee78 100644
--- a/web/src/ui/editor/ExperimentsView.jsx
+++ b/web/src/ui/editor/ExperimentsView.jsx
@@ -1,10 +1,9 @@
import React from 'react'
import { Button } from 'antd'
import { navigate } from 'raviger'
-import BibTeXViewer from '../BibtexViewer'
+import BibTeXViewer from './BibtexViewer'
export function ExperimentsView(props) {
- //console.log(props)
const [isGood, setIsGood] = React.useState({})
return (
<>
diff --git a/web/src/ui/Puzzle.jsx b/web/src/ui/editor/Puzzle.jsx
similarity index 96%
rename from web/src/ui/Puzzle.jsx
rename to web/src/ui/editor/Puzzle.jsx
index f4b2f36..66fa45a 100644
--- a/web/src/ui/Puzzle.jsx
+++ b/web/src/ui/editor/Puzzle.jsx
@@ -1,10 +1,10 @@
import React, { useEffect, useRef, useState } from 'react'
import Muuri from 'muuri'
-import '../puzzle.css'
-import { stringToColour } from '../lib/color'
-import { postProcessTitle } from '../lib/position'
-import useMuuriGrid from '../lib/useMuuriGrid'
-import calculateFontSize from '../lib/FontSize'
+import '../../puzzle.css'
+import { stringToColour } from '../../lib/color'
+import { postProcessTitle } from '../../lib/position'
+import useMuuriGrid from '../../lib/useMuuriGrid'
+import calculateFontSize from '../../lib/FontSize'
// Define the base length for the largest triangle
const BASE_LENGTH = window.innerHeight * 0.5 // Modify as needed
@@ -168,6 +168,7 @@ const MutableTriangle = ({
overflowWrap: 'break-word',
width: size,
transform: 'translateX(-25%)',
+ zIndex: 1000000000 - 1000 * nest,
}}
title={title} // Full title as a tooltip
onMouseDown={(e) => {
diff --git a/web/src/ui/editor/PuzzleView.jsx b/web/src/ui/editor/PuzzleView.jsx
index 343e275..4734091 100644
--- a/web/src/ui/editor/PuzzleView.jsx
+++ b/web/src/ui/editor/PuzzleView.jsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import { Puzzle } from '../Puzzle'
+import { Puzzle } from './Puzzle'
import PuzzleControls from './Controls'
export function PuzzleView(props) {
diff --git a/web/src/ui/editor/TreeView.jsx b/web/src/ui/editor/TreeView.jsx
index b06b8ca..0debebc 100644
--- a/web/src/ui/editor/TreeView.jsx
+++ b/web/src/ui/editor/TreeView.jsx
@@ -1,11 +1,10 @@
import React, { useState } from 'react'
import { Tree } from 'antd'
-import { convertToAntdTreeData } from '../Tooltips'
+import { convertToAntdTreeData } from '../../lib/convertToAntdTreeData'
export function TreeView(props) {
const treeData = convertToAntdTreeData(props.state)
const [expandedKeys, setExpandedKeys] = useState([])
- const [openedKeys, setOpenedKeys] = useState(null)
const onExpand = (expandedKeysValue) => {
console.log('onExpand', expandedKeysValue)
setExpandedKeys(expandedKeysValue)
@@ -21,7 +20,6 @@ export function TreeView(props) {
titleHeight={'10px'}
loadData={async (node) => {
console.log(node)
- setOpenedKeys(node.key.replace(/-/g, '/').slice(0, -2))
}}
/>
)
diff --git a/web/src/ui/editor/TriangleView.jsx b/web/src/ui/editor/TriangleView.jsx
index ce56695..4917b3b 100644
--- a/web/src/ui/editor/TriangleView.jsx
+++ b/web/src/ui/editor/TriangleView.jsx
@@ -1,5 +1,5 @@
import React from 'react'
-import Fractal from '../Fractal'
+import Fractal from '../viewer/Fractal'
export function TriangleView(props) {
return
diff --git a/web/src/ui/Fractal.jsx b/web/src/ui/viewer/Fractal.jsx
similarity index 97%
rename from web/src/ui/Fractal.jsx
rename to web/src/ui/viewer/Fractal.jsx
index 1e496ba..1926e4f 100644
--- a/web/src/ui/Fractal.jsx
+++ b/web/src/ui/viewer/Fractal.jsx
@@ -1,14 +1,14 @@
import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react'
import { useQuery } from 'react-query'
import Triangle from './Triangle'
-import { mergeDeep, lookupDeep, shiftIn, slashIt } from '../lib/nesting'
-import { parseHash } from '../lib/read_link_params'
+import { mergeDeep, lookupDeep, shiftIn, slashIt } from '../../lib/nesting'
+import { parseHash } from '../../lib/read_link_params'
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'
-import { go, beamDataTo } from '../lib/navigate'
+import { go, beamDataTo } from '../../lib/navigate'
import { Tooltips } from './Tooltips'
-import { MAX_LEVEL } from '../config/const'
+import { MAX_LEVEL } from '../../config/const'
import { MobileControls } from './MobileControls'
import { MuuriComponent } from './MuuriComponent'
diff --git a/web/src/ui/MobileControls.jsx b/web/src/ui/viewer/MobileControls.jsx
similarity index 98%
rename from web/src/ui/MobileControls.jsx
rename to web/src/ui/viewer/MobileControls.jsx
index 4a25faa..373e8ec 100644
--- a/web/src/ui/MobileControls.jsx
+++ b/web/src/ui/viewer/MobileControls.jsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
-import ShareModal from './ShareModal'
+import ShareModal from '../ShareModal'
import { Button, Input } from 'antd'
import { SearchOutlined } from '@ant-design/icons'
import { useNavigate } from 'raviger'
diff --git a/web/src/ui/MuuriComponent.js b/web/src/ui/viewer/MuuriComponent.js
similarity index 96%
rename from web/src/ui/MuuriComponent.js
rename to web/src/ui/viewer/MuuriComponent.js
index ad916f9..031bc9b 100644
--- a/web/src/ui/MuuriComponent.js
+++ b/web/src/ui/viewer/MuuriComponent.js
@@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react'
import Muuri from 'muuri'
-import '../muuri.css'
+import '../../muuri.css'
import { Pin } from './Pin'
const MuuriComponent = ({ labels, setHiddenId }) => {
diff --git a/web/src/ui/Pin.jsx b/web/src/ui/viewer/Pin.jsx
similarity index 98%
rename from web/src/ui/Pin.jsx
rename to web/src/ui/viewer/Pin.jsx
index 2a54a2e..afcb6eb 100644
--- a/web/src/ui/Pin.jsx
+++ b/web/src/ui/viewer/Pin.jsx
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import LeaderLine from 'leader-line'
-import useLinkedElementsStore from '../lib/PinnedElements'
+import useLinkedElementsStore from '../../lib/PinnedElements'
function findElementById(id) {
let currentId = id
diff --git a/web/src/ui/Tooltips.jsx b/web/src/ui/viewer/Tooltips.jsx
similarity index 50%
rename from web/src/ui/Tooltips.jsx
rename to web/src/ui/viewer/Tooltips.jsx
index 3e7fdc6..103b692 100644
--- a/web/src/ui/Tooltips.jsx
+++ b/web/src/ui/viewer/Tooltips.jsx
@@ -1,103 +1,12 @@
-import { postProcessTitle } from '../lib/position'
import React, { useEffect, useMemo, useState } from 'react'
import { useQuery } from 'react-query'
import { Tree } from 'antd'
-import { mergeDeep } from '../lib/nesting'
-import ReactMarkdown from 'react-markdown'
-
-const generateExpandedKeys = (path) => {
- const segments = path.split('-')
- const keys = []
-
- for (let i = 1; i <= segments.length; i++) {
- keys.push(segments.slice(0, i).join('-'))
- }
-
- return keys
-}
-const customSort = (a, b) => {
- if (a === '.text') return -1
- if (b === '.text') return 1
- if (a === '_text') return 1
- if (b === '_text') return -1
- return Number(a) - Number(b)
-}
-export const convertToAntdTreeData = (node, prefix = '') => {
- const result = []
-
- if (!node) return []
- const sortedKeys = Object.keys(node).sort(customSort)
- for (let key of sortedKeys) {
- if (key === '.' || key === '_') continue
- const childNode = node[key]
- const currentKey = prefix ? `${prefix}-${key}` : key
- const isObject = typeof childNode === 'object'
-
- let title = key
- if (key.includes('text')) {
- title = (
-
- {' '}
- {postProcessTitle(node[key])}
-
- )
- }
- if (isObject) {
- title = postProcessTitle(childNode?.['.']) ?? ''
- }
-
- let treeNode = {
- title: title,
- key: currentKey,
- icon: isObject ? key : key === 'text' ? '◬' : '▰',
- }
-
- // Check if the node has children (ignoring special keys like "." and "_")
- if (
- typeof childNode === 'object' &&
- key !== '.' &&
- key !== '_' &&
- !key.includes('text')
- ) {
- treeNode.children = convertToAntdTreeData(childNode, currentKey)
- }
-
- result.push(treeNode)
- }
-
- return result
-}
-
-const nestTexts = (path, texts) => {
- if (!texts) return {}
- if (!path) return texts
-
- const keys = path.split('/').filter(Boolean) // Split by '/' and filter out empty strings
- let currentObject = {}
-
- keys.reduce((obj, key, index) => {
- if (index === keys.length - 1) {
- // If we are at the last key in the path
- obj[key] = Object.fromEntries(
- Object.entries(texts).map(([key, value]) => [
- ['.', '_'].includes(key) ? key + 'text' : key,
- ['.', '_'].includes(key) ? value : { text: value },
- ]),
- )
- } else {
- obj[key] = {} // Else create an empty object for the next level
- }
- return obj[key] // Return the nested object for the next iteration
- }, currentObject)
-
- return currentObject
-}
+import { mergeDeep } from '../../lib/nesting'
+import {
+ convertToAntdTreeData,
+ generateExpandedKeys,
+ nestTexts,
+} from '../../lib/convertToAntdTreeData'
export const Tooltips = ({
tree: _tree,
diff --git a/web/src/ui/Triangle.jsx b/web/src/ui/viewer/Triangle.jsx
similarity index 92%
rename from web/src/ui/Triangle.jsx
rename to web/src/ui/viewer/Triangle.jsx
index 0dcadb7..5069bcb 100644
--- a/web/src/ui/Triangle.jsx
+++ b/web/src/ui/viewer/Triangle.jsx
@@ -4,12 +4,16 @@ import {
getTopPosition,
isElementInViewportAndBigAndNoChildren,
postProcessTitle,
-} from '../lib/position'
-import { addHoverObject, hoverObjects, removeHoverObject } from '../lib/hover'
-import { MAX_LEVEL } from '../config/const'
-import { stringToColour } from '../lib/color'
-import { trim } from '../lib/string'
-import calculateFontSize from '../lib/FontSize'
+} from '../../lib/position'
+import {
+ addHoverObject,
+ hoverObjects,
+ removeHoverObject,
+} from '../../lib/hover'
+import { MAX_LEVEL } from '../../config/const'
+import { stringToColour } from '../../lib/color'
+import { trim } from '../../lib/string'
+import calculateFontSize from '../../lib/FontSize'
function getRandomElement(arr) {
const randomIndex = Math.floor(Math.random() * arr.length)
@@ -69,8 +73,6 @@ function Triangle({
}, [transformState, scale, fullId])
if (!data) return null
- //const { linkedElements, linkedElementsHas } = useLinkedElementsStore()
-
const hover = hoverObjects.has(fullId)
if (hover) {
setHoverId(fullId)
@@ -78,7 +80,7 @@ function Triangle({
const title = data?.['.']
const anto = data?.['_']
- const { shortTitle, fontSize } = calculateFontSize(size, title)
+ const { shortTitle, fontSize } = calculateFontSize(size, title, 0.7)
return (
{word}