mirror of
https://github.com/netzbegruenung/groupfolders.git
synced 2024-05-03 09:53:41 +02:00
switch to typescript
Signed-off-by: Robin Appelman <robin@icewind.nl>
This commit is contained in:
parent
bff7bdd700
commit
958d647459
File diff suppressed because one or more lines are too long
|
@ -1,54 +1,64 @@
|
|||
import {OCSResult} from "NC";
|
||||
import Thenable = JQuery.Thenable;
|
||||
|
||||
export interface Folder {
|
||||
mount_point: string;
|
||||
quota: number;
|
||||
size: number;
|
||||
groups: { [group: string]: number };
|
||||
}
|
||||
|
||||
export class Api {
|
||||
getUrl (endpoint) {
|
||||
getUrl(endpoint: string): string {
|
||||
return OC.generateUrl(`apps/groupfolders/${endpoint}`);
|
||||
}
|
||||
|
||||
listFolders () {
|
||||
listFolders(): Thenable<Folder[]> {
|
||||
return $.getJSON(this.getUrl('folders'));
|
||||
}
|
||||
|
||||
listGroups () {
|
||||
listGroups(): Thenable<string[]> {
|
||||
return $.getJSON(OC.linkToOCS('cloud', 1) + 'groups?format=json')
|
||||
.then((data) => {
|
||||
.then((data: OCSResult<{ groups: string[]; }>) => {
|
||||
return data.ocs.data.groups;
|
||||
});
|
||||
}
|
||||
|
||||
createFolder (mountPoint) {
|
||||
createFolder(mountPoint: string): Thenable<number> {
|
||||
return $.post(this.getUrl('folders'), {
|
||||
mountpoint: mountPoint
|
||||
}).then((data) => {
|
||||
}).then((data: { id: number; }) => {
|
||||
return data.id;
|
||||
});
|
||||
}
|
||||
|
||||
deleteFolder (id) {
|
||||
deleteFolder(id: number): Thenable<void> {
|
||||
return $.ajax({
|
||||
url: this.getUrl(`folders/${id}`),
|
||||
type: 'DELETE'
|
||||
});
|
||||
}
|
||||
|
||||
addGroup (folderId, group) {
|
||||
addGroup(folderId: number, group: string): Thenable<void> {
|
||||
return $.post(this.getUrl(`folders/${folderId}/groups`), {
|
||||
group
|
||||
});
|
||||
}
|
||||
|
||||
removeGroup (folderId, group) {
|
||||
removeGroup(folderId: number, group: string): Thenable<void> {
|
||||
return $.ajax({
|
||||
url: this.getUrl(`folders/${folderId}/groups/${group}`),
|
||||
type: 'DELETE'
|
||||
});
|
||||
}
|
||||
|
||||
setPermissions (folderId, group, permissions) {
|
||||
setPermissions(folderId: number, group: string, permissions: number): Thenable<void> {
|
||||
return $.post(this.getUrl(`folders/${folderId}/groups/${group}`), {
|
||||
permissions
|
||||
});
|
||||
}
|
||||
|
||||
setQuota (folderId, quota) {
|
||||
setQuota(folderId: number, quota: number): Thenable<void> {
|
||||
return $.post(this.getUrl(`folders/${folderId}/quota`), {
|
||||
quota
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import {Component} from 'react';
|
||||
|
||||
import {Api} from './Api';
|
||||
import {Api, Folder} from './Api';
|
||||
import {FolderGroups} from './FolderGroups';
|
||||
import {QuotaSelect} from './QuotaSelect';
|
||||
|
||||
|
@ -13,20 +14,24 @@ const defaultQuotaOptions = {
|
|||
'Unlimited': -3
|
||||
};
|
||||
|
||||
export class App extends Component {
|
||||
state = {
|
||||
export interface AppState {
|
||||
folders: Folder[];
|
||||
groups: string[],
|
||||
newMountPoint: string;
|
||||
editing: number;
|
||||
}
|
||||
|
||||
export class App extends Component<{}, AppState> {
|
||||
api = new Api();
|
||||
|
||||
state: AppState = {
|
||||
folders: [],
|
||||
groups: [],
|
||||
newMountPoint: '',
|
||||
editing: 0
|
||||
};
|
||||
|
||||
constructor (params) {
|
||||
super(params);
|
||||
this.api = new Api();
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.api.listFolders().then((folders) => {
|
||||
this.setState({folders});
|
||||
});
|
||||
|
@ -46,13 +51,14 @@ export class App extends Component {
|
|||
folders[id] = {
|
||||
mount_point: mountPoint,
|
||||
groups: {},
|
||||
quota: -3
|
||||
quota: -3,
|
||||
size: 0
|
||||
};
|
||||
this.setState({folders});
|
||||
});
|
||||
};
|
||||
|
||||
deleteFolder (id) {
|
||||
deleteFolder(id) {
|
||||
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}),
|
||||
|
@ -69,36 +75,37 @@ export class App extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
addGroup (folderId, group) {
|
||||
addGroup(folderId, group) {
|
||||
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, group) {
|
||||
const folders = this.state.folders;
|
||||
delete folders[folderId].groups[group];
|
||||
this.setState({folders});
|
||||
this.api.removeGroup(folderId, group);
|
||||
}
|
||||
|
||||
setPermissions (folderId, group, newPermissions) {
|
||||
setPermissions(folderId, group, newPermissions) {
|
||||
const folders = this.state.folders;
|
||||
folders[folderId].groups[group] = newPermissions;
|
||||
this.setState({folders});
|
||||
this.api.setPermissions(folderId, group, newPermissions);
|
||||
}
|
||||
|
||||
setQuota (folderId, quota) {
|
||||
setQuota(folderId, quota) {
|
||||
const folders = this.state.folders;
|
||||
folders[folderId].quota = quota;
|
||||
this.setState({folders});
|
||||
this.api.setQuota(folderId, quota);
|
||||
}
|
||||
|
||||
render () {
|
||||
const rows = Object.keys(this.state.folders).map((id) => {
|
||||
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>
|
|
@ -1,16 +1,29 @@
|
|||
import * as React from 'react';
|
||||
import './FolderGroups.css';
|
||||
import {SyntheticEvent} from "react";
|
||||
|
||||
export function FolderGroups ({groups, allGroups, onAddGroup, removeGroup, edit, showEdit, onSetPermissions}) {
|
||||
function hasPermissions(value: number, check: number): boolean {
|
||||
return (value & check) === check;
|
||||
}
|
||||
|
||||
export interface FolderGroupsProps {
|
||||
groups: { [group: string]: number },
|
||||
allGroups?: string[],
|
||||
onAddGroup: (name: string) => void;
|
||||
removeGroup: (name: string) => void;
|
||||
edit: boolean;
|
||||
showEdit: (event: SyntheticEvent<any>) => void;
|
||||
onSetPermissions: (name: string, permissions: number) => void;
|
||||
}
|
||||
|
||||
export function FolderGroups({groups, allGroups = [], onAddGroup, removeGroup, edit, showEdit, onSetPermissions}: FolderGroupsProps) {
|
||||
if (edit) {
|
||||
if (!allGroups) {
|
||||
allGroups = {};
|
||||
}
|
||||
const setPermissions = (change, groupId) => {
|
||||
const setPermissions = (change: number, groupId: string): void => {
|
||||
const newPermissions = groups[groupId] ^ change;
|
||||
onSetPermissions(groupId, newPermissions);
|
||||
};
|
||||
|
||||
const rows = Object.keys(groups).map((groupId) => {
|
||||
const rows = Object.keys(groups).map(groupId => {
|
||||
const permissions = groups[groupId];
|
||||
return <tr key={groupId}>
|
||||
<td>
|
||||
|
@ -19,12 +32,12 @@ export function FolderGroups ({groups, allGroups, onAddGroup, removeGroup, edit,
|
|||
<td className="permissions">
|
||||
<input type="checkbox"
|
||||
onChange={setPermissions.bind(null, OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_DELETE, groupId)}
|
||||
checked={permissions & (OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_DELETE)}/>
|
||||
checked={hasPermissions(permissions, (OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_DELETE))}/>
|
||||
</td>
|
||||
<td className="permissions">
|
||||
<input type="checkbox"
|
||||
onChange={setPermissions.bind(null, OC.PERMISSION_SHARE, groupId)}
|
||||
checked={permissions & (OC.PERMISSION_SHARE)}/>
|
||||
checked={hasPermissions(permissions, OC.PERMISSION_SHARE)}/>
|
||||
</td>
|
||||
<td>
|
||||
<a onClick={removeGroup.bind(this, groupId)}>
|
||||
|
@ -41,7 +54,7 @@ export function FolderGroups ({groups, allGroups, onAddGroup, removeGroup, edit,
|
|||
<th>Group</th>
|
||||
<th>Write</th>
|
||||
<th>Share</th>
|
||||
<th></th>
|
||||
<th/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -68,16 +81,21 @@ export function FolderGroups ({groups, allGroups, onAddGroup, removeGroup, edit,
|
|||
}
|
||||
}
|
||||
|
||||
function GroupSelect ({allGroups, onChange}) {
|
||||
interface GroupSelectProps {
|
||||
allGroups: string[];
|
||||
onChange: (name: string) => void;
|
||||
}
|
||||
|
||||
function GroupSelect({allGroups, onChange}: GroupSelectProps) {
|
||||
if (allGroups.length === 0) {
|
||||
return <div/>;
|
||||
}
|
||||
const options = allGroups.map((group) => {
|
||||
const options = allGroups.map(group => {
|
||||
return <option key={group} value={group}>{group}</option>;
|
||||
});
|
||||
|
||||
return <select
|
||||
onChange={(event) => {
|
||||
onChange={event => {
|
||||
onChange && onChange(event.target.value)
|
||||
}}
|
||||
>
|
53
js/Nextcloud.d.ts
vendored
Normal file
53
js/Nextcloud.d.ts
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
interface EscapeOptions {
|
||||
escape?: boolean;
|
||||
}
|
||||
|
||||
declare namespace OC {
|
||||
namespace Util {
|
||||
function humanFileSize(size: number): string;
|
||||
|
||||
function computerFileSize(size: string): number;
|
||||
}
|
||||
|
||||
namespace dialogs {
|
||||
function info(text: string, title: string, callback: () => void, modal?: boolean): void;
|
||||
|
||||
function confirm(text: string, title: string, callback: (result: boolean) => void, modal?: boolean): void;
|
||||
|
||||
function confirmHtml(text: string, title: string, callback: (result: boolean) => void, modal?: boolean): void;
|
||||
|
||||
function prompt(text: string, title: string, callback: (result: string) => void, modal?: boolean, name?: string, password?: boolean): void;
|
||||
|
||||
function filepicket(title: string, callback: (result: string | string[]) => void, multiselect?: boolean, mimetypeFilter?: string, modal?: boolean): void;
|
||||
}
|
||||
|
||||
function generateUrl(url: string, parameters?: { [key: string]: string }, options?: EscapeOptions)
|
||||
|
||||
function linkToOCS(service: string, version: number): string;
|
||||
|
||||
function imagePath(app: string, file: string): string;
|
||||
|
||||
const PERMISSION_CREATE = 4;
|
||||
const PERMISSION_READ = 1;
|
||||
const PERMISSION_UPDATE = 2;
|
||||
const PERMISSION_DELETE = 8;
|
||||
const PERMISSION_SHARE = 16;
|
||||
const PERMISSION_ALL = 31;
|
||||
}
|
||||
|
||||
declare function t(app: string, string: string, vars?: { [key: string]: string }, count?: number, options?: EscapeOptions): string;
|
||||
|
||||
declare module 'NC' {
|
||||
export interface OCSResult<T> {
|
||||
ocs: {
|
||||
data: T;
|
||||
meta: {
|
||||
status: 'ok' | 'failure';
|
||||
message: string;
|
||||
statuscode: number;
|
||||
totalitems: number;
|
||||
itemsperpage: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,29 @@
|
|||
import * as React from 'react';
|
||||
import {Component} from 'react';
|
||||
|
||||
import './EditSelect.css';
|
||||
|
||||
export class QuotaSelect extends Component {
|
||||
state = {
|
||||
export interface QuotaSelectProps {
|
||||
options: { [name: string]: number };
|
||||
value: number;
|
||||
size: number;
|
||||
onChange: (quota: number) => void;
|
||||
}
|
||||
|
||||
export interface QuotaSelectState {
|
||||
options: { [name: string]: number };
|
||||
isEditing: boolean;
|
||||
isValidInput: boolean;
|
||||
}
|
||||
|
||||
export class QuotaSelect extends Component<QuotaSelectProps, QuotaSelectState> {
|
||||
state: QuotaSelectState = {
|
||||
options: {},
|
||||
isEditing: false,
|
||||
isValidInput: true
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state.options = props.options;
|
||||
if (props.value >= 0) {
|
||||
|
@ -39,7 +53,7 @@ export class QuotaSelect extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
getUsedPercentage () {
|
||||
getUsedPercentage() {
|
||||
if (this.props.value >= 0) {
|
||||
return Math.min((this.props.size / this.props.value) * 100, 100);
|
||||
} else {
|
||||
|
@ -49,14 +63,14 @@ export class QuotaSelect extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
if (this.state.isEditing) {
|
||||
return <input
|
||||
onBlur={() => {
|
||||
this.setState({isEditing: false})
|
||||
}}
|
||||
onKeyPress={(e) => {
|
||||
(e.key === 'Enter' ? this.onEditedValue(e.target.value) : null)
|
||||
(e.key === 'Enter' ? this.onEditedValue((e.target as HTMLInputElement).value) : null)
|
||||
}}
|
||||
className={'editselect-input' + (this.state.isValidInput ? '' : ' error')}
|
||||
autoFocus={true}/>
|
||||
|
@ -72,7 +86,7 @@ export class QuotaSelect extends Component {
|
|||
<select className="editselect"
|
||||
onChange={this.onSelect}
|
||||
ref={(ref) => {
|
||||
$(ref).tooltip({
|
||||
ref && $(ref).tooltip({
|
||||
title: t('settings', '{size} used', {size: humanSize}, 0, {escape: false}).replace('<', '<'),
|
||||
delay: {
|
||||
show: 100,
|
||||
|
@ -82,8 +96,9 @@ export class QuotaSelect extends Component {
|
|||
}}
|
||||
value={this.props.value}>
|
||||
{options}
|
||||
<option
|
||||
value="other">{t('groupfolders', 'Other …')}</option>
|
||||
<option value="other">
|
||||
{t('groupfolders', 'Other …')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"@types/react": "^15.0.21",
|
||||
"@types/bootstrap": "^3.3.35",
|
||||
"@types/jquery": "^3.2.11",
|
||||
"@types/react": "^15.6.1",
|
||||
"@types/react-dom": "^0.14.23",
|
||||
"@types/webpack": "^3.0.9",
|
||||
"autoprefixer-loader": "^3.2.0",
|
||||
"babel-core": "^6.24.0",
|
||||
"babel-loader": "^6.4.1",
|
||||
|
@ -20,6 +23,8 @@
|
|||
"postcss-nested": "^1.0.0",
|
||||
"react-hot-loader": "3.0.0-beta.6",
|
||||
"style-loader": "^0.16.1",
|
||||
"ts-loader": "^2.3.2",
|
||||
"typescript": "^2.4.2",
|
||||
"webpack": "^2.3.3",
|
||||
"webpack-dev-server": "^2.4.2"
|
||||
},
|
||||
|
|
|
@ -26,13 +26,17 @@ module.exports = {
|
|||
publicPath: '/'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NamedModulesPlugin()
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: ['react-hot-loader/webpack', 'ts-loader']
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ['react-hot-loader/webpack', 'babel-loader']
|
||||
|
|
|
@ -21,7 +21,7 @@ module.exports = {
|
|||
publicPath: '/'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js']
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
plugins: [
|
||||
new CleanPlugin(['build']),
|
||||
|
@ -40,6 +40,10 @@ module.exports = {
|
|||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: ['ts-loader']
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ['babel-loader']
|
||||
|
|
Loading…
Reference in a new issue