refactor: new Main.tsx

This commit is contained in:
tiddlygit-test 2021-02-17 22:17:38 +08:00
parent c2282b7ed4
commit fd580c6462
28 changed files with 455 additions and 492 deletions

View file

@ -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],

5
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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 (
<WorkspaceSelector
active={active}
id={id}
key={id}
name={name}
picturePath={picturePath}
transparentBackground={transparentBackground}
order={index}
hibernated={hibernated}
onClick={async () => {
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) => <div>{children}</div>);
interface SidebarContainerProps {
className: string;
children: React.ReactNode;
}
const SidebarContainer = ({ className, children }: SidebarContainerProps) => {
// use native scroll bar on macOS
if (window.remote.getPlatform() === 'darwin') {
return <div className={className}>{children}</div>;
}
return <SimpleBar className={className}>{children}</SimpleBar>;
};
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 (
<div className={classes.outerRoot}>
{workspacesList.length > 0 && <DraggableRegion />}
{showTitleBar && <FakeTitleBar />}
<div className={classes.root}>
{sidebar && (
<SidebarContainer className={classes.sidebarRoot}>
<div
className={classNames(
classes.sidebarTop,
(isFullScreen || showTitleBar || window.meta.windowName === 'menubar') && classes.sidebarTopFullScreen,
)}>
<SortableContainer
distance={10}
helperClass={classes.grabbing}
onSortEnd={async ({ oldIndex, newIndex }: any) => {
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'.
<SortableItem key={`item-${workspace.id}`} index={index} value={{ index: index, workspace }} />
))}
</SortableContainer>
<WorkspaceSelector id="add" onClick={async () => await window.service.window.open(WindowNames.addWorkspace)} />
</div>
{!navigationBar && (
<div className={classes.end}>
<IconButton
aria-label="Notifications"
onClick={async () => await window.service.window.open(WindowNames.notifications)}
className={classes.iconButton}>
{shouldPauseNotifications ? <NotificationsPausedIcon /> : <NotificationsIcon />}
</IconButton>
{window.meta.windowName === 'menubar' && (
<IconButton
aria-label="Preferences"
onClick={async () => await window.service.window.open(WindowNames.preferences)}
className={classes.iconButton}>
<SettingsIcon />
</IconButton>
)}
</div>
)}
</SidebarContainer>
)}
<div className={classes.contentRoot}>
{navigationBar && <NavigationBar />}
<FindInPage />
<div className={classes.innerContentRoot}>
{Object.keys(workspaces).length > 0 && didFailLoad && !isLoading && (
<div>
<Typography align="left" variant="h5">
Wiki is not started or not loaded
</Typography>
<Typography align="left" variant="body2">
{didFailLoad}
</Typography>
<br />
<Typography align="left" variant="body2">
<>
Try:
<ul className={classes.ul}>
<li>
Click{' '}
<b onClick={requestReload} onKeyPress={requestReload} role="button" tabIndex={0} style={{ cursor: 'pointer' }}>
Reload
</b>{' '}
button below or press <b>CMD_or_Ctrl + R</b> to reload the page.
</li>
<li>
Check the{' '}
<b
onClick={async () => 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
</b>{' '}
to see what happened.
</li>
<li>Backup your file, remove workspace and recreate one.</li>
</ul>
</>
</Typography>
<Button variant="outlined" onClick={requestReload}>
Reload
</Button>
</div>
)}
{Object.keys(workspaces).length > 0 && isLoading && (
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
<Typography type="body1" color="textSecondary">
Loading..
</Typography>
)}
{Object.keys(workspaces).length === 0 && (
<div>
{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 */}
<div alt="Arrow" className={classes.arrow} />
<div className={classes.tip}>
<span className={classes.inlineBlock}>Click</span>
<div className={classes.avatar}>+</div>
<span className={classes.inlineBlock}>to get started!</span>
</div>
</>
) : (
<div className={classes.tip2}>
<span className={classes.inlineBlock}>
<span>Click </span>
<strong>Workspaces &gt; Add Workspace</strong>
<span> to get started!</span>
</span>
</div>
)}
</div>
)}
</div>
</div>
</div>
// @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'div'.
</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);

View file

@ -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<T>(valuePromise: Promise<T> | (() => Promise<T>), defaultValue?: T): T | undefined {
const [value, valueSetter] = useState<T | undefined>(defaultValue);
export function usePromiseValue<T, DefaultValueType = T | undefined>(
asyncValue: () => Promise<T>,
defaultValue?: AsyncReturnType<typeof asyncValue>,
): T | DefaultValueType {
const [value, valueSetter] = useState<T | DefaultValueType>(defaultValue as T | DefaultValueType);
useEffect(() => {
void (async () => {
valueSetter(typeof valuePromise === 'function' ? await valuePromise() : await valuePromise);
valueSetter(await asyncValue());
});
}, []);

View file

@ -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<string | undefined>(window.service.context.get('ICON_PATH') as Promise<string>);
const appVersion = usePromiseValue<string | undefined>(window.service.context.get('appVersion') as Promise<string>);
const iconPath = usePromiseValue<string>(async () => (await window.service.context.get('ICON_PATH')) as string);
const appVersion = usePromiseValue<string>(async () => (await window.service.context.get('appVersion')) as string);
const platform = usePromiseValue<string>(async () => (await window.service.context.get('platform')) as string);
return (
<div>
<DialogContentSC>
<Icon src={iconPath} alt="TiddlyGit" />
<Title>TiddlyGit</Title>
<Title>TiddlyGit ({platform ?? 'Unknown Platform'})</Title>
<Version>{`Version v${appVersion ?? ' - '}.`}</Version>
<VersionSmallContainer>
{versions?.map(({ name, version }) => (

View file

@ -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 }) => <div>{children}</div>);
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 (
<WorkspaceSelector
active={active}
id={id}
key={id}
name={name}
picturePath={picturePath}
transparentBackground={transparentBackground}
order={index}
hibernated={hibernated}
onClick={async () => {
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);
}}
/>
);
}),
);

309
src/pages/Main/index.tsx Normal file
View file

@ -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 <SidebarRoot>{children}</SidebarRoot>;
}
return <SidebarWithStyle>{children}</SidebarWithStyle>;
};
function Main(): JSX.Element {
// TODO: make workspacesList observable
const workspacesList = usePromiseValue(
window.service.workspace.getWorkspacesAsList,
[] as AsyncReturnType<typeof window.service.workspace.getWorkspacesAsList>,
)!;
const platform = usePromiseValue<string>(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<typeof window.service.workspace.getMetaData>);
const requestReload = async (): Promise<void> => await window.service.window.reload(window.meta.windowName);
return (
<OuterRoot>
{workspacesList.length > 0 && <DraggableRegion />}
{showTitleBar && <FakeTitleBar />}
<Root>
{sidebar === true && (
<SidebarContainer>
<SidebarTop fullscreen={isFullScreen === true || showTitleBar || attachToMenubar}>
<SortableContainer
distance={10}
onSortEnd={async ({ oldIndex, newIndex }) => {
if (oldIndex === newIndex) return;
const newWorkspacesList = arrayMove(workspacesList, oldIndex, newIndex);
const newWorkspaces: Record<string, IWorkspace> = {};
newWorkspacesList.forEach((workspace, index) => {
newWorkspaces[workspace.id] = workspace;
newWorkspaces[workspace.id].order = index;
});
await window.service.workspace.setWorkspaces(newWorkspaces);
}}>
{workspacesList.map((workspace, index) => (
<SortableItem key={`item-${workspace.id}`} index={index} value={{ index: index, workspace }} />
))}
</SortableContainer>
<WorkspaceSelector id="add" onClick={async () => await window.service.window.open(WindowNames.addWorkspace)} />
</SidebarTop>
{navigationBar === false && (
<End>
<IconButton aria-label="Notifications" onClick={async () => await window.service.window.open(WindowNames.notifications)}>
{typeof pauseNotifications === 'string' && pauseNotifications.length > 0 ? <NotificationsPausedIcon /> : <NotificationsIcon />}
</IconButton>
{attachToMenubar === true && (
<IconButton aria-label="Preferences" onClick={async () => await window.service.window.open(WindowNames.preferences)}>
<SettingsIcon />
</IconButton>
)}
</End>
)}
</SidebarContainer>
)}
<ContentRoot>
{navigationBar === true && <NavigationBar />}
<FindInPage />
<InnerContentRoot>
{workspacesList.length > 0 && mainWorkspaceMetaData?.didFailLoadErrorMessage && mainWorkspaceMetaData?.isLoading === false && (
<div>
<Typography align="left" variant="h5">
Wiki is not started or not loaded
</Typography>
<Typography align="left" variant="body2">
{mainWorkspaceMetaData.didFailLoadErrorMessage}
</Typography>
<br />
<Typography align="left" variant="body2">
<>
Try:
<HelperTextsList>
<li>
Click{' '}
<b onClick={requestReload} onKeyPress={requestReload} role="button" tabIndex={0} style={{ cursor: 'pointer' }}>
Reload
</b>{' '}
button below or press <b>CMD_or_Ctrl + R</b> to reload the page.
</li>
<li>
Check the{' '}
<b
onClick={async () => 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
</b>{' '}
to see what happened.
</li>
<li>Backup your file, remove workspace and recreate one.</li>
</HelperTextsList>
</>
</Typography>
<Button variant="outlined" onClick={requestReload}>
Reload
</Button>
</div>
)}
{workspacesList.length > 0 && mainWorkspaceMetaData?.isLoading && <Typography color="textSecondary">Loading..</Typography>}
{workspacesList.length === 0 && (
<div>
{sidebar === true ? (
<>
<Arrow image={themeSource === 'dark' ? arrowWhite : arrowBlack} />
<Tip>
<Tip2Text>Click</Tip2Text>
<Avatar>+</Avatar>
<Tip2Text>to get started!</Tip2Text>
</Tip>
</>
) : (
<Tip2>
<Tip2Text>
<span>Click </span>
<strong>Workspaces &gt; Add Workspace</strong>
<span> to get started!</span>
</Tip2Text>
</Tip2>
)}
</div>
)}
</InnerContentRoot>
</ContentRoot>
</Root>
</OuterRoot>
);
}
Main.defaultProps = {
isLoading: false,
};

View file

@ -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_)),

View file

@ -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 => {

View file

@ -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) {

View file

@ -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';

View file

@ -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 });
}
}
}

View file

@ -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<MenuItemConstru
export interface IMenuService {
buildMenu(): void;
insertMenu(menuID: string, menuItems: DeferredMenuItemConstructorOptions[], afterSubMenu?: string | null, withSeparator?: boolean): void;
buildContextMenuAndPopup(template: MenuItemConstructorOptions[], windowName?: WindowNames): void;
}
export const MenuServiceIPCDescriptor = {
channel: MenuChannel.name,
properties: {
buildMenu: ProxyPropertyType.Function,
insertMenu: ProxyPropertyType.Function,
buildContextMenuAndPopup: ProxyPropertyType.Function,
},
};

View file

@ -112,10 +112,10 @@ export default function setupViewEventHandlers(
if (workspaceObject === undefined) {
return;
}
// isLoading is now controlled by wiki-worker-manager.js
// workspaceService.updateMetaData(workspace.id, {
// isLoading: false,
// });
// FIXME: seems isLoading is now controlled by wiki-worker-manager.js , and should not update here
workspaceService.updateMetaData(workspace.id, {
isLoading: false,
});
if (workspaceObject.active) {
windowService.sendToAllWindows(WindowChannel.updateAddress, view.webContents.getURL(), false);
}

View file

@ -276,6 +276,10 @@ export class Window implements IWindowService {
});
}
public isFullScreen(windowName = WindowNames.main): boolean | undefined {
return this.windows[windowName]?.isFullScreen();
}
public setWindowMeta<N extends WindowNames>(windowName: N, meta: WindowMeta[N]): void {
this.windowMeta[windowName] = meta;
}

View file

@ -15,6 +15,7 @@ export interface IWindowService {
getWindowMeta<N extends WindowNames>(windowName: N): WindowMeta[N] | undefined;
sendToAllWindows: (channel: Channels, ...arguments_: unknown[]) => void;
requestShowRequireRestartDialog(): Promise<void>;
isFullScreen(windowName?: WindowNames): boolean | undefined;
goHome(windowName: WindowNames): Promise<void>;
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,

View file

@ -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();
}

View file

@ -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];

View file

@ -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;

View file

@ -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);

View file

@ -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 {};

4
src/type.d.ts vendored
View file

@ -1 +1,5 @@
declare module 'errio';
declare module '*.png' {
const value: string;
export default value;
}