diff --git a/package.json b/package.json index 6d2cbfa1..a123f878 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "@fortawesome/react-fontawesome": "^0.1.4", "@hot-loader/react-dom": "^16.8.6", "axios": "^0.18.0", + "ayu-ace": "^2.0.4", + "brace": "^0.11.1", "chart.js": "^2.8.0", "classnames": "^2.2.6", "date-fns": "^1.29.0", @@ -70,6 +72,7 @@ "eslint-plugin-import": "^2.17.3", "eslint-plugin-node": "^9.1.0", "eslint-plugin-promise": "^4.1.1", + "eslint-plugin-react-hooks": "^2.1.2", "eslint-plugin-standard": "^4.0.0", "fork-ts-checker-webpack-plugin": "^1.5.0", "glob-all": "^3.1.0", diff --git a/resources/scripts/.eslintrc.yml b/resources/scripts/.eslintrc.yml index 7e808e2d..c5575fc2 100644 --- a/resources/scripts/.eslintrc.yml +++ b/resources/scripts/.eslintrc.yml @@ -8,6 +8,7 @@ env: es6: true plugins: - "@typescript-eslint" + - "react-hooks" extends: - "standard" - "plugin:@typescript-eslint/recommended" @@ -20,6 +21,10 @@ rules: comma-dangle: - error - always-multiline + "react-hooks/rules-of-hooks": + - error + "react-hooks/exhaustive-deps": + - warn "@typescript-eslint/explicit-function-return-type": 0 "@typescript-eslint/explicit-member-accessibility": 0 "@typescript-eslint/no-unused-vars": 0 diff --git a/resources/scripts/components/dashboard/DesignElementsContainer.tsx b/resources/scripts/components/dashboard/DesignElementsContainer.tsx index fc4ad61f..1e974786 100644 --- a/resources/scripts/components/dashboard/DesignElementsContainer.tsx +++ b/resources/scripts/components/dashboard/DesignElementsContainer.tsx @@ -36,6 +36,13 @@ export default class DesignElementsContainer extends React.PureComponent {
+ + +
diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 2e10efcf..0d0c5ee6 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -12,8 +12,8 @@ import TitledGreyBox from '@/components/elements/TitledGreyBox'; type PowerAction = 'start' | 'stop' | 'restart' | 'kill'; -const ChunkedConsole = lazy(() => import('@/components/server/Console')); -const ChunkedStatGraphs = lazy(() => import('@/components/server/StatGraphs')); +const ChunkedConsole = lazy(() => import(/* webpackChunkName: "console" */'@/components/server/Console')); +const ChunkedStatGraphs = lazy(() => import(/* webpackChunkName: "graphs" */'@/components/server/StatGraphs')); const StopOrKillButton = ({ onPress }: { onPress: (action: PowerAction) => void }) => { const [ clicked, setClicked ] = useState(false); diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 681c821b..9119c7af 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -1,25 +1,115 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import useRouter from 'use-react-router'; import { ServerContext } from '@/state/server'; import getFileContents from '@/api/server/files/getFileContents'; +import ace, { Editor } from 'brace'; +import styled from 'styled-components'; + +const EditorContainer = styled.div` + height: calc(100vh - 16rem); + ${tw`relative`}; + + #editor { + ${tw`rounded h-full`}; + } +`; + +const modes: { [k: string]: string } = { + // eslint-disable-next-line @typescript-eslint/camelcase + assembly_x86: 'Assembly (x86)', + // eslint-disable-next-line @typescript-eslint/camelcase + c_cpp: 'C++', + coffee: 'Coffeescript', + css: 'CSS', + dockerfile: 'Dockerfile', + golang: 'Go', + html: 'HTML', + ini: 'Ini', + java: 'Java', + javascript: 'Javascript', + json: 'JSON', + kotlin: 'Kotlin', + lua: 'Luascript', + perl: 'Perl', + php: 'PHP', + properties: 'Properties', + python: 'Python', + ruby: 'Ruby', + text: 'Plaintext', + toml: 'TOML', + typescript: 'Typescript', + xml: 'XML', + yaml: 'YAML', +}; export default () => { const { location: { hash } } = useRouter(); - const [content, setContent] = useState(''); + const [ content, setContent ] = useState(''); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const [ editor, setEditor ] = useState(); + const ref = useCallback(node => { + if (node) { + setEditor(ace.edit('editor')); + } + }, []); + + useEffect(() => { + Object.keys(modes).forEach(mode => { + import(/* webpackMode: "lazy-once", webpackChunkName: "ace_mode" */`brace/mode/${mode}`); + }); + }, []); + useEffect(() => { getFileContents(uuid, hash.replace(/^#/, '')) .then(setContent) .catch(error => console.error(error)); - }, []); + }, [ uuid, hash ]); + + useEffect(() => { + editor && editor.session.setValue(content); + }, [ editor, content ]); + + useEffect(() => { + if (!editor) { + return; + } + + require('ayu-ace/mirage'); + editor.setTheme('ace/theme/ayu-mirage'); + + editor.$blockScrolling = Infinity; + editor.container.style.lineHeight = '1.375rem'; + editor.container.style.fontWeight = '500'; + editor.renderer.updateFontSize(); + editor.renderer.setShowPrintMargin(false); + editor.session.setTabSize(4); + editor.session.setUseSoftTabs(true); + }, [ editor ]); return (
-