Merge branch 'main' into release/v1.0.4
This commit is contained in:
@@ -13,7 +13,7 @@ import { useUserQuota } from 'features/users/hooks/use-user-quota';
|
||||
* Returns the children of a drive item
|
||||
* @returns
|
||||
*/
|
||||
export const useDriveActions = () => {
|
||||
export const useDriveActions = (inPublicSharing?: boolean) => {
|
||||
const companyId = useRouterCompany();
|
||||
const sharedFilter = useRecoilValue(SharedWithMeFilterState);
|
||||
const sortItem = useRecoilValue(DriveItemSort);
|
||||
@@ -142,7 +142,7 @@ export const useDriveActions = () => {
|
||||
try {
|
||||
await DriveApiClient.update(companyId, id, update);
|
||||
await refresh(id || '', true);
|
||||
await refresh(parentId || '', true);
|
||||
if (!inPublicSharing) await refresh(parentId || '', true);
|
||||
if (update?.parent_id !== parentId) await refresh(update?.parent_id || '', true);
|
||||
} catch (e) {
|
||||
ToasterService.error(Languages.t('hooks.use-drive-actions.unable_update_file'));
|
||||
|
||||
@@ -8,10 +8,10 @@ export enum FeatureNames {
|
||||
EDIT_FILES = 'chat:edit_files',
|
||||
UNLIMITED_STORAGE = 'chat:unlimited_storage', //Currently inactive
|
||||
COMPANY_INVITE_MEMBER = 'company:invite_member',
|
||||
COMPANY_SEARCH_USERS = 'company:search_users',
|
||||
COMPANY_SHARED_DRIVE = 'company:shared_drive',
|
||||
COMPANY_DISPLAY_EMAIL = 'company:display_email',
|
||||
COMPANY_USER_QUOTA = 'company:user_quota',
|
||||
COMPANY_MANAGE_ACCESS = 'company:managed_access',
|
||||
}
|
||||
|
||||
export type FeatureValueType = boolean | number;
|
||||
@@ -27,10 +27,10 @@ availableFeaturesWithDefaults.set(FeatureNames.EDIT_FILES, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.UNLIMITED_STORAGE, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_INVITE_MEMBER, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_INVITE_MEMBER, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_SEARCH_USERS, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_SHARED_DRIVE, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_DISPLAY_EMAIL, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_USER_QUOTA, false);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_MANAGE_ACCESS, true);
|
||||
|
||||
/**
|
||||
* ChannelServiceImpl that allow you to manage feature flipping in Tdrive using react feature toggles
|
||||
|
||||
@@ -160,7 +160,7 @@ export default memo(
|
||||
|
||||
const selectedCount = Object.values(checked).filter(v => v).length;
|
||||
|
||||
const onBuildContextMenu = useOnBuildContextMenu(children, initialParentId);
|
||||
const onBuildContextMenu = useOnBuildContextMenu(children, initialParentId, inPublicSharing);
|
||||
const onBuildSortContextMenu = useOnBuildSortContextMenu();
|
||||
|
||||
const handleDragOver = (event: { preventDefault: () => void }) => {
|
||||
@@ -227,8 +227,8 @@ export default memo(
|
||||
const commonProps = {
|
||||
key: index,
|
||||
className:
|
||||
(index === 0 ? 'rounded-t-md ' : '') +
|
||||
(index === documents.length - 1 ? 'rounded-b-md ' : ''),
|
||||
(index === 0 ? 'rounded-t-md ' : '-mt-px ') +
|
||||
(index === items.length - 1 ? 'rounded-b-md ' : ''),
|
||||
item: child,
|
||||
checked: checked[child.id] || false,
|
||||
onCheck: (v: boolean) => setChecked(_.pickBy({ ...checked, [child.id]: v }, _.identity)),
|
||||
@@ -238,7 +238,7 @@ export default memo(
|
||||
return isMobile ? (
|
||||
<DocumentRow {...commonProps} />
|
||||
) : (
|
||||
<Draggable id={index}>
|
||||
<Draggable id={index} key={index}>
|
||||
<DocumentRow {...commonProps} />
|
||||
</Draggable>
|
||||
);
|
||||
@@ -450,7 +450,7 @@ export default memo(
|
||||
<FolderRow
|
||||
key={index}
|
||||
className={
|
||||
(index === 0 ? 'rounded-t-md ' : '') +
|
||||
(index === 0 ? 'rounded-t-md ' : '-mt-px ') +
|
||||
(index === items.length - 1 ? 'rounded-b-md ' : '')
|
||||
}
|
||||
item={child}
|
||||
@@ -476,7 +476,7 @@ export default memo(
|
||||
{activeIndex ? (
|
||||
<DocumentRowOverlay
|
||||
className={
|
||||
(activeIndex === 0 ? 'rounded-t-md ' : '') +
|
||||
(activeIndex === 0 ? 'rounded-t-md ' : '-mt-px ') +
|
||||
(activeIndex === items.length - 1 ? 'rounded-b-md ' : '')
|
||||
}
|
||||
item={activeChild}
|
||||
|
||||
@@ -27,11 +27,18 @@ import useRouterCompany from '@features/router/hooks/use-router-company';
|
||||
import _, { set } from 'lodash';
|
||||
import Languages from 'features/global/services/languages-service';
|
||||
import { hasAnyPublicLinkAccess } from '@features/files/utils/access-info-helpers';
|
||||
import FeatureTogglesService, {
|
||||
FeatureNames,
|
||||
} from '@features/global/services/feature-toggles-service';
|
||||
|
||||
/**
|
||||
* This will build the context menu in different contexts
|
||||
*/
|
||||
export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: string) => {
|
||||
export const useOnBuildContextMenu = (
|
||||
children: DriveItem[],
|
||||
initialParentId?: string,
|
||||
inPublicSharing?: boolean,
|
||||
) => {
|
||||
const [checkedIds, setChecked] = useRecoilState(DriveItemSelectedList);
|
||||
const checked = children.filter(c => checkedIds[c.id]);
|
||||
|
||||
@@ -55,7 +62,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
const company = useRouterCompany();
|
||||
|
||||
function getIdsFromArray(arr: DriveItem[]): string[] {
|
||||
return arr.map((obj) => obj.id);
|
||||
return arr.map(obj => obj.id);
|
||||
}
|
||||
|
||||
return useCallback(
|
||||
@@ -63,7 +70,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
if (!parent || !parent.access) return [];
|
||||
|
||||
try {
|
||||
const inTrash = parent.path?.[0]?.id.includes('trash') || viewId?.includes("trash");
|
||||
const inTrash = parent.path?.[0]?.id.includes('trash') || viewId?.includes('trash');
|
||||
const isPersonal = item?.scope === 'personal';
|
||||
const selectedCount = checked.length;
|
||||
|
||||
@@ -85,10 +92,14 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
type: 'menu',
|
||||
icon: 'users-alt',
|
||||
text: Languages.t('components.item_context_menu.manage_access'),
|
||||
hide: access === 'read' || getPublicLinkToken() || inTrash,
|
||||
hide:
|
||||
access === 'read' ||
|
||||
getPublicLinkToken() ||
|
||||
inTrash ||
|
||||
!FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_MANAGE_ACCESS),
|
||||
onClick: () => setAccessModalState({ open: true, id: item.id }),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ type: 'separator', hide: inTrash },
|
||||
{
|
||||
type: 'menu',
|
||||
icon: 'download-alt',
|
||||
@@ -100,7 +111,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
} else {
|
||||
download(item.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
/*TODO: fix loading of preview in new window and uncomment. See https://github.com/linagora/twake-drive/issues/603 .
|
||||
{
|
||||
@@ -144,13 +155,16 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
icon: 'file-edit-alt',
|
||||
text: Languages.t('components.item_context_menu.rename'),
|
||||
hide: access === 'read' || inTrash,
|
||||
onClick: () => setPropertiesModalState({ open: true, id: item.id }),
|
||||
onClick: () => setPropertiesModalState({ open: true, id: item.id, inPublicSharing }),
|
||||
},
|
||||
{
|
||||
type: 'menu',
|
||||
icon: 'link',
|
||||
text: Languages.t('components.item_context_menu.copy_link'),
|
||||
hide: !item.access_info.public?.level || item.access_info.public?.level === 'none' || inTrash,
|
||||
hide:
|
||||
!item.access_info.public?.level ||
|
||||
item.access_info.public?.level === 'none' ||
|
||||
inTrash,
|
||||
onClick: () => {
|
||||
copyToClipboard(getPublicLink(item || parent?.item));
|
||||
ToasterService.success(
|
||||
@@ -165,7 +179,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
hide: item.is_directory || inTrash,
|
||||
onClick: () => setVersionModal({ open: true, id: item.id }),
|
||||
},
|
||||
{ type: 'separator', hide: access !== 'manage' || inTrash, },
|
||||
{ type: 'separator', hide: access !== 'manage' || inTrash },
|
||||
{
|
||||
type: 'menu',
|
||||
icon: 'trash',
|
||||
@@ -203,7 +217,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
{
|
||||
type: 'menu',
|
||||
text: Languages.t('components.item_context_menu.move_multiple'),
|
||||
hide: parent.access === 'read',
|
||||
hide: parent.access === 'read' || inTrash,
|
||||
onClick: () =>
|
||||
setSelectorModalState({
|
||||
open: true,
|
||||
@@ -227,6 +241,7 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
{
|
||||
type: 'menu',
|
||||
text: Languages.t('components.item_context_menu.download_multiple'),
|
||||
hide: inTrash,
|
||||
onClick: () =>
|
||||
selectedCount === 1 ? download(checked[0].id) : downloadZip(checked.map(c => c.id)),
|
||||
},
|
||||
@@ -298,10 +313,12 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
if (parent.children && parent.children.length > 0) {
|
||||
downloadZip([parent.item!.id]);
|
||||
} else if (parent.item) {
|
||||
console.log("Download folder itself");
|
||||
console.log('Download folder itself');
|
||||
download(parent.item.id);
|
||||
} else {
|
||||
console.error("Very strange, everything is null, you are trying to download undefined");
|
||||
console.error(
|
||||
'Very strange, everything is null, you are trying to download undefined',
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -316,13 +333,13 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
||||
);
|
||||
},
|
||||
},
|
||||
{ type: 'separator', hide: parent.item!.id != 'root', },
|
||||
{ type: 'separator', hide: parent.item!.id != 'root' },
|
||||
{
|
||||
type: 'menu',
|
||||
text: Languages.t('components.item_context_menu.manage_users'),
|
||||
hide: parent.item!.id != 'root',
|
||||
onClick: () => setUsersModalState({ open: true }),
|
||||
}
|
||||
},
|
||||
];
|
||||
if (menu.length && newMenuActions.filter(a => !a.hide).length) {
|
||||
menu = [...menu, { type: 'separator' }];
|
||||
|
||||
@@ -39,7 +39,7 @@ export const DocumentRow = ({
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'flex flex-row items-center border border-zinc-200 dark:border-zinc-800 -mt-px px-4 py-3 cursor-pointer ' +
|
||||
'flex flex-row items-center border border-zinc-200 dark:border-zinc-800 px-4 py-3 cursor-pointer ' +
|
||||
(checked
|
||||
? 'bg-blue-500 bg-opacity-10 hover:bg-opacity-25 '
|
||||
: 'hover:bg-zinc-500 hover:bg-opacity-10 ') +
|
||||
@@ -96,7 +96,7 @@ export const DocumentRowOverlay = ({ item, className }: DriveItemOverlayProps) =
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'flex flex-row items-center border border-zinc-200 dark:border-zinc-800 -mt-px px-4 py-3 cursor-pointer ' +
|
||||
'flex flex-row items-center border border-zinc-200 dark:border-zinc-800 px-4 py-3 cursor-pointer ' +
|
||||
(className || '')
|
||||
}
|
||||
>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const FolderRow = ({
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'flex flex-row items-center border border-zinc-200 dark:border-zinc-800 -mt-px px-4 py-3 cursor-pointer ' +
|
||||
'flex flex-row items-center border border-zinc-200 dark:border-zinc-800 px-4 py-3 cursor-pointer ' +
|
||||
(className || '') +
|
||||
(checked
|
||||
? 'bg-blue-500 bg-opacity-10 hover:bg-opacity-25 '
|
||||
|
||||
@@ -23,7 +23,7 @@ export const CreateFolder = () => {
|
||||
}, []);
|
||||
|
||||
const createFolderHandler = async () => {
|
||||
await create({ name, parent_id: state.parent_id, is_directory: true }, {});
|
||||
await create({ name: (name || '').trim(), parent_id: state.parent_id, is_directory: true }, {});
|
||||
setState({ ...state, open: false });
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const CreateFolder = () => {
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
disabled={!name}
|
||||
disabled={!(name || '').trim()}
|
||||
loading={loading}
|
||||
className="mt-4 float-right"
|
||||
onClick={createFolderHandler}
|
||||
|
||||
@@ -11,6 +11,7 @@ import Languages from '@features/global/services/languages-service';
|
||||
export type PropertiesModalType = {
|
||||
open: boolean;
|
||||
id: string;
|
||||
inPublicSharing?: boolean;
|
||||
};
|
||||
|
||||
export const PropertiesModalAtom = atom<PropertiesModalType>({
|
||||
@@ -18,6 +19,7 @@ export const PropertiesModalAtom = atom<PropertiesModalType>({
|
||||
default: {
|
||||
open: false,
|
||||
id: '',
|
||||
inPublicSharing: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,15 +29,19 @@ export const PropertiesModal = () => {
|
||||
return (
|
||||
<Modal open={state.open} onClose={() => setState({ ...state, open: false })}>
|
||||
{!!state.id && (
|
||||
<PropertiesModalContent id={state.id} onClose={() => setState({ ...state, open: false })} />
|
||||
<PropertiesModalContent
|
||||
id={state.id}
|
||||
onClose={() => setState({ ...state, open: false })}
|
||||
inPublicSharing={state.inPublicSharing}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const PropertiesModalContent = ({ id, onClose }: { id: string; onClose: () => void }) => {
|
||||
const PropertiesModalContent = ({ id, onClose, inPublicSharing }: { id: string; onClose: () => void, inPublicSharing?: boolean }) => {
|
||||
const { item, refresh } = useDriveItem(id);
|
||||
const { update } = useDriveActions();
|
||||
const { update } = useDriveActions(inPublicSharing);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [name, setName] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -62,7 +68,7 @@ const PropertiesModalContent = ({ id, onClose }: { id: string; onClose: () => vo
|
||||
const doSave = async () => {
|
||||
setLoading(true);
|
||||
if (item) {
|
||||
let finalName = name;
|
||||
let finalName = (name || '').trim();
|
||||
//TODO: Confirm rename if extension changed ?
|
||||
if (!item?.is_directory) {
|
||||
//TODO: Why do we trim extensions on folders ?
|
||||
@@ -104,7 +110,7 @@ const PropertiesModalContent = ({ id, onClose }: { id: string; onClose: () => vo
|
||||
/>
|
||||
<br />
|
||||
<Button
|
||||
disabled={!name}
|
||||
disabled={!((name || '').trim())}
|
||||
className="float-right mt-4"
|
||||
theme="primary"
|
||||
loading={loading}
|
||||
|
||||
@@ -71,7 +71,7 @@ const ChangePublicLinkAccessLevelRow = (props: {
|
||||
subtitle={Languages.t("components.public-link-access-level-update-subtitle")}
|
||||
suffix={
|
||||
<AccessLevelDropdown
|
||||
className='!p-0 leading-tight text-end !pr-8 border-none bg-transparent dark:bg-transparent'
|
||||
className='leading-tight text-end !pl-5 !pr-8 border-none bg-white dark:bg-zinc-900'
|
||||
disabled={props.disabled}
|
||||
size={'sm'}
|
||||
noRedWhenLevelNone={true}
|
||||
|
||||
@@ -58,7 +58,7 @@ const AccessModalContent = (props: {
|
||||
}
|
||||
>
|
||||
<div className={loading ? 'opacity-50' : ''}>
|
||||
{FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_SEARCH_USERS) && (
|
||||
{FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_MANAGE_ACCESS) && (
|
||||
<InternalUsersAccessManager id={id} disabled={access !== 'manage'} onCloseModal={props.onCloseModal} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
+5
-2
@@ -81,7 +81,10 @@ export const InternalUsersAccessManager = ({
|
||||
return (
|
||||
<>
|
||||
<div className="rounded-md border-t mt-2 dark:border-zinc-700">
|
||||
<div className="p-4 flex flex-row items-center justify-center rounded-t-md border-x dark:border-zinc-700">
|
||||
<div className={
|
||||
"p-4 flex flex-row items-center justify-center rounded-t-md border-x dark:border-zinc-700"
|
||||
+ (showResults ? ' rounded-b-md' : '')
|
||||
}>
|
||||
<div className="grow">
|
||||
<InputDecorationIcon
|
||||
prefix={SearchIcon}
|
||||
@@ -157,7 +160,7 @@ export const InternalUsersAccessManager = ({
|
||||
id={id}
|
||||
userId={user?.id}
|
||||
disabled={disabled}
|
||||
className={(showResults ? '!rounded-none !rounded-t-md' : '') + (index === usersWithAccess.length - 1 ? ' rounded-b-md border-y' : ' border-t')}
|
||||
className={(showResults ? '!rounded-none' : '') + (index === usersWithAccess.length - 1 ? ' !rounded-b-md border-y' : ' border-t')}
|
||||
/>
|
||||
)}
|
||||
{(usersWithAccess?.length ?? 0) < minUserHeight &&
|
||||
|
||||
@@ -68,7 +68,7 @@ export default () => {
|
||||
<Avatar avatar={group.logo} className="inline-block mr-3" size="sm" type="square" />
|
||||
)}
|
||||
<span className="text-white font-semibold" style={{ lineHeight: '32px' }}>
|
||||
{group.name}
|
||||
Twake Drive
|
||||
</span>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
@@ -82,7 +82,7 @@ export default () => {
|
||||
</div>
|
||||
<div className="h-full main-view public p-4 pb-16">
|
||||
<AccessChecker folderId={documentId} token={token}>
|
||||
<Drive initialParentId={documentId} inPublicSharing />
|
||||
<Drive initialParentId={documentId} inPublicSharing={true} />
|
||||
</AccessChecker>
|
||||
</div>
|
||||
<MenusBodyLayer />
|
||||
|
||||
@@ -75,6 +75,7 @@ export default class UserParameter extends Component {
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="ru">Русский</option>
|
||||
<option value="vi">Tiếng Việt</option>
|
||||
</select>
|
||||
</div>
|
||||
</Attribute>
|
||||
|
||||
@@ -111,7 +111,7 @@ export default () => {
|
||||
<CloudIcon className="w-5 h-5 mr-4" /> {Languages.t('components.side_menu.home')}
|
||||
</Button>
|
||||
)}
|
||||
{FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_SEARCH_USERS) && (
|
||||
{FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_MANAGE_ACCESS) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
history.push(
|
||||
|
||||
@@ -63,6 +63,8 @@ export const DrivePreview: React.FC<DrivePreviewProps> = ({ items }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (items.length < 2)
|
||||
return () => {};
|
||||
addShortcut({ shortcut: 'Right', handler: handleSwitchRight });
|
||||
addShortcut({ shortcut: 'Left', handler: handleSwitchLeft });
|
||||
|
||||
@@ -146,22 +148,26 @@ export const DrivePreview: React.FC<DrivePreviewProps> = ({ items }) => {
|
||||
</div>
|
||||
<div className="whitespace-nowrap flex items-center">
|
||||
<Controls type={type} />
|
||||
<Button
|
||||
iconSize="lg"
|
||||
className="ml-4 !rounded-full"
|
||||
theme="dark"
|
||||
size="lg"
|
||||
icon={ArrowLeftIcon}
|
||||
onClick={ handleSwitchLeft }
|
||||
/>
|
||||
<Button
|
||||
iconSize="lg"
|
||||
className="ml-4 !rounded-full"
|
||||
theme="dark"
|
||||
size="lg"
|
||||
icon={ArrowRightIcon}
|
||||
onClick={ handleSwitchRight }
|
||||
/>
|
||||
{items.length > 1 &&
|
||||
<>
|
||||
<Button
|
||||
iconSize="lg"
|
||||
className="ml-4 !rounded-full"
|
||||
theme="dark"
|
||||
size="lg"
|
||||
icon={ArrowLeftIcon}
|
||||
onClick={ handleSwitchLeft }
|
||||
/>
|
||||
<Button
|
||||
iconSize="lg"
|
||||
className="ml-4 !rounded-full"
|
||||
theme="dark"
|
||||
size="lg"
|
||||
icon={ArrowRightIcon}
|
||||
onClick={ handleSwitchRight }
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<Button
|
||||
iconSize="lg"
|
||||
className="ml-4 !rounded-full"
|
||||
|
||||
Reference in New Issue
Block a user