1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-11-22 17:12:30 +01:00

Add support for non-existent files being edited

This commit is contained in:
Dane Everitt 2019-05-27 16:30:23 -07:00
parent bfdc1f766b
commit 06337e45d8
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
4 changed files with 84 additions and 28 deletions

View File

@ -6,7 +6,24 @@
<div class="modal-close-icon" v-on:click="closeModal">
<Icon name="x" aria-label="Close modal" role="button"/>
</div>
<MessageBox class="alert error mb-2" title="Error" :message="error" v-if="error"/>
<MessageBox class="alert error mb-4" title="Error" :message="error" v-if="error"/>
<div class="flex items-center mb-4 bg-white rounded p-2">
<div class="mx-2">
<label class="input-label mb-0" for="file-name-input">File name:</label>
</div>
<div class="flex-1">
<input
type="text"
name="file_name"
class="input"
id="file-name-input"
:disabled="typeof file !== 'undefined'"
v-model="fileName"
v-validate="'required'"
/>
<p class="input-help error" v-show="errors.has('file_name')">{{ errors.first('file_name') }}</p>
</div>
</div>
<div id="editor"></div>
<div class="flex mt-4 bg-white rounded p-2">
<div class="flex-1">
@ -35,7 +52,7 @@
import {ApplicationState, FileManagerState} from '@/store/types';
import {mapState} from "vuex";
import * as Ace from 'brace';
import { join } from 'path';
import {join} from 'path';
import {DirectoryContentObject} from "@/api/server/types";
import getFileContents from '@/api/server/files/getFileContents';
import SpinnerModal from "@/components/core/SpinnerModal.vue";
@ -46,11 +63,12 @@
file?: DirectoryContentObject,
serverUuid?: string,
fm?: FileManagerState,
fileName?: string,
error: string | null,
editor: Ace.Editor | null,
isVisible: boolean,
isLoading: boolean,
supportedTypes: {type: string, name: string, default?: boolean}[],
supportedTypes: { type: string, name: string, default?: boolean }[],
}
const defaults = {
@ -58,6 +76,8 @@
editor: null,
isVisible: false,
isLoading: true,
file: undefined,
fileName: undefined,
};
export default Vue.extend({
@ -101,6 +121,8 @@
this.file = file;
this.isVisible = true;
this.isLoading = true;
this.fileName = file ? file.name : undefined;
this.errors.clear();
this.$nextTick(() => {
this.editor = Ace.edit('editor');
@ -120,13 +142,36 @@
});
},
watch: {
fileName: function (newValue?: string, oldValue?: string) {
if (newValue === oldValue || !newValue) {
return;
}
this.updateFileLanguageFromName(newValue);
},
},
methods: {
submit: function () {
if (!this.file && (!this.fileName || this.fileName.length === 0)) {
this.error = 'You must provide a file name before saving.';
return;
}
this.isLoading = true;
const content = this.editor!.getValue();
writeFileContents(this.serverUuid!, join(this.fm!.currentDirectory, this.file!.name), content)
.then(() => this.error = null)
writeFileContents(this.serverUuid!, join(this.fm!.currentDirectory, this.fileName!), content)
.then(() => {
this.error = null;
// @todo come up with a more graceful solution here
if (!this.file) {
this.$emit('refresh');
this.closeModal();
}
})
.catch(error => {
console.log(error);
this.error = httpErrorToHuman(error);
@ -136,7 +181,7 @@
loadFileContent: function (): Promise<void> {
return new Promise((resolve, reject) => {
const { editor, file } = this;
const {editor, file} = this;
if (!file || !editor || file.directory) {
return resolve();
@ -147,29 +192,32 @@
editor.$blockScrolling = Infinity;
editor.setValue(contents, 1);
})
.then(() => {
// Set the correct MIME type on the editor for the user.
const modelist = Ace.acequire('ace/ext/modelist');
if (modelist) {
const mode = modelist.getModeForPath(file.name).mode || 'ace/mode/text';
editor.getSession().setMode(mode);
const parts = mode.split('/');
const element = (this.$refs.fileLanguageSelector as HTMLSelectElement | null);
if (element) {
const index = this.supportedTypes.findIndex(value => value.type === parts[parts.length - 1]);
if (index >= 0) {
element.selectedIndex = index;
}
}
}
})
.then(() => this.updateFileLanguageFromName(file.name))
.then(() => resolve())
.catch(reject);
});
},
updateFileLanguageFromName: function (name: string) {
const modelist = Ace.acequire('ace/ext/modelist');
if (!modelist || !this.editor) {
return;
}
const mode = modelist.getModeForPath(name).mode || 'ace/mode/text';
const parts = mode.split('/');
const element = (this.$refs.fileLanguageSelector as HTMLSelectElement | null);
if (element) {
const index = this.supportedTypes.findIndex(value => value.type === parts[parts.length - 1]);
if (index >= 0) {
element.selectedIndex = index;
this.editor.getSession().setMode(mode);
}
}
},
updateFileLanguage: function (e: MouseEvent) {
if (!this.editor) {
return;
@ -185,7 +233,7 @@
/* webpackMode: "lazy-once" */
/* webpackInclude: /(dockerfile|golang|html|java|javascript|json|kotlin|lua|markdown|text|php|properties|python|ruby|sh|sql|xml|yaml).js$/ */
`brace/mode/${o.type}`
))
))
);
},

View File

@ -52,7 +52,7 @@
</div>
</div>
<CreateFolderModal v-on:created="directoryCreated"/>
<EditFileModal/>
<EditFileModal v-on:refresh="listDirectory"/>
</div>
</template>

File diff suppressed because one or more lines are too long

View File

@ -65,6 +65,10 @@ input[type=number] {
}
}
.input:disabled {
@apply .bg-neutral-100 .border-neutral-200;
}
select:not(.appearance-none) {
@apply .outline-none .appearance-none .block .bg-white .border .border-neutral-200 .text-neutral-400 .p-3 .pr-8 rounded;
transition: border-color 150ms linear, color 150ms linear;
@ -86,7 +90,11 @@ select:not(.appearance-none) {
}
.input-label {
@apply .block .uppercase .tracking-wide .text-neutral-800 .text-xs .font-bold .mb-2;
@apply .block .uppercase .tracking-wide .text-neutral-800 .text-xs .font-bold;
&:not(.mb-0) {
@apply .mb-2;
}
}
.input-help {