web/satellite: type + lint files store

This change makes the files store fully type-safe. It builds with `npm run build` and lints with `npm run lint`, displaying no errors or warnings.

There was an issue where I was unable to use the newer web APIs for filesystem operations, I think TypesScript (and Vue?) may need to be updated - I already tried updating `@types/web`. To mitigate this, I added slim type definitions for only the parts we use. The definitions are exactly as they appear on MDN and even include links to the relevant documentation. In future they can be removed with no compatibility issues.

Change-Id: I7b8b4a5f95caabdb546157c65e9f2f42c5132a6f
This commit is contained in:
Monty Anderson 2022-02-15 18:18:19 +00:00 committed by Egon Elbre
parent 6294334de6
commit 2c83503712
6 changed files with 413 additions and 215 deletions

View File

@ -41,6 +41,7 @@
"@babel/core": "7.14.8",
"@babel/plugin-proposal-object-rest-spread": "7.11.0",
"@types/chart.js": "2.9.34",
"@types/filesystem": "^0.0.32",
"@types/node": "13.11.1",
"@types/pbkdf2": "3.1.0",
"@types/qrcode": "1.4.1",
@ -4073,6 +4074,21 @@
"@types/range-parser": "*"
}
},
"node_modules/@types/filesystem": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz",
"integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==",
"dev": true,
"dependencies": {
"@types/filewriter": "*"
}
},
"node_modules/@types/filewriter": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
"integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==",
"dev": true
},
"node_modules/@types/glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz",
@ -33794,6 +33810,21 @@
"@types/range-parser": "*"
}
},
"@types/filesystem": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz",
"integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==",
"dev": true,
"requires": {
"@types/filewriter": "*"
}
},
"@types/filewriter": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
"integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==",
"dev": true
},
"@types/glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz",
@ -40085,8 +40116,7 @@
},
"eslint-plugin-storj": {
"version": "git+https://git@github.com/storj/eslint-storj.git#5f952ffab7141e752cc095e5f024c39bab89679f",
"dev": true,
"from": "eslint-plugin-storj@https://github.com/storj/eslint-storj"
"dev": true
},
"eslint-plugin-vue": {
"version": "7.16.0",

View File

@ -14,8 +14,8 @@
},
"dependencies": {
"@aws-crypto/sha256-browser": "2.0.1",
"@aws-sdk/types": "3.47.1",
"@aws-sdk/signature-v4": "3.47.2",
"@aws-sdk/types": "3.47.1",
"apollo-cache-inmemory": "1.6.6",
"apollo-client": "2.6.10",
"apollo-link": "1.2.14",
@ -46,6 +46,7 @@
"@babel/core": "7.14.8",
"@babel/plugin-proposal-object-rest-spread": "7.11.0",
"@types/chart.js": "2.9.34",
"@types/filesystem": "0.0.32",
"@types/node": "13.11.1",
"@types/pbkdf2": "3.1.0",
"@types/qrcode": "1.4.1",

View File

@ -22,7 +22,7 @@ import { makeProjectsModule, PROJECTS_MUTATIONS, ProjectsState } from '@/store/m
import { makeUsersModule } from '@/store/modules/users';
import { User } from '@/types/users';
import files from '@/store/modules/files';
import { FilesState, makeFilesModule } from '@/store/modules/files';
Vue.use(Vuex);
@ -62,7 +62,7 @@ export interface ModulesState {
usersModule: User;
projectsModule: ProjectsState;
objectsModule: ObjectsState;
files: any; // eslint-disable-line @typescript-eslint/no-explicit-any
files: FilesState;
}
// Satellite store (vuex)
@ -77,7 +77,7 @@ export const store = new Vuex.Store<ModulesState>({
projectsModule: makeProjectsModule(projectsApi),
bucketUsageModule: makeBucketsModule(bucketsApi),
objectsModule: makeObjectsModule(),
files,
files: makeFilesModule(),
},
});

View File

@ -1,19 +1,106 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
/* eslint-disable */
import { StoreModule } from "@/store";
import S3 from "aws-sdk/clients/s3";
import S3, { CommonPrefix } from "aws-sdk/clients/s3";
const listCache = new Map();
interface BrowserObject {
Key: string;
Size: number;
LastModified: number;
type Promisable<T> = T | PromiseLike<T>;
type BrowserObject = {
Key: string;
Size: number;
LastModified: number;
type?: "file" | "folder";
progress?: number;
upload?: {
abort: () => void;
};
path?: string;
};
export type FilesState = {
s3: S3 | null;
accessKey: null | string;
path: string;
bucket: string;
browserRoot: string;
files: BrowserObject[];
uploadChain: Promise<void>;
uploading: BrowserObject[];
selectedAnchorFile: BrowserObject | null;
unselectedAnchorFile: null | string;
selectedFiles: BrowserObject[];
shiftSelectedFiles: BrowserObject[];
filesToBeDeleted: BrowserObject[];
fetchSharedLink: (arg0: string) => Promisable<string>;
fetchObjectPreview: (arg0: string) => Promisable<string>;
fetchObjectMap: (arg0) => Promisable<string>;
openedDropdown: null | string;
headingSorted: string;
orderBy: "asc" | "desc";
createFolderInputShow: boolean;
openModalOnFirstUpload: boolean;
modalPath: null | string;
fileShareModal: null | string;
};
type InitializedFilesState = FilesState & {
s3: S3;
};
function assertIsInitialized(
state: FilesState
): asserts state is InitializedFilesState {
if (state.s3 === null) {
throw new Error(
'FilesModule: S3 Client is uninitialized. "state.s3" is null.'
);
}
}
export default {
interface FilesContext {
state: FilesState;
commit: (string, ...unknown) => void;
dispatch: (string, ...unknown) => Promise<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
rootState: {
files: FilesState;
};
}
declare global {
interface FileSystemEntry {
// https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileEntry/file
file: (
successCallback: (arg0: File) => void,
errorCallback?: (arg0: Error) => void
) => void;
createReader: () => FileSystemDirectoryReader;
}
interface File {
// https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath
webkitRelativePath: string;
}
interface FileSystemDirectoryReader {
// https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryReader/readEntries
readEntries: (
successCallback: (arg0: FileSystemEntry[]) => void,
errorCallback?: (arg0: Error) => void
) => void;
}
}
type FilesModule = StoreModule<FilesState, FilesContext> & { namespaced: true };
export const makeFilesModule = (): FilesModule => ({
namespaced: true,
state: {
s3: null,
@ -30,9 +117,10 @@ export default {
selectedFiles: [],
shiftSelectedFiles: [],
filesToBeDeleted: [],
fetchSharedLink: null,
fetchObjectMap: null,
fetchObjectPreview: null,
fetchSharedLink: () => "javascript:null",
fetchObjectMap: () => "javascript:null",
fetchObjectPreview: () =>
"https://link.us1.storjshare.io/s/jx7t2i4lky36b3pomls6upakdzba/filebrowser%2Fsto-1.jpeg?map=1",
openedDropdown: null,
headingSorted: "name",
orderBy: "asc",
@ -40,16 +128,18 @@ export default {
openModalOnFirstUpload: false,
modalPath: null,
fileShareModal: null
fileShareModal: null,
},
getters: {
sortedFiles: (state) => {
sortedFiles: (state: FilesState) => {
// key-specific sort cases
const fns = {
date: (a: BrowserObject, b: BrowserObject): number =>
(new Date(a.LastModified)).getTime() - (new Date(b.LastModified)).getTime(),
name: (a: BrowserObject, b: BrowserObject): number => a.Key.localeCompare(b.Key),
size: (a: BrowserObject, b: BrowserObject): number => a.Size - b.Size
new Date(a.LastModified).getTime() -
new Date(b.LastModified).getTime(),
name: (a: BrowserObject, b: BrowserObject): number =>
a.Key.localeCompare(b.Key),
size: (a: BrowserObject, b: BrowserObject): number => a.Size - b.Size,
};
// TODO(performance): avoid several passes over the slice.
@ -58,24 +148,24 @@ export default {
const sortedFiles = state.files.slice();
sortedFiles.sort(fns[state.headingSorted]);
// reverse if descending order
if(state.orderBy !== "asc") {
if (state.orderBy !== "asc") {
sortedFiles.reverse();
}
// display folders and then files
const groupedFiles = [
...sortedFiles.filter((file) => file.type === "folder"),
...sortedFiles.filter((file) => file.type === "file")
...sortedFiles.filter((file) => file.type === "file"),
];
return groupedFiles;
},
isInitialized: (state) => state.s3 !== null
isInitialized: (state: FilesState) => state.s3 !== null,
},
mutations: {
init(
state,
state: FilesState,
{
accessKey,
secretKey,
@ -94,8 +184,18 @@ export default {
),
1000
)
)
}
),
}: {
accessKey: string;
secretKey: string;
bucket: string;
endpoint: string;
browserRoot: string;
openModalOnFirstUpload: boolean;
fetchSharedLink: (arg0: string) => Promisable<string>;
fetchObjectPreview: (arg0: string) => Promisable<string>;
fetchObjectMap: (arg0) => Promisable<string>;
}
) {
const s3Config = {
accessKeyId: accessKey,
@ -104,7 +204,7 @@ export default {
s3ForcePathStyle: true,
signatureVersion: "v4",
connectTimeout: 0,
httpOptions: { timeout: 0 }
httpOptions: { timeout: 0 },
};
state.s3 = new S3(s3Config);
@ -118,149 +218,185 @@ export default {
state.path = "";
},
updateFiles(state, { path, files }) {
updateFiles(state: FilesState, { path, files }) {
state.path = path;
state.files = files;
},
setSelectedFiles(state, files) {
setSelectedFiles(state: FilesState, files) {
state.selectedFiles = files;
},
setSelectedAnchorFile(state, file) {
setSelectedAnchorFile(state: FilesState, file) {
state.selectedAnchorFile = file;
},
setUnselectedAnchorFile(state, file) {
setUnselectedAnchorFile(state: FilesState, file) {
state.unselectedAnchorFile = file;
},
setFilesToBeDeleted(state, files) {
setFilesToBeDeleted(state: FilesState, files) {
state.filesToBeDeleted = [...state.filesToBeDeleted, ...files];
},
removeFileToBeDeleted(state, file) {
removeFileToBeDeleted(state: FilesState, file) {
state.filesToBeDeleted = state.filesToBeDeleted.filter(
(singleFile) => singleFile.Key !== file.Key
);
},
removeAllFilesToBeDeleted(state) {
removeAllFilesToBeDeleted(state: FilesState) {
state.filesToBeDeleted = [];
},
removeAllSelectedFiles(state) {
removeAllSelectedFiles(state: FilesState) {
state.selectedAnchorFile = null;
state.unselectedAnchorFile = null;
state.shiftSelectedFiles = [];
state.selectedFiles = [];
},
setShiftSelectedFiles(state, files) {
setShiftSelectedFiles(state: FilesState, files) {
state.shiftSelectedFiles = files;
},
pushUpload(state, file) {
pushUpload(state: FilesState, file) {
state.uploading.push(file);
},
setProgress(state, { Key, progress }) {
state.uploading.find((file) => file.Key === Key).progress =
progress;
setProgress(state: FilesState, { Key, progress }) {
const file = state.uploading.find((file) => file.Key === Key);
if (file === undefined) {
throw new Error(`No file found with key ${JSON.stringify(Key)}`);
}
file.progress = progress;
},
finishUpload(state, Key) {
state.uploading = state.uploading.filter(
(file) => file.Key !== Key
);
finishUpload(state: FilesState, Key) {
state.uploading = state.uploading.filter((file) => file.Key !== Key);
},
setOpenedDropdown(state, id) {
setOpenedDropdown(state: FilesState, id) {
state.openedDropdown = id;
},
sort(state, headingSorted) {
sort(state: FilesState, headingSorted) {
const flip = (orderBy) => (orderBy === "asc" ? "desc" : "asc");
state.orderBy =
state.headingSorted === headingSorted
? flip(state.orderBy)
: "asc";
state.headingSorted === headingSorted ? flip(state.orderBy) : "asc";
state.headingSorted = headingSorted;
},
setCreateFolderInputShow(state, value) {
setCreateFolderInputShow(state: FilesState, value) {
state.createFolderInputShow = value;
},
openModal(state, path) {
openModal(state: FilesState, path) {
state.modalPath = path;
},
closeModal(state) {
closeModal(state: FilesState) {
state.modalPath = null;
},
setFileShareModal(state, path) {
setFileShareModal(state: FilesState, path) {
state.fileShareModal = path;
},
closeFileShareModal(state) {
closeFileShareModal(state: FilesState) {
state.fileShareModal = null;
},
addUploadToChain(state, fn) {
addUploadToChain(state: FilesState, fn) {
state.uploadChain = state.uploadChain.then(fn);
}
},
},
actions: {
async list({ commit, state }, path = state.path) {
if (listCache.has(path) === true) {
commit("updateFiles", {
path,
files: listCache.get(path)
files: listCache.get(path),
});
}
assertIsInitialized(state);
const response = await state.s3
.listObjects({
Bucket: state.bucket,
Delimiter: "/",
Prefix: path
Prefix: path,
})
.promise();
const { Contents, CommonPrefixes } = response;
Contents.sort((a, b) =>
a.LastModified < b.LastModified ? -1 : -1
);
if (Contents === undefined) {
throw new Error('Bad S3 listObjects() response: "Contents" undefined');
}
const prefixToFolder = ({ Prefix }) => ({
Key: Prefix.slice(path.length, -1),
LastModified: new Date(0),
type: "folder"
if (CommonPrefixes === undefined) {
throw new Error(
'Bad S3 listObjects() response: "CommonPrefixes" undefined'
);
}
Contents.sort((a, b) => {
if (
a === undefined ||
a.LastModified === undefined ||
b === undefined ||
b.LastModified === undefined ||
a.LastModified === b.LastModified
) {
return 0;
}
return a.LastModified < b.LastModified ? -1 : 1;
});
const makeFileRelative = (file) => ({
...file,
Key: file.Key.slice(path.length),
type: "file"
});
type DefinedCommonPrefix = CommonPrefix & {
Prefix: string;
};
const isFileVisible = (file) =>
file.Key.length > 0 && file.Key !== ".file_placeholder";
const isPrefixDefined = (
value: CommonPrefix
): value is DefinedCommonPrefix => value.Prefix !== undefined;
const files = [
...CommonPrefixes.map(prefixToFolder),
...Contents.map(makeFileRelative).filter(isFileVisible)
];
const prefixToFolder = ({
Prefix,
}: {
Prefix: string;
}): BrowserObject => ({
Key: Prefix.slice(path.length, -1),
LastModified: 0,
Size: 0,
type: "folder",
});
listCache.set(path, files);
commit("updateFiles", {
path,
files
});
const makeFileRelative = (file) => ({
...file,
Key: file.Key.slice(path.length),
type: "file",
});
const isFileVisible = (file) =>
file.Key.length > 0 && file.Key !== ".file_placeholder";
const files = [
...CommonPrefixes.filter(isPrefixDefined).map(prefixToFolder),
...Contents.map(makeFileRelative).filter(isFileVisible),
];
listCache.set(path, files);
commit("updateFiles", {
path,
files,
});
},
async back({ state, dispatch }) {
@ -277,156 +413,164 @@ export default {
dispatch("list", getParentDirectory(state.path));
},
async upload({ commit, state, dispatch }, e) {
const items = e.dataTransfer
? e.dataTransfer.items
: e.target.files;
async upload({ commit, state, dispatch }, e: DragEvent) {
assertIsInitialized(state);
async function* traverse(item, path = "") {
if (item.isFile) {
const file = await new Promise(item.file.bind(item));
yield { path, file };
} else if (item instanceof File) {
let relativePath = (item as any).webkitRelativePath.split("/").slice(0, -1).join("/");
type Item = DataTransferItem | FileSystemEntry;
if (relativePath.length) {
relativePath += "/";
}
const items: Item[] = e.dataTransfer
? [...e.dataTransfer.items]
: e.target !== null
? ((e.target as unknown) as { files: FileSystemEntry[] }).files
: [];
yield { path: relativePath, file: item };
} else if (item.isDirectory) {
const dirReader = item.createReader();
async function* traverse(item: Item | Item[], path = "") {
if ('isFile' in item && item.isFile === true) {
const file = await new Promise(item.file.bind(item));
yield { path, file };
} else if (item instanceof File) {
let relativePath = item.webkitRelativePath
.split("/")
.slice(0, -1)
.join("/");
const entries = await new Promise(
dirReader.readEntries.bind(dirReader)
) as any[];
for (const entry of entries) {
yield* traverse(entry, path + item.name + "/");
}
} else if (typeof item.length === "number") {
for (const i of item) {
yield* traverse(i);
}
} else {
throw new Error("Item is not directory or file");
}
}
if (relativePath.length) {
relativePath += "/";
}
const iterator =
items instanceof FileList
? [...items]
: [...items].map(
(item) =>
item.webkitGetAsEntry() || item.getAsEntry()
);
yield { path: relativePath, file: item };
} else if ('isFile' in item && item.isDirectory) {
const dirReader = item.createReader();
const fileNames = state.files.map((file) => file.Key);
const entries = await new Promise(
dirReader.readEntries.bind(dirReader)
);
function getUniqueFileName(fileName) {
for (let count = 1; fileNames.includes(fileName); count++) {
if (count > 1) {
fileName = fileName.replace(
/\((\d+)\)(.*)/,
`(${count})$2`
);
} else {
fileName = fileName.replace(
/([^.]*)(.*)/,
`$1 (${count})$2`
);
}
}
for (const entry of entries) {
yield* traverse(
(entry as FileSystemEntry) as Item,
path + item.name + "/"
);
}
} else if ("length" in item && typeof item.length === "number") {
for (const i of item) {
yield* traverse(i);
}
} else {
throw new Error("Item is not directory or file");
}
}
return fileName;
}
const isFileSystemEntry = (
a: FileSystemEntry | null
): a is FileSystemEntry => a !== null;
for await (const { path, file } of traverse(iterator)) {
const directories = path.split("/");
const uniqueFirstDirectory = getUniqueFileName(directories[0]);
directories[0] = uniqueFirstDirectory;
const iterator = items
.map((item) =>
"webkitGetAsEntry" in item ? item.webkitGetAsEntry() : item
)
.filter(isFileSystemEntry) as FileSystemEntry[];
const fileName = getUniqueFileName(
directories.join("/") + file.name
);
const fileNames = state.files.map((file) => file.Key);
const params = {
Bucket: state.bucket,
Key: state.path + fileName,
Body: file
};
function getUniqueFileName(fileName) {
for (let count = 1; fileNames.includes(fileName); count++) {
if (count > 1) {
fileName = fileName.replace(/\((\d+)\)(.*)/, `(${count})$2`);
} else {
fileName = fileName.replace(/([^.]*)(.*)/, `$1 (${count})$2`);
}
}
const upload = state.s3.upload(
{ ...params },
{ partSize: 64 * 1024 * 1024 }
);
return fileName;
}
upload.on("httpUploadProgress", (progress) => {
commit("setProgress", {
Key: params.Key,
progress: Math.round(
(progress.loaded / progress.total) * 100
)
});
});
for await (const { path, file } of traverse(iterator)) {
const directories = path.split("/");
const uniqueFirstDirectory = getUniqueFileName(directories[0]);
directories[0] = uniqueFirstDirectory;
commit("pushUpload", {
...params,
upload,
progress: 0
});
const fileName = getUniqueFileName(directories.join("/") + file.name);
commit("addUploadToChain", async () => {
if (
state.uploading.findIndex(
(file) => file.Key === params.Key
) === -1
) {
// upload cancelled or removed
return -1;
}
const params = {
Bucket: state.bucket,
Key: state.path + fileName,
Body: file,
};
try {
await upload.promise();
} catch (e) {
// An error is raised if the upload is aborted by the user
console.log(e);
}
const upload = state.s3.upload(
{ ...params },
{ partSize: 64 * 1024 * 1024 }
);
await dispatch("list");
upload.on("httpUploadProgress", (progress) => {
commit("setProgress", {
Key: params.Key,
progress: Math.round((progress.loaded / progress.total) * 100),
});
});
const uploadedFiles = state.files.filter(
(file) => file.type === "file"
);
commit("pushUpload", {
...params,
upload,
progress: 0,
});
if (uploadedFiles.length === 1) {
const [{ Key }] = uploadedFiles;
commit("addUploadToChain", async () => {
if (
state.uploading.findIndex((file) => file.Key === params.Key) === -1
) {
// upload cancelled or removed
return -1;
}
if (state.openModalOnFirstUpload === true) {
commit("openModal", params.Key);
}
}
try {
await upload.promise();
} catch (e) {
// An error is raised if the upload is aborted by the user
console.log(e);
}
commit("finishUpload", params.Key);
});
}
await dispatch("list");
const uploadedFiles = state.files.filter(
(file) => file.type === "file"
);
if (uploadedFiles.length === 1) {
if (state.openModalOnFirstUpload === true) {
commit("openModal", params.Key);
}
}
commit("finishUpload", params.Key);
});
}
},
async createFolder({ state, dispatch }, name) {
assertIsInitialized(state);
await state.s3
.putObject({
Bucket: state.bucket,
Key: state.path + name + "/.file_placeholder"
Key: state.path + name + "/.file_placeholder",
})
.promise();
dispatch("list");
},
async delete({ commit, dispatch, state }, { path, file, folder }) {
async delete(
{ commit, dispatch, state }: FilesContext,
{ path, file, folder }
) {
assertIsInitialized(state);
await state.s3
.deleteObject({
Bucket: state.bucket,
Key: path + file.Key
Key: path + file.Key,
})
.promise();
@ -437,23 +581,45 @@ export default {
},
async deleteFolder({ commit, dispatch, state }, { file, path }) {
assertIsInitialized(state);
async function recurse(filePath) {
assertIsInitialized(state);
const { Contents, CommonPrefixes } = await state.s3
.listObjects({
Bucket: state.bucket,
Delimiter: "/",
Prefix: filePath
Prefix: filePath,
})
.promise();
if (Contents === undefined) {
throw new Error(
'Bad S3 listObjects() response: "Contents" undefined'
);
}
if (CommonPrefixes === undefined) {
throw new Error(
'Bad S3 listObjects() response: "CommonPrefixes" undefined'
);
}
async function thread() {
if (Contents === undefined) {
throw new Error(
'Bad S3 listObjects() response: "Contents" undefined'
);
}
while (Contents.length) {
const file = Contents.pop();
await dispatch("delete", {
path: "",
file,
folder: true
folder: true,
});
}
}
@ -471,10 +637,10 @@ export default {
await dispatch("list");
},
async deleteSelected({ rootState, state, dispatch, commit }) {
async deleteSelected({ state, dispatch, commit }) {
const filesToDelete = [
...state.selectedFiles,
...state.shiftSelectedFiles
...state.shiftSelectedFiles,
];
if (state.selectedAnchorFile) {
@ -488,12 +654,12 @@ export default {
if (file.type === "file")
await dispatch("delete", {
file,
path: rootState.files.path
path: state.path,
});
else
await dispatch("deleteFolder", {
file,
path: rootState.files.path
path: state.path,
});
})
);
@ -502,12 +668,14 @@ export default {
},
async download({ state }, file) {
assertIsInitialized(state);
const url = state.s3.getSignedUrl("getObject", {
Bucket: state.bucket,
Key: state.path + file.Key
Key: state.path + file.Key,
});
const downloadURL = function (data, fileName) {
let a = document.createElement("a");
const downloadURL = function(data, fileName) {
const a = document.createElement("a");
a.href = data;
a.download = fileName;
a.click();
@ -559,8 +727,7 @@ export default {
const file = state.uploading.find((file) => file.Key === key);
if (typeof file === "object") {
// if the file has already started uploading, then abort
if (file.progress > 0) {
if (file.progress !== undefined && file.upload && file.progress > 0) {
file.upload.abort();
}
@ -586,6 +753,6 @@ export default {
if (state.selectedAnchorFile) {
dispatch("clearAllSelectedFiles");
}
}
}
};
},
},
});

View File

@ -6,6 +6,7 @@ import S3, { Bucket } from 'aws-sdk/clients/s3';
import { StoreModule } from '@/store';
import { EdgeCredentials } from '@/types/accessGrants';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { FilesState } from '@/store/modules/files';
export const OBJECTS_ACTIONS = {
CLEAR: 'clearObjects',
@ -63,10 +64,8 @@ interface ObjectsContext {
state: ObjectsState
commit: (string, ...unknown) => void
dispatch: (string, ...unknown) => Promise<any> // eslint-disable-line @typescript-eslint/no-explicit-any
rootState: {
files: {
uploading: []
}
rootState: {
files: FilesState
}
}

View File

@ -15,7 +15,8 @@
"strictPropertyInitialization": false,
"types": [
"webpack-env",
"jest"
"jest",
"filesystem"
],
"paths": {
"@/*": [