allow changing mountpoint of existing group folders

Signed-off-by: Robin Appelman <robin@icewind.nl>
This commit is contained in:
Robin Appelman 2017-08-15 16:48:12 +02:00
parent 958d647459
commit 6c78ef13d5
11 changed files with 188 additions and 21 deletions

View file

@ -44,6 +44,11 @@ $application->registerRoutes(
'name' => 'Folder#setQuota',
'url' => '/folders/{id}/quota',
'verb' => 'POST'
],
[
'name' => 'Folder#renameFolder',
'url' => '/folders/{id}/mountpoint',
'verb' => 'POST'
]
],
]

View file

@ -63,4 +63,10 @@ export class Api {
quota
});
}
renameFolder(folderId: number, mountpoint: string): Thenable<void> {
return $.post(this.getUrl(`folders/${folderId}/mountpoint`), {
mountpoint
});
}
}

View file

@ -21,32 +21,73 @@
position: relative;
display: table-cell;
&.mountpoint {
& > form {
margin: -10px;
input[type='text'] {
width: 100%;
}
}
.action-rename {
&::after {
display: inline-block;
position: relative;
top: 2px;
left: 2px;
content: '';
width: 16px;
height: 16px;
background: url('../../../core/img/actions/rename.svg') no-repeat center;
opacity: 0;
&:hover {
opacity: 1;
}
}
}
&.groups {
width: 400px;
a.icon {
margin-left: 5px;
}
}
&.remove {
width: 32px;
}
a.icon {
margin-left: 5px;
}
}
tr:hover {
.icon {
opacity: 1;
opacity: 0.5;
&:hover {
opacity: 1;
}
}
.action-rename {
&::after {
opacity: 0.5;
}
&:hover::after {
opacity: 1;
}
}
}
.icon {
display: inline-block;
opacity: 0.5;
transition: opacity 0.5s;
opacity: 0;
//transition: opacity 0.5s;
&.icon-visible {
opacity: 0.5;
}
}
.newgroup-name {

View file

@ -1,11 +1,12 @@
import * as React from 'react';
import {Component} from 'react';
import {ChangeEvent, Component} from 'react';
import {Api, Folder} from './Api';
import {FolderGroups} from './FolderGroups';
import {QuotaSelect} from './QuotaSelect';
import './App.css';
import {SubmitInput} from "./SubmitInput";
const defaultQuotaOptions = {
'1 GB': 1073741274,
@ -18,7 +19,9 @@ export interface AppState {
folders: Folder[];
groups: string[],
newMountPoint: string;
editing: number;
editingGroup: number;
editingMountPoint: number;
renameMountPoint: string;
}
export class App extends Component<{}, AppState> {
@ -28,7 +31,9 @@ export class App extends Component<{}, AppState> {
folders: [],
groups: [],
newMountPoint: '',
editing: 0
editingGroup: 0,
editingMountPoint: 0,
renameMountPoint: ''
};
componentDidMount() {
@ -58,7 +63,7 @@ export class App extends Component<{}, AppState> {
});
};
deleteFolder(id) {
deleteFolder(id: number) {
const folderName = this.state.folders[id].mount_point;
OC.dialogs.confirm(
t('groupfolders', 'Are you sure you want to delete "{folderName}" and all files inside. This operation can not be undone', {folderName}),
@ -75,46 +80,74 @@ export class App extends Component<{}, AppState> {
);
};
addGroup(folderId, group) {
addGroup(folderId: number, group: string) {
const folders = this.state.folders;
folders[folderId].groups[group] = OC.PERMISSION_ALL;
this.setState({folders});
this.api.addGroup(folderId, group);
}
removeGroup(folderId, group) {
removeGroup(folderId: number, group: string) {
const folders = this.state.folders;
delete folders[folderId].groups[group];
this.setState({folders});
this.api.removeGroup(folderId, group);
}
setPermissions(folderId, group, newPermissions) {
setPermissions(folderId: number, group: string, newPermissions: number) {
const folders = this.state.folders;
folders[folderId].groups[group] = newPermissions;
this.setState({folders});
this.api.setPermissions(folderId, group, newPermissions);
}
setQuota(folderId, quota) {
setQuota(folderId: number, quota: number) {
const folders = this.state.folders;
folders[folderId].quota = quota;
this.setState({folders});
this.api.setQuota(folderId, quota);
}
renameFolder(folderId: number, newName: string) {
const folders = this.state.folders;
folders[folderId].mount_point = newName;
// this.api.setQuota(folderId, quota);
this.setState({folders, editingMountPoint: 0});
this.api.renameFolder(folderId, newName);
}
render() {
const rows = Object.keys(this.state.folders).map((key) => {
const id = parseInt(key, 10);
const row = this.state.folders[id];
return <tr key={id}>
<td className="mountpoint">{row.mount_point}</td>
<td className="mountpoint">
{this.state.editingMountPoint === id ?
<SubmitInput
autoFocus={true}
onSubmitValue={this.renameFolder.bind(this, id)}
onClick={event => {
event.stopPropagation();
}}
initialValue={row.mount_point}
/> :
<a
className="action-rename"
onClick={event => {
event.stopPropagation();
this.setState({editingMountPoint: id})
}}
>
{row.mount_point}
</a>
}
</td>
<td className="groups">
<FolderGroups
edit={this.state.editing === id}
edit={this.state.editingGroup === id}
showEdit={event => {
event.stopPropagation();
this.setState({editing: id})
this.setState({editingGroup: id})
}}
groups={row.groups}
allGroups={this.state.groups}
@ -130,7 +163,7 @@ export class App extends Component<{}, AppState> {
onChange={this.setQuota.bind(this, id)}/>
</td>
<td className="remove">
<a className="icon icon-delete"
<a className="icon icon-delete icon-visible"
onClick={this.deleteFolder.bind(this, id)}
title={t('groupfolders', 'Delete')}/>
</td>
@ -139,7 +172,7 @@ export class App extends Component<{}, AppState> {
return <div id="groupfolders-react-root"
onClick={() => {
this.setState({editing: 0})
this.setState({editingGroup: 0, editingMountPoint: 0})
}}>
<table>
<thead>

40
js/SubmitInput.tsx Normal file
View file

@ -0,0 +1,40 @@
import * as React from 'react';
import {
Component, InputHTMLAttributes,
SyntheticEvent
} from 'react';
export interface SubmitInputProps extends InputHTMLAttributes<HTMLInputElement> {
initialValue?: string;
onSubmitValue: (value: string) => void;
}
export interface SubmitInputState {
value: string;
}
export class SubmitInput extends Component<SubmitInputProps, SubmitInputState> {
state: SubmitInputState = {
value: ''
};
constructor(props: SubmitInputProps) {
super(props);
this.state.value = props.initialValue || '';
}
onSubmit = (event: SyntheticEvent<any>) => {
event.preventDefault();
this.props.onSubmitValue(this.state.value);
};
render() {
const {initialValue, onSubmitValue, ...props} = this.props;
return <form onSubmit={this.onSubmit}>
<input type="text" value={this.state.value}
{...props}
onChange={event => this.setState({value: event.currentTarget.value})}/>
</form>;
}
}

View file

@ -113,4 +113,12 @@ class FolderController extends Controller {
public function setQuota($id, $quota) {
$this->manager->setFolderQuota($id, $quota);
}
/**
* @param int $id
* @param string $mountpoint
*/
public function renameFolder($id, $mountpoint) {
$this->manager->renameFolder($id, $mountpoint);
}
}

View file

@ -172,4 +172,13 @@ class FolderManager {
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
$query->execute();
}
public function renameFolder($folderId, $newMountPoint) {
$query = $this->connection->getQueryBuilder();
$query->update('group_folders')
->set('mount_point', $query->createNamedParameter($newMountPoint))
->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
$query->execute();
}
}

View file

@ -25,6 +25,7 @@
"style-loader": "^0.16.1",
"ts-loader": "^2.3.2",
"typescript": "^2.4.2",
"url-loader": "^0.5.9",
"webpack": "^2.3.3",
"webpack-dev-server": "^2.4.2"
},

View file

@ -135,4 +135,16 @@ class FolderManagerTest extends TestCase {
['mount_point' => 'bar', 'groups' => []]
]);
}
public function testRenameFolder() {
$folderId1 = $this->manager->createFolder('foo');
$this->manager->createFolder('other');
$this->manager->renameFolder($folderId1, 'bar');
$this->assertHasFolders([
['mount_point' => 'bar', 'groups' => []],
['mount_point' => 'other', 'groups' => []],
]);
}
}

View file

@ -37,6 +37,12 @@ module.exports = {
test: /\.tsx?$/,
use: ['react-hot-loader/webpack', 'ts-loader']
},
{
test: /.*\.(gif|png|jpe?g|svg|webp)(\?.+)?$/i,
use: [
'url-loader?limit=5000&hash=sha512&digest=hex&name=[hash].[ext]'
]
},
{
test: /\.js$/,
use: ['react-hot-loader/webpack', 'babel-loader']

View file

@ -44,6 +44,12 @@ module.exports = {
test: /\.tsx?$/,
use: ['ts-loader']
},
{
test: /.*\.(gif|png|jpe?g|svg|webp)(\?.+)?$/i,
use: [
'url-loader?limit=5000&hash=sha512&digest=hex&name=[hash].[ext]'
]
},
{
test: /\.js$/,
use: ['babel-loader']