🍄 Separate trash for the personal and shared drive (#189)
This commit is contained in:
+1
-17
@@ -5,7 +5,7 @@ import { defer, Subject, throwError, timer } from "rxjs";
|
|||||||
import { concat, delayWhen, retryWhen, take, tap } from "rxjs/operators";
|
import { concat, delayWhen, retryWhen, take, tap } from "rxjs/operators";
|
||||||
import { UpsertOptions } from "..";
|
import { UpsertOptions } from "..";
|
||||||
import { logger } from "../../../../../../framework";
|
import { logger } from "../../../../../../framework";
|
||||||
import { getEntityDefinition, unwrapIndexes, unwrapPrimarykey } from "../../utils";
|
import { getEntityDefinition, unwrapPrimarykey } from "../../utils";
|
||||||
import { EntityDefinition, ColumnDefinition, ObjectType } from "../../types";
|
import { EntityDefinition, ColumnDefinition, ObjectType } from "../../types";
|
||||||
import { AbstractConnector } from "../abstract-connector";
|
import { AbstractConnector } from "../abstract-connector";
|
||||||
import {
|
import {
|
||||||
@@ -451,22 +451,6 @@ export class CassandraConnector extends AbstractConnector<
|
|||||||
const instance = new (entityType as any)();
|
const instance = new (entityType as any)();
|
||||||
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);
|
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);
|
||||||
|
|
||||||
const pk = unwrapPrimarykey(entityDefinition);
|
|
||||||
const indexes = unwrapIndexes(entityDefinition);
|
|
||||||
|
|
||||||
if (
|
|
||||||
Object.keys(filters).some(key => pk.indexOf(key) < 0) &&
|
|
||||||
Object.keys(filters).some(key => indexes.indexOf(key) < 0)
|
|
||||||
) {
|
|
||||||
//Filter not in primary key
|
|
||||||
throw new Error(
|
|
||||||
`All filter parameters must be defined in entity primary key,
|
|
||||||
got: ${JSON.stringify(Object.keys(filters))}
|
|
||||||
on table ${entityDefinition.name} but pk is ${JSON.stringify(pk)},
|
|
||||||
instance was ${JSON.stringify(instance)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = buildSelectQuery<Table>(
|
const query = buildSelectQuery<Table>(
|
||||||
entityType as unknown as ObjectType<Table>,
|
entityType as unknown as ObjectType<Table>,
|
||||||
filters,
|
filters,
|
||||||
|
|||||||
+7
-2
@@ -1,6 +1,6 @@
|
|||||||
import { FindOptions } from "../../repository/repository";
|
import { FindOptions } from "../../repository/repository";
|
||||||
import { ObjectType } from "../../types";
|
import { ObjectType } from "../../types";
|
||||||
import { getEntityDefinition, secureOperators } from "../../utils";
|
import { getEntityDefinition, secureOperators, filteringRequired } from "../../utils";
|
||||||
import { transformValueToDbString } from "./typeTransforms";
|
import { transformValueToDbString } from "./typeTransforms";
|
||||||
|
|
||||||
export function buildSelectQuery<Entity>(
|
export function buildSelectQuery<Entity>(
|
||||||
@@ -17,12 +17,17 @@ export function buildSelectQuery<Entity>(
|
|||||||
): string {
|
): string {
|
||||||
const instance = new (entityType as any)();
|
const instance = new (entityType as any)();
|
||||||
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);
|
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);
|
||||||
|
let allowFiltering = false;
|
||||||
|
|
||||||
const where = Object.keys(filters)
|
const where = Object.keys(filters)
|
||||||
.map(key => {
|
.map(key => {
|
||||||
let result: string;
|
let result: string;
|
||||||
const filter = filters[key];
|
const filter = filters[key];
|
||||||
|
|
||||||
|
if (filteringRequired(key)) {
|
||||||
|
allowFiltering = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,7 +82,7 @@ export function buildSelectQuery<Entity>(
|
|||||||
whereClause.trim().length ? "WHERE " + whereClause : ""
|
whereClause.trim().length ? "WHERE " + whereClause : ""
|
||||||
} ${orderByClause.trim().length ? "ORDER BY " + orderByClause : ""}`
|
} ${orderByClause.trim().length ? "ORDER BY " + orderByClause : ""}`
|
||||||
.trimEnd()
|
.trimEnd()
|
||||||
.concat(";");
|
.concat(allowFiltering ? " ALLOW FILTERING;" : ";");
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-19
@@ -3,7 +3,7 @@ import { UpsertOptions } from "..";
|
|||||||
import { ListResult, Paginable, Pagination } from "../../../../../../framework/api/crud-service";
|
import { ListResult, Paginable, Pagination } from "../../../../../../framework/api/crud-service";
|
||||||
import { FindOptions } from "../../repository/repository";
|
import { FindOptions } from "../../repository/repository";
|
||||||
import { ColumnDefinition, EntityDefinition, ObjectType } from "../../types";
|
import { ColumnDefinition, EntityDefinition, ObjectType } from "../../types";
|
||||||
import { getEntityDefinition, unwrapIndexes, unwrapPrimarykey } from "../../utils";
|
import { getEntityDefinition, unwrapPrimarykey } from "../../utils";
|
||||||
import { AbstractConnector } from "../abstract-connector";
|
import { AbstractConnector } from "../abstract-connector";
|
||||||
import { buildSelectQuery } from "./query-builder";
|
import { buildSelectQuery } from "./query-builder";
|
||||||
import { transformValueFromDbString, transformValueToDbString } from "./typeTransforms";
|
import { transformValueFromDbString, transformValueToDbString } from "./typeTransforms";
|
||||||
@@ -186,24 +186,6 @@ export class MongoConnector extends AbstractConnector<MongoConnectionOptions, mo
|
|||||||
const instance = new (entityType as any)();
|
const instance = new (entityType as any)();
|
||||||
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);
|
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);
|
||||||
|
|
||||||
const pk = unwrapPrimarykey(entityDefinition);
|
|
||||||
|
|
||||||
const indexes = unwrapIndexes(entityDefinition);
|
|
||||||
if (
|
|
||||||
Object.keys(filters).some(key => pk.indexOf(key) < 0) &&
|
|
||||||
Object.keys(filters).some(key => indexes.indexOf(key) < 0)
|
|
||||||
) {
|
|
||||||
//Filter not in primary key
|
|
||||||
throw Error(
|
|
||||||
"All filter parameters must be defined in entity primary key, got: " +
|
|
||||||
JSON.stringify(Object.keys(filters)) +
|
|
||||||
" on table " +
|
|
||||||
entityDefinition.name +
|
|
||||||
" but pk is " +
|
|
||||||
JSON.stringify(pk),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = await this.getDatabase();
|
const db = await this.getDatabase();
|
||||||
const collection = db.collection(`${entityDefinition.name}`);
|
const collection = db.collection(`${entityDefinition.name}`);
|
||||||
|
|
||||||
|
|||||||
@@ -106,3 +106,12 @@ export function toMongoDbOrderable(timeuuid?: string): string {
|
|||||||
const time_str = [uuid_arr[2], uuid_arr[1], uuid_arr[0], uuid_arr[3], uuid_arr[4]].join("-");
|
const time_str = [uuid_arr[2], uuid_arr[1], uuid_arr[0], uuid_arr[3], uuid_arr[4]].join("-");
|
||||||
return time_str;
|
return time_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if filtering is necessary
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean} Returns true if key is "is_in_trash" or "scope", otherwise returns false.
|
||||||
|
*/
|
||||||
|
export const filteringRequired = (key: string) => {
|
||||||
|
return key === "is_in_trash" || key === "scope";
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ import { FileVersion } from "./file-version";
|
|||||||
import search from "./drive-file.search";
|
import search from "./drive-file.search";
|
||||||
|
|
||||||
export const TYPE = "drive_files";
|
export const TYPE = "drive_files";
|
||||||
|
export type DriveScope = "personal" | "shared";
|
||||||
|
|
||||||
@Entity(TYPE, {
|
@Entity(TYPE, {
|
||||||
globalIndexes: [["company_id", "parent_id"]],
|
globalIndexes: [
|
||||||
|
["company_id", "parent_id"],
|
||||||
|
["company_id", "is_in_trash"],
|
||||||
|
],
|
||||||
primaryKey: [["company_id"], "id"],
|
primaryKey: [["company_id"], "id"],
|
||||||
type: TYPE,
|
type: TYPE,
|
||||||
search,
|
search,
|
||||||
@@ -73,6 +77,10 @@ export class DriveFile {
|
|||||||
|
|
||||||
@Column("last_version_cache", "encoded_json")
|
@Column("last_version_cache", "encoded_json")
|
||||||
last_version_cache: Partial<FileVersion>;
|
last_version_cache: Partial<FileVersion>;
|
||||||
|
|
||||||
|
@Type(() => String)
|
||||||
|
@Column("scope", "string")
|
||||||
|
scope: DriveScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccessInformation = {
|
export type AccessInformation = {
|
||||||
|
|||||||
@@ -169,28 +169,61 @@ export const getAccessLevel = async (
|
|||||||
repository: Repository<DriveFile>,
|
repository: Repository<DriveFile>,
|
||||||
context: CompanyExecutionContext & { public_token?: string; tdrive_tab_token?: string },
|
context: CompanyExecutionContext & { public_token?: string; tdrive_tab_token?: string },
|
||||||
): Promise<DriveFileAccessLevel | "none"> => {
|
): Promise<DriveFileAccessLevel | "none"> => {
|
||||||
|
const isAdmin = !context?.user?.id || (await isCompanyAdmin(context));
|
||||||
const isMember = !context?.user?.id || (await isCompanyMember(context));
|
const isMember = !context?.user?.id || (await isCompanyMember(context));
|
||||||
|
|
||||||
if (!id || id === "root") {
|
if (!id || id === "root") {
|
||||||
if (!context?.user?.id || (await isCompanyGuest(context))) {
|
if (!context?.user?.id || (await isCompanyGuest(context))) {
|
||||||
return "none";
|
return "none";
|
||||||
} else {
|
|
||||||
if (isMember) return "read";
|
|
||||||
return "manage";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (id === "trash")
|
|
||||||
return (await isCompanyGuest(context)) || !context?.user?.id
|
if (id.startsWith("user_")) {
|
||||||
? "none"
|
if (await isCompanyApplication(context)) return "read";
|
||||||
: (await isCompanyAdmin(context))
|
}
|
||||||
? "manage"
|
|
||||||
: "write";
|
if (context?.user?.id) {
|
||||||
|
/**
|
||||||
|
* Drive root directories access management
|
||||||
|
*/
|
||||||
|
if (id === "user_" + context.user?.id) return "manage";
|
||||||
|
if (id === "trash_" + context.user?.id) return "manage";
|
||||||
|
|
||||||
|
if (id === "root") {
|
||||||
|
if (isAdmin) return "manage";
|
||||||
|
|
||||||
|
return "read";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id === "trash") {
|
||||||
|
if (isAdmin) return "manage";
|
||||||
|
|
||||||
|
return "read";
|
||||||
|
}
|
||||||
|
|
||||||
if (id === "shared_with_me") return "read";
|
if (id === "shared_with_me") return "read";
|
||||||
|
|
||||||
//If it is my personal folder, I have full access
|
/**
|
||||||
if (id.startsWith("user_")) {
|
* Entity based access management
|
||||||
if (id === "user_" + context.user?.id) return "manage";
|
*/
|
||||||
if (await isCompanyApplication(context)) return "manage";
|
|
||||||
return "none";
|
if (!item) {
|
||||||
|
item = await repository.findOne({
|
||||||
|
id,
|
||||||
|
company_id: context.company.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
throw Error("Drive item doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.scope === "personal" && item.creator == context.user.id) return "manage";
|
||||||
|
|
||||||
|
if (item.scope === "shared") {
|
||||||
|
if (isAdmin) return "manage";
|
||||||
|
if (isMember) return "read";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let publicToken = context.public_token;
|
let publicToken = context.public_token;
|
||||||
@@ -208,13 +241,6 @@ export const getAccessLevel = async (
|
|||||||
throw Error("Drive item doesn't exist");
|
throw Error("Drive item doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await isCompanyApplication(context)) {
|
|
||||||
if (!id.startsWith("user_") && isMember && item.creator != context.user.id) {
|
|
||||||
return "read";
|
|
||||||
}
|
|
||||||
return "manage";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Specific user or channel rule is applied first. Then less restrictive level will be chosen
|
* Specific user or channel rule is applied first. Then less restrictive level will be chosen
|
||||||
* between the parent folder and company accesses.
|
* between the parent folder and company accesses.
|
||||||
@@ -383,6 +409,30 @@ export const getSharedByUser = (
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getItemScope = async (
|
||||||
|
item: DriveFile | null,
|
||||||
|
repository: Repository<DriveFile>,
|
||||||
|
context: CompanyExecutionContext,
|
||||||
|
): Promise<"personal" | "shared"> => {
|
||||||
|
let scope: "personal" | "shared";
|
||||||
|
if (item.parent_id === "user_" + context.user?.id) {
|
||||||
|
scope = "personal";
|
||||||
|
} else if (item.parent_id === "root") {
|
||||||
|
scope = "shared";
|
||||||
|
} else {
|
||||||
|
const driveItemParent = await repository.findOne(
|
||||||
|
{
|
||||||
|
company_id: context.company.id,
|
||||||
|
id: item.parent_id,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
scope = driveItemParent.scope;
|
||||||
|
}
|
||||||
|
return scope;
|
||||||
|
};
|
||||||
|
|
||||||
const getGrantorAndThrowIfEmpty = (entity: AuthEntity) => {
|
const getGrantorAndThrowIfEmpty = (entity: AuthEntity) => {
|
||||||
if (!entity.grantor) {
|
if (!entity.grantor) {
|
||||||
logger.warn(`For the file permissions ${entity.id} grantor is not defined`);
|
logger.warn(`For the file permissions ${entity.id} grantor is not defined`);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export class DriveFileDTOBuilder {
|
|||||||
"access_info",
|
"access_info",
|
||||||
"content_keywords",
|
"content_keywords",
|
||||||
"creator",
|
"creator",
|
||||||
|
"scope",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -40,12 +40,14 @@ import {
|
|||||||
isSharedWithMeFolder,
|
isSharedWithMeFolder,
|
||||||
isVirtualFolder,
|
isVirtualFolder,
|
||||||
updateItemSize,
|
updateItemSize,
|
||||||
|
isInTrash,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import {
|
import {
|
||||||
checkAccess,
|
checkAccess,
|
||||||
getAccessLevel,
|
getAccessLevel,
|
||||||
hasAccessLevel,
|
hasAccessLevel,
|
||||||
makeStandaloneAccessLevel,
|
makeStandaloneAccessLevel,
|
||||||
|
getItemScope,
|
||||||
} from "./access-check";
|
} from "./access-check";
|
||||||
import { websocketEventBus } from "../../../core/platform/services/realtime/bus";
|
import { websocketEventBus } from "../../../core/platform/services/realtime/bus";
|
||||||
|
|
||||||
@@ -153,7 +155,6 @@ export class DocumentsService {
|
|||||||
throw Error("user does not have access to this item");
|
throw Error("user does not have access to this item");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
|
||||||
this.logger.error({ error: `${error}` }, "Failed to grant access to the drive item");
|
this.logger.error({ error: `${error}` }, "Failed to grant access to the drive item");
|
||||||
throw new CrudException("User does not have access to this item or its children", 401);
|
throw new CrudException("User does not have access to this item or its children", 401);
|
||||||
}
|
}
|
||||||
@@ -179,7 +180,22 @@ export class DocumentsService {
|
|||||||
await this.repository.find(
|
await this.repository.find(
|
||||||
{
|
{
|
||||||
company_id: context.company.id,
|
company_id: context.company.id,
|
||||||
|
...(id.includes(this.TRASH)
|
||||||
|
? {
|
||||||
|
is_in_trash: true,
|
||||||
|
...(id == this.TRASH
|
||||||
|
? {
|
||||||
|
scope: "shared",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
scope: "personal",
|
||||||
|
creator: context?.user?.id,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
: {
|
||||||
parent_id: id,
|
parent_id: id,
|
||||||
|
is_in_trash: false,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
context,
|
context,
|
||||||
@@ -257,6 +273,7 @@ export class DocumentsService {
|
|||||||
try {
|
try {
|
||||||
const driveItem = getDefaultDriveItem(content, context);
|
const driveItem = getDefaultDriveItem(content, context);
|
||||||
const driveItemVersion = getDefaultDriveItemVersion(version, context);
|
const driveItemVersion = getDefaultDriveItemVersion(version, context);
|
||||||
|
driveItem.scope = await getItemScope(driveItem, this.repository, context);
|
||||||
|
|
||||||
const hasAccess = await checkAccess(
|
const hasAccess = await checkAccess(
|
||||||
driveItem.parent_id,
|
driveItem.parent_id,
|
||||||
@@ -387,7 +404,7 @@ export class DocumentsService {
|
|||||||
throw Error("content mismatch");
|
throw Error("content mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatable = ["access_info", "name", "tags", "parent_id", "description"];
|
const updatable = ["access_info", "name", "tags", "parent_id", "description", "is_in_trash"];
|
||||||
|
|
||||||
for (const key of updatable) {
|
for (const key of updatable) {
|
||||||
if ((content as any)[key]) {
|
if ((content as any)[key]) {
|
||||||
@@ -434,6 +451,9 @@ export class DocumentsService {
|
|||||||
await updateItemSize(item.parent_id, this.repository, context);
|
await updateItemSize(item.parent_id, this.repository, context);
|
||||||
|
|
||||||
if (oldParent) {
|
if (oldParent) {
|
||||||
|
item.scope = await getItemScope(item, this.repository, context);
|
||||||
|
this.repository.save(item);
|
||||||
|
|
||||||
await updateItemSize(oldParent, this.repository, context);
|
await updateItemSize(oldParent, this.repository, context);
|
||||||
this.notifyWebsocket(oldParent, context);
|
this.notifyWebsocket(oldParent, context);
|
||||||
}
|
}
|
||||||
@@ -529,6 +549,7 @@ export class DocumentsService {
|
|||||||
|
|
||||||
const previousParentId = item.parent_id;
|
const previousParentId = item.parent_id;
|
||||||
if (
|
if (
|
||||||
|
(await isInTrash(item, this.repository, context)) ||
|
||||||
item.parent_id === this.TRASH ||
|
item.parent_id === this.TRASH ||
|
||||||
(await getPath(item.parent_id, this.repository, true, context))[0].id === this.TRASH
|
(await getPath(item.parent_id, this.repository, true, context))[0].id === this.TRASH
|
||||||
) {
|
) {
|
||||||
@@ -568,7 +589,7 @@ export class DocumentsService {
|
|||||||
await this.repository.remove(item);
|
await this.repository.remove(item);
|
||||||
} else {
|
} else {
|
||||||
//This item is not in trash, we move it to trash
|
//This item is not in trash, we move it to trash
|
||||||
item.parent_id = this.TRASH;
|
item.is_in_trash = true;
|
||||||
await this.update(item.id, item, context);
|
await this.update(item.id, item, context);
|
||||||
}
|
}
|
||||||
await updateItemSize(previousParentId, this.repository, context);
|
await updateItemSize(previousParentId, this.repository, context);
|
||||||
@@ -579,6 +600,61 @@ export class DocumentsService {
|
|||||||
await this.notifyWebsocket("trash", context);
|
await this.notifyWebsocket("trash", context);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* restore a Drive Document and its children
|
||||||
|
*
|
||||||
|
* @param {string} id - the item id
|
||||||
|
* @param {DriveExecutionContext} context - the execution context
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
restore = async (
|
||||||
|
id: string | RootType | TrashType,
|
||||||
|
item?: DriveFile,
|
||||||
|
context?: DriveExecutionContext,
|
||||||
|
): Promise<void> => {
|
||||||
|
if (!id) {
|
||||||
|
//We can't remove the root folder
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item =
|
||||||
|
item ||
|
||||||
|
(await this.repository.findOne({
|
||||||
|
company_id: context.company.id,
|
||||||
|
id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
this.logger.error("item to delete not found");
|
||||||
|
throw new CrudException("Drive item not found", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!(await checkAccess(item.id, item, "manage", this.repository, context))) {
|
||||||
|
this.logger.error("user does not have access drive item ", id);
|
||||||
|
throw Error("user does not have access to this item");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error({ error: `${error}` }, "Failed to grant access to the drive item");
|
||||||
|
throw new CrudException("User does not have access to this item or its children", 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInTrash(item, this.repository, context)) {
|
||||||
|
if (item.is_in_trash != true) {
|
||||||
|
if (item.scope === "personal") {
|
||||||
|
item.parent_id = "user_" + context.user.id;
|
||||||
|
} else {
|
||||||
|
item.parent_id = "root";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.is_in_trash = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.repository.save(item);
|
||||||
|
|
||||||
|
this.notifyWebsocket("trash", context);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Drive item version
|
* Create a Drive item version
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -30,7 +30,13 @@ const TRASH: TrashType = "trash";
|
|||||||
const SHARED_WITH_ME: SharedWithMeType = "shared_with_me";
|
const SHARED_WITH_ME: SharedWithMeType = "shared_with_me";
|
||||||
|
|
||||||
export const isVirtualFolder = (id: string) => {
|
export const isVirtualFolder = (id: string) => {
|
||||||
return id === ROOT || id === TRASH || id.startsWith("user_") || id == SHARED_WITH_ME;
|
return (
|
||||||
|
id === ROOT ||
|
||||||
|
id === TRASH ||
|
||||||
|
id.startsWith("trash_") ||
|
||||||
|
id.startsWith("user_") ||
|
||||||
|
id == SHARED_WITH_ME
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isSharedWithMeFolder = (id: string) => {
|
export const isSharedWithMeFolder = (id: string) => {
|
||||||
@@ -72,6 +78,7 @@ export const getDefaultDriveItem = (
|
|||||||
last_modified: new Date().getTime(),
|
last_modified: new Date().getTime(),
|
||||||
parent_id: item.parent_id || "root",
|
parent_id: item.parent_id || "root",
|
||||||
content_keywords: item.content_keywords || "",
|
content_keywords: item.content_keywords || "",
|
||||||
|
scope: "personal",
|
||||||
description: item.description || "",
|
description: item.description || "",
|
||||||
access_info: item.access_info || {
|
access_info: item.access_info || {
|
||||||
entities: [
|
entities: [
|
||||||
@@ -163,7 +170,17 @@ export const calculateItemSize = async (
|
|||||||
): Promise<number> => {
|
): Promise<number> => {
|
||||||
if (item.id === "trash") {
|
if (item.id === "trash") {
|
||||||
const trashedItems = await repository.find(
|
const trashedItems = await repository.find(
|
||||||
{ company_id: context.company.id, parent_id: "trash" },
|
{ company_id: context.company.id, is_in_trash: true, scope: "shared" },
|
||||||
|
{},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
|
return trashedItems.getEntities().reduce((acc, curr) => acc + curr.size, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.id === "trash_" + context.user.id) {
|
||||||
|
const trashedItems = await repository.find(
|
||||||
|
{ company_id: context.company.id, is_in_trash: true, scope: "personal" },
|
||||||
{},
|
{},
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
@@ -173,7 +190,7 @@ export const calculateItemSize = async (
|
|||||||
|
|
||||||
if (isVirtualFolder(item.id) || !item) {
|
if (isVirtualFolder(item.id) || !item) {
|
||||||
const rootFolderItems = await repository.find(
|
const rootFolderItems = await repository.find(
|
||||||
{ company_id: context.company.id, parent_id: item.id || "root" },
|
{ company_id: context.company.id, parent_id: item.id || "root", is_in_trash: false },
|
||||||
{},
|
{},
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
@@ -186,6 +203,7 @@ export const calculateItemSize = async (
|
|||||||
{
|
{
|
||||||
company_id: context.company.id,
|
company_id: context.company.id,
|
||||||
parent_id: item.id,
|
parent_id: item.id,
|
||||||
|
is_in_trash: false,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
context,
|
context,
|
||||||
@@ -561,3 +579,26 @@ export function isFileType(
|
|||||||
const fileExtensions = [extension, ...secondaryExtensions];
|
const fileExtensions = [extension, ...secondaryExtensions];
|
||||||
return fileExtensions.some(e => requiredExtensions.includes(e));
|
return fileExtensions.some(e => requiredExtensions.includes(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isInTrash = async (
|
||||||
|
item: DriveFile,
|
||||||
|
repository: Repository<DriveFile>,
|
||||||
|
context: CompanyExecutionContext,
|
||||||
|
) => {
|
||||||
|
if (item.is_in_trash === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVirtualFolder(item.parent_id)) {
|
||||||
|
return false; // Stop condition
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the parent item
|
||||||
|
const parentItem = await repository.findOne({
|
||||||
|
company_id: context.company.id,
|
||||||
|
id: item.parent_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recursively check the parent item
|
||||||
|
return isInTrash(parentItem, repository, context);
|
||||||
|
};
|
||||||
|
|||||||
@@ -114,6 +114,29 @@ export class DocumentsController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore a DriveFile item from the trashÒ
|
||||||
|
*
|
||||||
|
* @param {FastifyRequest} request
|
||||||
|
* @param {FastifyReply} reply
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
restore = async (
|
||||||
|
request: FastifyRequest<{ Params: ItemRequestParams; Querystring: { public_token?: string } }>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const context = getDriveExecutionContext(request);
|
||||||
|
|
||||||
|
await globalResolver.services.documents.documents.restore(request.params.id, null, context);
|
||||||
|
|
||||||
|
reply.status(200).send();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({ error: `${error}` }, "Failed to restore drive item");
|
||||||
|
throw new CrudException("Failed to restore drive item", 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists the drive root folder.
|
* Lists the drive root folder.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next)
|
|||||||
handler: documentsController.delete.bind(documentsController),
|
handler: documentsController.delete.bind(documentsController),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.route({
|
||||||
|
method: "POST",
|
||||||
|
url: `${serviceUrl}/:id/restore`,
|
||||||
|
preValidation: [fastify.authenticateOptional],
|
||||||
|
handler: documentsController.restore.bind(documentsController),
|
||||||
|
});
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `${serviceUrl}/:id/user/:user_id/access`,
|
url: `${serviceUrl}/:id/user/:user_id/access`,
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ const documentSchema = {
|
|||||||
last_user: { type: "string" },
|
last_user: { type: "string" },
|
||||||
attachements: { type: "array" },
|
attachements: { type: "array" },
|
||||||
last_version_cache: fileVersionSchema,
|
last_version_cache: fileVersionSchema,
|
||||||
|
scope: { type: "string" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export class DriveFileMockClass {
|
|||||||
access_info: MockAccessInformation;
|
access_info: MockAccessInformation;
|
||||||
creator: string;
|
creator: string;
|
||||||
is_directory: boolean;
|
is_directory: boolean;
|
||||||
|
scope: "personal" | "shared";
|
||||||
created_by: Record<string, any>;
|
created_by: Record<string, any>;
|
||||||
shared_by: Record<string, any>;
|
shared_by: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,11 +59,12 @@ describe("the Drive feature", () => {
|
|||||||
|
|
||||||
const createItem = async (): Promise<DriveFileMockClass> => {
|
const createItem = async (): Promise<DriveFileMockClass> => {
|
||||||
await TestDbService.getInstance(platform, true);
|
await TestDbService.getInstance(platform, true);
|
||||||
|
const scope: "personal" | "shared" = "shared";
|
||||||
const item = {
|
const item = {
|
||||||
name: "new test file",
|
name: "new test file",
|
||||||
parent_id: "root",
|
parent_id: "root",
|
||||||
company_id: platform.workspace.company_id,
|
company_id: platform.workspace.company_id,
|
||||||
|
scope,
|
||||||
};
|
};
|
||||||
|
|
||||||
const version = {};
|
const version = {};
|
||||||
@@ -141,8 +142,9 @@ describe("the Drive feature", () => {
|
|||||||
DriveItemDetailsMockClass,
|
DriveItemDetailsMockClass,
|
||||||
listTrashResponse.body,
|
listTrashResponse.body,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(listTrashResult.item.name).toEqual("Trash");
|
expect(listTrashResult.item.name).toEqual("Trash");
|
||||||
|
expect(createItemResult).toBeDefined();
|
||||||
|
expect(createItemResult.scope).toEqual("shared");
|
||||||
expect(listTrashResult.children.some(({ id }) => id === createItemResult.id)).toBeTruthy();
|
expect(listTrashResult.children.some(({ id }) => id === createItemResult.id)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ describe("the public links feature", () => {
|
|||||||
platform = null;
|
platform = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("Basic Flow", () => {
|
describe("Basic Flow", () => {
|
||||||
|
|
||||||
const createItem = async (): Promise<DriveFileMockClass> => {
|
const createItem = async (): Promise<DriveFileMockClass> => {
|
||||||
await TestHelpers.getInstance(platform, true, { companyRole: "admin" });
|
await TestHelpers.getInstance(platform, true, { companyRole: "admin" });
|
||||||
|
|
||||||
@@ -284,33 +282,7 @@ describe("the public links feature", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Share file from My Drive", () => {
|
|
||||||
|
|
||||||
it("Share file from some folder", async () => {
|
|
||||||
const user = await TestHelpers.getInstance(platform, true);
|
|
||||||
const anotherUser = await TestHelpers.getInstance(platform, true);
|
|
||||||
|
|
||||||
//create directory in "My Drive" and upload a file
|
|
||||||
const directory = await user.createDirectory("user_" + user.user.id);
|
|
||||||
const doc = await user.createRandomDocument(directory.id);
|
|
||||||
|
|
||||||
//check that another user doesn't see any file
|
|
||||||
expect((await anotherUser.getDocument(doc.id)).statusCode).toBe(401);
|
|
||||||
|
|
||||||
//share file with the public link
|
|
||||||
await user.shareWithPublicLink(doc, "read");
|
|
||||||
|
|
||||||
const token = await anotherUser.getPublicLinkAccessToken(doc);
|
|
||||||
|
|
||||||
anotherUser.jwt = token.value;
|
|
||||||
await anotherUser.getDocumentOKCheck(doc.id);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Download Folder from shared link", () => {
|
describe("Download Folder from shared link", () => {
|
||||||
|
|
||||||
it("Share folder", async () => {
|
it("Share folder", async () => {
|
||||||
const user = await TestHelpers.getInstance(platform, true);
|
const user = await TestHelpers.getInstance(platform, true);
|
||||||
const anotherUser = await TestHelpers.getInstance(platform, true);
|
const anotherUser = await TestHelpers.getInstance(platform, true);
|
||||||
@@ -332,9 +304,5 @@ describe("the public links feature", () => {
|
|||||||
|
|
||||||
expect((await anotherUser.getFolder(doc.id)).statusCode).toBe(200);
|
expect((await anotherUser.getFolder(doc.id)).statusCode).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -236,6 +236,7 @@
|
|||||||
"hooks.use-drive-actions.unable_create_file":"Unable to create a new file.",
|
"hooks.use-drive-actions.unable_create_file":"Unable to create a new file.",
|
||||||
"hooks.use-drive-actions.unable_download_file":"Unable to download this files.",
|
"hooks.use-drive-actions.unable_download_file":"Unable to download this files.",
|
||||||
"hooks.use-drive-actions.unable_remove_file":"Unable to remove this file.",
|
"hooks.use-drive-actions.unable_remove_file":"Unable to remove this file.",
|
||||||
|
"hooks.use-drive-actions.unable_restore_file":"Unable to restore this item.",
|
||||||
"hooks.use-drive-actions.unable_update_file":"Unable to update this file.",
|
"hooks.use-drive-actions.unable_update_file":"Unable to update this file.",
|
||||||
"general.resume": "Resume",
|
"general.resume": "Resume",
|
||||||
"general.pause": "Pause",
|
"general.pause": "Pause",
|
||||||
@@ -264,6 +265,8 @@
|
|||||||
"components.side_menu.buttons.upload": "Upload",
|
"components.side_menu.buttons.upload": "Upload",
|
||||||
"components.side_menu.buttons.create": "Create",
|
"components.side_menu.buttons.create": "Create",
|
||||||
"components.side_menu.buttons.empty_trash": "Empty trash",
|
"components.side_menu.buttons.empty_trash": "Empty trash",
|
||||||
|
"components.header_path.shared_trash": "Shared Trash",
|
||||||
|
"components.header_path.my_trash": "My Trash",
|
||||||
"scenes.app.drive.nothing": "Nothing here.",
|
"scenes.app.drive.nothing": "Nothing here.",
|
||||||
"scenes.app.drive.drag_and_drop": "Drag and drop files to upload them or click on the 'Add document' button.",
|
"scenes.app.drive.drag_and_drop": "Drag and drop files to upload them or click on the 'Add document' button.",
|
||||||
"scenes.app.drive.add_doc": "Add document or folder",
|
"scenes.app.drive.add_doc": "Add document or folder",
|
||||||
@@ -297,6 +300,7 @@
|
|||||||
"components.item_context_menu.move.modal_header": "Move",
|
"components.item_context_menu.move.modal_header": "Move",
|
||||||
"components.item_context_menu.move_to_trash": "Delete",
|
"components.item_context_menu.move_to_trash": "Delete",
|
||||||
"components.item_context_menu.delete": "Delete",
|
"components.item_context_menu.delete": "Delete",
|
||||||
|
"components.item_context_menu.restore": "Restore",
|
||||||
"components.item_context_menu.move_multiple": "Move selected items",
|
"components.item_context_menu.move_multiple": "Move selected items",
|
||||||
"components.item_context_menu.move_multiple.modal_header": "Move selected items",
|
"components.item_context_menu.move_multiple.modal_header": "Move selected items",
|
||||||
"components.item_context_menu.download_multiple": "Download selected items",
|
"components.item_context_menu.download_multiple": "Download selected items",
|
||||||
|
|||||||
@@ -220,6 +220,7 @@
|
|||||||
"hooks.use-drive-actions.unable_create_file":"Impossible de créer ce fichier",
|
"hooks.use-drive-actions.unable_create_file":"Impossible de créer ce fichier",
|
||||||
"hooks.use-drive-actions.unable_download_file":"Impossible de télécharger ce fichier",
|
"hooks.use-drive-actions.unable_download_file":"Impossible de télécharger ce fichier",
|
||||||
"hooks.use-drive-actions.unable_remove_file":"Impossible de supprimer ce fichier",
|
"hooks.use-drive-actions.unable_remove_file":"Impossible de supprimer ce fichier",
|
||||||
|
"hooks.use-drive-actions.unable_restore_file":"Impossible de restaurer cet élément.",
|
||||||
"hooks.use-drive-actions.unable_update_file":"Impossible de mettre à jour ce fichier",
|
"hooks.use-drive-actions.unable_update_file":"Impossible de mettre à jour ce fichier",
|
||||||
"general.resume": "Reprendre",
|
"general.resume": "Reprendre",
|
||||||
"general.pause": "Pause",
|
"general.pause": "Pause",
|
||||||
@@ -264,6 +265,8 @@
|
|||||||
"components.side_menu.buttons.upload": "Télécharger",
|
"components.side_menu.buttons.upload": "Télécharger",
|
||||||
"components.side_menu.buttons.create": "Créer",
|
"components.side_menu.buttons.create": "Créer",
|
||||||
"components.side_menu.buttons.empty_trash": "Vider la corbeille",
|
"components.side_menu.buttons.empty_trash": "Vider la corbeille",
|
||||||
|
"components.header_path.shared_trash": "Corbeille partagée",
|
||||||
|
"components.header_path.my_trash": "Ma corbeille",
|
||||||
"scenes.app.drive.nothing": "Rien ici.",
|
"scenes.app.drive.nothing": "Rien ici.",
|
||||||
"scenes.app.drive.drag_and_drop": "Faites glisser et déposez des fichiers pour les télécharger ou cliquez sur le bouton 'Ajouter un document'.",
|
"scenes.app.drive.drag_and_drop": "Faites glisser et déposez des fichiers pour les télécharger ou cliquez sur le bouton 'Ajouter un document'.",
|
||||||
"scenes.app.drive.add_doc": "Ajouter un document ou un dossier",
|
"scenes.app.drive.add_doc": "Ajouter un document ou un dossier",
|
||||||
@@ -297,6 +300,7 @@
|
|||||||
"components.item_context_menu.move.modal_header": "Déplacer",
|
"components.item_context_menu.move.modal_header": "Déplacer",
|
||||||
"components.item_context_menu.move_to_trash": "Supprimer",
|
"components.item_context_menu.move_to_trash": "Supprimer",
|
||||||
"components.item_context_menu.delete": "Supprimer",
|
"components.item_context_menu.delete": "Supprimer",
|
||||||
|
"components.item_context_menu.restore": "Restaurer",
|
||||||
"components.item_context_menu.move_multiple": "Déplacer les éléments sélectionnés",
|
"components.item_context_menu.move_multiple": "Déplacer les éléments sélectionnés",
|
||||||
"components.item_context_menu.move_multiple.modal_header": "Déplacer les éléments sélectionnés",
|
"components.item_context_menu.move_multiple.modal_header": "Déplacer les éléments sélectionnés",
|
||||||
"components.item_context_menu.download_multiple": "Télécharger les éléments sélectionnés",
|
"components.item_context_menu.download_multiple": "Télécharger les éléments sélectionnés",
|
||||||
|
|||||||
@@ -220,6 +220,7 @@
|
|||||||
"hooks.use-drive-actions.unable_create_file":"Unable to create a new file.",
|
"hooks.use-drive-actions.unable_create_file":"Unable to create a new file.",
|
||||||
"hooks.use-drive-actions.unable_download_file":"Unable to download this files.",
|
"hooks.use-drive-actions.unable_download_file":"Unable to download this files.",
|
||||||
"hooks.use-drive-actions.unable_remove_file":"Unable to remove this file.",
|
"hooks.use-drive-actions.unable_remove_file":"Unable to remove this file.",
|
||||||
|
"hooks.use-drive-actions.unable_restore_file":"Unable to restore this item.",
|
||||||
"hooks.use-drive-actions.unable_update_file":"Unable to update this file.",
|
"hooks.use-drive-actions.unable_update_file":"Unable to update this file.",
|
||||||
"general.resume": "Продолжить",
|
"general.resume": "Продолжить",
|
||||||
"general.pause": "Пауза",
|
"general.pause": "Пауза",
|
||||||
@@ -264,6 +265,8 @@
|
|||||||
"components.side_menu.buttons.upload": "Загрузить",
|
"components.side_menu.buttons.upload": "Загрузить",
|
||||||
"components.side_menu.buttons.create": "Создать",
|
"components.side_menu.buttons.create": "Создать",
|
||||||
"components.side_menu.buttons.empty_trash": "Очистить козину",
|
"components.side_menu.buttons.empty_trash": "Очистить козину",
|
||||||
|
"components.header_path.shared_trash": "Корзина \"Общего диска\"",
|
||||||
|
"components.header_path.my_trash": "Корзина \"Моего диска\"",
|
||||||
"scenes.app.drive.nothing": "Здесь ничего нет.",
|
"scenes.app.drive.nothing": "Здесь ничего нет.",
|
||||||
"scenes.app.drive.drag_and_drop": "Перенесите сюда файлы чтобы загрузить их или нажмите кнопку \"Добавить файл\"",
|
"scenes.app.drive.drag_and_drop": "Перенесите сюда файлы чтобы загрузить их или нажмите кнопку \"Добавить файл\"",
|
||||||
"scenes.app.drive.add_doc": "Добавить файл или папку",
|
"scenes.app.drive.add_doc": "Добавить файл или папку",
|
||||||
@@ -297,6 +300,7 @@
|
|||||||
"components.item_context_menu.move.modal_header": "Переместить",
|
"components.item_context_menu.move.modal_header": "Переместить",
|
||||||
"components.item_context_menu.move_to_trash": "Отправить в корзину",
|
"components.item_context_menu.move_to_trash": "Отправить в корзину",
|
||||||
"components.item_context_menu.delete": "Удалить",
|
"components.item_context_menu.delete": "Удалить",
|
||||||
|
"components.item_context_menu.restore": "Restore",
|
||||||
"components.item_context_menu.move_multiple": "Переместить все",
|
"components.item_context_menu.move_multiple": "Переместить все",
|
||||||
"components.item_context_menu.move_multiple.modal_header": "Переместить выбранные елементы",
|
"components.item_context_menu.move_multiple.modal_header": "Переместить выбранные елементы",
|
||||||
"components.item_context_menu.download_multiple": "Скачать все",
|
"components.item_context_menu.download_multiple": "Скачать все",
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ export class DriveApiClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async restore(companyId: string, id: string | 'trash' | '') {
|
||||||
|
return await Api.post<any, any>(
|
||||||
|
`/internal/services/documents/v1/companies/${companyId}/item/${id}/restore${appendTdriveToken()}`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static async update(companyId: string, id: string, update: Partial<DriveItem>) {
|
static async update(companyId: string, id: string, update: Partial<DriveItem>) {
|
||||||
return await Api.post<Partial<DriveItem>, DriveItem>(
|
return await Api.post<Partial<DriveItem>, DriveItem>(
|
||||||
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendTdriveToken()}`,
|
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendTdriveToken()}`,
|
||||||
|
|||||||
@@ -96,6 +96,18 @@ export const useDriveActions = () => {
|
|||||||
[refresh],
|
[refresh],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const restore = useCallback(
|
||||||
|
async (id: string, parentId: string) => {
|
||||||
|
try {
|
||||||
|
await DriveApiClient.restore(companyId, id);
|
||||||
|
await refresh(parentId || '');
|
||||||
|
} catch (e) {
|
||||||
|
ToasterService.error(Languages.t('hooks.use-drive-actions.unable_restore_file'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[refresh],
|
||||||
|
);
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
async (update: Partial<DriveItem>, id: string, parentId: string) => {
|
async (update: Partial<DriveItem>, id: string, parentId: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -127,5 +139,5 @@ export const useDriveActions = () => {
|
|||||||
[refresh],
|
[refresh],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { create, refresh, download, downloadZip, remove, update, updateLevel };
|
return { create, refresh, download, downloadZip, remove, restore, update, updateLevel };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const useDriveItem = (id: string) => {
|
|||||||
const item = useRecoilValue(DriveItemAtom(id));
|
const item = useRecoilValue(DriveItemAtom(id));
|
||||||
const children = useRecoilValue(DriveItemChildrenAtom(id));
|
const children = useRecoilValue(DriveItemChildrenAtom(id));
|
||||||
const [loading, setLoading] = useRecoilState(LoadingStateInitTrue('useDriveItem-' + id));
|
const [loading, setLoading] = useRecoilState(LoadingStateInitTrue('useDriveItem-' + id));
|
||||||
const { refresh: refreshItem, create, update: _update, updateLevel: _updateLevel, remove: _remove } = useDriveActions();
|
const { refresh: refreshItem, create, update: _update, updateLevel: _updateLevel, remove: _remove, restore: _restore } = useDriveActions();
|
||||||
const { uploadVersion: _uploadVersion } = useDriveUpload();
|
const { uploadVersion: _uploadVersion } = useDriveUpload();
|
||||||
|
|
||||||
const refresh = useCallback(
|
const refresh = useCallback(
|
||||||
@@ -44,6 +44,16 @@ export const useDriveItem = (id: string) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, [id, setLoading, refresh, item?.item?.parent_id]);
|
}, [id, setLoading, refresh, item?.item?.parent_id]);
|
||||||
|
|
||||||
|
const restore = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await _restore(id, item?.item?.parent_id || '');
|
||||||
|
} catch (e) {
|
||||||
|
ToasterService.error('Unable to restore this item.');
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}, [id, setLoading, refresh, item?.item?.parent_id]);
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
async (update: Partial<DriveItem>) => {
|
async (update: Partial<DriveItem>) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -83,7 +93,7 @@ export const useDriveItem = (id: string) => {
|
|||||||
[companyId, id, setLoading, refresh, item?.item?.parent_id],
|
[companyId, id, setLoading, refresh, item?.item?.parent_id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const inTrash = id === 'trash' || item?.path?.some(i => i.parent_id === 'trash');
|
const inTrash = id.includes('trash') || item?.path?.some(i => i?.parent_id?.includes('trash')) || item?.item?.is_in_trash;
|
||||||
const sharedWithMe = id =="shared_with_me";
|
const sharedWithMe = id =="shared_with_me";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export const useDrivePreviewModal = () => {
|
|||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
setStatus({ item: null, loading: true });
|
setStatus({ item: null, loading: true });
|
||||||
history.push(RouterServices.generateRouteFromState({companyId: company, itemId: ""}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { open, close, isOpen: !!status.item };
|
return { open, close, isOpen: !!status.item };
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export type DriveItem = {
|
|||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
|
|
||||||
parent_id: string;
|
parent_id: string;
|
||||||
in_trash: boolean;
|
is_in_trash: boolean;
|
||||||
is_directory: boolean;
|
is_directory: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
extension: string;
|
extension: string;
|
||||||
@@ -35,6 +35,7 @@ export type DriveItem = {
|
|||||||
access_info: DriveItemAccessInfo;
|
access_info: DriveItemAccessInfo;
|
||||||
|
|
||||||
size: number;
|
size: number;
|
||||||
|
scope: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DriveFileAccessLevel = 'none' | 'read' | 'write' | 'manage';
|
export type DriveFileAccessLevel = 'none' | 'read' | 'write' | 'manage';
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export type ClientStateType = {
|
|||||||
companyId?: string;
|
companyId?: string;
|
||||||
viewId?: string;
|
viewId?: string;
|
||||||
itemId?: string;
|
itemId?: string;
|
||||||
|
dirId?: string;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
@@ -52,6 +53,7 @@ class RouterServices extends Observable {
|
|||||||
'/client/:companyId/v/:viewId',
|
'/client/:companyId/v/:viewId',
|
||||||
'/client/:companyId/preview/:itemId',
|
'/client/:companyId/preview/:itemId',
|
||||||
'/client/:companyId/v/:viewId/preview/:itemId',
|
'/client/:companyId/v/:viewId/preview/:itemId',
|
||||||
|
'/client/:companyId/v/:viewId/d/:dirId',
|
||||||
'/client/:companyId/w/:workspaceId',
|
'/client/:companyId/w/:workspaceId',
|
||||||
'/client/:companyId/w/:workspaceId/c/:channelId',
|
'/client/:companyId/w/:workspaceId/c/:channelId',
|
||||||
'/client/:companyId/w/:workspaceId/c/:channelId/t/:threadId',
|
'/client/:companyId/w/:workspaceId/c/:channelId/t/:threadId',
|
||||||
@@ -185,6 +187,7 @@ class RouterServices extends Observable {
|
|||||||
companyId: match?.params?.companyId || '',
|
companyId: match?.params?.companyId || '',
|
||||||
viewId: match?.params?.viewId || '',
|
viewId: match?.params?.viewId || '',
|
||||||
itemId: match?.params?.itemId || '',
|
itemId: match?.params?.itemId || '',
|
||||||
|
dirId: match?.params?.dirId || '',
|
||||||
workspaceId: match?.params?.workspaceId || '',
|
workspaceId: match?.params?.workspaceId || '',
|
||||||
channelId: match?.params?.channelId || '',
|
channelId: match?.params?.channelId || '',
|
||||||
messageId: match?.params?.messageId || '',
|
messageId: match?.params?.messageId || '',
|
||||||
@@ -291,6 +294,7 @@ class RouterServices extends Observable {
|
|||||||
(state.companyId ? `/${state.companyId}` : '') +
|
(state.companyId ? `/${state.companyId}` : '') +
|
||||||
(state.viewId ? `/v/${state.viewId}` : '') +
|
(state.viewId ? `/v/${state.viewId}` : '') +
|
||||||
(state.itemId ? `/preview/${state.itemId}` : '') +
|
(state.itemId ? `/preview/${state.itemId}` : '') +
|
||||||
|
(state.dirId ? `/d/${state.dirId}` : '') +
|
||||||
(state.sharedWithMe ? `/shared-with-me` : '') +
|
(state.sharedWithMe ? `/shared-with-me` : '') +
|
||||||
(state.workspaceId ? `/w/${state.workspaceId}` : '') +
|
(state.workspaceId ? `/w/${state.workspaceId}` : '') +
|
||||||
(state.channelId ? `/c/${state.channelId}` : '') +
|
(state.channelId ? `/c/${state.channelId}` : '') +
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ export const RoutePreviewSelector = selector<string>({
|
|||||||
get: ({ get }) => get(RouterState)?.itemId || '',
|
get: ({ get }) => get(RouterState)?.itemId || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const RouteDirectorySelector = selector<string>({
|
||||||
|
key: 'RouterDirectorySelector',
|
||||||
|
get: ({ get }) => get(RouterState)?.dirId || '',
|
||||||
|
});
|
||||||
|
|
||||||
export const RouterWorkspaceSelector = selector<string>({
|
export const RouterWorkspaceSelector = selector<string>({
|
||||||
key: 'RouterWorkspaceSelector',
|
key: 'RouterWorkspaceSelector',
|
||||||
get: ({ get }) => get(RouterState)?.workspaceId || '',
|
get: ({ get }) => get(RouterState)?.workspaceId || '',
|
||||||
|
|||||||
@@ -113,13 +113,6 @@ export default memo(
|
|||||||
[_setParentId],
|
[_setParentId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//In case we are kicked out of the current folder, we need to reset the parent id
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && !path?.length && !inPublicSharing && !sharedWithMe) setParentId('user_'+user?.id);
|
|
||||||
}, [path, loading, setParentId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChecked({});
|
setChecked({});
|
||||||
refresh(parentId);
|
refresh(parentId);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { ToasterService } from '@features/global/services/toaster-service';
|
|||||||
import { copyToClipboard } from '@features/global/utils/CopyClipboard';
|
import { copyToClipboard } from '@features/global/utils/CopyClipboard';
|
||||||
import { SharedWithMeFilterState } from '@features/drive/state/shared-with-me-filter';
|
import { SharedWithMeFilterState } from '@features/drive/state/shared-with-me-filter';
|
||||||
import { getCurrentUserList } from '@features/users/hooks/use-user-list';
|
import { getCurrentUserList } from '@features/users/hooks/use-user-list';
|
||||||
|
import useRouteState from 'app/features/router/hooks/use-route-state';
|
||||||
import RouterServices from '@features/router/services/router-service';
|
import RouterServices from '@features/router/services/router-service';
|
||||||
import useRouterCompany from '@features/router/hooks/use-router-company';
|
import useRouterCompany from '@features/router/hooks/use-router-company';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -35,7 +36,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
DriveCurrentFolderAtom({ initialFolderId: initialParentId || 'root' }),
|
DriveCurrentFolderAtom({ initialFolderId: initialParentId || 'root' }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { download, downloadZip, update } = useDriveActions();
|
const { download, downloadZip, update, restore } = useDriveActions();
|
||||||
const setCreationModalState = useSetRecoilState(CreateModalAtom);
|
const setCreationModalState = useSetRecoilState(CreateModalAtom);
|
||||||
const setSelectorModalState = useSetRecoilState(SelectorModalAtom);
|
const setSelectorModalState = useSetRecoilState(SelectorModalAtom);
|
||||||
const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom);
|
const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom);
|
||||||
@@ -45,6 +46,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
const setPropertiesModalState = useSetRecoilState(PropertiesModalAtom);
|
const setPropertiesModalState = useSetRecoilState(PropertiesModalAtom);
|
||||||
const setUsersModalState = useSetRecoilState(UsersModalAtom);
|
const setUsersModalState = useSetRecoilState(UsersModalAtom);
|
||||||
const { open: preview } = useDrivePreview();
|
const { open: preview } = useDrivePreview();
|
||||||
|
const { viewId } = useRouteState();
|
||||||
const company = useRouterCompany();
|
const company = useRouterCompany();
|
||||||
|
|
||||||
function getIdsFromArray(arr: DriveItem[]): string[] {
|
function getIdsFromArray(arr: DriveItem[]): string[] {
|
||||||
@@ -56,7 +58,8 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
if (!parent || !parent.access) return [];
|
if (!parent || !parent.access) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const inTrash = parent.path?.[0]?.id === 'trash';
|
const inTrash = parent.path?.[0]?.id.includes('trash') || viewId?.includes("trash");
|
||||||
|
const isPersonal = item?.scope === 'personal';
|
||||||
const selectedCount = checked.length;
|
const selectedCount = checked.length;
|
||||||
|
|
||||||
let menu: any[] = [];
|
let menu: any[] = [];
|
||||||
@@ -75,7 +78,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.share'),
|
text: Languages.t('components.item_context_menu.share'),
|
||||||
hide: access === 'read' || getPublicLinkToken(),
|
hide: access === 'read' || getPublicLinkToken() || inTrash,
|
||||||
onClick: () => setAccessModalState({ open: true, id: item.id }),
|
onClick: () => setAccessModalState({ open: true, id: item.id }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -106,13 +109,13 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.manage_access'),
|
text: Languages.t('components.item_context_menu.manage_access'),
|
||||||
hide: access === 'read' || getPublicLinkToken(),
|
hide: access === 'read' || getPublicLinkToken() || inTrash,
|
||||||
onClick: () => setAccessModalState({ open: true, id: item.id }),
|
onClick: () => setAccessModalState({ open: true, id: item.id }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.move'),
|
text: Languages.t('components.item_context_menu.move'),
|
||||||
hide: access === 'read',
|
hide: access === 'read' || inTrash,
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
setSelectorModalState({
|
setSelectorModalState({
|
||||||
open: true,
|
open: true,
|
||||||
@@ -135,13 +138,13 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.rename'),
|
text: Languages.t('components.item_context_menu.rename'),
|
||||||
hide: access === 'read',
|
hide: access === 'read' || inTrash,
|
||||||
onClick: () => setPropertiesModalState({ open: true, id: item.id }),
|
onClick: () => setPropertiesModalState({ open: true, id: item.id }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.copy_link'),
|
text: Languages.t('components.item_context_menu.copy_link'),
|
||||||
hide: !item.access_info.public?.level || item.access_info.public?.level === 'none',
|
hide: !item.access_info.public?.level || item.access_info.public?.level === 'none' || inTrash,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
copyToClipboard(getPublicLink(item || parent?.item));
|
copyToClipboard(getPublicLink(item || parent?.item));
|
||||||
ToasterService.success(
|
ToasterService.success(
|
||||||
@@ -152,10 +155,10 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.versions'),
|
text: Languages.t('components.item_context_menu.versions'),
|
||||||
hide: item.is_directory,
|
hide: item.is_directory || inTrash,
|
||||||
onClick: () => setVersionModal({ open: true, id: item.id }),
|
onClick: () => setVersionModal({ open: true, id: item.id }),
|
||||||
},
|
},
|
||||||
{ type: 'separator', hide: access !== 'manage' },
|
{ type: 'separator', hide: access !== 'manage' || inTrash, },
|
||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.move_to_trash'),
|
text: Languages.t('components.item_context_menu.move_to_trash'),
|
||||||
@@ -163,6 +166,16 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
hide: inTrash || access !== 'manage',
|
hide: inTrash || access !== 'manage',
|
||||||
onClick: () => setConfirmTrashModalState({ open: true, items: [item] }),
|
onClick: () => setConfirmTrashModalState({ open: true, items: [item] }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: Languages.t('components.item_context_menu.restore'),
|
||||||
|
className: 'error',
|
||||||
|
hide: !inTrash || (access !== 'manage' && !isPersonal),
|
||||||
|
onClick: () => {
|
||||||
|
const parentId = item.is_in_trash ? viewId || '' : item.parent_id;
|
||||||
|
restore(item.id, parentId);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.delete'),
|
text: Languages.t('components.item_context_menu.delete'),
|
||||||
@@ -257,7 +270,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
type: 'menu',
|
type: 'menu',
|
||||||
text: Languages.t('components.item_context_menu.trash.empty'),
|
text: Languages.t('components.item_context_menu.trash.empty'),
|
||||||
className: 'error',
|
className: 'error',
|
||||||
hide: parent.item!.id != 'trash' || parent.access !== 'manage',
|
hide: !inTrash,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setConfirmDeleteModalState({
|
setConfirmDeleteModalState({
|
||||||
open: true,
|
open: true,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const DocumentRow = ({
|
|||||||
const device = getDevice();
|
const device = getDevice();
|
||||||
console.log("DEVICE:: " + device);
|
console.log("DEVICE:: " + device);
|
||||||
if (device != "ios" && device != "android") {
|
if (device != "ios" && device != "android") {
|
||||||
history.push(RouterServices.generateRouteFromState({companyId: company, itemId: item.id}));
|
history.push(RouterServices.generateRouteFromState({companyId: company, itemId: item.id, dirId: ''}));
|
||||||
open(item);
|
open(item);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import { useEffect, useState } from 'react';
|
|||||||
import { PublicIcon } from './components/public-icon';
|
import { PublicIcon } from './components/public-icon';
|
||||||
import MenusManager from '@components/menus/menus-manager.jsx';
|
import MenusManager from '@components/menus/menus-manager.jsx';
|
||||||
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
||||||
import Languages from "features/global/services/languages-service";
|
import { useDriveItem } from '@features/drive/hooks/use-drive-item';
|
||||||
|
import Languages from 'features/global/services/languages-service';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import useRouterCompany from '@features/router/hooks/use-router-company';
|
||||||
|
import RouterServices from '@features/router/services/router-service';
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
path: livePath,
|
path: livePath,
|
||||||
@@ -17,18 +21,36 @@ export default ({
|
|||||||
setParentId: (id: string) => void;
|
setParentId: (id: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [savedPath, setSavedPath] = useState<DriveItem[]>([]);
|
const [savedPath, setSavedPath] = useState<DriveItem[]>([]);
|
||||||
|
const { user } = useCurrentUser();
|
||||||
|
const history = useHistory();
|
||||||
|
const company = useRouterCompany();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (livePath) setSavedPath(livePath);
|
if (livePath) setSavedPath(livePath);
|
||||||
}, [livePath]);
|
}, [livePath]);
|
||||||
const path = livePath || savedPath;
|
const path = livePath || savedPath;
|
||||||
|
|
||||||
return <PathRender inTrash={inTrash || false} path={path} onClick={id => setParentId(id)} />;
|
return (
|
||||||
|
<PathRender
|
||||||
|
inTrash={inTrash || false}
|
||||||
|
path={path}
|
||||||
|
onClick={(viewId, dirId) => {
|
||||||
|
history.push(
|
||||||
|
RouterServices.generateRouteFromState({
|
||||||
|
companyId: company,
|
||||||
|
viewId,
|
||||||
|
dirId
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setParentId(dirId? dirId: viewId)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function cutFileName(name: any) {
|
function cutFileName(name: any) {
|
||||||
if (typeof name !== "undefined" ){
|
if (typeof name !== 'undefined') {
|
||||||
if (name.length >= 30) {
|
if (name.length >= 30) {
|
||||||
return name.substring(0,30)+" ...";
|
return name.substring(0, 30) + ' ...';
|
||||||
} else {
|
} else {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -43,7 +65,7 @@ export const PathRender = ({
|
|||||||
}: {
|
}: {
|
||||||
path: DriveItem[];
|
path: DriveItem[];
|
||||||
inTrash: boolean;
|
inTrash: boolean;
|
||||||
onClick: (id: string) => void;
|
onClick: (viewId: string, dirId: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const pathLength = (path || []).reduce((acc, curr) => acc + curr.name.length, 0);
|
const pathLength = (path || []).reduce((acc, curr) => acc + curr.name.length, 0);
|
||||||
|
|
||||||
@@ -100,30 +122,61 @@ const PathItem = ({
|
|||||||
item: Partial<DriveItem>;
|
item: Partial<DriveItem>;
|
||||||
last?: boolean;
|
last?: boolean;
|
||||||
first?: boolean;
|
first?: boolean;
|
||||||
onClick: (id: string) => void;
|
onClick: (viewId: string, dirId: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
|
const { viewId } = RouterServices.getStateFromRoute();
|
||||||
|
const { access: trashAccess } = useDriveItem('trash');
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
className="text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white"
|
className="text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white"
|
||||||
onClick={evt => {
|
onClick={evt => {
|
||||||
|
const driveMenuItems = [
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: Languages.t('components.side_menu.home',),
|
||||||
|
onClick: () => onClick('root', ''),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: Languages.t('components.side_menu.my_drive'),
|
||||||
|
onClick: () => onClick('user_' + user?.id, ''),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const trashMenuItems = [
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: Languages.t('components.header_path.my_trash'),
|
||||||
|
onClick: () => onClick('trash_' + user?.id, ''),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: Languages.t('components.header_path.shared_trash'),
|
||||||
|
onClick: () => onClick('trash', ''),
|
||||||
|
hide: trashAccess === 'read',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
if (first && user?.id) {
|
if (first && user?.id) {
|
||||||
MenusManager.openMenu(
|
if (viewId?.includes('trash')) {
|
||||||
[
|
MenusManager.openMenu(trashMenuItems, { x: evt.clientX, y: evt.clientY }, 'center');
|
||||||
{ type: 'menu', text: Languages.t('components.side_menu.home'), onClick: () => onClick('root') },
|
|
||||||
{ type: 'menu', text: Languages.t('components.side_menu.my_drive'), onClick: () => onClick('user_' + user?.id) },
|
|
||||||
],
|
|
||||||
{ x: evt.clientX, y: evt.clientY },
|
|
||||||
'center',
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
onClick(item?.id || '');
|
MenusManager.openMenu(driveMenuItems, { x: evt.clientX, y: evt.clientY }, 'center');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onClick(viewId || '',item?.id || '');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Title>{cutFileName(item?.name) || ''}</Title>
|
<Title>
|
||||||
|
{viewId?.includes('trash_') && first && Languages.t('components.header_path.my_trash')}
|
||||||
|
{viewId === 'trash' && first && Languages.t('components.header_path.shared_trash')}
|
||||||
|
{(viewId === 'trash' || viewId?.includes('trash_')) && !first && (cutFileName(item?.name) || '')}
|
||||||
|
{!viewId?.includes('trash') && (cutFileName(item?.name) || '')}
|
||||||
|
</Title>
|
||||||
</a>
|
</a>
|
||||||
{item?.access_info?.public?.level && item?.access_info?.public?.level !== 'none' && (
|
{item?.access_info?.public?.level && item?.access_info?.public?.level !== 'none' && (
|
||||||
<PublicIcon className="h-5 w-5 ml-2" />
|
<PublicIcon className="h-5 w-5 ml-2" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { DriveItemSelectedList } from '@features/drive/state/store';
|
|||||||
import { DriveItem } from '@features/drive/types';
|
import { DriveItem } from '@features/drive/types';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { atom, useRecoilState } from 'recoil';
|
import { atom, useRecoilState } from 'recoil';
|
||||||
|
import RouterServices from '@features/router/services/router-service';
|
||||||
|
|
||||||
export type ConfirmDeleteModalType = {
|
export type ConfirmDeleteModalType = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -40,6 +41,7 @@ const ConfirmDeleteModalContent = ({ items }: { items: DriveItem[] }) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [state, setState] = useRecoilState(ConfirmDeleteModalAtom);
|
const [state, setState] = useRecoilState(ConfirmDeleteModalAtom);
|
||||||
const [, setSelected] = useRecoilState(DriveItemSelectedList);
|
const [, setSelected] = useRecoilState(DriveItemSelectedList);
|
||||||
|
const { viewId } = RouterServices.getStateFromRoute();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refresh(items[0].id);
|
refresh(items[0].id);
|
||||||
@@ -64,7 +66,7 @@ const ConfirmDeleteModalContent = ({ items }: { items: DriveItem[] }) => {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
await remove(item.id, item.parent_id);
|
await remove(item.id, viewId || "");
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setSelected({});
|
setSelected({});
|
||||||
|
|||||||
+2
-14
@@ -111,9 +111,9 @@ const PublicLinkOptions = (props: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* useEffect(() => {
|
useEffect(() => {
|
||||||
props.onChangePassword(usePassword ? password : '');
|
props.onChangePassword(usePassword ? password : '');
|
||||||
}, [usePassword, password]); */
|
}, [usePassword, password]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
props.onChangeExpiration(useExpiration ? expiration : 0);
|
props.onChangeExpiration(useExpiration ? expiration : 0);
|
||||||
@@ -144,18 +144,6 @@ const PublicLinkOptions = (props: {
|
|||||||
className="max-w-xs"
|
className="max-w-xs"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={e => setPassword(e.target.value)}
|
onChange={e => setPassword(e.target.value)}
|
||||||
onBlur={handlePasswordBlur}
|
|
||||||
// changes password only when press enter
|
|
||||||
onKeyPress={e => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
props.onChangePassword(password);
|
|
||||||
e.preventDefault();
|
|
||||||
if (password) {
|
|
||||||
copyToClipboard(password);
|
|
||||||
ToasterService.success(Languages.t('components.public-link-security_password_copied'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
// saves and copies password
|
// saves and copies password
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (password) copyToClipboard(password);
|
if (password) copyToClipboard(password);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { CreateModal, CreateModalAtom } from '../body/drive/modals/create';
|
|||||||
import { Button } from '@atoms/button/button';
|
import { Button } from '@atoms/button/button';
|
||||||
import Languages from "features/global/services/languages-service";
|
import Languages from "features/global/services/languages-service";
|
||||||
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
||||||
|
import RouterServices from '@features/router/services/router-service';
|
||||||
|
|
||||||
export const CreateModalWithUploadZones = ({ initialParentId }: { initialParentId?: string }) => {
|
export const CreateModalWithUploadZones = ({ initialParentId }: { initialParentId?: string }) => {
|
||||||
const companyId = useRouterCompany();
|
const companyId = useRouterCompany();
|
||||||
@@ -25,8 +26,6 @@ export const CreateModalWithUploadZones = ({ initialParentId }: { initialParentI
|
|||||||
DriveCurrentFolderAtom({ initialFolderId: initialParentId || 'user_'+user?.id }),
|
DriveCurrentFolderAtom({ initialFolderId: initialParentId || 'user_'+user?.id }),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("Upload Zone:: " + parentId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UploadZone
|
<UploadZone
|
||||||
@@ -81,12 +80,14 @@ export const CreateModalWithUploadZones = ({ initialParentId }: { initialParentI
|
|||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
|
const { viewId } = RouterServices.getStateFromRoute();
|
||||||
const [parentId, _] = useRecoilState(DriveCurrentFolderAtom({ initialFolderId: 'user_'+user?.id }));
|
const [parentId, _] = useRecoilState(DriveCurrentFolderAtom({ initialFolderId: 'user_'+user?.id }));
|
||||||
const { access, item, inTrash } = useDriveItem(parentId);
|
const { access, item } = useDriveItem(parentId);
|
||||||
const { children: trashChildren } = useDriveItem('trash');
|
const { children: trashChildren } = useDriveItem(viewId === 'trash' ? 'trash' : 'trash_'+user?.id);
|
||||||
const uploadZoneRef = useRef<UploadZone | null>(null);
|
const uploadZoneRef = useRef<UploadZone | null>(null);
|
||||||
const { uploadTree } = useDriveUpload();
|
const { uploadTree } = useDriveUpload();
|
||||||
const companyId = useRouterCompany();
|
const companyId = useRouterCompany();
|
||||||
|
const inTrash = viewId?.includes("trash") || false;
|
||||||
|
|
||||||
const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom);
|
const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom);
|
||||||
const setCreationModalState = useSetRecoilState(CreateModalAtom);
|
const setCreationModalState = useSetRecoilState(CreateModalAtom);
|
||||||
@@ -101,7 +102,7 @@ export default () => {
|
|||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<CreateModalWithUploadZones initialParentId={parentId} />
|
<CreateModalWithUploadZones initialParentId={parentId} />
|
||||||
|
|
||||||
{inTrash && access === 'manage' && (
|
{inTrash && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@@ -113,6 +114,7 @@ export default () => {
|
|||||||
size="lg"
|
size="lg"
|
||||||
theme="danger"
|
theme="danger"
|
||||||
className="w-full mb-2 justify-center"
|
className="w-full mb-2 justify-center"
|
||||||
|
disabled={!(trashChildren.length > 0)}
|
||||||
>
|
>
|
||||||
<TruckIcon className="w-5 h-5 mr-2" /> { Languages.t('components.side_menu.buttons.empty_trash') }
|
<TruckIcon className="w-5 h-5 mr-2" /> { Languages.t('components.side_menu.buttons.empty_trash') }
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
} from '@heroicons/react/outline';
|
} from '@heroicons/react/outline';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import useRouterCompany from '@features/router/hooks/use-router-company';
|
import useRouterCompany from '@features/router/hooks/use-router-company';
|
||||||
import useRouterWorkspace from '@features/router/hooks/use-router-workspace';
|
|
||||||
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { Title } from '../../../atoms/text';
|
import { Title } from '../../../atoms/text';
|
||||||
@@ -21,17 +20,15 @@ import Account from '../common/account';
|
|||||||
import AppGrid from '../common/app-grid';
|
import AppGrid from '../common/app-grid';
|
||||||
import DiskUsage from '../common/disk-usage';
|
import DiskUsage from '../common/disk-usage';
|
||||||
import Actions from './actions';
|
import Actions from './actions';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import RouterServices from '@features/router/services/router-service';
|
import RouterServices from '@features/router/services/router-service';
|
||||||
import Languages from "features/global/services/languages-service";
|
import Languages from "features/global/services/languages-service";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
const location = useLocation();
|
|
||||||
const company = useRouterCompany();
|
const company = useRouterCompany();
|
||||||
const workspace = useRouterWorkspace();
|
const { viewId, itemId, dirId } = RouterServices.getStateFromRoute();
|
||||||
const { viewId, itemId } = RouterServices.getStateFromRoute();
|
|
||||||
const [parentId, setParentId] = useRecoilState(
|
const [parentId, setParentId] = useRecoilState(
|
||||||
DriveCurrentFolderAtom({ initialFolderId: viewId || 'user_'+user?.id }),
|
DriveCurrentFolderAtom({ initialFolderId: viewId || 'user_'+user?.id }),
|
||||||
);
|
);
|
||||||
@@ -46,7 +43,8 @@ export default () => {
|
|||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
!itemId && viewId && setParentId(viewId);
|
!itemId && !dirId && viewId && setParentId(viewId);
|
||||||
|
dirId && viewId && setParentId(dirId);
|
||||||
}, [viewId, itemId]);
|
}, [viewId, itemId]);
|
||||||
return (
|
return (
|
||||||
<div className="grow flex flex-col overflow-auto -m-4 p-4 relative">
|
<div className="grow flex flex-col overflow-auto -m-4 p-4 relative">
|
||||||
@@ -68,15 +66,15 @@ export default () => {
|
|||||||
<div className="mt-4" />
|
<div className="mt-4" />
|
||||||
<Title>Drive</Title>
|
<Title>Drive</Title>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {history.push(RouterServices.generateRouteFromState({companyId: company, viewId: 'user_' + user?.id})); setParentId('user_' + user?.id)}}
|
onClick={() => {history.push(RouterServices.generateRouteFromState({companyId: company, viewId: 'user_' + user?.id, itemId: '', dirId: ''})); setParentId('user_' + user?.id)}}
|
||||||
size="lg"
|
size="lg"
|
||||||
theme="white"
|
theme="white"
|
||||||
className={'w-full mb-1 ' + (folderType === 'personal' && (viewId == '' || 'user_' + user?.id) ? activeClass : '')}
|
className={'w-full mb-1 ' + (folderType === 'personal' && (viewId == '' || viewId == 'user_' + user?.id) ? activeClass : '')}
|
||||||
>
|
>
|
||||||
<UserIcon className="w-5 h-5 mr-4" /> {Languages.t('components.side_menu.my_drive')}
|
<UserIcon className="w-5 h-5 mr-4" /> {Languages.t('components.side_menu.my_drive')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {history.push(RouterServices.generateRouteFromState({companyId: company, viewId: "root"})); setParentId('root')}}
|
onClick={() => {history.push(RouterServices.generateRouteFromState({companyId: company, viewId: "root", itemId: '', dirId: ''})); setParentId('root')}}
|
||||||
size="lg"
|
size="lg"
|
||||||
theme="white"
|
theme="white"
|
||||||
className={'w-full mb-1 ' + (folderType === 'home' && viewId == 'root' ? activeClass : '')}
|
className={'w-full mb-1 ' + (folderType === 'home' && viewId == 'root' ? activeClass : '')}
|
||||||
@@ -84,7 +82,7 @@ export default () => {
|
|||||||
<CloudIcon className="w-5 h-5 mr-4" /> {Languages.t('components.side_menu.home')}
|
<CloudIcon className="w-5 h-5 mr-4" /> {Languages.t('components.side_menu.home')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {history.push(RouterServices.generateRouteFromState({companyId: company, viewId: "shared_with_me"})); setParentId('shared_with_me')}}
|
onClick={() => {history.push(RouterServices.generateRouteFromState({companyId: company, viewId: "shared_with_me", itemId: '', dirId: ''})); setParentId('shared_with_me')}}
|
||||||
size="lg"
|
size="lg"
|
||||||
theme="white"
|
theme="white"
|
||||||
className={'w-full mb-1 ' + (folderType === 'shared' && viewId == 'shared_with_me'? activeClass : '')}
|
className={'w-full mb-1 ' + (folderType === 'shared' && viewId == 'shared_with_me'? activeClass : '')}
|
||||||
@@ -109,16 +107,14 @@ export default () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{rootAccess === 'manage' && (
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>{history.push(RouterServices.generateRouteFromState({companyId: company, viewId: ""}));setParentId('trash')}}
|
onClick={() =>{history.push(RouterServices.generateRouteFromState({companyId: company, viewId: 'trash_'+user?.id, itemId: '', dirId: ''}));setParentId('trash_'+user?.id)}}
|
||||||
size="lg"
|
size="lg"
|
||||||
theme="white"
|
theme="white"
|
||||||
className={'w-full mb-1 ' + (folderType === 'trash' && viewId == ''? activeClass : '')}
|
className={'w-full mb-1 ' + (viewId?.includes("trash")? activeClass : '')}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-5 h-5 mr-4 text-rose-500" /> {Languages.t('components.side_menu.trash')}
|
<TrashIcon className="w-5 h-5 mr-4 text-rose-500" /> {Languages.t('components.side_menu.trash')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
|
|
||||||
{false && (
|
{false && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export const DrivePreview: React.FC<DrivePreviewProps> = ({ items }) => {
|
|||||||
if (device != 'ios' && device != 'android') {
|
if (device != 'ios' && device != 'android') {
|
||||||
close();
|
close();
|
||||||
history.push(
|
history.push(
|
||||||
RouterServices.generateRouteFromState({ companyId: company, viewId: '', itemId: item.id }),
|
RouterServices.generateRouteFromState({ companyId: company, viewId: '', itemId: item.id, dirId: '' }),
|
||||||
);
|
);
|
||||||
open(item);
|
open(item);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user