diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx
index e7f6d42..989e123 100644
--- a/frontend/app/layout.tsx
+++ b/frontend/app/layout.tsx
@@ -1,9 +1,24 @@
+'use client'
+
+import { ProjectProvider } from '../context/Project/provider'
import '../styles/globals.css'
+import { ipc } from '../wailsjs/wailsjs/go/models'
type AppLayoutProps = {
children: React.ReactNode
}
export default function MainAppLayout({ children }: AppLayoutProps) {
- return
{children}
+ return
+
+
+ {children}
+
+
+
}
diff --git a/frontend/components/workspace/AppBar.tsx b/frontend/components/workspace/AppBar.tsx
new file mode 100644
index 0000000..cc68b22
--- /dev/null
+++ b/frontend/components/workspace/AppBar.tsx
@@ -0,0 +1,91 @@
+import { Menu, Transition } from "@headlessui/react"
+import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"
+import { BellIcon } from "@heroicons/react/24/outline"
+import { Fragment } from "react"
+
+const userNavigation = [
+ { name: 'Your Profile' },
+ { name: 'Settings' },
+ { name: 'Sign out' },
+]
+
+function classNames(...classes: any[]) {
+ return classes.filter(Boolean).join(' ')
+}
+
+const AppBar = () =>
+
+
+
+
+
+
+ {/* Profile dropdown */}
+
+
+
+
+
+
+export default AppBar
\ No newline at end of file
diff --git a/frontend/components/workspace/Navigation.tsx b/frontend/components/workspace/Navigation.tsx
index 853e2d3..7f2099d 100644
--- a/frontend/components/workspace/Navigation.tsx
+++ b/frontend/components/workspace/Navigation.tsx
@@ -1,366 +1,16 @@
'use client'
-import React, { Fragment, useRef, useState } from 'react'
-import { Menu, Transition } from '@headlessui/react'
-import { BellIcon } from '@heroicons/react/24/outline'
-import { MagnifyingGlassIcon, PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
-import { GetDocuments, RequestAddDocument, RequestAddDocumentGroup } from '../../wailsjs/wailsjs/go/ipc/Channel'
-import { LogPrint } from '../../wailsjs/wailsjs/runtime/runtime'
-import { ipc } from '../../wailsjs/wailsjs/go/models'
-
-type NavigationItem = {
- id: string,
- name: string,
- children: { id: string, name: string }[]
-}
-
-const userNavigation = [
- { name: 'Your Profile' },
- { name: 'Settings' },
- { name: 'Sign out' },
-]
-
-const getNavigationProps = (documentsAndGroups: ipc.GetDocumentsResponse): NavigationItem[] => {
- const { documents, documentGroups } = documentsAndGroups
-
- const groupsWithDocuments = documentGroups.map(g => {
- const childrenDocuments = documents
- .filter(d => d.groupId === g.id)
- .map(d => ({ id: d.id, name: d.name }))
-
- return {
- id: g.id,
- name: g.name,
- children: childrenDocuments
- }
- })
-
- const documentsWithoutGroup = documents
- .filter(d => !d.groupId || d.groupId === 'Uncategorized')
- .map(d => ({ id: d.id, name: d.name }))
-
- return [
- ...groupsWithDocuments,
- {
- id: 'Uncategorized',
- name: 'Uncategorized',
- children: documentsWithoutGroup
- }
- ]
-}
-
-const initDocumentsAndGroups = new ipc.GetDocumentsResponse({ documents: [], documentGroups: [] })
-
-function classNames(...classes: any[]) {
- return classes.filter(Boolean).join(' ')
-}
+import React, { useRef, useState } from 'react'
+import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
+import AppBar from './AppBar'
+import Sidebar from './Sidebar'
function WorkspaceNavigation() {
- const [selectedItemId, setSelectedItemId] = useState('')
- const [selectedGroupId, setSelectedGroupId] = useState('')
- const [isAddNewDocumentInputShowing, setIsAddNewDocumentInputShowing] = useState(false)
- const [isAddNewGroupInputShowing, setIsAddNewGroupInputShowing] = useState(false)
- const [documentsAndGroups, setDocumentsAndGroups] = useState(initDocumentsAndGroups)
- const addDocumentTextInput = useRef(null)
- const addGroupTextInput = useRef(null)
-
-
- const navigation = getNavigationProps(documentsAndGroups)
-
- const updateDocuments = async () => {
- GetDocuments().then(response => {
- LogPrint(JSON.stringify(response, null, 2))
- setDocumentsAndGroups(response)
- Promise.resolve(response)
- })
- }
-
- if (!documentsAndGroups.documents.length
- || !documentsAndGroups.documentGroups.length) {
- updateDocuments()
- }
-
-
- const getParentGroupIdFromItemId = (itemId: string) => {
- let parentGroupId = ''
- navigation.forEach(n => {
- const foundItem = n.children.find(c => c.id === itemId)
- if (foundItem) parentGroupId = n.id
- })
- return parentGroupId
- }
-
- const onAddNewDocumentLineItemClickHandler = (groupId: string) => {
- setSelectedGroupId(groupId)
- setIsAddNewDocumentInputShowing(true)
- setIsAddNewGroupInputShowing(false)
- }
-
- const onAddNewGroupLineItemClickHandler = () => {
- setIsAddNewGroupInputShowing(true)
- setIsAddNewDocumentInputShowing(false)
- }
-
- const onItemClickHandler = (itemId: string) => {
- setSelectedItemId(itemId)
- setSelectedGroupId(getParentGroupIdFromItemId(itemId))
- setIsAddNewDocumentInputShowing(false)
- setIsAddNewGroupInputShowing(false)
- }
-
- const onCancelAddGroupClickHandler = () => {
- setIsAddNewGroupInputShowing(false)
- }
-
- const onCancelAddItemClickHandler = () => {
- setIsAddNewDocumentInputShowing(false)
- }
-
- const onConfirmAddDocumentClickHandler = async (groupId: string) => {
- const documentName = addDocumentTextInput.current?.value
- if (!documentName) return
-
- const response = await RequestAddDocument(groupId, documentName)
- if (!response.id) return
-
- let newDocumentsAndGroups = new ipc.GetDocumentsResponse(documentsAndGroups)
- newDocumentsAndGroups.documents.push(response)
- setDocumentsAndGroups(newDocumentsAndGroups)
- setSelectedItemId(response.id)
- setSelectedGroupId(groupId)
- setIsAddNewDocumentInputShowing(false)
- }
-
- const onConfirmAddGroupClickHandler = async(e: React.MouseEvent) => {
- const groupName = addGroupTextInput.current?.value
- if (!groupName) return
-
- const response = await RequestAddDocumentGroup(groupName)
- if (!response.id) return
-
- let newDocumentsAndGroups = new ipc.GetDocumentsResponse(documentsAndGroups)
- newDocumentsAndGroups.documentGroups.push(response)
- setDocumentsAndGroups(newDocumentsAndGroups)
- setSelectedGroupId(response.id)
- setIsAddNewGroupInputShowing(false)
- }
-
- const renderAddGroupInput = () => {
- return isAddNewGroupInputShowing
- ?
- :
-
- Add Group
-
- }
-
- const renderAddNewDocument = (groupId: string) => {
- return isAddNewDocumentInputShowing && selectedGroupId === groupId
- ?
-
-
-
-
-
-
- : onAddNewDocumentLineItemClickHandler(groupId)}
- >
-
- Add Document
-
- }
-
- const renderNavigationItems = () => (
-
- )
return (
<>
-
- {/* Sidebar component */}
-
-
-

-
-
- {renderNavigationItems()}
-
-
-
-
-
-
-
-
-
-
- {/* Profile dropdown */}
-
-
-
-
-
+
+
>
)
}
diff --git a/frontend/components/workspace/Sidebar.tsx b/frontend/components/workspace/Sidebar.tsx
new file mode 100644
index 0000000..325afd4
--- /dev/null
+++ b/frontend/components/workspace/Sidebar.tsx
@@ -0,0 +1,260 @@
+'use client'
+
+import React, { useRef, useState } from 'react'
+import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
+import { ipc } from '../../wailsjs/wailsjs/go/models'
+import { useProject } from '../../context/Project/provider'
+
+type NavigationItem = {
+ id: string,
+ name: string,
+ children: { id: string, name: string }[]
+}
+
+const getNavigationProps = (documents: ipc.Document[], groups: ipc.DocumentGroup[]): NavigationItem[] => {
+ const groupsWithDocuments = groups.map(g => {
+ const childrenDocuments = documents
+ .filter(d => d.groupId === g.id)
+ .map(d => ({ id: d.id, name: d.name }))
+
+ return {
+ id: g.id,
+ name: g.name,
+ children: childrenDocuments
+ }
+ })
+
+ const documentsWithoutGroup = documents
+ .filter(d => !d.groupId || d.groupId === 'Uncategorized')
+ .map(d => ({ id: d.id, name: d.name }))
+
+ return [
+ ...groupsWithDocuments,
+ {
+ id: 'Uncategorized',
+ name: 'Uncategorized',
+ children: documentsWithoutGroup
+ }
+ ]
+}
+
+function classNames(...classes: any[]) {
+ return classes.filter(Boolean).join(' ')
+}
+
+function Sidebar() {
+ const [selectedItemId, setSelectedItemId] = useState('')
+ const [selectedGroupId, setSelectedGroupId] = useState('')
+ const [isAddNewDocumentInputShowing, setIsAddNewDocumentInputShowing] = useState(false)
+ const [isAddNewGroupInputShowing, setIsAddNewGroupInputShowing] = useState(false)
+ const addDocumentTextInput = useRef(null)
+ const addGroupTextInput = useRef(null)
+
+ const { documents, groups, requestAddDocument, requestAddDocumentGroup } = useProject()
+
+ const navigation = getNavigationProps(documents, groups)
+
+ const getParentGroupIdFromItemId = (itemId: string) => {
+ let parentGroupId = ''
+ navigation.forEach(n => {
+ const foundItem = n.children.find(c => c.id === itemId)
+ if (foundItem) parentGroupId = n.id
+ })
+ return parentGroupId
+ }
+
+ const onAddNewDocumentLineItemClickHandler = (groupId: string) => {
+ setSelectedGroupId(groupId)
+ setIsAddNewDocumentInputShowing(true)
+ setIsAddNewGroupInputShowing(false)
+ }
+
+ const onAddNewGroupLineItemClickHandler = () => {
+ setIsAddNewGroupInputShowing(true)
+ setIsAddNewDocumentInputShowing(false)
+ }
+
+ const onItemClickHandler = (itemId: string) => {
+ setSelectedItemId(itemId)
+ setSelectedGroupId(getParentGroupIdFromItemId(itemId))
+ setIsAddNewDocumentInputShowing(false)
+ setIsAddNewGroupInputShowing(false)
+ }
+
+ const onCancelAddGroupClickHandler = () => {
+ setIsAddNewGroupInputShowing(false)
+ }
+
+ const onCancelAddItemClickHandler = () => {
+ setIsAddNewDocumentInputShowing(false)
+ }
+
+ const onConfirmAddDocumentClickHandler = async (groupId: string) => {
+ const documentName = addDocumentTextInput.current?.value
+ if (!documentName) return
+
+ const response = await requestAddDocument(groupId, documentName)
+ if (!response.id) return
+
+ setSelectedItemId(response.id)
+ setSelectedGroupId(groupId)
+ setIsAddNewDocumentInputShowing(false)
+ }
+
+ const onConfirmAddGroupClickHandler = async(e: React.MouseEvent) => {
+ const groupName = addGroupTextInput.current?.value
+ if (!groupName) return
+
+ const response = await requestAddDocumentGroup(groupName)
+ if (!response.id) return
+
+ setSelectedGroupId(response.id)
+ setIsAddNewGroupInputShowing(false)
+ }
+
+ const renderAddGroupInput = () => {
+ return isAddNewGroupInputShowing
+ ?
+ :
+
+ Add Group
+
+ }
+
+ const renderAddNewDocument = (groupId: string) => {
+ return isAddNewDocumentInputShowing && selectedGroupId === groupId
+ ?
+
+
+
+
+
+
+ : onAddNewDocumentLineItemClickHandler(groupId)}
+ >
+
+ Add Document
+
+ }
+
+ const renderNavigationItems = () => (
+
+ )
+
+ return (
+ <>
+
+
+
+

+
+
+ {renderNavigationItems()}
+
+
+
+ >
+ )
+}
+
+export default Sidebar
diff --git a/frontend/context/Project/makeDefaultProject.ts b/frontend/context/Project/makeDefaultProject.ts
new file mode 100644
index 0000000..60370c7
--- /dev/null
+++ b/frontend/context/Project/makeDefaultProject.ts
@@ -0,0 +1,12 @@
+import { ipc } from "../../wailsjs/wailsjs/go/models"
+import { ProjectContextType } from "./types"
+
+const makeDefaultProject = (): ProjectContextType => ({
+ id: '',
+ documents: [] as ipc.Document[],
+ groups: [] as ipc.DocumentGroup[],
+ requestAddDocument: (groupId: string, documentName: string) => Promise.resolve(new ipc.Document()),
+ requestAddDocumentGroup: (groupName: string) => Promise.resolve(new ipc.DocumentGroup())
+})
+
+export default makeDefaultProject
diff --git a/frontend/context/Project/provider.tsx b/frontend/context/Project/provider.tsx
new file mode 100644
index 0000000..af928cb
--- /dev/null
+++ b/frontend/context/Project/provider.tsx
@@ -0,0 +1,51 @@
+import { createContext, ReactNode, useContext, useState } from 'react'
+import { GetDocuments, RequestAddDocument, RequestAddDocumentGroup } from '../../wailsjs/wailsjs/go/ipc/Channel'
+import { ipc } from '../../wailsjs/wailsjs/go/models'
+import { ProjectContextType, ProjectProps } from './types'
+import makeDefaultProject from './makeDefaultProject'
+
+const ProjectContext = createContext(makeDefaultProject())
+
+export function useProject() {
+ return useContext(ProjectContext)
+}
+
+type Props = { children: ReactNode, projectProps: ProjectProps }
+export function ProjectProvider({ children, projectProps }: Props) {
+ const [ documents, setDocuments ] = useState(projectProps.documents)
+ const [ groups, setGroups ] = useState(projectProps.groups)
+
+ const updateDocuments = async () => {
+ GetDocuments().then(response => {
+ setDocuments(response.documents)
+ setGroups(response.documentGroups)
+ Promise.resolve(response)
+ })
+ }
+
+ const requestAddDocument = async (groupId: string, documentName: string) => {
+ const response = await RequestAddDocument(groupId, documentName)
+ if (response.id) updateDocuments()
+ return response
+ }
+
+ const requestAddDocumentGroup = async (groupName: string) => {
+ const response = await RequestAddDocumentGroup(groupName)
+ if (response.id) updateDocuments()
+ return response
+ }
+
+ if (!documents.length || !groups.length) updateDocuments()
+
+ const value = {
+ id: '',
+ documents,
+ groups,
+ requestAddDocument,
+ requestAddDocumentGroup,
+ }
+
+ return
+ { children }
+
+}
\ No newline at end of file
diff --git a/frontend/context/Project/types.ts b/frontend/context/Project/types.ts
new file mode 100644
index 0000000..e6859cd
--- /dev/null
+++ b/frontend/context/Project/types.ts
@@ -0,0 +1,12 @@
+import { ipc } from "../../wailsjs/wailsjs/go/models"
+
+export type ProjectProps = {
+ id: string,
+ documents: ipc.Document[],
+ groups: ipc.DocumentGroup[],
+}
+
+export type ProjectContextType = {
+ requestAddDocument: (groupId: string, documentName: string) => Promise,
+ requestAddDocumentGroup: (groupName: string) => Promise,
+} & ProjectProps
\ No newline at end of file
diff --git a/main.go b/main.go
index d306f07..f391ff5 100644
--- a/main.go
+++ b/main.go
@@ -15,7 +15,7 @@ import (
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
-//go:embed frontend/out frontend/out/_next/static/*/* frontend/out/_next/static/*/*/*
+//go:embed frontend/out frontend/out/_next/static
var assets embed.FS
//go:embed build/appicondark.png