diff --git a/renderer/client-pages/config.js b/renderer/client-pages/config.js new file mode 100644 index 00000000..4858599d --- /dev/null +++ b/renderer/client-pages/config.js @@ -0,0 +1,76 @@ +import React from 'react'; +import {Provider} from 'unstated'; +import {ipcRenderer as ipc} from 'electron-better-ipc'; + +import {ConfigContainer} from '../containers'; +import Config from '../components/config'; +import WindowHeader from '../components/window-header'; + +const configContainer = new ConfigContainer(); + +export default class ConfigPage extends React.Component { + state = {title: ''}; + + componentDidMount() { + ipc.answerMain('plugin', pluginName => { + configContainer.setPlugin(pluginName); + this.setState({title: pluginName.replace(/^kap-/, '')}); + }); + + ipc.answerMain('edit-service', ({pluginName, serviceTitle}) => { + configContainer.setEditService(pluginName, serviceTitle); + this.setState({title: serviceTitle}); + }); + } + + render() { + const {title} = this.state; + + return ( +
+
+ + + + +
+ +
+ ); + } +} diff --git a/renderer/client-pages/cropper.js b/renderer/client-pages/cropper.js new file mode 100644 index 00000000..a11e798e --- /dev/null +++ b/renderer/client-pages/cropper.js @@ -0,0 +1,199 @@ +import electron from 'electron'; +import React from 'react'; +import {Provider} from 'unstated'; + +import Overlay from '../components/cropper/overlay'; +import Cropper from '../components/cropper'; +import ActionBar from '../components/action-bar'; + +import CursorContainer from '../containers/cursor'; +import CropperContainer from '../containers/cropper'; +import ActionBarContainer from '../containers/action-bar'; + +const cursorContainer = new CursorContainer(); +const cropperContainer = new CropperContainer(); +const actionBarContainer = new ActionBarContainer(); + +cropperContainer.bindCursor(cursorContainer); +cropperContainer.bindActionBar(actionBarContainer); +actionBarContainer.bindCursor(cursorContainer); +actionBarContainer.bindCropper(cropperContainer); + +let lastRatioLockState = null; + +export default class CropperPage extends React.Component { + remote = electron.remote || false; + + dev = false; + + constructor(props) { + super(props); + + if (!electron.ipcRenderer) { + return; + } + + const {ipcRenderer, remote} = electron; + + ipcRenderer.on('display', (_, display) => { + cropperContainer.setDisplay(display); + actionBarContainer.setDisplay(display); + }); + + ipcRenderer.on('select-app', (_, app) => { + cropperContainer.selectApp(app); + cropperContainer.setActive(true); + }); + + ipcRenderer.on('blur', () => { + cropperContainer.setActive(false); + }); + + ipcRenderer.on('start-recording', () => { + cropperContainer.setRecording(); + }); + + const window = remote.getCurrentWindow(); + window.on('focus', () => { + cropperContainer.setActive(true); + }); + + window.on('blur', event => { + if (!event.defaultPrevented) { + cropperContainer.setActive(false); + } + }); + } + + componentDidMount() { + document.addEventListener('keydown', this.handleKeyEvent); + document.addEventListener('keyup', this.handleKeyEvent); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.handleKeyEvent); + document.removeEventListener('keyup', this.handleKeyEvent); + } + + handleKeyEvent = event => { + switch (event.key) { + case 'Escape': + this.remote.getCurrentWindow().close(); + break; + case 'Shift': + if (event.type === 'keydown' && !event.defaultPrevented) { + lastRatioLockState = actionBarContainer.state.ratioLocked; + actionBarContainer.toggleRatioLock(true); + } else if (event.type === 'keyup' && lastRatioLockState !== null) { + actionBarContainer.toggleRatioLock(lastRatioLockState); + lastRatioLockState = null; + } + + break; + case 'Alt': + cropperContainer.toggleResizeFromCenter(event.type === 'keydown'); + break; + case 'i': + this.remote.getCurrentWindow().setIgnoreMouseEvents(true); + this.dev = !this.dev; + break; + default: + break; + } + }; + + render() { + return ( +
+ + + + + + + +
+ ); + } +} diff --git a/renderer/client-pages/dialog.js b/renderer/client-pages/dialog.js new file mode 100644 index 00000000..69d7f637 --- /dev/null +++ b/renderer/client-pages/dialog.js @@ -0,0 +1,123 @@ +import Actions from '../components/dialog/actions'; +import Icon from '../components/dialog/icon'; +import Body from '../components/dialog/body'; +import React, {useState, useEffect, useRef, useCallback} from 'react'; +import {ipcRenderer as ipc} from 'electron-better-ipc'; + +let measureResolve; + +const Dialog = () => { + const [data, setData] = useState(); + const [measureSize, setMeasureSize] = useState(false); + const [isDisabled, setIsDisabled] = useState(false); + const container = useRef(null); + + const performAction = useCallback(async index => { + if (!isDisabled || data.cancelId === index) { + setIsDisabled(true); + return ipc.callMain(`dialog-action-${data.id}`, index); + } + }, [data, isDisabled, setIsDisabled]); + + useEffect(() => { + return ipc.answerMain('data', async newData => new Promise(resolve => { + setData(newData); + setMeasureSize(true); + measureResolve = resolve; + })); + }, []); + + useEffect(() => { + if (data) { + setIsDisabled(false); + } + + if (data && data.cancelId) { + const handler = event => { + if (event.code === 'Escape') { + performAction(data.cancelId); + } + }; + + document.addEventListener('keydown', handler); + + return () => { + document.removeEventListener('keydown', handler); + }; + } + }, [data, performAction, isDisabled, setIsDisabled]); + + useEffect(() => { + if (measureSize && measureResolve) { + const {offsetWidth, offsetHeight} = container.current; + measureResolve({width: offsetWidth, height: offsetHeight}); + measureResolve = undefined; + setMeasureSize(false); + } + }, [measureSize]); + + if (!data) { + return null; + } + + return ( +
+ +
+ + +
+ +
+ ); +}; + +export default Dialog; diff --git a/renderer/client-pages/editor.tsx b/renderer/client-pages/editor.tsx new file mode 100644 index 00000000..71c095d4 --- /dev/null +++ b/renderer/client-pages/editor.tsx @@ -0,0 +1,90 @@ +import Head from 'next/head'; +// Import EditorPreview from '../components/editor/editor-preview'; +import combineUnstatedContainers from '../utils/combine-unstated-containers'; +import VideoMetadataContainer from '../components/editor/video-metadata-container'; +import VideoTimeContainer from '../components/editor/video-time-container'; +import VideoControlsContainer from '../components/editor/video-controls-container'; +import OptionsContainer from '../components/editor/options-container'; +import useEditorWindowState from 'hooks/editor/use-editor-window-state'; +import {ConversionIdContextProvider} from 'hooks/editor/use-conversion-id'; +import Editor from 'components/editor'; + +const ContainerProvider = combineUnstatedContainers([ + OptionsContainer, + VideoMetadataContainer, + VideoTimeContainer, + VideoControlsContainer +]) as any; + +const EditorPage = () => { + const args = useEditorWindowState(); + + if (!args) { + return null; + } + + return ( +
+ + + + + + + + + +
+ ); +}; + +export default EditorPage; diff --git a/renderer/client-pages/exports.tsx b/renderer/client-pages/exports.tsx new file mode 100644 index 00000000..0df8da41 --- /dev/null +++ b/renderer/client-pages/exports.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import WindowHeader from '../components/window-header'; +import Exports from '../components/exports'; + +const ExportsPage = () => ( +
+ + + +
+); + +export default ExportsPage; diff --git a/renderer/client-pages/preferences.js b/renderer/client-pages/preferences.js new file mode 100644 index 00000000..2c53b7b0 --- /dev/null +++ b/renderer/client-pages/preferences.js @@ -0,0 +1,136 @@ +import React from 'react'; +import {Provider} from 'unstated'; +import classNames from 'classnames'; +import {ipcRenderer as ipc} from 'electron-better-ipc'; + +import PreferencesNavigation from '../components/preferences/navigation'; +import WindowHeader from '../components/window-header'; +import Categories from '../components/preferences/categories'; + +import PreferencesContainer from '../containers/preferences'; + +const preferencesContainer = new PreferencesContainer(); + +export default class PreferencesPage extends React.Component { + state = {overlay: false}; + + componentDidMount() { + ipc.answerMain('open-plugin-config', preferencesContainer.openPluginsConfig); + ipc.answerMain('options', preferencesContainer.setNavigation); + ipc.answerMain('mount', async () => preferencesContainer.mount(this.setOverlay)); + } + + setOverlay = overlay => { + this.setState({overlay}); + }; + + render() { + const {overlay} = this.state; + const className = classNames('overlay', {active: overlay}); + + return ( +
+
+ + + + + + + +
+ ); + } +} diff --git a/renderer/components/main-app.tsx b/renderer/components/main-app.tsx new file mode 100644 index 00000000..b3f24443 --- /dev/null +++ b/renderer/components/main-app.tsx @@ -0,0 +1,24 @@ +import {AppProps} from 'next/app'; +import useDarkMode from '../hooks/dark-mode'; +import GlobalStyles from '../utils/global-styles'; +import SentryErrorBoundary from '../utils/sentry-error-boundary'; +import {WindowStateProvider} from '../hooks/window-state'; +import classNames from 'classnames'; + +const MainApp = ({Component, pageProps}: AppProps) => { + const isDarkMode = useDarkMode(); + const className = classNames('cover-window', {dark: isDarkMode}); + + return ( +
+ + + + + + +
+ ); +}; + +export default MainApp; diff --git a/renderer/next.config.js b/renderer/next.config.js index 67e3d827..19b0eed5 100644 --- a/renderer/next.config.js +++ b/renderer/next.config.js @@ -12,7 +12,10 @@ module.exports = (nextConfig) => { ] }); - config.target = 'electron-renderer'; + if (!options.isServer) { + config.target = 'electron-renderer'; + } + config.devtool = 'cheap-module-source-map'; if (typeof nextConfig.webpack === 'function') { diff --git a/renderer/pages/_app.tsx b/renderer/pages/_app.tsx index 3023fe93..a1c7f6ec 100644 --- a/renderer/pages/_app.tsx +++ b/renderer/pages/_app.tsx @@ -1,39 +1,8 @@ import {AppProps} from 'next/app'; -import {useState, useEffect} from 'react'; -import useDarkMode from '../hooks/dark-mode'; -import GlobalStyles from '../utils/global-styles'; -import SentryErrorBoundary from '../utils/sentry-error-boundary'; -import {WindowStateProvider} from '../hooks/window-state'; -import classNames from 'classnames'; +import dynamic from 'next/dynamic'; -const Kap = (props: AppProps) => { - const [isMounted, setIsMounted] = useState(false); +const MainApp = dynamic(async () => import('../components/main-app'), {ssr: false}); - useEffect(() => { - setIsMounted(true); - }, []); - - if (!isMounted) { - return null; - } - - return ; -}; - -const MainApp = ({Component, pageProps}: AppProps) => { - const isDarkMode = useDarkMode(); - const className = classNames('cover-window', {dark: isDarkMode}); - - return ( -
- - - - - - -
- ); -}; +const Kap = (props: AppProps) => ; export default Kap; diff --git a/renderer/pages/config.js b/renderer/pages/config.js index 4858599d..46c5e36b 100644 --- a/renderer/pages/config.js +++ b/renderer/pages/config.js @@ -1,76 +1,5 @@ -import React from 'react'; -import {Provider} from 'unstated'; -import {ipcRenderer as ipc} from 'electron-better-ipc'; +import dynamic from 'next/dynamic'; -import {ConfigContainer} from '../containers'; -import Config from '../components/config'; -import WindowHeader from '../components/window-header'; +const ConfigPage = dynamic(async () => import('../client-pages/config'), {ssr: false}); -const configContainer = new ConfigContainer(); - -export default class ConfigPage extends React.Component { - state = {title: ''}; - - componentDidMount() { - ipc.answerMain('plugin', pluginName => { - configContainer.setPlugin(pluginName); - this.setState({title: pluginName.replace(/^kap-/, '')}); - }); - - ipc.answerMain('edit-service', ({pluginName, serviceTitle}) => { - configContainer.setEditService(pluginName, serviceTitle); - this.setState({title: serviceTitle}); - }); - } - - render() { - const {title} = this.state; - - return ( -
-
- - - - -
- -
- ); - } -} +export default ConfigPage; diff --git a/renderer/pages/cropper.js b/renderer/pages/cropper.js index a11e798e..5afcda15 100644 --- a/renderer/pages/cropper.js +++ b/renderer/pages/cropper.js @@ -1,199 +1,5 @@ -import electron from 'electron'; -import React from 'react'; -import {Provider} from 'unstated'; +import dynamic from 'next/dynamic'; -import Overlay from '../components/cropper/overlay'; -import Cropper from '../components/cropper'; -import ActionBar from '../components/action-bar'; +const CropperPage = dynamic(async () => import('../client-pages/cropper'), {ssr: false}); -import CursorContainer from '../containers/cursor'; -import CropperContainer from '../containers/cropper'; -import ActionBarContainer from '../containers/action-bar'; - -const cursorContainer = new CursorContainer(); -const cropperContainer = new CropperContainer(); -const actionBarContainer = new ActionBarContainer(); - -cropperContainer.bindCursor(cursorContainer); -cropperContainer.bindActionBar(actionBarContainer); -actionBarContainer.bindCursor(cursorContainer); -actionBarContainer.bindCropper(cropperContainer); - -let lastRatioLockState = null; - -export default class CropperPage extends React.Component { - remote = electron.remote || false; - - dev = false; - - constructor(props) { - super(props); - - if (!electron.ipcRenderer) { - return; - } - - const {ipcRenderer, remote} = electron; - - ipcRenderer.on('display', (_, display) => { - cropperContainer.setDisplay(display); - actionBarContainer.setDisplay(display); - }); - - ipcRenderer.on('select-app', (_, app) => { - cropperContainer.selectApp(app); - cropperContainer.setActive(true); - }); - - ipcRenderer.on('blur', () => { - cropperContainer.setActive(false); - }); - - ipcRenderer.on('start-recording', () => { - cropperContainer.setRecording(); - }); - - const window = remote.getCurrentWindow(); - window.on('focus', () => { - cropperContainer.setActive(true); - }); - - window.on('blur', event => { - if (!event.defaultPrevented) { - cropperContainer.setActive(false); - } - }); - } - - componentDidMount() { - document.addEventListener('keydown', this.handleKeyEvent); - document.addEventListener('keyup', this.handleKeyEvent); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.handleKeyEvent); - document.removeEventListener('keyup', this.handleKeyEvent); - } - - handleKeyEvent = event => { - switch (event.key) { - case 'Escape': - this.remote.getCurrentWindow().close(); - break; - case 'Shift': - if (event.type === 'keydown' && !event.defaultPrevented) { - lastRatioLockState = actionBarContainer.state.ratioLocked; - actionBarContainer.toggleRatioLock(true); - } else if (event.type === 'keyup' && lastRatioLockState !== null) { - actionBarContainer.toggleRatioLock(lastRatioLockState); - lastRatioLockState = null; - } - - break; - case 'Alt': - cropperContainer.toggleResizeFromCenter(event.type === 'keydown'); - break; - case 'i': - this.remote.getCurrentWindow().setIgnoreMouseEvents(true); - this.dev = !this.dev; - break; - default: - break; - } - }; - - render() { - return ( -
- - - - - - - -
- ); - } -} +export default CropperPage; diff --git a/renderer/pages/dialog.js b/renderer/pages/dialog.js index 69d7f637..9fa0391e 100644 --- a/renderer/pages/dialog.js +++ b/renderer/pages/dialog.js @@ -1,123 +1,5 @@ -import Actions from '../components/dialog/actions'; -import Icon from '../components/dialog/icon'; -import Body from '../components/dialog/body'; -import React, {useState, useEffect, useRef, useCallback} from 'react'; -import {ipcRenderer as ipc} from 'electron-better-ipc'; +import dynamic from 'next/dynamic'; -let measureResolve; +const DialogPage = dynamic(async () => import('../client-pages/dialog'), {ssr: false}); -const Dialog = () => { - const [data, setData] = useState(); - const [measureSize, setMeasureSize] = useState(false); - const [isDisabled, setIsDisabled] = useState(false); - const container = useRef(null); - - const performAction = useCallback(async index => { - if (!isDisabled || data.cancelId === index) { - setIsDisabled(true); - return ipc.callMain(`dialog-action-${data.id}`, index); - } - }, [data, isDisabled, setIsDisabled]); - - useEffect(() => { - return ipc.answerMain('data', async newData => new Promise(resolve => { - setData(newData); - setMeasureSize(true); - measureResolve = resolve; - })); - }, []); - - useEffect(() => { - if (data) { - setIsDisabled(false); - } - - if (data && data.cancelId) { - const handler = event => { - if (event.code === 'Escape') { - performAction(data.cancelId); - } - }; - - document.addEventListener('keydown', handler); - - return () => { - document.removeEventListener('keydown', handler); - }; - } - }, [data, performAction, isDisabled, setIsDisabled]); - - useEffect(() => { - if (measureSize && measureResolve) { - const {offsetWidth, offsetHeight} = container.current; - measureResolve({width: offsetWidth, height: offsetHeight}); - measureResolve = undefined; - setMeasureSize(false); - } - }, [measureSize]); - - if (!data) { - return null; - } - - return ( -
- -
- - -
- -
- ); -}; - -export default Dialog; +export default DialogPage; diff --git a/renderer/pages/editor.tsx b/renderer/pages/editor.tsx index 71c095d4..e891fb11 100644 --- a/renderer/pages/editor.tsx +++ b/renderer/pages/editor.tsx @@ -1,90 +1,5 @@ -import Head from 'next/head'; -// Import EditorPreview from '../components/editor/editor-preview'; -import combineUnstatedContainers from '../utils/combine-unstated-containers'; -import VideoMetadataContainer from '../components/editor/video-metadata-container'; -import VideoTimeContainer from '../components/editor/video-time-container'; -import VideoControlsContainer from '../components/editor/video-controls-container'; -import OptionsContainer from '../components/editor/options-container'; -import useEditorWindowState from 'hooks/editor/use-editor-window-state'; -import {ConversionIdContextProvider} from 'hooks/editor/use-conversion-id'; -import Editor from 'components/editor'; +import dynamic from 'next/dynamic'; -const ContainerProvider = combineUnstatedContainers([ - OptionsContainer, - VideoMetadataContainer, - VideoTimeContainer, - VideoControlsContainer -]) as any; - -const EditorPage = () => { - const args = useEditorWindowState(); - - if (!args) { - return null; - } - - return ( -
- - - - - - - - - -
- ); -}; +const EditorPage = dynamic(async () => import('../client-pages/editor'), {ssr: false}); export default EditorPage; diff --git a/renderer/pages/exports.tsx b/renderer/pages/exports.tsx index 0df8da41..72f7aa55 100644 --- a/renderer/pages/exports.tsx +++ b/renderer/pages/exports.tsx @@ -1,26 +1,5 @@ -import React from 'react'; +import dynamic from 'next/dynamic'; -import WindowHeader from '../components/window-header'; -import Exports from '../components/exports'; - -const ExportsPage = () => ( -
- - - -
-); +const ExportsPage = dynamic(async () => import('../client-pages/exports'), {ssr: false}); export default ExportsPage; diff --git a/renderer/pages/preferences.js b/renderer/pages/preferences.js index 2c53b7b0..b6fd4a22 100644 --- a/renderer/pages/preferences.js +++ b/renderer/pages/preferences.js @@ -1,136 +1,5 @@ -import React from 'react'; -import {Provider} from 'unstated'; -import classNames from 'classnames'; -import {ipcRenderer as ipc} from 'electron-better-ipc'; +import dynamic from 'next/dynamic'; -import PreferencesNavigation from '../components/preferences/navigation'; -import WindowHeader from '../components/window-header'; -import Categories from '../components/preferences/categories'; +const PreferencesPage = dynamic(async () => import('../client-pages/preferences'), {ssr: false}); -import PreferencesContainer from '../containers/preferences'; - -const preferencesContainer = new PreferencesContainer(); - -export default class PreferencesPage extends React.Component { - state = {overlay: false}; - - componentDidMount() { - ipc.answerMain('open-plugin-config', preferencesContainer.openPluginsConfig); - ipc.answerMain('options', preferencesContainer.setNavigation); - ipc.answerMain('mount', async () => preferencesContainer.mount(this.setOverlay)); - } - - setOverlay = overlay => { - this.setState({overlay}); - }; - - render() { - const {overlay} = this.state; - const className = classNames('overlay', {active: overlay}); - - return ( -
-
- - - - - - - -
- ); - } -} +export default PreferencesPage;