From d7d8d96f339ef3eb7a36492cd669b25ddcfd042c Mon Sep 17 00:00:00 2001 From: Lin Onetwo Date: Sun, 12 Jul 2020 23:31:21 +0800 Subject: [PATCH] feat: allow open a nodejs wiki folder as workspace --- public/libs/create-wiki.js | 19 +- public/listeners/index.js | 4 +- .../description-and-mode-switch.js | 6 +- .../existed-wiki-done-button.js | 133 ++++++++++++++ .../existed-wiki-path-form.js | 170 ++++++++++++++++++ src/components/dialog-add-workspace/index.js | 124 ++++++++----- ...done-button.js => new-wiki-done-button.js} | 6 +- ...iki-path-form.js => new-wiki-path-form.js} | 7 +- .../dialog-add-workspace/tab-bar.js | 34 ++++ src/senders/index.js | 4 +- 10 files changed, 441 insertions(+), 66 deletions(-) create mode 100644 src/components/dialog-add-workspace/existed-wiki-done-button.js create mode 100644 src/components/dialog-add-workspace/existed-wiki-path-form.js rename src/components/dialog-add-workspace/{done-button.js => new-wiki-done-button.js} (97%) rename src/components/dialog-add-workspace/{wiki-path-form.js => new-wiki-path-form.js} (97%) create mode 100644 src/components/dialog-add-workspace/tab-bar.js diff --git a/public/libs/create-wiki.js b/public/libs/create-wiki.js index 7698eac0..4fe5a112 100644 --- a/public/libs/create-wiki.js +++ b/public/libs/create-wiki.js @@ -21,7 +21,14 @@ async function createWiki(newFolderPath, folderName) { } } -async function createSubWiki(newFolderPath, folderName, mainWikiToLink) { +/** + * + * @param {string} newFolderPath + * @param {string} folderName + * @param {string} mainWikiToLink + * @param {boolean} onlyLink not creating new subwiki folder, just link existed subwiki folder to main wiki folder + */ +async function createSubWiki(newFolderPath, folderName, mainWikiToLink, onlyLink = false) { const newWikiPath = path.join(newFolderPath, folderName); const mainWikiTiddlersFolderPath = path.join(mainWikiToLink, TIDDLERS_PATH, folderName); if (!(await fs.pathExists(newFolderPath))) { @@ -30,10 +37,12 @@ async function createSubWiki(newFolderPath, folderName, mainWikiToLink) { if (await fs.pathExists(newWikiPath)) { throw new Error(`Wiki已经存在于该位置 "${newWikiPath}"`); } - try { - await fs.mkdirs(newWikiPath); - } catch { - throw new Error(`无法在该处创建文件夹 "${newWikiPath}"`); + if (!onlyLink) { + try { + await fs.mkdirs(newWikiPath); + } catch { + throw new Error(`无法在该处创建文件夹 "${newWikiPath}"`); + } } try { await fs.createSymlink(newWikiPath, mainWikiTiddlersFolderPath); diff --git a/public/listeners/index.js b/public/listeners/index.js index 49799dc8..7670bb02 100755 --- a/public/listeners/index.js +++ b/public/listeners/index.js @@ -64,9 +64,9 @@ const loadListeners = () => { return String(error); } }); - ipcMain.handle('create-sub-wiki', async (event, newFolderPath, folderName, mainWikiToLink) => { + ipcMain.handle('create-sub-wiki', async (event, newFolderPath, folderName, mainWikiToLink, onlyLink) => { try { - await createSubWiki(newFolderPath, folderName, mainWikiToLink); + await createSubWiki(newFolderPath, folderName, mainWikiToLink, onlyLink); } catch (error) { return String(error); } diff --git a/src/components/dialog-add-workspace/description-and-mode-switch.js b/src/components/dialog-add-workspace/description-and-mode-switch.js index 638c5452..bc89290b 100644 --- a/src/components/dialog-add-workspace/description-and-mode-switch.js +++ b/src/components/dialog-add-workspace/description-and-mode-switch.js @@ -26,12 +26,12 @@ export default function Description({ isCreateMainWorkspace, isCreateMainWorkspa onChange={event => isCreateMainWorkspaceSetter(event.target.checked)} /> } - label={`创建${isCreateMainWorkspace ? '主' : '子'}知识库`} + label={`${isCreateMainWorkspace ? '主' : '子'}知识库`} /> {isCreateMainWorkspace - ? '主知识库包含了TiddlyWiki的配置文件,以及发布为博客时的公开内容。' - : '子知识库必须依附于一个主知识库,可用于存放私有内容,同步到一个私有的Github仓库内,仅本人可读写。子知识库通过创建一个到主知识库的软链接(快捷方式)来生效,创建链接后主知识库内便可看到子知识库内的内容了。'} + ? '包含了TiddlyWiki的配置文件,以及发布为博客时的公开内容。' + : '必须依附于一个主知识库,可用于存放私有内容,同步到一个私有的Github仓库内,仅本人可读写。子知识库通过创建一个到主知识库的软链接(快捷方式)来生效,创建链接后主知识库内便可看到子知识库内的内容了。'} ); diff --git a/src/components/dialog-add-workspace/existed-wiki-done-button.js b/src/components/dialog-add-workspace/existed-wiki-done-button.js new file mode 100644 index 00000000..afc59abe --- /dev/null +++ b/src/components/dialog-add-workspace/existed-wiki-done-button.js @@ -0,0 +1,133 @@ +// @flow +import React from 'react'; +import styled from 'styled-components'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import { basename, dirname } from 'path'; + +import * as actions from '../../state/dialog-add-workspace/actions'; + +import type { IUserInfo } from './user-info'; +import { requestCreateSubWiki, getIconPath, initWikiGit } from '../../senders'; + +const CloseButton = styled(Button)` + white-space: nowrap; + width: 100%; +`; + +interface Props { + isCreateMainWorkspace: boolean; + wikiPort: number; + mainWikiToLink: string; + githubWikiUrl: string; + existedFolderLocation: string; + userInfo: IUserInfo; +} +interface ActionProps { + updateForm: Object => void; + setWikiCreationMessage: string => void; + save: () => void; +} + +function DoneButton({ + isCreateMainWorkspace, + wikiPort, + mainWikiToLink, + githubWikiUrl, + existedFolderLocation, + updateForm, + setWikiCreationMessage, + save, + userInfo, +}: Props & ActionProps) { + const workspaceFormData = { + name: existedFolderLocation, + isSubWiki: !isCreateMainWorkspace, + mainWikiToLink, + port: wikiPort, + homeUrl: `http://localhost:${wikiPort}/`, + gitUrl: githubWikiUrl, // don't need .git suffix + picturePath: getIconPath(), + userInfo, + }; + return isCreateMainWorkspace ? ( + { + updateForm(workspaceFormData); + save(); + }} + > + {existedFolderLocation && ( + <> + + 打开位于 + + + {existedFolderLocation} + + + )} + + 的WIKI + + + ) : ( + { + const wikiFolderName = basename(existedFolderLocation); + const parentFolderLocation = dirname(existedFolderLocation); + updateForm(workspaceFormData); + const creationError = await requestCreateSubWiki(parentFolderLocation, wikiFolderName, mainWikiToLink, true); + if (creationError) { + setWikiCreationMessage(creationError); + } else { + save(); + } + }} + > + {existedFolderLocation && ( + <> + + 打开位于 + + + {existedFolderLocation} + + + )} + + 的WIKI + + + 并链接到主知识库 + + + ); +} + +const mapStateToProps = state => ({ + wikiCreationMessage: state.dialogAddWorkspace.wikiCreationMessage, +}); + +export default connect(mapStateToProps, dispatch => bindActionCreators(actions, dispatch))(DoneButton); diff --git a/src/components/dialog-add-workspace/existed-wiki-path-form.js b/src/components/dialog-add-workspace/existed-wiki-path-form.js new file mode 100644 index 00000000..e95f3081 --- /dev/null +++ b/src/components/dialog-add-workspace/existed-wiki-path-form.js @@ -0,0 +1,170 @@ +// @flow +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import Paper from '@material-ui/core/Paper'; +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import InputLabel from '@material-ui/core/InputLabel'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; +import FolderIcon from '@material-ui/icons/Folder'; + +import * as actions from '../../state/dialog-add-workspace/actions'; + +import { getWorkspaces } from '../../senders'; +import { log } from 'isomorphic-git'; + +const CreateContainer = styled(Paper)` + margin-top: 5px; +`; +const LocationPickerContainer = styled.div` + display: flex; + flex-direction: row; +`; +const LocationPickerInput = styled(TextField)``; +const LocationPickerButton = styled(Button)` + white-space: nowrap; + width: fit-content; +`; +const SoftLinkToMainWikiSelect = styled(Select)` + width: 100%; +`; +const SoftLinkToMainWikiSelectInputLabel = styled(InputLabel)` + margin-top: 5px; +`; + +interface Props { + wikiCreationMessage?: string; + existedFolderLocationSetter: string => void; + wikiFolderName: string; + wikiFolderNameSetter: string => void; + mainWikiToLink: string; + mainWikiToLinkSetter: string => void; + existedFolderLocation: string; + wikiPort: Number; + wikiPortSetter: number => void; + isCreateMainWorkspace: boolean; +} +interface ActionProps { + setWikiCreationMessage: string => void; +} +interface StateProps { + wikiCreationMessage: string; +} + +function WikiPathForm({ + setWikiCreationMessage, + wikiCreationMessage = '', + existedFolderLocation, + existedFolderLocationSetter, + wikiFolderName, + wikiFolderNameSetter, + mainWikiToLink, + mainWikiToLinkSetter, + wikiPort, + wikiPortSetter, + isCreateMainWorkspace, +}: Props & ActionProps & StateProps) { + const [workspaces, workspacesSetter] = useState({}); + useEffect(() => { + workspacesSetter(getWorkspaces()); + }, []); + + return ( + + + { + existedFolderLocationSetter(event.target.value); + setWikiCreationMessage(''); + }} + label="知识库所在的的文件夹" + value={existedFolderLocation} + /> + { + const { remote } = window.require('electron'); + // eslint-disable-next-line promise/catch-or-return + remote.dialog + .showOpenDialog(remote.getCurrentWindow(), { + properties: ['openDirectory'], + }) + .then(({ canceled, filePaths }) => { + console.log(filePaths) + // eslint-disable-next-line promise/always-return + if (!canceled && filePaths.length > 0) { + existedFolderLocationSetter(filePaths[0]); + } + }); + }} + variant="outlined" + color={existedFolderLocation ? 'default' : 'primary'} + disableElevation + endIcon={} + > + + 选择 + + + + { + wikiPortSetter(event.target.value); + }} + label="WIKI服务器端口号(出现冲突再改,一般默认即可)" + value={wikiPort} + /> + {!isCreateMainWorkspace && ( + <> + + 主知识库位置 + + mainWikiToLinkSetter(event.target.value)} + > + {Object.keys(workspaces).map(workspaceID => ( + + {workspaces[workspaceID].name} + + ))} + + {mainWikiToLink && ( + + + 子知识库将链接到 + + + {mainWikiToLink}/tiddlers/{wikiFolderName} + + + )} + + )} + + ); +} + +const mapStateToProps = state => ({ + wikiCreationMessage: state.dialogAddWorkspace.wikiCreationMessage, +}); + +export default connect(mapStateToProps, dispatch => bindActionCreators(actions, dispatch))(WikiPathForm); diff --git a/src/components/dialog-add-workspace/index.js b/src/components/dialog-add-workspace/index.js index e7c221a8..7d48a886 100644 --- a/src/components/dialog-add-workspace/index.js +++ b/src/components/dialog-add-workspace/index.js @@ -1,5 +1,5 @@ // @flow -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { GraphQLClient, ClientContext } from 'graphql-hooks'; @@ -10,10 +10,13 @@ import { GITHUB_GRAPHQL_API } from '../../constants/auth'; import Description from './description-and-mode-switch'; import SearchRepo from './search-repo'; -import DoneButton from './done-button'; -import WikiPathForm from './wiki-path-form'; +import NewWikiDoneButton from './new-wiki-done-button'; +import NewWikiPathForm from './new-wiki-path-form'; +import ExistedWikiPathForm from './existed-wiki-path-form'; +import ExistedWikiDoneButton from './existed-wiki-done-button'; import { getGithubUserInfo, setGithubUserInfo } from './user-info'; import type { IUserInfo } from './user-info'; +import TabBar from './tab-bar'; import { requestSetPreference, getPreference, getDesktopPath, countWorkspace } from '../../senders'; @@ -22,14 +25,12 @@ const graphqlClient = new GraphQLClient({ }); const Container = styled.main` - height: 100vh; display: flex; flex-direction: column; overflow: scroll; &::-webkit-scrollbar { width: 0; } - padding-bottom: 35px; `; const SyncContainer = styled(Paper)` margin-top: 5px; @@ -42,8 +43,10 @@ const previousToken = getGithubToken(); previousToken && setHeaderToGraphqlClient(previousToken); export default function AddWorkspace() { + const [currentTab, currentTabSetter] = useState(0); const [isCreateMainWorkspace, isCreateMainWorkspaceSetter] = useState(countWorkspace() === 0); const [parentFolderLocation, parentFolderLocationSetter] = useState(getDesktopPath()); + const [existedFolderLocation, existedFolderLocationSetter] = useState(getDesktopPath()); const [wikiPort, wikiPortSetter] = useState(5212 + countWorkspace()); // try get token on start up @@ -71,50 +74,79 @@ export default function AddWorkspace() { return ( - - + + {currentTab === 0 ? ( + + - - - 同步到云端 - - {userInfo && ( - - )} - + + + 同步到云端 + + {userInfo && ( + + )} + - + - - + + + ) : ( + + + + + + )} ); } diff --git a/src/components/dialog-add-workspace/done-button.js b/src/components/dialog-add-workspace/new-wiki-done-button.js similarity index 97% rename from src/components/dialog-add-workspace/done-button.js rename to src/components/dialog-add-workspace/new-wiki-done-button.js index 772f6c7f..bcb27989 100644 --- a/src/components/dialog-add-workspace/done-button.js +++ b/src/components/dialog-add-workspace/new-wiki-done-button.js @@ -15,8 +15,6 @@ import { requestCopyWikiTemplate, requestCreateSubWiki, getIconPath, initWikiGit const CloseButton = styled(Button)` white-space: nowrap; width: 100%; - position: absolute; - bottom: 0; `; interface Props { @@ -34,7 +32,7 @@ interface ActionProps { save: () => void; } -function DoneButton({ +function NewWikiDoneButton({ isCreateMainWorkspace, wikiPort, mainWikiToLink, @@ -145,4 +143,4 @@ const mapStateToProps = state => ({ wikiCreationMessage: state.dialogAddWorkspace.wikiCreationMessage, }); -export default connect(mapStateToProps, dispatch => bindActionCreators(actions, dispatch))(DoneButton); +export default connect(mapStateToProps, dispatch => bindActionCreators(actions, dispatch))(NewWikiDoneButton); diff --git a/src/components/dialog-add-workspace/wiki-path-form.js b/src/components/dialog-add-workspace/new-wiki-path-form.js similarity index 97% rename from src/components/dialog-add-workspace/wiki-path-form.js rename to src/components/dialog-add-workspace/new-wiki-path-form.js index 69095dfb..012044cd 100644 --- a/src/components/dialog-add-workspace/wiki-path-form.js +++ b/src/components/dialog-add-workspace/new-wiki-path-form.js @@ -39,7 +39,6 @@ const SoftLinkToMainWikiSelectInputLabel = styled(InputLabel)` interface Props { wikiCreationMessage?: string; - parentFolderLocation: string; parentFolderLocationSetter: string => void; wikiFolderName: string; wikiFolderNameSetter: string => void; @@ -57,7 +56,7 @@ interface StateProps { wikiCreationMessage: string; } -function WikiPathForm({ +function NewWikiPathForm({ setWikiCreationMessage, wikiCreationMessage = '', parentFolderLocation, @@ -121,7 +120,7 @@ function WikiPathForm({ wikiFolderNameSetter(event.target.value); setWikiCreationMessage(''); }} - label="知识库文件夹名" + label="即将新建的知识库文件夹名" value={wikiFolderName} /> ({ wikiCreationMessage: state.dialogAddWorkspace.wikiCreationMessage, }); -export default connect(mapStateToProps, dispatch => bindActionCreators(actions, dispatch))(WikiPathForm); +export default connect(mapStateToProps, dispatch => bindActionCreators(actions, dispatch))(NewWikiPathForm); diff --git a/src/components/dialog-add-workspace/tab-bar.js b/src/components/dialog-add-workspace/tab-bar.js new file mode 100644 index 00000000..8c889dbb --- /dev/null +++ b/src/components/dialog-add-workspace/tab-bar.js @@ -0,0 +1,34 @@ +// @flow +import React from 'react'; +import Paper from '@material-ui/core/Paper'; +import AppBar from '@material-ui/core/AppBar'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; + +function a11yProps(index) { + return { + id: `simple-tab-${index}`, + 'aria-controls': `simple-tabpanel-${index}`, + }; +} + +export interface IProps { + currentTab: number; + currentTabSetter: number => void; +} +export default function TabBar({ currentTab, currentTabSetter }: IProps) { + return ( + + + currentTabSetter(newValue)} + aria-label="切换创建新的还是打开现有的WIKI" + > + + + + + + ); +} diff --git a/src/senders/index.js b/src/senders/index.js index c99f9ac8..1727c2cd 100644 --- a/src/senders/index.js +++ b/src/senders/index.js @@ -3,8 +3,8 @@ const { ipcRenderer } = window.require('electron'); export const requestCopyWikiTemplate = (newFolderPath, folderName) => ipcRenderer.invoke('copy-wiki-template', newFolderPath, folderName); -export const requestCreateSubWiki = (newFolderPath, folderName, mainWikiToLink) => - ipcRenderer.invoke('create-sub-wiki', newFolderPath, folderName, mainWikiToLink); +export const requestCreateSubWiki = (newFolderPath: string, folderName: string, mainWikiToLink: string, onlyLink?: boolean) => + ipcRenderer.invoke('create-sub-wiki', newFolderPath, folderName, mainWikiToLink, onlyLink); export const requestOpenInBrowser = url => ipcRenderer.send('request-open-in-browser', url); export const requestShowMessageBox = (message, type) => ipcRenderer.send('request-show-message-box', message, type); export const requestLoadUrl = (url, id) => ipcRenderer.send('request-load-url', url, id);