switch to typescript

Signed-off-by: Robin Appelman <robin@icewind.nl>
This commit is contained in:
Robin Appelman 2017-08-15 15:24:17 +02:00
parent bff7bdd700
commit 958d647459
9 changed files with 171 additions and 55 deletions

File diff suppressed because one or more lines are too long

View file

@ -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
});

View file

@ -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>

View file

@ -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
View 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;
}
}
}
}

View file

@ -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('&lt;', '<'),
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>
}

View file

@ -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"
},

View file

@ -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']

View file

@ -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']