Beginning of work on templates.
This commit is contained in:
parent
47b8d80c22
commit
508d6b3b2f
40 changed files with 1685 additions and 1031 deletions
148
client/src/lib/files.js
Normal file
148
client/src/lib/files.js
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {translate} from "react-i18next";
|
||||
import {requiresAuthenticatedUser} from "./lib/page";
|
||||
import {ACEEditor, Button, Form, FormSendMethod, withForm} from "./lib/form";
|
||||
import {withErrorHandling} from "./lib/error-handling";
|
||||
import {Table} from "./lib/table";
|
||||
import Dropzone from "react-dropzone";
|
||||
import {ModalDialog} from "./lib/modals";
|
||||
import {Icon} from "./lib/bootstrap-components";
|
||||
import axios from './axios';
|
||||
|
||||
@translate()
|
||||
@withForm
|
||||
@withErrorHandling
|
||||
@requiresAuthenticatedUser
|
||||
export default class Files extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
fileToDeleteName: null,
|
||||
fileToDeleteId: null
|
||||
};
|
||||
|
||||
const t = props.t;
|
||||
|
||||
this.initForm();
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
entity: PropTypes.object,
|
||||
entityTypeId: PropTypes.string
|
||||
}
|
||||
|
||||
|
||||
getFilesUploadedMessage(response){
|
||||
const t = this.props.t;
|
||||
const details = [];
|
||||
if (response.data.added) {
|
||||
details.push(t('{{count}} file(s) added', {count: response.data.added}));
|
||||
}
|
||||
if (response.data.replaced) {
|
||||
details.push(t('{{count}} file(s) replaced', {count: response.data.replaced}));
|
||||
}
|
||||
if (response.data.ignored) {
|
||||
details.push(t('{{count}} file(s) ignored', {count: response.data.ignored}));
|
||||
}
|
||||
const detailsMessage = details ? ' (' + details.join(', ') + ')' : '';
|
||||
return t('{{count}} file(s) uploaded', {count: response.data.uploaded}) + detailsMessage;
|
||||
}
|
||||
|
||||
onDrop(files){
|
||||
const t = this.props.t;
|
||||
if (files.length > 0) {
|
||||
this.setFormStatusMessage('info', t('Uploading {{count}} file(s)', files.length));
|
||||
const data = new FormData();
|
||||
for (const file of files) {
|
||||
data.append('file', file)
|
||||
}
|
||||
axios.put(`/rest/files/${this.props.entityTypeId}, ${this.props.entity.id}`, data)
|
||||
.then(res => {
|
||||
this.filesTable.refresh();
|
||||
const message = this.getFilesUploadedMessage(res);
|
||||
this.setFormStatusMessage('info', message);
|
||||
})
|
||||
.catch(res => this.setFormStatusMessage('danger', t('File upload failed: ') + res.message));
|
||||
}
|
||||
else{
|
||||
this.setFormStatusMessage('info', t('No files to upload'));
|
||||
}
|
||||
}
|
||||
|
||||
deleteFile(fileId, fileName){
|
||||
this.setState({fileToDeleteId: fileId, fileToDeleteName: fileName})
|
||||
}
|
||||
|
||||
async hideDeleteFile(){
|
||||
this.setState({fileToDeleteId: null, fileToDeleteName: null})
|
||||
}
|
||||
|
||||
async performDeleteFile() {
|
||||
const t = this.props.t;
|
||||
const fileToDeleteId = this.state.fileToDeleteId;
|
||||
await this.hideDeleteFile();
|
||||
|
||||
try {
|
||||
this.disableForm();
|
||||
this.setFormStatusMessage('info', t('Deleting file ...'));
|
||||
await axios.delete(`/rest/files/${this.props.entityTypeId}/${fileToDeleteId}`);
|
||||
this.filesTable.refresh();
|
||||
this.setFormStatusMessage('info', t('File deleted'));
|
||||
this.enableForm();
|
||||
} catch (err) {
|
||||
this.filesTable.refresh();
|
||||
this.setFormStatusMessage('danger', t('Delete file failed: ') + err.message);
|
||||
this.enableForm();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: "Name" },
|
||||
{ data: 2, title: "Size" },
|
||||
{
|
||||
actions: data => {
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: <Icon icon="download" title={t('Download')}/>,
|
||||
href: `/rest/files/${this.props.entityTypeId}/${data[0]}`
|
||||
},
|
||||
{
|
||||
label: <Icon icon="remove" title={t('Delete')}/>,
|
||||
action: () => this.deleteFile(data[0], data[1])
|
||||
}
|
||||
];
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ModalDialog
|
||||
hidden={this.state.fileToDeleteId === null}
|
||||
title={t('Confirm file deletion')}
|
||||
onCloseAsync={::this.hideDeleteFile}
|
||||
buttons={[
|
||||
{ label: t('No'), className: 'btn-primary', onClickAsync: ::this.hideDeleteFile },
|
||||
{ label: t('Yes'), className: 'btn-danger', onClickAsync: ::this.performDeleteFile }
|
||||
]}>
|
||||
{t('Are you sure you want to delete file "{{name}}"?', {name: this.state.fileToDeleteName})}
|
||||
</ModalDialog>
|
||||
<Dropzone onDrop={::this.onDrop} className="dropZone" activeClassName="dropZoneActive">
|
||||
{state => state.isDragActive ? t('Drop {{count}} file(s)', {count:state.draggedFiles.length}) : t('Drop files here')}
|
||||
</Dropzone>
|
||||
<Table withHeader ref={node => this.filesTable = node} dataUrl={`/rest/template-files-table/${this.props.entity.id}`} columns={columns} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,9 +13,11 @@ import { Table, TableSelectMode } from './table';
|
|||
import {Button, Icon} from "./bootstrap-components";
|
||||
|
||||
import brace from 'brace';
|
||||
import AceEditor from 'react-ace';
|
||||
import ACEEditorRaw from 'react-ace';
|
||||
import 'brace/theme/github';
|
||||
|
||||
import CKEditorRaw from "react-ckeditor-component";
|
||||
|
||||
import DayPicker from 'react-day-picker';
|
||||
import 'react-day-picker/lib/style.css';
|
||||
import { parseDate, parseBirthday, formatDate, formatBirthday, DateFormat, birthdayYear, getDateFormatString, getBirthdayFormatString } from '../../../shared/date';
|
||||
|
|
@ -823,7 +825,7 @@ class ACEEditor extends Component {
|
|||
const htmlId = 'form_' + id;
|
||||
|
||||
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
|
||||
<AceEditor
|
||||
<ACEEditorRaw
|
||||
id={htmlId}
|
||||
mode={props.mode}
|
||||
theme="github"
|
||||
|
|
@ -841,6 +843,35 @@ class ACEEditor extends Component {
|
|||
}
|
||||
|
||||
|
||||
class CKEditor extends Component {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
height: PropTypes.string
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
formStateOwner: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const owner = this.context.formStateOwner;
|
||||
const id = this.props.id;
|
||||
const htmlId = 'form_' + id;
|
||||
|
||||
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
|
||||
<CKEditorRaw
|
||||
onChange={evt => owner.updateFormValue(id, evt.editor.getData())}
|
||||
content={owner.getFormValue(id)}
|
||||
config={{width: '100%', height: props.height}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function withForm(target) {
|
||||
const inst = target.prototype;
|
||||
|
||||
|
|
@ -1251,5 +1282,6 @@ export {
|
|||
TableSelect,
|
||||
TableSelectMode,
|
||||
ACEEditor,
|
||||
CKEditor,
|
||||
FormSendMethod
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue