From 30693ccb3a8dd16e8a73b9047432f5eb091b5e12 Mon Sep 17 00:00:00 2001 From: Joshua Shoemaker Date: Mon, 12 Dec 2022 22:11:43 -0600 Subject: [PATCH] feat: project provider --- frontend/app/layout.tsx | 17 +- frontend/components/workspace/AppBar.tsx | 91 +++++ frontend/components/workspace/Navigation.tsx | 362 +----------------- frontend/components/workspace/Sidebar.tsx | 260 +++++++++++++ .../context/Project/makeDefaultProject.ts | 12 + frontend/context/Project/provider.tsx | 51 +++ frontend/context/Project/types.ts | 12 + main.go | 2 +- 8 files changed, 449 insertions(+), 358 deletions(-) create mode 100644 frontend/components/workspace/AppBar.tsx create mode 100644 frontend/components/workspace/Sidebar.tsx create mode 100644 frontend/context/Project/makeDefaultProject.ts create mode 100644 frontend/context/Project/provider.tsx create mode 100644 frontend/context/Project/types.ts 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 */} + +
+ + Open user menu + + +
+ + + {userNavigation.map((item) => ( + + {({ active }) => ( + + {item.name} + + )} + + ))} + + +
+
+
+
+
+ +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 - ?
-
- -
- - - -
- : - - } - - const renderAddNewDocument = (groupId: string) => { - return isAddNewDocumentInputShowing && selectedGroupId === groupId - ?
-
- -
- - -
- : onAddNewDocumentLineItemClickHandler(groupId)} - > - - } - - const renderNavigationItems = () => ( - - ) return ( <> -
- {/* Sidebar component */} -
-
- Textualize -
-
- {renderNavigationItems()} -
-
-
-
-
-
-
-
- -
-
-
- -
-
-
-
- - - {/* Profile dropdown */} - -
- - Open user menu - - -
- - - {userNavigation.map((item) => ( - - {({ active }) => ( - - {item.name} - - )} - - ))} - - -
-
-
-
-
+ + ) } 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 + ?
+
+ +
+ + + +
+ : + + } + + const renderAddNewDocument = (groupId: string) => { + return isAddNewDocumentInputShowing && selectedGroupId === groupId + ?
+
+ +
+ + +
+ : onAddNewDocumentLineItemClickHandler(groupId)} + > + + } + + const renderNavigationItems = () => ( + + ) + + return ( + <> +
+
+
+ Textualize +
+
+ {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