From fd580c6462d9deb7cef092ca72e2ae7400a9be7a Mon Sep 17 00:00:00 2001 From: tiddlygit-test Date: Wed, 17 Feb 2021 22:17:38 +0800 Subject: [PATCH] refactor: new Main.tsx --- .eslintrc.js | 4 + package-lock.json | 5 + package.json | 1 + src/components/main/index.tsx | 419 ------------------ src/helpers/use-service-value.ts | 10 +- src/pages/About.tsx | 11 +- src/pages/Main/SortableWorkspaceSelector.tsx | 75 ++++ .../main => pages/Main}/draggable-region.tsx | 0 .../main => pages/Main}/fake-title-bar.tsx | 0 .../main => pages/Main}/find-in-page.tsx | 0 src/pages/Main/index.tsx | 309 +++++++++++++ .../main => pages/Main}/navigation-bar.tsx | 0 .../Main}/workspace-selector.tsx | 0 src/preload/common/require-nodejs.ts | 1 - src/preload/index.ts | 2 +- src/renderer.tsx | 2 +- src/services/constants/paths.ts | 2 +- src/services/menu/index.ts | 14 + src/services/menu/interface.ts | 3 + src/services/view/setupViewEventHandlers.ts | 8 +- src/services/windows/index.ts | 4 + src/services/windows/interface.ts | 2 + src/services/workspacesView/index.ts | 19 +- src/state/dialog-code-injection/actions.ts | 2 +- src/state/dialog-proxy/actions.ts | 2 +- .../dialog-spellcheck-languages/actions.ts | 2 +- src/state/index.ts | 46 +- src/type.d.ts | 4 + 28 files changed, 455 insertions(+), 492 deletions(-) delete mode 100644 src/components/main/index.tsx create mode 100644 src/pages/Main/SortableWorkspaceSelector.tsx rename src/{components/main => pages/Main}/draggable-region.tsx (100%) rename src/{components/main => pages/Main}/fake-title-bar.tsx (100%) rename src/{components/main => pages/Main}/find-in-page.tsx (100%) create mode 100644 src/pages/Main/index.tsx rename src/{components/main => pages/Main}/navigation-bar.tsx (100%) rename src/{components/main => pages/Main}/workspace-selector.tsx (100%) diff --git a/.eslintrc.js b/.eslintrc.js index f6b2b3c1..132f0941 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -59,6 +59,8 @@ module.exports = { ], 'comma-dangle': [2, 'always-multiline'], 'no-undef': 'off', + 'unicorn/no-array-for-each': 'off', + 'multiline-ternary': 'off', 'unicorn/filename-case': [ 0, { @@ -68,6 +70,8 @@ module.exports = { ], 'unicorn/consistent-function-scoping': [0], 'no-void': [0], + 'unicorn/prefer-ternary': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', semi: [0], 'no-use-before-define': [0], '@typescript-eslint/no-use-before-define': [1], diff --git a/package-lock.json b/package-lock.json index 9bfc95b8..d6e9ffb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4777,6 +4777,11 @@ "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", "dev": true }, + "array-move": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-move/-/array-move-3.0.1.tgz", + "integrity": "sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg==" + }, "array-reduce": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", diff --git a/package.json b/package.json index c11a7dbd..6eed1f8f 100755 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "dependencies": { "@rematch/core": "^2.0.0", "@tiddlygit/tiddlywiki": "5.1.24-prerelease.20210103", + "array-move": "^3.0.1", "bluebird": "^3.7.2", "chokidar": "^3.5.1", "darkreader": "4.9.27", diff --git a/src/components/main/index.tsx b/src/components/main/index.tsx deleted file mode 100644 index 5d5a7165..00000000 --- a/src/components/main/index.tsx +++ /dev/null @@ -1,419 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { withTranslation } from 'react-i18next'; - -import SimpleBar from 'simplebar-react'; -import 'simplebar/dist/simplebar.min.css'; - -import Button from '@material-ui/core/Button'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; - -import NotificationsIcon from '@material-ui/icons/Notifications'; -import NotificationsPausedIcon from '@material-ui/icons/NotificationsPaused'; -import SettingsIcon from '@material-ui/icons/Settings'; - -import { WindowNames } from '@services/windows/WindowProperties'; - -import { SortableContainer as sortableContainer, SortableElement as sortableElement } from 'react-sortable-hoc'; - -import connectComponent from '../../helpers/connect-component'; - -import WorkspaceSelector from './workspace-selector'; -import FindInPage from './find-in-page'; -import NavigationBar from './navigation-bar'; -import FakeTitleBar from './fake-title-bar'; -import DraggableRegion from './draggable-region'; - -// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module '../../images/arrow-white.png' ... Remove this comment to see the full error message -import arrowWhite from '../../images/arrow-white.png'; -// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module '../../images/arrow-black.png' ... Remove this comment to see the full error message -import arrowBlack from '../../images/arrow-black.png'; - -// https://github.com/sindresorhus/array-move/blob/master/index.js -const arrayMove = (array: any, from: any, to: any) => { - const newArray = array.slice(); - const startIndex = to < 0 ? newArray.length + to : to; - const item = newArray.splice(from, 1)[0]; - newArray.splice(startIndex, 0, item); - return newArray; -}; - -const styles = (theme: any) => ({ - outerRoot: { - display: 'flex', - flexDirection: 'column', - height: '100vh', - width: '100vw', - overflow: 'hidden', - }, - - root: { - display: 'flex', - flexDirection: 'row', - flex: 1, - height: '100%', - width: '100%', - overflow: 'hidden', - }, - - sidebarRoot: { - height: '100%', - width: 68, - borderRight: '1px solid rgba(0, 0, 0, 0.2)', - backgroundColor: theme.palette.background.paper, - WebkitAppRegion: 'drag', - WebkitUserSelect: 'none', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - paddingBottom: theme.spacing(1), - boxSizing: 'border-box', - overflowY: 'auto', - overflowX: 'hidden', - }, - - sidebarTop: { - flex: 1, - paddingTop: window.remote.getPlatform() === 'darwin' ? theme.spacing(3) : 0, - }, - - sidebarTopFullScreen: { - paddingTop: 0, - }, - - innerContentRoot: { - flex: 1, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - padding: theme.spacing(1), - }, - - contentRoot: { - flex: 1, - display: 'flex', - flexDirection: 'column', - width: '100%', - }, - - arrow: { - height: 202, - width: 150, - position: 'absolute', - top: 50, - left: 72, - backgroundImage: `url('${theme.palette.type === 'dark' ? arrowWhite : arrowBlack}')`, - backgroundSize: '150px 202px', - }, - - avatar: { - fontFamily: theme.typography.fontFamily, - display: 'inline-block', - height: 32, - width: 32, - background: theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black, - borderRadius: 4, - color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black), - lineHeight: '32px', - textAlign: 'center', - fontWeight: 500, - textTransform: 'uppercase', - marginLeft: theme.spacing(1), - marginRight: theme.spacing(1), - border: theme.palette.type === 'dark' ? 'none' : '1px solid rgba(0, 0, 0, 0.12)', - }, - - inlineBlock: { - display: 'inline-block', - fontSize: '18px', - color: theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black, - }, - - tip: { - position: 'absolute', - top: 112, - left: 180, - fontFamily: theme.typography.fontFamily, - userSelect: 'none', - }, - - tip2: { - fontFamily: theme.typography.fontFamily, - userSelect: 'none', - }, - - grabbing: { - cursor: 'grabbing !important', - pointerEvents: 'auto !important', - }, - - end: { - display: 'flex', - flexDirection: 'column', - }, - - ul: { - marginTop: 0, - marginBottom: '1.5rem', - }, -}); - -const SortableItem = withTranslation()( - sortableElement(({ value, t }: any) => { - const { index, workspace } = value; - const { active, id, name, picturePath, hibernated, transparentBackground, isSubWiki, tagName } = workspace; - return ( - { - if (isSubWiki) { - await window.service.wiki.requestOpenTiddlerInWiki(tagName); - } else { - const activeWorkspace = await window.service.workspace.getActiveWorkspace(); - if (activeWorkspace?.id === id) { - await window.service.wiki.requestWikiSendActionMessage('tm-home'); - } else { - await window.service.workspaceView.setActiveWorkspaceView(id); - } - } - }} - onContextMenu={(event: any) => { - event.preventDefault(); - - const template = [ - { - label: t('WorkspaceSelector.EditWorkspace'), - click: async () => await window.service.window.open(WindowNames.editWorkspace, { workspaceID: id }), - }, - { - label: t('WorkspaceSelector.RemoveWorkspace'), - click: async () => await window.service.workspaceView.removeWorkspaceView(id), - }, - ]; - - if (!active && !isSubWiki) { - template.splice(1, 0, { - label: hibernated ? 'Wake Up Workspace' : 'Hibernate Workspace', - click: async () => { - if (hibernated) { - return await window.service.workspaceView.wakeUpWorkspaceView(id); - } - return await window.service.workspaceView.hibernateWorkspaceView(id); - }, - }); - } - - window.remote.menu.buildFromTemplateAndPopup(template); - }} - /> - ); - }), -); - -const SortableContainer = sortableContainer(({ children }: any) =>
{children}
); - -interface SidebarContainerProps { - className: string; - children: React.ReactNode; -} - -const SidebarContainer = ({ className, children }: SidebarContainerProps) => { - // use native scroll bar on macOS - if (window.remote.getPlatform() === 'darwin') { - return
{children}
; - } - return {children}; -}; - -interface OwnMainProps { - classes: any; - didFailLoad?: string; - isFullScreen: boolean; - isLoading?: boolean; - navigationBar: boolean; - shouldPauseNotifications: boolean; - sidebar: boolean; - titleBar: boolean; - workspaces: any; -} - -// @ts-expect-error ts-migrate(2456) FIXME: Type alias 'MainProps' circularly references itsel... Remove this comment to see the full error message -type MainProps = OwnMainProps & typeof Main.defaultProps; - -// @ts-expect-error ts-migrate(7022) FIXME: 'Main' implicitly has type 'any' because it does n... Remove this comment to see the full error message -const Main = ({ classes, didFailLoad, isFullScreen, isLoading, navigationBar, shouldPauseNotifications, sidebar, titleBar, workspaces }: MainProps) => { - const workspacesList = Object.values(workspaces); - const showTitleBar = window.remote.getPlatform() === 'darwin' && titleBar && !isFullScreen; - const requestReload = async () => await window.service.window.reload(window.meta.windowName); - - return ( -
- {workspacesList.length > 0 && } - {showTitleBar && } -
- {sidebar && ( - -
- { - if (oldIndex === newIndex) return; - - const newWorkspacesList = arrayMove(workspacesList, oldIndex, newIndex); - const newWorkspaces = { ...workspaces }; - newWorkspacesList.forEach((workspace: any, index: any) => { - newWorkspaces[workspace.id].order = index; - }); - - await window.service.workspace.set(newWorkspaces); - }}> - {workspacesList.map((workspace, index) => ( - // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'. - - ))} - - await window.service.window.open(WindowNames.addWorkspace)} /> -
- {!navigationBar && ( -
- await window.service.window.open(WindowNames.notifications)} - className={classes.iconButton}> - {shouldPauseNotifications ? : } - - {window.meta.windowName === 'menubar' && ( - await window.service.window.open(WindowNames.preferences)} - className={classes.iconButton}> - - - )} -
- )} -
- )} -
- {navigationBar && } - -
- {Object.keys(workspaces).length > 0 && didFailLoad && !isLoading && ( -
- - Wiki is not started or not loaded - - - {didFailLoad} - - -
- - <> - Try: -
    -
  • - Click{' '} - - Reload - {' '} - button below or press CMD_or_Ctrl + R to reload the page. -
  • -
  • - Check the{' '} - await window.service.native.open(getLogFolderPath(), true)} - onKeyPress={async () => await window.service.native.open(getLogFolderPath(), true)} - role="button" - // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'number | ... Remove this comment to see the full error message - tabIndex="0" - style={{ cursor: 'pointer' }}> - Log Folder - {' '} - to see what happened. -
  • -
  • Backup your file, remove workspace and recreate one.
  • -
- -
- - -
- )} - {Object.keys(workspaces).length > 0 && isLoading && ( - // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. - - Loading.. - - )} - {Object.keys(workspaces).length === 0 && ( -
- {sidebar ? ( - <> - {/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ alt: string; className: any; }' is not ass... Remove this comment to see the full error message */} -
-
- Click -
+
- to get started! -
- - ) : ( -
- - Click - Workspaces > Add Workspace - to get started! - -
- )} -
- )} -
-
-
- // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'div'. -
- ); -}; - -Main.defaultProps = { - isLoading: false, -}; - -const mapStateToProps = (state: any) => { - // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'. - const activeWorkspace = Object.values(state.workspaces).find((workspace) => workspace.active); - - return { - // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'. - didFailLoad: activeWorkspace && state.workspaceMetas[activeWorkspace.id] ? state.workspaceMetas[activeWorkspace.id].didFailLoad : undefined, - isFullScreen: state.general.isFullScreen, - // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'. - isLoading: activeWorkspace && state.workspaceMetas[activeWorkspace.id] ? Boolean(state.workspaceMetas[activeWorkspace.id].isLoading) : false, - navigationBar: - (window.remote.getPlatform() === 'linux' && state.preferences.attachToMenubar && !state.preferences.sidebar) || state.preferences.navigationBar, - shouldPauseNotifications: state.notifications.pauseNotificationsInfo !== null, - sidebar: state.preferences.sidebar, - titleBar: state.preferences.titleBar, - workspaces: state.workspaces, - }; -}; - -export default connectComponent(Main, mapStateToProps, undefined, styles); diff --git a/src/helpers/use-service-value.ts b/src/helpers/use-service-value.ts index 08c094d8..3bb86c7c 100644 --- a/src/helpers/use-service-value.ts +++ b/src/helpers/use-service-value.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { AsyncReturnType } from 'type-fest'; /** * Use value from service, especially constant value that never changes @@ -6,11 +7,14 @@ import { useEffect, useState } from 'react'; * @param valuePromise A promise contain the value we want to use in React * @param defaultValue empty array or undefined, as initial value */ -export function usePromiseValue(valuePromise: Promise | (() => Promise), defaultValue?: T): T | undefined { - const [value, valueSetter] = useState(defaultValue); +export function usePromiseValue( + asyncValue: () => Promise, + defaultValue?: AsyncReturnType, +): T | DefaultValueType { + const [value, valueSetter] = useState(defaultValue as T | DefaultValueType); useEffect(() => { void (async () => { - valueSetter(typeof valuePromise === 'function' ? await valuePromise() : await valuePromise); + valueSetter(await asyncValue()); }); }, []); diff --git a/src/pages/About.tsx b/src/pages/About.tsx index ec290740..2a6bfba4 100644 --- a/src/pages/About.tsx +++ b/src/pages/About.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Button, DialogContent } from '@material-ui/core'; import { usePromiseValue } from '@/helpers/use-service-value'; @@ -54,16 +54,17 @@ export default function About(): JSX.Element { { name: 'Node Version', version: processVersions.node }, { name: 'Chromium Version', version: processVersions.chrome }, ]; - }, []); + }, [] as Array<{ name: string; version: string }>); - const iconPath = usePromiseValue(window.service.context.get('ICON_PATH') as Promise); - const appVersion = usePromiseValue(window.service.context.get('appVersion') as Promise); + const iconPath = usePromiseValue(async () => (await window.service.context.get('ICON_PATH')) as string); + const appVersion = usePromiseValue(async () => (await window.service.context.get('appVersion')) as string); + const platform = usePromiseValue(async () => (await window.service.context.get('platform')) as string); return (
- TiddlyGit + TiddlyGit ({platform ?? 'Unknown Platform'}) {`Version v${appVersion ?? ' - '}.`} {versions?.map(({ name, version }) => ( diff --git a/src/pages/Main/SortableWorkspaceSelector.tsx b/src/pages/Main/SortableWorkspaceSelector.tsx new file mode 100644 index 00000000..97329689 --- /dev/null +++ b/src/pages/Main/SortableWorkspaceSelector.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { withTranslation, WithTranslation } from 'react-i18next'; +import { SortableContainer as sortableContainer, SortableElement as sortableElement } from 'react-sortable-hoc'; +import { WindowNames } from '@services/windows/WindowProperties'; +import WorkspaceSelector from './workspace-selector'; +import { IWorkspace } from '@services/workspaces/interface'; + +export const SortableContainer = sortableContainer(({ children }: { children: React.ReactNode }) =>
{children}
); + +export interface ISortableItemProps { + value: { + index: number; + workspace: IWorkspace; + }; + t: WithTranslation['t']; +} + +export const SortableItem = sortableElement( + withTranslation()(({ value, t }: ISortableItemProps) => { + const { index, workspace } = value; + const { active, id, name, picturePath, hibernated, transparentBackground, isSubWiki, tagName } = workspace; + return ( + { + if (isSubWiki) { + await window.service.wiki.requestOpenTiddlerInWiki(tagName); + } else { + const activeWorkspace = await window.service.workspace.getActiveWorkspace(); + if (activeWorkspace?.id === id) { + await window.service.wiki.requestWikiSendActionMessage('tm-home'); + } else { + await window.service.workspaceView.setActiveWorkspaceView(id); + } + } + }} + onContextMenu={(event: Event) => { + event.preventDefault(); + + const template = [ + { + label: t('WorkspaceSelector.EditWorkspace'), + click: async () => await window.service.window.open(WindowNames.editWorkspace, { workspaceID: id }), + }, + { + label: t('WorkspaceSelector.RemoveWorkspace'), + click: async () => await window.service.workspaceView.removeWorkspaceView(id), + }, + ]; + + if (!active && !isSubWiki) { + template.splice(1, 0, { + label: hibernated ? 'Wake Up Workspace' : 'Hibernate Workspace', + click: async () => { + if (hibernated) { + return await window.service.workspaceView.wakeUpWorkspaceView(id); + } + return await window.service.workspaceView.hibernateWorkspaceView(id); + }, + }); + } + + void window.service.menu.buildContextMenuAndPopup(template); + }} + /> + ); + }), +); diff --git a/src/components/main/draggable-region.tsx b/src/pages/Main/draggable-region.tsx similarity index 100% rename from src/components/main/draggable-region.tsx rename to src/pages/Main/draggable-region.tsx diff --git a/src/components/main/fake-title-bar.tsx b/src/pages/Main/fake-title-bar.tsx similarity index 100% rename from src/components/main/fake-title-bar.tsx rename to src/pages/Main/fake-title-bar.tsx diff --git a/src/components/main/find-in-page.tsx b/src/pages/Main/find-in-page.tsx similarity index 100% rename from src/components/main/find-in-page.tsx rename to src/pages/Main/find-in-page.tsx diff --git a/src/pages/Main/index.tsx b/src/pages/Main/index.tsx new file mode 100644 index 00000000..f1d096e2 --- /dev/null +++ b/src/pages/Main/index.tsx @@ -0,0 +1,309 @@ +import React from 'react'; +import arrayMove from 'array-move'; +import styled, { css } from 'styled-components'; +import { AsyncReturnType } from 'type-fest'; + +import SimpleBar from 'simplebar-react'; +import 'simplebar/dist/simplebar.min.css'; + +import Button from '@material-ui/core/Button'; +import IconButtonRaw from '@material-ui/core/IconButton'; +import Typography from '@material-ui/core/Typography'; + +import NotificationsIcon from '@material-ui/icons/Notifications'; +import NotificationsPausedIcon from '@material-ui/icons/NotificationsPaused'; +import SettingsIcon from '@material-ui/icons/Settings'; + +import { WindowNames } from '@services/windows/WindowProperties'; + +import { usePromiseValue } from '@/helpers/use-service-value'; + +import WorkspaceSelector from './workspace-selector'; +import FindInPage from './find-in-page'; +import NavigationBar from './navigation-bar'; +import FakeTitleBar from './fake-title-bar'; +import DraggableRegion from './draggable-region'; + +import arrowWhite from '@/images/arrow-white.png'; +import arrowBlack from '@/images/arrow-black.png'; +import { SortableContainer, SortableItem } from './SortableWorkspaceSelector'; +import { IWorkspace } from '@services/workspaces/interface'; + +const OuterRoot = styled.div` + display: flex; + flex-direction: column; + height: 100vh; + width: 100vw; + overflow: hidden; +`; + +const Root = styled.div` + display: flex; + flex-direction: row; + flex: 1; + height: 100%; + width: 100%; + overflow: hidden; +`; + +const sideBarStyle = css` + height: 100%; + width: 68; + border-right: 1px solid rgba(0, 0, 0, 0.2); + background-color: #fffff0; + -webkit-app-region: drag; + user-select: none; + display: flex; + flex-direction: column; + align-items: center; + padding-bottom: 10px; + box-sizing: border-box; + overflow-y: auto; + overflow-x: hidden; +`; +const SidebarRoot = styled.div` + ${sideBarStyle} +`; +const SidebarWithStyle = styled(SimpleBar)` + ${sideBarStyle} +`; + +const SidebarTop = styled.div<{ fullscreen?: boolean }>` + flex: 1; + /** // TODO: darwin theme */ + /* padding-top-window-remote-get-platform-darwin-theme-spacing-3: 0; */ + ${({ fullscreen }) => fullscreen === true && `padding-top: 0;`} +`; + +const IconButton = styled(IconButtonRaw)``; + +const InnerContentRoot = styled.div` + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 10px; +`; + +const ContentRoot = styled.div` + flex: 1; + display: flex; + flex-direction: column; + width: 100%; +`; + +const Arrow = styled.div<{ image: string }>` + height: 202; + width: 150; + position: absolute; + top: 50; + left: 72; + + background-image: url(${({ image }) => image}); + background-size: 150px 202px; +`; + +const Avatar = styled.div` + display: inline-block; + height: 32; + width: 32; + /** // TODO: dark theme */ + /* background: theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black; */ + border-radius: 4; + /** // TODO: dark theme */ + /* color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.white: theme.palette.common.black); */ + line-height: 32px; + text-align: center; + font-weight: 500; + text-transform: uppercase; + margin-left: 10px; + margin-right: 10px; + /** // TODO: dark theme */ + /* border: theme.palette.type === 'dark' ? 'none' : 1px solid rgba(0, 0, 0, 0.12); */ +`; + +const Tip2Text = styled.span` + display: inline-block; + font-size: 18px; + /** // TODO: dark theme */ + /* color: theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black; */ +`; + +const Tip = styled.div` + position: absolute; + top: 112; + left: 180; + user-select: none; +`; + +const Tip2 = styled.div` + user-select: none; +`; + +const End = styled.div` + display: flex; + flex-direction: column; +`; + +const HelperTextsList = styled.ul` + margin-top: 0; + margin-bottom: 1.5rem; +`; + +const SidebarContainer = ({ children }: { children: React.ReactNode }): JSX.Element => { + const platform = usePromiseValue(async () => (await window.service.context.get('platform')) as string); + // use native scroll bar on macOS + if (platform === 'darwin') { + return {children}; + } + return {children}; +}; + +function Main(): JSX.Element { + // TODO: make workspacesList observable + const workspacesList = usePromiseValue( + window.service.workspace.getWorkspacesAsList, + [] as AsyncReturnType, + )!; + const platform = usePromiseValue(async () => (await window.service.context.get('platform')) as string); + const attachToMenubar = usePromiseValue(async () => (await window.service.preference.get('attachToMenubar')) as boolean); + const titleBar = usePromiseValue(async () => (await window.service.preference.get('titleBar')) as boolean); + const sidebar = usePromiseValue(async () => (await window.service.preference.get('sidebar')) as boolean); + const pauseNotifications = usePromiseValue(async () => (await window.service.preference.get('pauseNotifications')) as string); + const navigationBar = usePromiseValue(async () => (await window.service.preference.get('navigationBar')) as boolean); + const themeSource = usePromiseValue(async () => (await window.service.preference.get('themeSource')) as 'system' | 'light' | 'dark'); + const isFullScreen = usePromiseValue(window.service.window.isFullScreen); + const showTitleBar = platform === 'darwin' && titleBar === true && isFullScreen === false; + const mainWorkspaceMetaData = usePromiseValue(async () => { + const activeWorkspace = await window.service.workspace.getActiveWorkspace(); + if (activeWorkspace !== undefined) { + const metadata = await window.service.workspace.getMetaData(activeWorkspace.id); + return metadata; + } + return { didFailLoadErrorMessage: 'No ActiveWorkspace' }; + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + }, {} as AsyncReturnType); + const requestReload = async (): Promise => await window.service.window.reload(window.meta.windowName); + + return ( + + {workspacesList.length > 0 && } + {showTitleBar && } + + {sidebar === true && ( + + + { + if (oldIndex === newIndex) return; + + const newWorkspacesList = arrayMove(workspacesList, oldIndex, newIndex); + const newWorkspaces: Record = {}; + newWorkspacesList.forEach((workspace, index) => { + newWorkspaces[workspace.id] = workspace; + newWorkspaces[workspace.id].order = index; + }); + + await window.service.workspace.setWorkspaces(newWorkspaces); + }}> + {workspacesList.map((workspace, index) => ( + + ))} + + await window.service.window.open(WindowNames.addWorkspace)} /> + + {navigationBar === false && ( + + await window.service.window.open(WindowNames.notifications)}> + {typeof pauseNotifications === 'string' && pauseNotifications.length > 0 ? : } + + {attachToMenubar === true && ( + await window.service.window.open(WindowNames.preferences)}> + + + )} + + )} + + )} + + {navigationBar === true && } + + + {workspacesList.length > 0 && mainWorkspaceMetaData?.didFailLoadErrorMessage && mainWorkspaceMetaData?.isLoading === false && ( +
+ + Wiki is not started or not loaded + + + {mainWorkspaceMetaData.didFailLoadErrorMessage} + + +
+ + <> + Try: + +
  • + Click{' '} + + Reload + {' '} + button below or press CMD_or_Ctrl + R to reload the page. +
  • +
  • + Check the{' '} + await window.service.native.open((await window.service.context.get('LOG_FOLDER')) as string, true)} + onKeyPress={async () => await window.service.native.open((await window.service.context.get('LOG_FOLDER')) as string, true)} + role="button" + tabIndex={0} + style={{ cursor: 'pointer' }}> + Log Folder + {' '} + to see what happened. +
  • +
  • Backup your file, remove workspace and recreate one.
  • +
    + +
    + + +
    + )} + {workspacesList.length > 0 && mainWorkspaceMetaData?.isLoading && Loading..} + {workspacesList.length === 0 && ( +
    + {sidebar === true ? ( + <> + + + Click + + + to get started! + + + ) : ( + + + Click + Workspaces > Add Workspace + to get started! + + + )} +
    + )} +
    +
    +
    +
    + ); +} + +Main.defaultProps = { + isLoading: false, +}; diff --git a/src/components/main/navigation-bar.tsx b/src/pages/Main/navigation-bar.tsx similarity index 100% rename from src/components/main/navigation-bar.tsx rename to src/pages/Main/navigation-bar.tsx diff --git a/src/components/main/workspace-selector.tsx b/src/pages/Main/workspace-selector.tsx similarity index 100% rename from src/components/main/workspace-selector.tsx rename to src/pages/Main/workspace-selector.tsx diff --git a/src/preload/common/require-nodejs.ts b/src/preload/common/require-nodejs.ts index 4d39c9d5..f76ced12 100644 --- a/src/preload/common/require-nodejs.ts +++ b/src/preload/common/require-nodejs.ts @@ -2,7 +2,6 @@ import { contextBridge, remote, ipcRenderer, webFrame, desktopCapturer } from 'e import { Channels } from '@/constants/channels'; // contextBridge.exposeInMainWorld('remote', { -// isFullScreen: () => remote.getCurrentWindow().isFullScreen(), // ipcRenderer: { // on: (channel: Channels, callback: (event: Electron.IpcRendererEvent, ...arguments_: unknown[]) => void) => // ipcRenderer.on(channel, (event, ...arguments_) => callback(event, ...arguments_)), diff --git a/src/preload/index.ts b/src/preload/index.ts index 6755e6e2..3ad2376c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -56,7 +56,7 @@ declare global { if (windowName === WindowNames.view) { void import('./view'); } -if (window.meta.windowName === 'main') { +if (browserViewMetaData.windowName === 'main') { // automatically reload page when wifi/network is connected // https://www.electronjs.org/docs/tutorial/online-offline-events const handleOnlineOffline = (): void => { diff --git a/src/renderer.tsx b/src/renderer.tsx index d7e834ea..28176c3e 100644 --- a/src/renderer.tsx +++ b/src/renderer.tsx @@ -35,7 +35,7 @@ const DialogOpenUrlWith = React.lazy(async () => await import('./components/dial const DialogPreferences = React.lazy(async () => await import('./components/dialog-preferences')); const DialogProxy = React.lazy(async () => await import('./components/dialog-proxy')); const DialogSpellcheckLanguages = React.lazy(async () => await import('./components/dialog-spellcheck-languages')); -const Main = React.lazy(async () => await import('./components/main')); +const Main = React.lazy(async () => await import('./pages/Main')); const App = (): JSX.Element => { switch (window.meta.windowName) { diff --git a/src/services/constants/paths.ts b/src/services/constants/paths.ts index 5617b16b..025d3693 100644 --- a/src/services/constants/paths.ts +++ b/src/services/constants/paths.ts @@ -7,7 +7,7 @@ const isMac = process.platform === 'darwin'; const sourcePath = path.resolve(__dirname, '..', '..'); export const buildResourcePath = path.resolve(sourcePath, '..', 'build-resources'); -const REACT_PATH = MAIN_WINDOW_WEBPACK_ENTRY; +const REACT_PATH = 'MAIN_WINDOW_WEBPACK_ENTRY'; // .app/Contents/Resources/wiki/ const TIDDLYWIKI_TEMPLATE_FOLDER_PATH = isDev ? path.resolve(sourcePath, '..', 'template', 'wiki') : path.resolve(process.resourcesPath, '..', 'wiki'); const TIDDLERS_PATH = 'tiddlers'; diff --git a/src/services/menu/index.ts b/src/services/menu/index.ts index 0454c191..01a82807 100644 --- a/src/services/menu/index.ts +++ b/src/services/menu/index.ts @@ -2,9 +2,15 @@ import { Menu, MenuItemConstructorOptions, shell } from 'electron'; import { debounce, take, drop } from 'lodash'; import { injectable } from 'inversify'; import { IMenuService, DeferredMenuItemConstructorOptions } from './interface'; +import { WindowNames } from '@services/windows/WindowProperties'; +import { lazyInject } from '@services/container'; +import serviceIdentifier from '@services/serviceIdentifier'; +import { IWindowService } from '@services/windows/interface'; @injectable() export class MenuService implements IMenuService { + @lazyInject(serviceIdentifier.Window) private readonly windowService!: IWindowService; + private readonly menuTemplate: DeferredMenuItemConstructorOptions[]; /** @@ -169,4 +175,12 @@ export class MenuService implements IMenuService { }); } } + + public buildContextMenuAndPopup(template: MenuItemConstructorOptions[], windowName = WindowNames.main): void { + const menu = Menu.buildFromTemplate(template); + const windowToPopMenu = this.windowService.get(windowName); + if (windowToPopMenu !== undefined) { + menu.popup({ window: windowToPopMenu }); + } + } } diff --git a/src/services/menu/interface.ts b/src/services/menu/interface.ts index 617f4713..54533c6e 100644 --- a/src/services/menu/interface.ts +++ b/src/services/menu/interface.ts @@ -2,6 +2,7 @@ import { Menu, MenuItemConstructorOptions, shell } from 'electron'; import { ProxyPropertyType } from '@/helpers/electron-ipc-proxy/common'; import { MenuChannel } from '@/constants/channels'; +import { WindowNames } from '@services/windows/WindowProperties'; /** * MenuItemConstructorOptions that allows properties like "label", "enabled", "submenu" to be () => xxx @@ -21,11 +22,13 @@ export interface DeferredMenuItemConstructorOptions extends Omit(windowName: N, meta: WindowMeta[N]): void { this.windowMeta[windowName] = meta; } diff --git a/src/services/windows/interface.ts b/src/services/windows/interface.ts index 0b42a7fe..3540dc48 100644 --- a/src/services/windows/interface.ts +++ b/src/services/windows/interface.ts @@ -15,6 +15,7 @@ export interface IWindowService { getWindowMeta(windowName: N): WindowMeta[N] | undefined; sendToAllWindows: (channel: Channels, ...arguments_: unknown[]) => void; requestShowRequireRestartDialog(): Promise; + isFullScreen(windowName?: WindowNames): boolean | undefined; goHome(windowName: WindowNames): Promise; goBack(windowName: WindowNames): void; goForward(windowName: WindowNames): void; @@ -33,6 +34,7 @@ export const WindowServiceIPCDescriptor = { getWindowMeta: ProxyPropertyType.Function, requestShowRequireRestartDialog: ProxyPropertyType.Function, sendToAllWindows: ProxyPropertyType.Function, + isFullScreen: ProxyPropertyType.Function, goHome: ProxyPropertyType.Function, goBack: ProxyPropertyType.Function, goForward: ProxyPropertyType.Function, diff --git a/src/services/workspacesView/index.ts b/src/services/workspacesView/index.ts index 34fa72da..cacf73be 100644 --- a/src/services/workspacesView/index.ts +++ b/src/services/workspacesView/index.ts @@ -15,18 +15,19 @@ import { logger } from '@services/libs/log'; import { IAuthenticationService } from '@services/auth/interface'; import { IGitService } from '@services/git/interface'; import { IWorkspaceViewService } from './interface'; +import { lazyInject } from '@services/container'; @injectable() export class WorkspaceView implements IWorkspaceViewService { - constructor( - @inject(serviceIdentifier.Authentication) private readonly authService: IAuthenticationService, - @inject(serviceIdentifier.View) private readonly viewService: IViewService, - @inject(serviceIdentifier.Git) private readonly gitService: IGitService, - @inject(serviceIdentifier.Workspace) private readonly workspaceService: IWorkspaceService, - @inject(serviceIdentifier.Window) private readonly windowService: IWindowService, - @inject(serviceIdentifier.Preference) private readonly preferenceService: IPreferenceService, - @inject(serviceIdentifier.MenuService) private readonly menuService: IMenuService, - ) { + @lazyInject(serviceIdentifier.Authentication) private readonly authService!: IAuthenticationService; + @lazyInject(serviceIdentifier.View) private readonly viewService!: IViewService; + @lazyInject(serviceIdentifier.Git) private readonly gitService!: IGitService; + @lazyInject(serviceIdentifier.Workspace) private readonly workspaceService!: IWorkspaceService; + @lazyInject(serviceIdentifier.Window) private readonly windowService!: IWindowService; + @lazyInject(serviceIdentifier.Preference) private readonly preferenceService!: IPreferenceService; + @lazyInject(serviceIdentifier.MenuService) private readonly menuService!: IMenuService; + + constructor() { this.registerMenu(); } diff --git a/src/state/dialog-code-injection/actions.ts b/src/state/dialog-code-injection/actions.ts index 02e20c53..95f22695 100644 --- a/src/state/dialog-code-injection/actions.ts +++ b/src/state/dialog-code-injection/actions.ts @@ -12,7 +12,7 @@ export const updateForm = (changes: any) => (dispatch: any) => changes, }); -export const save = async () => (dispatch: any, getState: any) => { +export const save = async () => async (dispatch: any, getState: any) => { const { form } = getState().dialogCodeInjection; const { codeInjectionType } = window.meta as WindowMeta[WindowNames.codeInjection]; diff --git a/src/state/dialog-proxy/actions.ts b/src/state/dialog-proxy/actions.ts index ba091554..f2fef9fa 100644 --- a/src/state/dialog-proxy/actions.ts +++ b/src/state/dialog-proxy/actions.ts @@ -47,7 +47,7 @@ export const updateForm = (changes: any) => (dispatch: any, getState: any) => { } }; -export const save = async () => (dispatch: any, getState: any) => { +export const save = async () => async (dispatch: any, getState: any) => { const state = getState(); const { form } = state.dialogProxy; diff --git a/src/state/dialog-spellcheck-languages/actions.ts b/src/state/dialog-spellcheck-languages/actions.ts index 924de883..4b83e0b8 100644 --- a/src/state/dialog-spellcheck-languages/actions.ts +++ b/src/state/dialog-spellcheck-languages/actions.ts @@ -31,7 +31,7 @@ export const removeLanguage = (code: any) => (dispatch: any, getState: any) => { ); }; -export const save = async () => (dispatch: any, getState: any) => { +export const save = async () => async (dispatch: any, getState: any) => { const { form } = getState().dialogSpellcheckLanguages; void window.service.preference.set('spellcheckLanguages', form.spellcheckLanguages); diff --git a/src/state/index.ts b/src/state/index.ts index 540abc7e..30d44d33 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,47 +1,3 @@ import { applyMiddleware, combineReducers, createStore } from 'redux'; -import thunkMiddleware from 'redux-thunk'; -import dialogAddWorkspace from './dialog-add-workspace/reducers'; -import dialogCodeInjection from './dialog-code-injection/reducers'; -import dialogCustomUserAgent from './dialog-custom-user-agent/reducers'; -import dialogEditWorkspace from './dialog-edit-workspace/reducers'; -import dialogGoToUrl from './dialog-go-to-url/reducers'; -import dialogProxy from './dialog-proxy/reducers'; -import dialogSpellcheckLanguages from './dialog-spellcheck-languages/reducers'; -import findInPage from './find-in-page/reducers'; -import general from './general/reducers'; -import notifications from './notifications/reducers'; -import preferences from './preferences/reducers'; -import systemPreferences from './system-preferences/reducers'; -import updater from './updater/reducers'; -import workspaces from './workspaces/reducers'; -import workspaceMetas from './workspace-metas/reducers'; - -import loadListeners from '../listeners'; - -const rootReducer = combineReducers({ - dialogAddWorkspace, - dialogCodeInjection, - dialogCustomUserAgent, - dialogEditWorkspace, - dialogGoToUrl, - dialogProxy, - dialogSpellcheckLanguages, - findInPage, - general, - notifications, - preferences, - systemPreferences, - updater, - workspaces, - workspaceMetas, -}); - -const configureStore = (initialState: any) => createStore(rootReducer, initialState, applyMiddleware(thunkMiddleware)); - -// init store -const store = configureStore({}); - -loadListeners(store); - -export default store; +export default {}; diff --git a/src/type.d.ts b/src/type.d.ts index 3361179e..56117c7a 100644 --- a/src/type.d.ts +++ b/src/type.d.ts @@ -1 +1,5 @@ declare module 'errio'; +declare module '*.png' { + const value: string; + export default value; +}