@yomotech/view-toolkit 0.0.51 → 0.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,7 +6,7 @@ import * as i3 from '@angular/forms';
|
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
7
|
import * as i1$1 from '@angular/common/http';
|
|
8
8
|
import { HttpHeaders } from '@angular/common/http';
|
|
9
|
-
import {
|
|
9
|
+
import { Capacitor, CapacitorHttp } from '@capacitor/core';
|
|
10
10
|
import * as i10 from 'ng-zorro-antd/icon';
|
|
11
11
|
import { NzIconModule } from 'ng-zorro-antd/icon';
|
|
12
12
|
import * as i12 from 'ng-zorro-antd/button';
|
|
@@ -861,78 +861,83 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
861
861
|
|
|
862
862
|
class Couchdb {
|
|
863
863
|
utilityService;
|
|
864
|
-
|
|
864
|
+
remoteCouchDbBaseUrl = 'https://couchdb.yomotech.de';
|
|
865
|
+
webProxyBaseUrl = '/couchdb-api';
|
|
865
866
|
constructor(utilityService) {
|
|
866
867
|
this.utilityService = utilityService;
|
|
867
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* Angular web development:
|
|
871
|
+
* /couchdb-api -> Angular proxy -> https://couchdb.yomotech.de
|
|
872
|
+
*
|
|
873
|
+
* Capacitor Android/iOS:
|
|
874
|
+
* Direct native request to https://couchdb.yomotech.de
|
|
875
|
+
*/
|
|
876
|
+
get couchDbBaseUrl() {
|
|
877
|
+
return Capacitor.isNativePlatform()
|
|
878
|
+
? this.remoteCouchDbBaseUrl
|
|
879
|
+
: this.webProxyBaseUrl;
|
|
880
|
+
}
|
|
868
881
|
async getTenantDatabases(tenantKey) {
|
|
869
|
-
|
|
882
|
+
const normalizedTenantKey = tenantKey?.trim();
|
|
883
|
+
if (!normalizedTenantKey) {
|
|
870
884
|
throw new Error('Missing tenantKey');
|
|
871
885
|
}
|
|
872
|
-
const response = await fetch(
|
|
886
|
+
const response = await fetch(`${this.couchDbBaseUrl}/tenant-dbs`, {
|
|
873
887
|
method: 'GET',
|
|
888
|
+
cache: 'no-store',
|
|
874
889
|
headers: {
|
|
875
890
|
Accept: 'application/json',
|
|
876
|
-
'X-Tenant-Key':
|
|
891
|
+
'X-Tenant-Key': normalizedTenantKey,
|
|
877
892
|
},
|
|
878
893
|
});
|
|
879
894
|
const data = await this.parseJsonOrText(response);
|
|
880
895
|
if (!response.ok) {
|
|
881
|
-
throw new Error(`GET /tenant-dbs -> ${response.status}
|
|
896
|
+
throw new Error(`GET /tenant-dbs -> ${response.status} ` +
|
|
897
|
+
`${this.stringifyErrorDetails(data)}`);
|
|
882
898
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
async parseJsonOrText(response) {
|
|
886
|
-
const rawText = await response.text();
|
|
887
|
-
if (!rawText)
|
|
888
|
-
return null;
|
|
889
|
-
try {
|
|
890
|
-
return JSON.parse(rawText);
|
|
891
|
-
}
|
|
892
|
-
catch {
|
|
893
|
-
return rawText;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
stringifyErrorDetails(data) {
|
|
897
|
-
if (typeof data === 'string')
|
|
898
|
-
return data;
|
|
899
|
-
try {
|
|
900
|
-
return JSON.stringify(data);
|
|
901
|
-
}
|
|
902
|
-
catch {
|
|
903
|
-
return 'Unknown error';
|
|
899
|
+
if (!Array.isArray(data)) {
|
|
900
|
+
throw new Error(`Invalid /tenant-dbs response: ${this.stringifyErrorDetails(data)}`);
|
|
904
901
|
}
|
|
902
|
+
return data;
|
|
905
903
|
}
|
|
906
904
|
async getFileContentFromCouchDb(couchDbName, filePath) {
|
|
907
905
|
try {
|
|
908
|
-
const dbName = couchDbName?.toLowerCase();
|
|
906
|
+
const dbName = couchDbName?.trim().toLowerCase();
|
|
909
907
|
const docId = filePath?.trim();
|
|
908
|
+
if (!dbName) {
|
|
909
|
+
console.error('Missing CouchDB database name');
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
910
912
|
if (!docId) {
|
|
911
913
|
console.error('Missing file path');
|
|
912
914
|
return null;
|
|
913
915
|
}
|
|
914
916
|
const response = await fetch(this.getDocumentUrl(dbName, docId), {
|
|
915
917
|
method: 'GET',
|
|
918
|
+
cache: 'no-store',
|
|
916
919
|
headers: await this.getReadHeaders(),
|
|
917
920
|
});
|
|
918
921
|
const doc = await this.parseJsonOrText(response);
|
|
919
922
|
if (!response.ok) {
|
|
920
|
-
console.error(`Failed to fetch file "${docId}" from "${dbName}":
|
|
923
|
+
console.error(`Failed to fetch file "${docId}" from "${dbName}": ` +
|
|
924
|
+
`${response.status}`, doc);
|
|
921
925
|
return null;
|
|
922
926
|
}
|
|
923
927
|
if (!doc?.data) {
|
|
924
928
|
console.error(`Document "${docId}" does not contain file data`);
|
|
925
929
|
return null;
|
|
926
930
|
}
|
|
927
|
-
// do not decode images as utf8 text
|
|
928
931
|
if (doc.type === 'media' ||
|
|
929
932
|
doc.type === 'base64file' ||
|
|
930
933
|
this.isBinaryMediaFile(doc.name || doc.path || docId, doc.contentType)) {
|
|
934
|
+
const fileName = doc.name || doc.path || docId;
|
|
935
|
+
const mimeType = this.getMimeType(fileName, doc.contentType);
|
|
931
936
|
return {
|
|
932
937
|
type: doc.type || 'base64file',
|
|
933
|
-
mimeType
|
|
938
|
+
mimeType,
|
|
934
939
|
base64: doc.data,
|
|
935
|
-
src: `data:${
|
|
940
|
+
src: `data:${mimeType};base64,${doc.data}`,
|
|
936
941
|
name: doc.name,
|
|
937
942
|
path: doc.path,
|
|
938
943
|
metadata: doc.metadata || {},
|
|
@@ -951,13 +956,122 @@ class Couchdb {
|
|
|
951
956
|
return decodedContent;
|
|
952
957
|
}
|
|
953
958
|
catch (error) {
|
|
954
|
-
console.error(`Unexpected error while fetching file "${filePath}"
|
|
959
|
+
console.error(`Unexpected error while fetching file "${filePath}" ` +
|
|
960
|
+
`from "${couchDbName}"`, error);
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
async getImageSrcFromCouchDb(couchDbName, filePath) {
|
|
965
|
+
const dbName = couchDbName?.trim().toLowerCase();
|
|
966
|
+
const docId = filePath?.trim();
|
|
967
|
+
if (!dbName) {
|
|
968
|
+
throw new Error('Missing CouchDB database name');
|
|
969
|
+
}
|
|
970
|
+
if (!docId) {
|
|
971
|
+
throw new Error('Missing file path');
|
|
972
|
+
}
|
|
973
|
+
const response = await fetch(this.getDocumentUrl(dbName, docId), {
|
|
974
|
+
method: 'GET',
|
|
975
|
+
cache: 'no-store',
|
|
976
|
+
headers: await this.getReadHeaders(),
|
|
977
|
+
});
|
|
978
|
+
const doc = await this.parseJsonOrText(response);
|
|
979
|
+
if (!response.ok) {
|
|
980
|
+
throw new Error(`Failed to fetch image "${docId}" from "${dbName}": ` +
|
|
981
|
+
`${response.status} ${this.stringifyErrorDetails(doc)}`);
|
|
982
|
+
}
|
|
983
|
+
if (!doc?.data) {
|
|
984
|
+
throw new Error(`Document "${docId}" does not contain image data`);
|
|
985
|
+
}
|
|
986
|
+
const mimeType = this.getMimeType(doc.name || doc.path || docId, doc.contentType);
|
|
987
|
+
return `data:${mimeType};base64,${doc.data}`;
|
|
988
|
+
}
|
|
989
|
+
getDocumentUrl(dbName, docId) {
|
|
990
|
+
return (`${this.getDbUrl(dbName)}/` +
|
|
991
|
+
`${encodeURIComponent(docId)}`);
|
|
992
|
+
}
|
|
993
|
+
getDbUrl(dbName) {
|
|
994
|
+
const normalizedDbName = dbName
|
|
995
|
+
?.trim()
|
|
996
|
+
.toLowerCase();
|
|
997
|
+
if (!normalizedDbName) {
|
|
998
|
+
throw new Error('Missing CouchDB database name');
|
|
999
|
+
}
|
|
1000
|
+
return (`${this.couchDbBaseUrl}/` +
|
|
1001
|
+
`${encodeURIComponent(normalizedDbName)}`);
|
|
1002
|
+
}
|
|
1003
|
+
async getReadHeaders() {
|
|
1004
|
+
return {
|
|
1005
|
+
Accept: 'application/json',
|
|
1006
|
+
...(await this.buildTenantHeaders()),
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
async buildTenantHeaders() {
|
|
1010
|
+
const tenantKey = await this.utilityService.getStorage('tenantKey');
|
|
1011
|
+
const tenantName = await this.utilityService.getStorage('tenantName');
|
|
1012
|
+
const selectedTenant = await this.utilityService.getLocalStorage('selectedTenant');
|
|
1013
|
+
if (typeof tenantKey === 'string' &&
|
|
1014
|
+
tenantKey.trim()) {
|
|
1015
|
+
return {
|
|
1016
|
+
'X-Tenant-Key': tenantKey.trim(),
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
const normalizedTenantName = typeof tenantName === 'string'
|
|
1020
|
+
? tenantName.trim()
|
|
1021
|
+
: '';
|
|
1022
|
+
const normalizedSelectedTenant = typeof selectedTenant === 'string'
|
|
1023
|
+
? selectedTenant.trim()
|
|
1024
|
+
: '';
|
|
1025
|
+
const tenant = normalizedTenantName ||
|
|
1026
|
+
normalizedSelectedTenant;
|
|
1027
|
+
if (!tenant) {
|
|
1028
|
+
throw new Error('Missing tenantKey, tenantName, or selectedTenant');
|
|
1029
|
+
}
|
|
1030
|
+
return {
|
|
1031
|
+
'X-Tenant': tenant,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
async parseJsonOrText(response) {
|
|
1035
|
+
const rawText = await response.text();
|
|
1036
|
+
if (!rawText) {
|
|
955
1037
|
return null;
|
|
956
1038
|
}
|
|
1039
|
+
try {
|
|
1040
|
+
return JSON.parse(rawText);
|
|
1041
|
+
}
|
|
1042
|
+
catch {
|
|
1043
|
+
return rawText;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
stringifyErrorDetails(data) {
|
|
1047
|
+
if (typeof data === 'string') {
|
|
1048
|
+
return data;
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
return JSON.stringify(data);
|
|
1052
|
+
}
|
|
1053
|
+
catch {
|
|
1054
|
+
return 'Unknown error';
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
fromBase64(base64) {
|
|
1058
|
+
try {
|
|
1059
|
+
const binaryString = atob(base64);
|
|
1060
|
+
const bytes = Uint8Array.from(binaryString, character => character.charCodeAt(0));
|
|
1061
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
1062
|
+
}
|
|
1063
|
+
catch {
|
|
1064
|
+
return atob(base64);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
isJsonFile(fileNameOrPath, contentType) {
|
|
1068
|
+
const lower = (fileNameOrPath || '').toLowerCase();
|
|
1069
|
+
return (lower.endsWith('.json') ||
|
|
1070
|
+
contentType === 'application/json');
|
|
957
1071
|
}
|
|
958
1072
|
isBinaryMediaFile(fileNameOrPath, contentType) {
|
|
959
1073
|
const lower = (fileNameOrPath || '').toLowerCase();
|
|
960
|
-
const type = contentType || '';
|
|
1074
|
+
const type = (contentType || '').toLowerCase();
|
|
961
1075
|
return (type.startsWith('image/') ||
|
|
962
1076
|
type.startsWith('video/') ||
|
|
963
1077
|
type.startsWith('audio/') ||
|
|
@@ -977,111 +1091,48 @@ class Couchdb {
|
|
|
977
1091
|
lower.endsWith('.pdf'));
|
|
978
1092
|
}
|
|
979
1093
|
getMimeType(fileNameOrPath, contentType) {
|
|
980
|
-
if (contentType)
|
|
1094
|
+
if (contentType) {
|
|
981
1095
|
return contentType;
|
|
1096
|
+
}
|
|
982
1097
|
const lower = (fileNameOrPath || '').toLowerCase();
|
|
983
|
-
if (lower.endsWith('.png'))
|
|
1098
|
+
if (lower.endsWith('.png')) {
|
|
984
1099
|
return 'image/png';
|
|
985
|
-
|
|
1100
|
+
}
|
|
1101
|
+
if (lower.endsWith('.jpg') ||
|
|
1102
|
+
lower.endsWith('.jpeg')) {
|
|
986
1103
|
return 'image/jpeg';
|
|
987
|
-
|
|
1104
|
+
}
|
|
1105
|
+
if (lower.endsWith('.ico')) {
|
|
988
1106
|
return 'image/x-icon';
|
|
989
|
-
|
|
1107
|
+
}
|
|
1108
|
+
if (lower.endsWith('.svg')) {
|
|
990
1109
|
return 'image/svg+xml';
|
|
991
|
-
|
|
1110
|
+
}
|
|
1111
|
+
if (lower.endsWith('.webp')) {
|
|
992
1112
|
return 'image/webp';
|
|
993
|
-
|
|
1113
|
+
}
|
|
1114
|
+
if (lower.endsWith('.gif')) {
|
|
994
1115
|
return 'image/gif';
|
|
995
|
-
if (lower.endsWith('.mp4'))
|
|
996
|
-
return 'video/mp4';
|
|
997
|
-
if (lower.endsWith('.webm'))
|
|
998
|
-
return 'video/webm';
|
|
999
|
-
if (lower.endsWith('.mp3'))
|
|
1000
|
-
return 'audio/mpeg';
|
|
1001
|
-
if (lower.endsWith('.wav'))
|
|
1002
|
-
return 'audio/wav';
|
|
1003
|
-
if (lower.endsWith('.m4a'))
|
|
1004
|
-
return 'audio/mp4';
|
|
1005
|
-
if (lower.endsWith('.pdf'))
|
|
1006
|
-
return 'application/pdf';
|
|
1007
|
-
return 'application/octet-stream';
|
|
1008
|
-
}
|
|
1009
|
-
getImageMimeType(fileNameOrPath) {
|
|
1010
|
-
const lower = (fileNameOrPath || '').toLowerCase();
|
|
1011
|
-
if (lower.endsWith('.png'))
|
|
1012
|
-
return 'image/png';
|
|
1013
|
-
if (lower.endsWith('.jpg') || lower.endsWith('.jpeg'))
|
|
1014
|
-
return 'image/jpeg';
|
|
1015
|
-
if (lower.endsWith('.ico'))
|
|
1016
|
-
return 'image/x-icon';
|
|
1017
|
-
if (lower.endsWith('.svg'))
|
|
1018
|
-
return 'image/svg+xml';
|
|
1019
|
-
return 'image/png';
|
|
1020
|
-
}
|
|
1021
|
-
fromBase64(base64) {
|
|
1022
|
-
try {
|
|
1023
|
-
return decodeURIComponent(escape(atob(base64)));
|
|
1024
1116
|
}
|
|
1025
|
-
|
|
1026
|
-
return
|
|
1117
|
+
if (lower.endsWith('.mp4')) {
|
|
1118
|
+
return 'video/mp4';
|
|
1027
1119
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
const lower = (fileNameOrPath || '').toLowerCase();
|
|
1031
|
-
return lower.endsWith('.json') || contentType === 'application/json';
|
|
1032
|
-
}
|
|
1033
|
-
getDocumentUrl(dbName, docId) {
|
|
1034
|
-
return `${this.getDbUrl(dbName)}/${encodeURIComponent(docId)}`;
|
|
1035
|
-
}
|
|
1036
|
-
getDbUrl(dbName) {
|
|
1037
|
-
return `${this.couchDbBaseUrl}/${dbName}`;
|
|
1038
|
-
}
|
|
1039
|
-
async getReadHeaders() {
|
|
1040
|
-
return {
|
|
1041
|
-
Accept: 'application/json',
|
|
1042
|
-
...(await this.buildTenantHeaders()),
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
async buildTenantHeaders() {
|
|
1046
|
-
const tenantKey = await this.utilityService.getStorage('tenantKey');
|
|
1047
|
-
const tenantName = await this.utilityService.getStorage('tenantName');
|
|
1048
|
-
const selectedTenant = await this.utilityService.getLocalStorage('selectedTenant');
|
|
1049
|
-
if (tenantKey) {
|
|
1050
|
-
if (typeof tenantKey !== 'string' || !tenantKey.trim()) {
|
|
1051
|
-
throw new Error('Missing tenantKey');
|
|
1052
|
-
}
|
|
1053
|
-
return {
|
|
1054
|
-
'X-Tenant-Key': tenantKey.trim(),
|
|
1055
|
-
};
|
|
1120
|
+
if (lower.endsWith('.webm')) {
|
|
1121
|
+
return 'video/webm';
|
|
1056
1122
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
throw new Error('Missing tenantName');
|
|
1060
|
-
}
|
|
1061
|
-
return {
|
|
1062
|
-
'X-Tenant': tenantName ? tenantName.trim() : selectedTenant.trim(),
|
|
1063
|
-
};
|
|
1123
|
+
if (lower.endsWith('.mp3')) {
|
|
1124
|
+
return 'audio/mpeg';
|
|
1064
1125
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const dbName = couchDbName?.toLowerCase();
|
|
1068
|
-
const docId = filePath?.trim();
|
|
1069
|
-
if (!docId) {
|
|
1070
|
-
throw new Error('Missing file path');
|
|
1126
|
+
if (lower.endsWith('.wav')) {
|
|
1127
|
+
return 'audio/wav';
|
|
1071
1128
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
headers: await this.getReadHeaders(),
|
|
1075
|
-
});
|
|
1076
|
-
const doc = await this.parseJsonOrText(response);
|
|
1077
|
-
if (!response.ok) {
|
|
1078
|
-
throw new Error(`Failed to fetch image "${docId}" from "${dbName}": ${response.status} ${this.stringifyErrorDetails(doc)}`);
|
|
1129
|
+
if (lower.endsWith('.m4a')) {
|
|
1130
|
+
return 'audio/mp4';
|
|
1079
1131
|
}
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
1132
|
+
if (lower.endsWith('.pdf')) {
|
|
1133
|
+
return 'application/pdf';
|
|
1082
1134
|
}
|
|
1083
|
-
|
|
1084
|
-
return `data:${mimeType};base64,${doc.data}`;
|
|
1135
|
+
return 'application/octet-stream';
|
|
1085
1136
|
}
|
|
1086
1137
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: Couchdb, deps: [{ token: UtilityService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1087
1138
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: Couchdb, providedIn: 'root' });
|