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;