Merge branch 'develop' of https://github.com/Pterodactyl/Panel into develop

This commit is contained in:
Dane Everitt 2020-09-15 19:27:11 -07:00
commit 80e08572d6
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
7 changed files with 325 additions and 118 deletions

View file

@ -5,9 +5,8 @@
"@fortawesome/free-solid-svg-icons": "^5.9.0",
"@fortawesome/react-fontawesome": "0.1.4",
"axios": "^0.19.2",
"ayu-ace": "^2.0.4",
"brace": "^0.11.1",
"chart.js": "^2.8.0",
"codemirror": "^5.57.0",
"date-fns": "^2.14.0",
"debounce": "^1.2.0",
"deepmerge": "^4.2.2",
@ -57,6 +56,7 @@
"@babel/preset-typescript": "^7.7.4",
"@babel/runtime": "^7.7.5",
"@types/chart.js": "^2.8.5",
"@types/codemirror": "^0.0.98",
"@types/debounce": "^1.2.0",
"@types/events": "^3.0.0",
"@types/node": "^12.6.9",

View file

@ -1,84 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import ace, { Editor } from 'brace';
import styled from 'styled-components/macro';
import tw from 'twin.macro';
import modes from '@/modes';
// @ts-ignore
require('brace/ext/modelist');
require('ayu-ace/mirage');
const EditorContainer = styled.div`
min-height: 16rem;
height: calc(100vh - 20rem);
${tw`relative`};
#editor {
${tw`rounded h-full`};
}
`;
Object.keys(modes).forEach(mode => require(`brace/mode/${mode}`));
const modelist = ace.acequire('ace/ext/modelist');
export interface Props {
style?: React.CSSProperties;
initialContent?: string;
mode: string;
filename?: string;
onModeChanged: (mode: string) => void;
fetchContent: (callback: () => Promise<string>) => void;
onContentSaved: () => void;
}
export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => {
const [ editor, setEditor ] = useState<Editor>();
const ref = useCallback(node => {
if (node) setEditor(ace.edit('editor'));
}, []);
useEffect(() => {
if (modelist && filename) {
onModeChanged(modelist.getModeForPath(filename).mode.replace(/^ace\/mode\//, ''));
}
}, [ filename ]);
useEffect(() => {
editor && editor.session.setMode(`ace/mode/${mode}`);
}, [ editor, mode ]);
useEffect(() => {
editor && editor.session.setValue(initialContent || '');
}, [ editor, initialContent ]);
useEffect(() => {
if (!editor) {
fetchContent(() => Promise.reject(new Error('no editor session has been configured')));
return;
}
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.commands.addCommand({
name: 'Save',
bindKey: { win: 'Ctrl-s', mac: 'Command-s' },
exec: () => onContentSaved(),
});
fetchContent(() => Promise.resolve(editor.session.getValue()));
}, [ editor, fetchContent, onContentSaved ]);
return (
<EditorContainer style={style}>
<div id={'editor'} ref={ref}/>
</EditorContainer>
);
};

View file

@ -0,0 +1,241 @@
import React, { useCallback, useEffect, useState } from 'react';
import CodeMirror from 'codemirror';
import styled from 'styled-components/macro';
import tw from 'twin.macro';
import modes, { Mode } from '@/modes';
require('codemirror/lib/codemirror.css');
// Themes
require('codemirror/theme/ayu-mirage.css');
// Addons
require('codemirror/addon/edit/closebrackets');
require('codemirror/addon/edit/closetag');
require('codemirror/addon/edit/matchbrackets');
require('codemirror/addon/edit/matchtags');
require('codemirror/addon/edit/trailingspace');
require('codemirror/addon/fold/foldcode');
require('codemirror/addon/fold/foldgutter.css');
require('codemirror/addon/fold/foldgutter');
require('codemirror/addon/fold/brace-fold');
require('codemirror/addon/fold/comment-fold');
require('codemirror/addon/fold/indent-fold');
require('codemirror/addon/fold/markdown-fold');
require('codemirror/addon/fold/xml-fold');
require('codemirror/addon/hint/css-hint');
require('codemirror/addon/hint/html-hint');
require('codemirror/addon/hint/javascript-hint');
require('codemirror/addon/hint/show-hint.css');
require('codemirror/addon/hint/show-hint');
require('codemirror/addon/hint/sql-hint');
require('codemirror/addon/hint/xml-hint');
require('codemirror/addon/mode/simple');
require('codemirror/addon/dialog/dialog.css');
require('codemirror/addon/dialog/dialog');
require('codemirror/addon/scroll/annotatescrollbar');
require('codemirror/addon/scroll/scrollpastend');
require('codemirror/addon/scroll/simplescrollbars.css');
require('codemirror/addon/scroll/simplescrollbars');
require('codemirror/addon/search/jump-to-line');
require('codemirror/addon/search/match-highlighter');
require('codemirror/addon/search/matchesonscrollbar.css');
require('codemirror/addon/search/matchesonscrollbar');
require('codemirror/addon/search/search');
require('codemirror/addon/search/searchcursor');
// Modes
require('codemirror/mode/brainfuck/brainfuck');
require('codemirror/mode/clike/clike');
require('codemirror/mode/css/css');
require('codemirror/mode/dart/dart');
require('codemirror/mode/diff/diff');
require('codemirror/mode/dockerfile/dockerfile');
require('codemirror/mode/erlang/erlang');
require('codemirror/mode/gfm/gfm');
require('codemirror/mode/go/go');
require('codemirror/mode/handlebars/handlebars');
require('codemirror/mode/htmlembedded/htmlembedded');
require('codemirror/mode/htmlmixed/htmlmixed');
require('codemirror/mode/http/http');
require('codemirror/mode/javascript/javascript');
require('codemirror/mode/jsx/jsx');
require('codemirror/mode/julia/julia');
require('codemirror/mode/lua/lua');
require('codemirror/mode/markdown/markdown');
require('codemirror/mode/nginx/nginx');
require('codemirror/mode/perl/perl');
require('codemirror/mode/php/php');
require('codemirror/mode/properties/properties');
require('codemirror/mode/protobuf/protobuf');
require('codemirror/mode/pug/pug');
require('codemirror/mode/python/python');
require('codemirror/mode/rpm/rpm');
require('codemirror/mode/ruby/ruby');
require('codemirror/mode/rust/rust');
require('codemirror/mode/sass/sass');
require('codemirror/mode/shell/shell');
require('codemirror/mode/smarty/smarty');
require('codemirror/mode/sql/sql');
require('codemirror/mode/swift/swift');
require('codemirror/mode/toml/toml');
require('codemirror/mode/twig/twig');
require('codemirror/mode/vue/vue');
require('codemirror/mode/xml/xml');
require('codemirror/mode/yaml/yaml');
const EditorContainer = styled.div`
min-height: 16rem;
height: calc(100vh - 20rem);
${tw`relative`};
> div {
${tw`rounded h-full`};
}
.CodeMirror {
font-size: 12px;
line-height: 1.375rem;
}
.CodeMirror-linenumber {
padding: 1px 12px 0 12px !important;
}
.CodeMirror-foldmarker {
color: #CBCCC6;
text-shadow: none;
margin-left: 0.25rem;
margin-right: 0.25rem;
}
`;
export interface Props {
style?: React.CSSProperties;
initialContent?: string;
mode: string;
filename?: string;
onModeChanged: (mode: string) => void;
fetchContent: (callback: () => Promise<string>) => void;
onContentSaved: () => void;
}
export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => {
const [ editor, setEditor ] = useState<CodeMirror.Editor>();
const ref = useCallback((node) => {
if (!node) {
return;
}
const e = CodeMirror.fromTextArea(node, {
mode: 'text/plain',
theme: 'ayu-mirage',
indentUnit: 4,
smartIndent: true,
tabSize: 4,
indentWithTabs: true,
lineWrapping: true,
lineNumbers: true,
foldGutter: true,
fixedGutter: true,
scrollbarStyle: 'overlay',
coverGutterNextToScrollbar: false,
readOnly: false,
showCursorWhenSelecting: false,
autofocus: false,
spellcheck: true,
autocorrect: false,
autocapitalize: false,
lint: false,
// This property is actually used, the d.ts file for CodeMirror is incorrect.
// @ts-ignore
autoCloseBrackets: true,
matchBrackets: true,
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
});
setEditor(e);
}, []);
useEffect(() => {
if (filename === undefined) {
return;
}
const findModeByFilename = (filename: string): Mode|undefined => {
for (let i = 0; i < modes.length; i++) {
const info = modes[i];
if (info.file && info.file.test(filename)) {
return info;
}
}
const dot = filename.lastIndexOf('.');
const ext = dot > -1 && filename.substring(dot + 1, filename.length);
if (ext) {
for (let i = 0; i < modes.length; i++) {
const info = modes[i];
if (info.ext) {
for (let j = 0; j < info.ext.length; j++) {
if (info.ext[j] === ext) {
return info;
}
}
}
}
}
return undefined;
};
onModeChanged(findModeByFilename(filename)?.mime || 'text/plain');
}, [ filename ]);
useEffect(() => {
editor && editor.setOption('mode', mode);
}, [ editor, mode ]);
useEffect(() => {
editor && editor.setValue(initialContent || '');
}, [ editor, initialContent ]);
useEffect(() => {
if (!editor) {
fetchContent(() => Promise.reject(new Error('no editor session has been configured')));
return;
}
editor.addKeyMap({
'Ctrl-S': () => onContentSaved(),
'Cmd-S': () => onContentSaved(),
});
fetchContent(() => Promise.resolve(editor.getValue()));
}, [ editor, fetchContent, onContentSaved ]);
return (
<EditorContainer style={style}>
<textarea ref={ref}/>
</EditorContainer>
);
};

View file

@ -17,7 +17,7 @@ import modes from '@/modes';
import useFlash from '@/plugins/useFlash';
import { ServerContext } from '@/state/server';
const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor'));
const LazyCodemirrorEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/CodemirrorEditor'));
export default () => {
const [ error, setError ] = useState('');
@ -25,7 +25,7 @@ export default () => {
const [ loading, setLoading ] = useState(action === 'edit');
const [ content, setContent ] = useState('');
const [ modalVisible, setModalVisible ] = useState(false);
const [ mode, setMode ] = useState('plain_text');
const [ mode, setMode ] = useState('text/plain');
const history = useHistory();
const { hash } = useLocation();
@ -82,6 +82,11 @@ export default () => {
);
}
const actualModes: React.ReactChild[] = [];
for (let i = 0; i < modes.length; i++) {
actualModes.push(<option key={modes[i].mime} value={modes[i].mime}>{modes[i].name}</option>);
}
return (
<PageContentBlock>
<FlashMessageRender byKey={'files:view'} css={tw`mb-4`}/>
@ -108,7 +113,7 @@ export default () => {
/>
<div css={tw`relative`}>
<SpinnerOverlay visible={loading}/>
<LazyAceEditor
<LazyCodemirrorEditor
mode={mode}
filename={hash.replace(/^#/, '')}
onModeChanged={setMode}
@ -122,9 +127,7 @@ export default () => {
<div css={tw`flex justify-end mt-4`}>
<div css={tw`flex-1 sm:flex-none rounded bg-neutral-900 mr-4`}>
<Select value={mode} onChange={e => setMode(e.currentTarget.value)}>
{Object.keys(modes).map(key => (
<option key={key} value={key}>{modes[key]}</option>
))}
{actualModes}
</Select>
</div>
{action === 'edit' ?

View file

@ -1,3 +1,13 @@
declare const modes: Record<string, string>;
export interface Mode {
name: string,
mime: string,
mimes?: string[],
mode: string,
ext: string[],
alias?: string[],
file?: RegExp,
}
declare const modes: Mode[];
export default modes;

View file

@ -1,26 +1,39 @@
// This file must be plain Javascript since we're using it within Webpack.
module.exports = {
assembly_x86: 'Assembly (x86)',
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',
plain_text: 'Plaintext',
toml: 'TOML',
typescript: 'Typescript',
xml: 'XML',
yaml: 'YAML',
};
module.exports = [
{ name: "C", mime: "text/x-csrc", mode: "clike", ext: [ "c", "h", "ino" ] },
{ name: "C++", mime: "text/x-c++src", mode: "clike", ext: [ "cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx" ], alias: [ "cpp" ] },
{ name: "C#", mime: "text/x-csharp", mode: "clike", ext: [ "cs" ], alias: [ "csharp", "cs" ] },
{ name: "CSS", mime: "text/css", mode: "css", ext: [ "css" ] },
{ name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: [ "cql" ] },
{ name: "Diff", mime: "text/x-diff", mode: "diff", ext: [ "diff", "patch" ] },
{ name: "Dockerfile", mime: "text/x-dockerfile", mode: "dockerfile", file: /^Dockerfile$/ },
{ name: "Git Markdown", mime: "text/x-gfm", mode: "gfm", file: /^(readme|contributing|history|license).md$/i },
{ name: "Golang", mime: "text/x-go", mode: "go", ext: [ "go" ] },
{ name: "HTML", mime: "text/html", mode: "htmlmixed", ext: [ "html", "htm", "handlebars", "hbs" ], alias: [ "xhtml" ] },
{ name: "HTTP", mime: "message/http", mode: "http" },
{ name: "JavaScript", mime: "text/javascript", mimes: [ "text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript" ], mode: "javascript", ext: [ "js" ], alias: [ "ecmascript", "js", "node" ] },
{ name: "JSON", mime: "application/json", mimes: [ "application/json", "application/x-json" ], mode: "javascript", ext: [ "json", "map" ], alias: [ "json5" ] },
{ name: "Lua", mime: "text/x-lua", mode: "lua", ext: [ "lua" ] },
{ name: "Markdown", mime: "text/x-markdown", mode: "markdown", ext: [ "markdown", "md", "mkd" ] },
{ name: "MariaDB", mime: "text/x-mariadb", mode: "sql" },
{ name: "MS SQL", mime: "text/x-mssql", mode: "sql" },
{ name: "MySQL", mime: "text/x-mysql", mode: "sql" },
{ name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i },
{ name: "PHP", mime: "text/x-php", mimes: [ "text/x-php", "application/x-httpd-php", "application/x-httpd-php-open" ], mode: "php", ext: [ "php", "php3", "php4", "php5", "php7", "phtml" ] },
{ name: "Plain Text", mime: "text/plain", mode: "null", ext: [ "txt", "text", "conf", "def", "list", "log" ] },
{ name: "PostgreSQL", mime: "text/x-pgsql", mode: "sql" },
{ name: "Properties", mime: "text/x-properties", mode: "properties", ext: [ "properties", "ini", "in" ], alias: [ "ini", "properties" ] },
{ name: "Python", mime: "text/x-python", mode: "python", ext: [ "BUILD", "bzl", "py", "pyw" ], file: /^(BUCK|BUILD)$/ },
{ name: "Ruby", mime: "text/x-ruby", mode: "ruby", ext: [ "rb" ], alias: [ "jruby", "macruby", "rake", "rb", "rbx" ] },
{ name: "Rust", mime: "text/x-rustsrc", mode: "rust", ext: [ "rs" ] },
{ name: "Sass", mime: "text/x-sass", mode: "sass", ext: [ "sass" ] },
{ name: "SCSS", mime: "text/x-scss", mode: "css", ext: [ "scss" ] },
{ name: "Shell", mime: "text/x-sh", mimes: [ "text/x-sh", "application/x-sh" ], mode: "shell", ext: [ "sh", "ksh", "bash" ], alias: [ "bash", "sh", "zsh" ], file: /^PKGBUILD$/ },
{ name: "SQL", mime: "text/x-sql", mode: "sql", ext: [ "sql" ] },
{ name: "SQLite", mime: "text/x-sqlite", mode: "sql" },
{ name: "TOML", mime: "text/x-toml", mode: "toml", ext: [ "toml" ] },
{ name: "TypeScript", mime: "application/typescript", mode: "javascript", ext: [ "ts" ], alias: [ "ts" ] },
{ name: "Vue", mime: "script/x-vue", mimes: [ "script/x-vue", "text/x-vue" ], mode: "vue", ext: [ "vue" ] },
{ name: "XML", mime: "application/xml", mimes: [ "application/xml", "text/xml" ], mode: "xml", ext: [ "xml", "xsl", "xsd", "svg" ], alias: [ "rss", "wsdl", "xsd" ] },
{ name: "YAML", mime: "text/x-yaml", mimes: [ "text/x-yaml", "text/yaml" ], mode: "yaml", ext: [ "yaml", "yml" ], alias: [ "yml" ] },
];

View file

@ -932,6 +932,13 @@
version "2.8.5"
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.8.5.tgz#7d47cfd36f0a1c2c4ad6a585749bc68e8659492a"
"@types/codemirror@^0.0.98":
version "0.0.98"
resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.98.tgz#b35c7a4ab1fc1684b08a4e3eb65240020556ebfb"
integrity sha512-cbty5LPayy2vNSeuUdjNA9tggG+go5vAxmnLDRWpiZI5a+RDBi9dlozy4/jW/7P/gletbBWbQREEa7A81YxstA==
dependencies:
"@types/tern" "*"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -946,6 +953,11 @@
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
"@types/estree@*":
version "0.0.45"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884"
integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==
"@types/events@*", "@types/events@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@ -1083,6 +1095,13 @@
"@types/react-native" "*"
csstype "^2.2.0"
"@types/tern@*":
version "0.23.3"
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.3.tgz#4b54538f04a88c9ff79de1f6f94f575a7f339460"
integrity sha512-imDtS4TAoTcXk0g7u4kkWqedB3E4qpjXzCpD2LU5M5NAXHzCDsypyvXSaG7mM8DKYkCRa7tFp4tS/lp/Wo7Q3w==
dependencies:
"@types/estree" "*"
"@types/uuid@^3.4.5":
version "3.4.5"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.5.tgz#d4dc10785b497a1474eae0ba7f0cb09c0ddfd6eb"
@ -2075,6 +2094,11 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
codemirror@^5.57.0:
version "5.57.0"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.57.0.tgz#d26365b72f909f5d2dbb6b1209349ca1daeb2d50"
integrity sha512-WGc6UL7Hqt+8a6ZAsj/f1ApQl3NPvHY/UQSzG6fB6l4BjExgVdhFaxd7mRTw1UCiYe/6q86zHP+kfvBQcZGvUg==
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"