diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
index e4a126c..5271fdc 100644
--- a/android/app/capacitor.build.gradle
+++ b/android/app/capacitor.build.gradle
@@ -10,6 +10,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
+ implementation project(':capacitor-filesystem')
implementation project(':capacitor-haptics')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-splash-screen')
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 1bac385..e697f98 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -39,4 +39,6 @@
+
+
diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle
index c7e1694..de2e069 100644
--- a/android/capacitor.settings.gradle
+++ b/android/capacitor.settings.gradle
@@ -5,6 +5,9 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
+include ':capacitor-filesystem'
+project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
+
include ':capacitor-haptics'
project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android')
diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist
index f693a0e..a834c35 100644
--- a/ios/App/App/Info.plist
+++ b/ios/App/App/Info.plist
@@ -33,6 +33,10 @@
$(CURRENT_PROJECT_VERSION)
LSRequiresIPhoneOS
+ UIFileSharingEnabled
+
+ LSSupportsOpeningDocumentsInPlace
+
NSExtensionAttributes
NSExtensionActivationRule
diff --git a/ios/App/Podfile b/ios/App/Podfile
index 2a45b34..016794b 100644
--- a/ios/App/Podfile
+++ b/ios/App/Podfile
@@ -12,6 +12,7 @@ def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
+ pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
diff --git a/package-lock.json b/package-lock.json
index 3d77be0..cfaef47 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
"@capacitor/android": "4.6.3",
"@capacitor/app": "^4.1.1",
"@capacitor/core": "4.6.3",
+ "@capacitor/filesystem": "^4.1.4",
"@capacitor/haptics": "4.1.0",
"@capacitor/ios": "4.6.3",
"@capacitor/keyboard": "4.1.1",
@@ -33,6 +34,7 @@
"@ionic/angular": "^6.5.4",
"angular-oauth2-oidc": "^15.0.1",
"capacitor-share-extension": "^2.0.0",
+ "cordova-plugin-file-opener2": "^4.0.0",
"cordova-plugin-inappbrowser": "^5.0.0",
"ionicons": "^6.1.3",
"keycloak-angular": "^13.0.0",
@@ -2565,6 +2567,14 @@
"tslib": "^2.1.0"
}
},
+ "node_modules/@capacitor/filesystem": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-4.1.4.tgz",
+ "integrity": "sha512-ivko1RNK4hq63xhMacq8D6D97N5/SAafTsrmY/pghYrG6Cl2SEY0+IgRu7V9/VWeN3FSplyUPucjUTAFQxXN5g==",
+ "peerDependencies": {
+ "@capacitor/core": "^4.0.0"
+ }
+ },
"node_modules/@capacitor/haptics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-4.1.0.tgz",
@@ -6612,6 +6622,24 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/cordova-plugin-file-opener2": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cordova-plugin-file-opener2/-/cordova-plugin-file-opener2-4.0.0.tgz",
+ "integrity": "sha512-+O+MMldI2k5Kjoa62KXYxnZQIGE4k5VfoCmmOUmaV5XM6U6euiiKMfCkuMrOrdAFb1C5Jsx+SuBHbWx1NEB5lw==",
+ "engines": {
+ "cordovaDependencies": {
+ "2.0.0": {
+ "cordova": ">=6.0.0"
+ },
+ "3.0.0": {
+ "cordova": ">=7.0.0"
+ },
+ "4.0.0": {
+ "cordova-android": ">=10.0.0"
+ }
+ }
+ }
+ },
"node_modules/cordova-plugin-inappbrowser": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-inappbrowser/-/cordova-plugin-inappbrowser-5.0.0.tgz",
diff --git a/package.json b/package.json
index 3bb37e4..32fb08f 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"@capacitor/android": "4.6.3",
"@capacitor/app": "^4.1.1",
"@capacitor/core": "4.6.3",
+ "@capacitor/filesystem": "^4.1.4",
"@capacitor/haptics": "4.1.0",
"@capacitor/ios": "4.6.3",
"@capacitor/keyboard": "4.1.1",
@@ -38,6 +39,7 @@
"@ionic/angular": "^6.5.4",
"angular-oauth2-oidc": "^15.0.1",
"capacitor-share-extension": "^2.0.0",
+ "cordova-plugin-file-opener2": "^4.0.0",
"cordova-plugin-inappbrowser": "^5.0.0",
"ionicons": "^6.1.3",
"keycloak-angular": "^13.0.0",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 49fa746..9976be3 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -11,13 +11,14 @@ import { D4sAuthService } from './d4sauth.service';
import { D4sIntentService } from './d4s-intent.service';
import {initializeIntent} from './_helper/intent-init.factory'
import { StoragehubService } from './storagehub.service';
+import { EventService } from './event.service';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, HttpClientModule, HttpClientModule, KeycloakAngularModule],
providers: [
- D4sAuthService, ModalController, StoragehubService, ToastController,
+ D4sAuthService, ModalController, StoragehubService, ToastController, EventService,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
{
provide: APP_INITIALIZER,
diff --git a/src/app/d4sauth.service.ts b/src/app/d4sauth.service.ts
index fb1709a..432e89c 100644
--- a/src/app/d4sauth.service.ts
+++ b/src/app/d4sauth.service.ts
@@ -63,12 +63,30 @@ export class D4sAuthService {
mustUpdate = true;
console.log("token refreshed");
}
+
+ if (this.uma && this.isExpired(35)){
+ console.log("uma expires in 35, must update")
+ mustUpdate = true;
+ }
+
if (mustUpdate || !this.uma)
this.uma = await this.entitlement(this.audience);
return "Bearer " + this.uma.access_token;
}
+ isExpired(minValidityInSeconds : number ) {
+ if (!this.uma) return false;
+ const expires: number = this.parseJwt(this.uma.access_token).exp;
+
+ const time: number = Date.now()/1000;
+
+ console.log(`exiperes in ${expires} and now is ${time}`);
+
+ return time+minValidityInSeconds > expires;
+
+ }
+
entitlement(resourceServerId: any): Promise {
return new Promise((resolve, reject) => {
const keycloak = this.keycloak.getKeycloakInstance();
diff --git a/src/app/model/actions/copy-item.ts b/src/app/model/actions/copy-item.ts
index 84f6289..56ae525 100644
--- a/src/app/model/actions/copy-item.ts
+++ b/src/app/model/actions/copy-item.ts
@@ -19,7 +19,7 @@ export class CopyAction extends Action {
onSelected: (destinationItem: WSItem) => {
this.actionHandler({ item: item, destinationItem: destinationItem }, storagehub).then( (obs) => obs.subscribe(
() => {
- reload();
+ reload(destinationItem.item.id)
notify(`${item.getTitle()} copied`);
}
))
diff --git a/src/app/model/actions/create-folder.ts b/src/app/model/actions/create-folder.ts
index 76bec39..97eedd9 100644
--- a/src/app/model/actions/create-folder.ts
+++ b/src/app/model/actions/create-folder.ts
@@ -27,7 +27,7 @@ export class CreateFolderAction extends Action {
text: 'Create',
handler: (data) => {
this.actionHandler({ wsitem: item, name: data.name }, storagehub).then((obs) => obs.subscribe(
- () => reload()
+ () => reload(item.item.id)
)
);
}
diff --git a/src/app/model/actions/delete-item.ts b/src/app/model/actions/delete-item.ts
index e866044..ca4ccf0 100644
--- a/src/app/model/actions/delete-item.ts
+++ b/src/app/model/actions/delete-item.ts
@@ -18,7 +18,7 @@ export class DeleteAction extends Action {
text: 'Yes',
handler: () => {
this.actionHandler(item, storagehub).then((obs) => obs.subscribe(
- () => reload()
+ () => reload(item.item.parentId)
));
}
}]
diff --git a/src/app/model/actions/move-item.ts b/src/app/model/actions/move-item.ts
index c6dce87..1dee197 100644
--- a/src/app/model/actions/move-item.ts
+++ b/src/app/model/actions/move-item.ts
@@ -18,7 +18,7 @@ export class MoveAction extends Action {
onSelected: (destinationItem:WSItem) =>{
this.actionHandler({item: item, destinationItem: destinationItem}, storagehub).then( (obs) => obs.subscribe(
() => {
- reload();
+ reload(destinationItem.item.id);
notify(`${item.getTitle()} moved`);
}
))
diff --git a/src/app/model/actions/rename-item.ts b/src/app/model/actions/rename-item.ts
index 5487283..31ec6da 100644
--- a/src/app/model/actions/rename-item.ts
+++ b/src/app/model/actions/rename-item.ts
@@ -27,7 +27,7 @@ export class RenameAction extends Action {
text: 'Rename',
handler: (data) => {
this.actionHandler({item: item, newName: data.name}, storagehub).then( (obs) => obs.subscribe(
- () => reload()
+ () => reload(item.item.parentId)
));
}
}
diff --git a/src/app/show-folder/show-folder.component.html b/src/app/show-folder/show-folder.component.html
index f9eeb23..304c895 100644
--- a/src/app/show-folder/show-folder.component.html
+++ b/src/app/show-folder/show-folder.component.html
@@ -38,6 +38,6 @@
-
\ No newline at end of file
diff --git a/src/app/show-folder/show-folder.component.ts b/src/app/show-folder/show-folder.component.ts
index af2a5d0..ac72b96 100644
--- a/src/app/show-folder/show-folder.component.ts
+++ b/src/app/show-folder/show-folder.component.ts
@@ -12,11 +12,14 @@ import { CreateFolderAction } from '../model/actions/create-folder';
import { Actions } from '../model/actions/actions';
import { ItemsListComponent } from '../items-list/items-list.component';
import { Sorting, SortName, SortType } from '../model/sorting';
+import { UploaderInfoService } from '../uploader-info.service';
+import { EventService } from '../event.service';
+import { OpenFile } from '../model/actions/open-file';
@Component({
standalone: true,
selector: 'show-folder',
- providers: [FileOpener, StoragehubService],
+ providers: [FileOpener, StoragehubService, UploaderInfoService],
templateUrl: './show-folder.component.html',
styleUrls: ['./show-folder.component.scss'],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -27,7 +30,7 @@ import { Sorting, SortName, SortType } from '../model/sorting';
export class ShowFolderComponent implements OnInit {
@Output() folderClickedEvent = new EventEmitter();
-
+
filteredItems: WSItem[] | undefined;
_items: WSItem[] | undefined;
@@ -45,14 +48,14 @@ export class ShowFolderComponent implements OnInit {
@Input() root: boolean = false;
- underUploadItem: string[] = [];
-
selectedSegment = "all";
public get sortName(): typeof SortName {
- return SortName;
+ return SortName;
}
+ underUpload: string[];
+
@ViewChild('filepicker') uploader!: ElementRef;
customSortAlertOptions = {
@@ -65,18 +68,31 @@ export class ShowFolderComponent implements OnInit {
private storagehub: StoragehubService,
private alertCtrl: AlertController,
private modalCtrl: ModalController,
- private toastController: ToastController
+ private toastController: ToastController,
+ private uploaderInfo: UploaderInfoService,
+ private event: EventService,
+ private fileOpener: FileOpener
) {
+ this.underUpload = this.getUnderUploadItems();
}
-
ngOnInit(): void {
+ this.event.ReloadEvent.subscribe((val) => {
+ console.log(`event received with value ${val} in item ${this.parentItem?.item.id}`);
+ if (val === this.parentItem?.item.id)
+ this.loadDocuments();
+ });
+ }
+
+ getUnderUploadItems(): string[] {
+ const parentId = this.parentItem?.item.id;
+ return parentId ? this.uploaderInfo.getUnderUpload(parentId) : [];
}
loadDocuments() {
this.filteredItems = undefined;
if (this.parentItem) {
- this.storagehub.getChildren(this.parentItem.item.id).then( (obs) => obs.subscribe(
+ this.storagehub.getChildren(this.parentItem.item.id).then((obs) => obs.subscribe(
(res) => {
const tmpItems$: WSItem[] = []
const tmpFiltered$: WSItem[] = []
@@ -89,8 +105,9 @@ export class ShowFolderComponent implements OnInit {
});
this._items = tmpItems$.sort(Sorting.getSortFunction(this.currentSortName, this.currentSortType));
this.filteredItems = tmpFiltered$.sort(Sorting.getSortFunction(this.currentSortName, this.currentSortType));
+ this.underUpload = this.getUnderUploadItems();
})
- )
+ )
}
}
@@ -133,8 +150,8 @@ export class ShowFolderComponent implements OnInit {
}
changeSortName($event: any) {
- var sort : SortName = $event.target.value;
- if (this.currentSortName != sort){
+ var sort: SortName = $event.target.value;
+ if (this.currentSortName != sort) {
this.currentSortName = sort;
this.updateSort();
}
@@ -146,9 +163,13 @@ export class ShowFolderComponent implements OnInit {
}
itemClicked(item: WSItem) {
- if (item.isFolder()) {
+ if (item.isFolder())
this.folderClickedEvent.emit(item);
+ else if (item.isFile()){
+ new OpenFile().open(this.storagehub, this.fileOpener, item);
}
+
+
}
addFile() {
@@ -159,12 +180,23 @@ export class ShowFolderComponent implements OnInit {
const selected = $event.target.files[0];
if (selected && this.parentItem) {
- this.underUploadItem = [selected.name];
- const upload$ = this.storagehub.uploadFile(this.parentItem.item.id, selected.name, selected);
- upload$.then( (obs) => obs.subscribe({
- next: () => this.loadDocuments(),
- error: () => this.underUploadItem = [],
- complete: () => this.underUploadItem = []
+ const parentId: string = this.parentItem?.item?.id;
+ this.uploaderInfo.uploadStarted(parentId, selected.name);
+ const upload$ = this.storagehub.uploadFile(parentId, selected.name, selected);
+ var first = true;
+ upload$.then((obs) => obs.subscribe({
+ next: () => {
+ if (first) {
+ this.loadDocuments();
+ first = false;
+ }
+ },
+ error: () => this.uploaderInfo.uploadFinished(parentId, selected.name),
+ complete: () => {
+ console.log
+ this.uploaderInfo.uploadFinished(parentId, selected.name);
+ this.loadDocuments();
+ }
})
);
@@ -233,7 +265,7 @@ export class ShowFolderComponent implements OnInit {
await this.presentAlertControl(alertOptions);
} else {
var modalOptions: undefined | ModalOptions = action.getModalOptions(item, this.storagehub,
- () => this.loadDocuments(), (text: string) => this.presentToast(text));
+ (itemId: string) => this.event.ReloadEvent.emit(itemId), (text: string) => this.presentToast(text));
if (modalOptions)
await this.presentModal(modalOptions);
else
diff --git a/src/app/storagehub.service.ts b/src/app/storagehub.service.ts
index 87bdd02..a8d0472 100644
--- a/src/app/storagehub.service.ts
+++ b/src/app/storagehub.service.ts
@@ -8,6 +8,7 @@ import { Item } from './model/item.model';
import { ItemList } from './model/itemlist.model';
import { ItemWrapper } from './model/item-wrapper.model';
import { D4sAuthService } from './d4sauth.service';
+import { WSItem } from './model/ws-item';
const shURL ="https://api.dev.d4science.org/workspace"
@@ -87,6 +88,14 @@ export class StoragehubService {
);
}
+ async downloadFile(itemId: string){
+ const bearer = await this.auth.getSecureHeader();
+ let downloadUrl = `${shURL}/items/${itemId}/download`;
+ return this.http.get(downloadUrl, {headers: {"Authorization": bearer }, responseType: 'blob', observe: 'body'}).pipe(
+ catchError(this.error)
+ );
+ }
+
async createFolder(parentId: string, name: string, description: string) {
const bearer = await this.auth.getSecureHeader();
let createFolderURL = `${shURL}/items/${parentId}/create/FOLDER`;
@@ -100,7 +109,7 @@ export class StoragehubService {
async deleteItem(itemId: string) : Promise> {
const bearer = await this.auth.getSecureHeader();
let deleteItemUrl = `${shURL}/items/${itemId}`;
- return this.http.delete( deleteItemUrl).pipe(
+ return this.http.delete( deleteItemUrl, {headers: {"Authorization": bearer }}).pipe(
catchError(this.error)
);
}