Skip to content

Commit

Permalink
feat(clipboard): a task storage center
Browse files Browse the repository at this point in the history
user want to copy, move or delete item, the source item will storage clipboard service temporarily.
for copy and move operation, until user click paste button on destination dir, the real tasks are
commit to rclone server. For delete operation, until user click confirm button, the remove tasks are
post to remote.
  • Loading branch information
ElonH committed May 30, 2020
1 parent 90b0eaa commit 7a3713f
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { ClipboardRemotesTableComponent } from './clipboard-remotes-table.component';

describe('ClipboardRemotesTableComponent', () => {
let component: ClipboardRemotesTableComponent;
let fixture: ComponentFixture<ClipboardRemotesTableComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ClipboardRemotesTableComponent],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ClipboardRemotesTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { IManipulate, ClipboardService } from '../clipboard.service';
import { Config, Columns, DefaultConfig, APIDefinition, API } from 'ngx-easy-table';

type IRemotesTableItem = {
remote: string;
children: string[];
};

@Component({
selector: 'clipboard-remotes-table',
template: `
<ngx-table
#remotesTable
[configuration]="configuration"
[data]="data"
[id]="'remotes-tab-' + oper"
[detailsTemplate]="detailsTemplate"
[columns]="columns"
>
<ng-template let-row let-rowIdx="index">
<td>{{ row.remote }}</td>
<td>{{ row.children.length }}</td>
<td>
<button nbButton (click)="toggleDatail($event, rowIdx)">Detail</button>
</td>
</ng-template>
</ngx-table>
<ng-template #detailsTemplate let-row let-index="index">
<ngx-table
[configuration]="detailConfiguration"
[data]="row.children"
[detailsTemplate]="detailsTemplate"
[columns]="detailColumns"
>
<ng-template let-row>
<td>{{ row }}</td>
</ng-template>
</ngx-table>
</ng-template>
`,
styles: [],
})
export class ClipboardRemotesTableComponent implements OnInit {
@Input() oper: IManipulate;
constructor(private service: ClipboardService) {}
public configuration: Config;
public columns: Columns[] = [
{ key: 'remote', title: 'Remote', width: '80%' },
{ key: 'length', title: 'Total', width: '10%' },
{ key: '', title: 'Action', width: '10%' },
];

data: IRemotesTableItem[] = [];
ngOnInit() {
this.service.update$.getOutput().subscribe((node) => {
if (node[1].length !== 0) return;
this.data = this.transformData(node[0][this.oper]);
});

this.configuration = { ...DefaultConfig };
this.configuration.detailsTemplate = true;
this.configuration.tableLayout.hover = true;

this.detailConfiguration = { ...DefaultConfig };
this.detailConfiguration.rows = 5;
this.detailConfiguration.threeWaySort = true;
}

private transformData(pool: Set<string>): IRemotesTableItem[] {
const internal = new Map<string, string[]>();
pool.forEach((x) => {
const item = JSON.parse(x);
if (!internal.has(item.remote)) internal.set(item.remote, []);
internal.get(item.remote).push(item.path);
});
const ans: IRemotesTableItem[] = [];
internal.forEach((v, k) => {
ans.push({ remote: k, children: v });
});
return ans;
}

@ViewChild('remotesTable') table: APIDefinition;
toggleDatail($event: MouseEvent, rowidx: number) {
$event.preventDefault();
this.table.apiEvent({
type: API.toggleRowIndex,
value: rowidx,
});
}

public detailConfiguration: Config;
public detailColumns: Columns[] = [{ key: '', title: 'Path', width: '85%' }];
}
27 changes: 27 additions & 0 deletions src/app/pages/manager/clipboard/clipboard.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { ClipboardComponent } from './clipboard.component';

describe('ClipboardComponent', () => {
let component: ClipboardComponent;
let fixture: ComponentFixture<ClipboardComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ClipboardComponent],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ClipboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
37 changes: 37 additions & 0 deletions src/app/pages/manager/clipboard/clipboard.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Component, OnInit } from '@angular/core';
import { ClipboardService, IManipulate } from './clipboard.service';

@Component({
selector: 'manager-clipboard',
template: `
<nb-tabset fullWidth>
<nb-tab
*ngFor="let tab of data; index as tabIdx"
[tabTitle]="tab.title"
[tabIcon]="tab.icon"
[badgeText]="tab.len"
badgeStatus="primary"
>
<clipboard-remotes-table [oper]="tab.oper"> </clipboard-remotes-table>
</nb-tab>
</nb-tabset>
`,
styles: [],
})
export class ClipboardComponent implements OnInit {
data: { oper: IManipulate; title: string; icon: string; len: number }[] = [
{ oper: 'copy', title: 'Copy', icon: 'copy', len: 0 },
{ oper: 'move', title: 'Move', icon: 'move', len: 0 },
{ oper: 'del', title: 'Delete', icon: 'trash-2', len: 0 },
];
constructor(private service: ClipboardService) {}

ngOnInit() {
this.service.update$.getOutput().subscribe((node) => {
if (node[1].length !== 0) return;
this.data[ClipboardService.mapper['copy']].len = node[0].copy.size;
this.data[ClipboardService.mapper['move']].len = node[0].move.size;
this.data[ClipboardService.mapper['del']].len = node[0].del.size;
});
}
}
16 changes: 16 additions & 0 deletions src/app/pages/manager/clipboard/clipboard.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* tslint:disable:no-unused-variable */

import { TestBed, async, inject } from '@angular/core/testing';
import { ClipboardService } from './clipboard.service';

describe('Service: Clipboard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ClipboardService],
});
});

it('should ...', inject([ClipboardService], (service: ClipboardService) => {
expect(service).toBeTruthy();
}));
});
71 changes: 71 additions & 0 deletions src/app/pages/manager/clipboard/clipboard.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { NothingFlow, CombErr } from 'src/app/@dataflow/core';
import { map } from 'rxjs/operators';

export type IManipulate = 'copy' | 'move' | 'del';

export interface ClipboardNode {
copy: Set<string>;
move: Set<string>;
del: Set<string>;
}

@Injectable({
providedIn: 'root',
})
export class ClipboardService {
private sourcePool = [1, 2, 3].map(() => new Set<string>());
static readonly mapper: { [index: string]: number } = {
copy: 0,
move: 1,
del: 2,
};

static query(pools: ClipboardNode, remote: string, path: string): IManipulate {
const item = JSON.stringify({ remote: remote, path: path });
if (pools.copy.has(item)) return 'copy';
if (pools.move.has(item)) return 'move';
if (pools.del.has(item)) return 'del';
}

private excusiveAdd(pool: number, remote: string, path: string) {
const item = JSON.stringify({ remote: remote, path: path });
this.sourcePool[pool].add(item);
this.sourcePool.forEach((x, i) => {
if (i === pool) return;
if (x.has(item)) x.delete(item);
});
}
public manipulate(o: IManipulate, remote: string, path: string) {
const idx = ClipboardService.mapper[o];
if (typeof idx !== 'undefined') this.excusiveAdd(idx, remote, path);
}

private trigger = new Subject<number>();
update$: NothingFlow<ClipboardNode>;
public commit() {
this.trigger.next(1);
}

constructor() {
const outer = this;
this.update$ = new (class extends NothingFlow<ClipboardNode> {
public prerequest$ = outer.trigger.pipe(
map(
(): CombErr<ClipboardNode> => [
{
copy: outer.sourcePool[ClipboardService.mapper.copy],
move: outer.sourcePool[ClipboardService.mapper.move],
del: outer.sourcePool[ClipboardService.mapper.del],
},
[],
]
)
);
})();
this.update$.deploy();
this.update$.getOutput().subscribe();
this.trigger.next(1);
}
}
12 changes: 10 additions & 2 deletions src/app/pages/manager/fileMode/fileMode.component.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { NavigationFlow, NavigationFlowOutNode } from 'src/app/@dataflow/extra';
import { ConnectionService } from '../../connection.service';
import { Subject } from 'rxjs';
import { OperationsListFlow, OperationsListFlowInNode } from 'src/app/@dataflow/rclone';
import { combineLatest, map, filter } from 'rxjs/operators';
import { CombErr } from 'src/app/@dataflow/core';
import { ListViewComponent } from './listView/listView.component';
import { IManipulate, ClipboardService } from '../clipboard/clipboard.service';

@Component({
selector: 'manager-fileMode',
template: ` <manager-listView [list$]="list$" (jump)="jump.emit($event)"> </manager-listView> `,
styles: [],
})
export class FileModeComponent implements OnInit {
constructor(private connectService: ConnectionService) {}
constructor(private connectService: ConnectionService, private clipboard: ClipboardService) {}

@Input() nav$: NavigationFlow;

Expand All @@ -25,6 +27,12 @@ export class FileModeComponent implements OnInit {
this.listTrigger.next(1);
}

@ViewChild(ListViewComponent) listView: ListViewComponent;
manipulate(o: IManipulate) {
this.listView.manipulate(o);
this.clipboard.commit();
}

ngOnInit() {
const outer = this;
this.list$ = new (class extends OperationsListFlow {
Expand Down
Loading

0 comments on commit 7a3713f

Please sign in to comment.