💄 front: user rights search and add dialog (#50)
This commit is contained in:
committed by
ericlinagora
parent
a524df0168
commit
62dcb71c8a
@@ -45,7 +45,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Verwaltung",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Sonstige",
|
||||
"components.drive_dropzone.uploading": "Hochladen ...",
|
||||
"components.user_picker.modal_no_result": "Kein Ergebnis gefunden",
|
||||
"components.user_picker.modal_results_count_none": "Kein Ergebnis gefunden",
|
||||
"components.users_picker.add_me": "Mich hinzufügen",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Konfigurieren Sie Ihren Arbeitsbereich.",
|
||||
"scenes.app.header.disconnected": "Sie sind nicht verbunden",
|
||||
|
||||
@@ -51,13 +51,14 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Administration",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Other",
|
||||
"components.drive_dropzone.uploading": "Uploading...",
|
||||
"components.user_picker.modal_no_result": "No results found",
|
||||
"components.user_picker.modal_results_count_none": "No results found",
|
||||
"components.user_picker.modal.result_add.none": "No rights",
|
||||
"components.user_picker.modal.result_add.read": "Grant read",
|
||||
"components.user_picker.modal.result_add.write": "Grant write",
|
||||
"components.user_picker.modal.result_add.manage": "Grant full",
|
||||
"components.user_picker.modal.result_add.remove": "Remove rights",
|
||||
"components.user_picker.modal_results_trucated": "First {{$1}} results of {{$2}} shown...",
|
||||
"components.user_picker.modal_results_count_none": "No user found matching \"{{$1}}\"",
|
||||
"components.user_picker.modal_results_truncated": "First {{$1}} results of {{$2}} shown...",
|
||||
"components.users_picker.add_me": "Add me",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Configure your workspace.",
|
||||
"scenes.app.header.disconnected": "You are disconnected",
|
||||
@@ -222,7 +223,8 @@
|
||||
"components.public-link-security_expiration_removal_no_password": "Warning: about to remove the expiry on the public link making available to anyone knowing it",
|
||||
"components.public-link-security_expiration_removal_but_password": "Warning: about to remove the expiry on the public link making available to anyone with the password",
|
||||
"components.public-link-security_expiration_removal_confirm": "Remove expiration",
|
||||
"components.internal-access_access_manage":"General access management",
|
||||
"components.internal-access_manage_title":"Manage access to",
|
||||
"components.internal-access_manage_subtitle":"Grant access",
|
||||
"components.internal-access_inherit_parent":"Inherit parent folder",
|
||||
"components.internal-access_inherit_parent_perm":"Choose to inherit or not the parent folder permissions.",
|
||||
"components.internal-access_company_member":"Every member from the company",
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Administrado",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Alia",
|
||||
"components.drive_dropzone.uploading": "Alŝutante…",
|
||||
"components.user_picker.modal_no_result": "Neniuj rezultoj trovitaj",
|
||||
"components.user_picker.modal_results_count_none": "Neniuj rezultoj trovitaj",
|
||||
"components.users_picker.add_me": "Aldonu min",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Agordu vian laborspacon.",
|
||||
"scenes.app.header.disconnected": "Vi estas malkonektita",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"general.continue": "Continuar",
|
||||
"general.back": "Atrás",
|
||||
"components.drive_dropzone.uploading": "Subiendo ...",
|
||||
"components.user_picker.modal_no_result": "No se han encontrado resultados",
|
||||
"components.user_picker.modal_results_count_none": "No se han encontrado resultados",
|
||||
"scenes.app.header.disconnected": "Estás desconectado",
|
||||
"scenes.app.header.disconnected.reload": "Recargar",
|
||||
"scenes.apps.parameters.workspace_sections.members": "Participantes",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.publishing_communication_multimedia": "Argitalpena / Komunikazioa / Multimedia",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.education": "Hezkuntza",
|
||||
"general.continue": "Jarraitu",
|
||||
"components.user_picker.modal_no_result": "Ez da emaitzarik aurkitu",
|
||||
"components.user_picker.modal_results_count_none": "Ez da emaitzarik aurkitu",
|
||||
"scenes.app.workspaces.create_company.group_data.group_type.public_organization": "Erakunde publikoa",
|
||||
"scenes.app.workspaces.create_company.group_data.group_type.society_club_charity": "Elkartea, kluba edo ongintza",
|
||||
"scenes.app.workspaces.create_company.group_data.group_type.other_group": "Bestela",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_size.10_50": "11 - 49 käyttäjää",
|
||||
"scenes.app.workspaces.create_company.group_data.group_size.5000_more": "Yli 5000 käyttäjää",
|
||||
"components.drive_dropzone.uploading": "Lähetetään...",
|
||||
"components.user_picker.modal_no_result": "Tuloksia ei löytynyt",
|
||||
"components.user_picker.modal_results_count_none": "Tuloksia ei löytynyt",
|
||||
"components.users_picker.add_me": "Lisää minut",
|
||||
"scenes.app.header.disconnected": "Et ole yhdistettynä",
|
||||
"scenes.app.header.disconnected.reload": "Lataa uudelleen",
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Administration",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Autre",
|
||||
"components.drive_dropzone.uploading": "Téléchargement...",
|
||||
"components.user_picker.modal_no_result": "Aucun résultat trouvé",
|
||||
"components.user_picker.modal_results_count_none": "Aucun résultat trouvé",
|
||||
"components.user_picker.modal_results_truncated": "{{$1}} premiers résultats sur {{$2}} affichés...",
|
||||
"components.users_picker.add_me": "M'ajouter",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Configurez votre espace de travail.",
|
||||
"scenes.app.header.disconnected": "Vous êtes déconnecté",
|
||||
@@ -196,7 +197,8 @@
|
||||
"components.public-link-security-change": "Modifier",
|
||||
"components.public-link-security-change-subtitle": "Définir le mot de passe, la date d'expiration, etc.",
|
||||
"components.public-link-security-title": "Partager avec un lien",
|
||||
"components.internal-access_access_manage": "Gestion générale des accès",
|
||||
"components.internal-access_manage_title":"Gérer l'accès à",
|
||||
"components.internal-access_manage_subtitle":"Accorder l'accès",
|
||||
"components.internal-access_inherit_parent": "Hériter du dossier parent",
|
||||
"components.internal-access_inherit_parent_perm": "Hériter des permissions du dossier parent",
|
||||
"components.internal-access_company_member": "Toutes les personnes de l'entreprise",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Amministrazione",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Altro",
|
||||
"components.drive_dropzone.uploading": "Caricamento in corso...",
|
||||
"components.user_picker.modal_no_result": "Nessun risultato trovato",
|
||||
"components.user_picker.modal_results_count_none": "Nessun risultato trovato",
|
||||
"components.users_picker.add_me": "Aggiungimi",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Configura il tuo spazio di lavoro.",
|
||||
"scenes.app.header.disconnected": "Sei disconnesso",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"general.continue": "持続する",
|
||||
"general.back": "バック",
|
||||
"components.drive_dropzone.uploading": "アップロード中...",
|
||||
"components.user_picker.modal_no_result": "結果が見つかりません",
|
||||
"components.user_picker.modal_results_count_none": "結果が見つかりません",
|
||||
"scenes.app.header.disconnected": "あなたは切断されています",
|
||||
"scenes.app.header.disconnected.reload": "リロード",
|
||||
"scenes.apps.parameters.workspace_sections.members": "メンバー",
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Administrasjon",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Annet",
|
||||
"components.drive_dropzone.uploading": "Laster opp…",
|
||||
"components.user_picker.modal_no_result": "Ingen resultater funnet",
|
||||
"components.user_picker.modal_results_count_none": "Ingen resultater funnet",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Konfigurer arbeidsrommet ditt.",
|
||||
"scenes.app.popup.adduserfromtdriveconsole.current_users_state": "{{$1}} e-post(er) vil bli lagt til",
|
||||
"components.newversion.new_version_component.row.part_1": "Du kjører en utdatert Tdrive-versjon",
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Administração",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Outras",
|
||||
"components.drive_dropzone.uploading": "Enviando...",
|
||||
"components.user_picker.modal_no_result": "Nenhum resultado encontrado",
|
||||
"components.user_picker.modal_results_count_none": "Nenhum resultado encontrado",
|
||||
"components.users_picker.add_me": "Me adicione",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Configure seu espaço de trabalho.",
|
||||
"scenes.app.header.disconnected": "Você está desconectado",
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Управление",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Прочее",
|
||||
"components.drive_dropzone.uploading": "Загрузка...",
|
||||
"components.user_picker.modal_no_result": "Результатов не найдено",
|
||||
"components.user_picker.modal_results_count_none": "Результатов не найдено",
|
||||
"components.user_picker.modal_results_truncated": "Показаны первые {{$1}} результата из {{$2}}...",
|
||||
"components.users_picker.add_me": "Добавить меня",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Настройка рабочей среды.",
|
||||
"scenes.app.header.disconnected": "Соединение отсутствует",
|
||||
@@ -196,7 +197,8 @@
|
||||
"components.public-link-security-change": "Изменить",
|
||||
"components.public-link-security-change-subtitle": "Установить пароль, срок действия и т.д.",
|
||||
"components.public-link-security-title": "Поделиться ссылкой",
|
||||
"components.internal-access_access_manage":"General access management",
|
||||
"components.internal-access_manage_title":"Управляйте доступом к",
|
||||
"components.internal-access_manage_subtitle":"Предоставление доступа",
|
||||
"components.internal-access_inherit_parent":"Inherit parent folder",
|
||||
"components.internal-access_inherit_parent_perm":"Choose to inherit or not the parent folder permissions.",
|
||||
"components.internal-access_company_member":"Every member from the company",
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.electronics_electricity_energy": "ඉලෙක්ට්රොනික / විදුලි / බලශක්ති",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.machinery_automotive": "යන්ත්රෝපකරණ / වාහන",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.transport_logistics": "ප්රවාහන / සැපයුම්",
|
||||
"components.user_picker.modal_no_result": "ප්රතිඵල හමු නොවිණි",
|
||||
"components.user_picker.modal_results_count_none": "ප්රතිඵල හමු නොවිණි",
|
||||
"scenes.apps.account.title": "Account parameters",
|
||||
"components.alert.confirm": "ඔබේ ක්රියාව තහවුරු කරන්න",
|
||||
"scenes.app.popup.adduser.adresses_message": "කරුණාකර ලිපින කොමාවකින් හෝ ඉඩකින් වෙන් කරන්න. Tdrive ඔබගේ දායකත්වය මත පදනම්ව ආරාධනා ගණන සීමා කළ හැකි බව මතක තබා ගන්න.",
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
"scenes.join.create_the_company_button": "şirket oluştur",
|
||||
"scenes.join.join_workspace_from_company": "{{$2}}'ten {{$1}}'e katılın!",
|
||||
"scenes.join.tdrive_description": "Tdrive, ekip üretkenliğinizi artırmak için oluşturulmuş açık kaynaklı bir dijital çalışma alanıdır.",
|
||||
"components.user_picker.modal_no_result": "Sonuç bulunamadı",
|
||||
"components.user_picker.modal_results_count_none": "Sonuç bulunamadı",
|
||||
"components.users_picker.add_me": "beni ekle",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Çalışma alanınızı yapılandırın.",
|
||||
"scenes.app.header.disconnected": "bağlantınız kesildi",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.electronics_electricity_energy": "Điện tử / Điện / Năng lượng",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.studies_and_advice": "Học và lời khuyên",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.printing_paper": "In ấn / Giấy",
|
||||
"components.user_picker.modal_no_result": "Không tìm thấy kết quả",
|
||||
"components.user_picker.modal_results_count_none": "Không tìm thấy kết quả",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.business_services": "Dịch vụ kinh doanh",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.entertainment": "Giải trí",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.textile_clothing_shoes": "Dệt may / Quần áo / Giày",
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.administration": "Quản trị",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "Khác",
|
||||
"components.drive_dropzone.uploading": "Đang tải lên...",
|
||||
"components.user_picker.modal_no_result": "Không tìm thấy kết quả",
|
||||
"components.user_picker.modal_results_count_none": "Không tìm thấy kết quả",
|
||||
"components.user_picker.modal_results_truncated": "{{$1}} kết quả đầu tiên trong số {{$2}} kết quả được hiển thị...",
|
||||
"components.users_picker.add_me": "Thêm tôi",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "Cấu hình không gian làm việc của bạn.",
|
||||
"scenes.app.header.disconnected": "Bạn đang ngoại tuyến",
|
||||
@@ -212,7 +213,8 @@
|
||||
"components.public-link-security-change": "Thay đổi",
|
||||
"components.public-link-security-change-subtitle": "Thiết lập mật khẩu, ngày hết hạn, vv.",
|
||||
"components.public-link-security-title": "Chia sẻ với liên kết",
|
||||
"components.internal-access_access_manage": "Quản lý quyền truy cập chung",
|
||||
"components.internal-access_manage_title":"Quản lý quyền truy cập vào",
|
||||
"components.internal-access_manage_subtitle":"Cấp phép truy cập",
|
||||
"components.internal-access_inherit_parent": "Thừa hưởng từ thư mục cha",
|
||||
"components.internal-access_inherit_parent_perm": "Chọn có thừa hưởng quyền của thư mục cha hay không.",
|
||||
"components.internal-access_company_member": "Mọi thành viên của công ty",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.pharmaceutical_industry": "医药行业",
|
||||
"scenes.app.workspaces.create_company.group_data.group_main_activity.other": "其他",
|
||||
"components.drive_dropzone.uploading": "正在上传...",
|
||||
"components.user_picker.modal_no_result": "未找到结果",
|
||||
"components.user_picker.modal_results_count_none": "未找到结果",
|
||||
"components.users_picker.add_me": "添加我",
|
||||
"scenes.app.workspaces.create_company.invitations.title": "配置你的工作空间。",
|
||||
"scenes.app.header.disconnected": "您已断开连接",
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import UserBlock from '@molecules/grouped-rows/user';
|
||||
import { Input } from '@atoms/input/input-text';
|
||||
import { useSearchUsers } from '@features/users/hooks/use-search-user-list';
|
||||
import { Button } from '@atoms/button/button';
|
||||
import { UserType } from '@features/users/types/user';
|
||||
import { InputDecorationIcon } from '@atoms/input/input-decoration-icon';
|
||||
import { SearchIcon } from '@heroicons/react/solid';
|
||||
import { Info } from '@atoms/text';
|
||||
import Languages from '@features/global/services/languages-service';
|
||||
import _ from 'lodash';
|
||||
import { DriveFileAccessLevel } from 'app/features/drive/types';
|
||||
|
||||
|
||||
export default (props: {
|
||||
className?: string;
|
||||
onChange: (users: UserType[]) => void;
|
||||
initialUsers: UserType[];
|
||||
level: DriveFileAccessLevel;
|
||||
}) => {
|
||||
const [users, setUsers] = useState<UserType[]>(props.initialUsers);
|
||||
const { search, result, query } = useSearchUsers({ scope: 'company' });
|
||||
const [isFocus, setFocus] = useState(false);
|
||||
const inputElement = useRef<HTMLInputElement>(null);
|
||||
const maxUserResultsShown = 5;
|
||||
const [selectedUserKey, setSelectedUserKey] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (users.length) props.onChange(users);
|
||||
}, [users]);
|
||||
|
||||
const shownResults = _.reverse(result.slice(0, maxUserResultsShown));
|
||||
const selectedKeyIndex = shownResults.findIndex(({id}) => id === selectedUserKey);
|
||||
|
||||
const moveSelection = (offset: number) => {
|
||||
if (selectedKeyIndex === -1) {
|
||||
if (offset < 0)
|
||||
setSelectedUserKey(shownResults[shownResults.length - 1].id || '');
|
||||
else if (offset > 0)
|
||||
setSelectedUserKey(shownResults[0].id || '');
|
||||
} else {
|
||||
const nextIndex = (selectedKeyIndex + shownResults.length + offset) % shownResults.length;
|
||||
setSelectedUserKey(shownResults[nextIndex].id || '');
|
||||
}
|
||||
};
|
||||
const doEnterKey = () => {
|
||||
const user = shownResults[selectedKeyIndex];
|
||||
if (user) {
|
||||
setUsers([user]);
|
||||
search('');
|
||||
}
|
||||
setSelectedUserKey('');
|
||||
};
|
||||
|
||||
const keyEvents : { [key: string]: () => void } = {
|
||||
"ArrowUp": () => moveSelection(-1),
|
||||
"ArrowDown": () => moveSelection(1),
|
||||
"Enter": () => doEnterKey(),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full relative">
|
||||
<InputDecorationIcon
|
||||
prefix={SearchIcon}
|
||||
input={({ className }) => (
|
||||
<Input
|
||||
onFocus={() => setFocus(true)}
|
||||
onBlur={() => {
|
||||
setTimeout(() => {
|
||||
if (inputElement.current !== document.activeElement) {
|
||||
setFocus(false);
|
||||
}
|
||||
}, 200);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.code in keyEvents) {
|
||||
keyEvents[e.code]();
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
placeholder={Languages.t('components.select-users_search_users')}
|
||||
className={props.className + ' ' + className + ' w-full'}
|
||||
theme="plain"
|
||||
onChange={e => {
|
||||
search(e.target.value);
|
||||
setSelectedUserKey('');
|
||||
}}
|
||||
inputRef={inputElement}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{isFocus && query?.trim() && (
|
||||
<div className="absolute w-full end-1 z-10 bg-white dark:bg-zinc-800 dark:text-white rounded-md border shadow-md p-2">
|
||||
<div>
|
||||
{result.length === 0 && (
|
||||
<div className="text-center">
|
||||
<Info>{Languages.t('components.user_picker.modal_no_result')}</Info>
|
||||
</div>
|
||||
)}
|
||||
{shownResults.map((user, i) => {
|
||||
return (
|
||||
<UserBlock
|
||||
key={user.id}
|
||||
user={user}
|
||||
className={
|
||||
"rounded m-1 p-3 new-direct-channel-proposed-user flex flex-row items-center justify-center align-baseline"
|
||||
+ (i === selectedKeyIndex ? ' ring' : (i > 0 && i !== (selectedKeyIndex + 1) ? ' border-t' : ''))}
|
||||
suffix={
|
||||
<Button
|
||||
onClick={() => {
|
||||
setUsers([user]);
|
||||
search('');
|
||||
setSelectedUserKey('');
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
{Languages.t('components.user_picker.modal.result_add.' + props.level)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{result.length > maxUserResultsShown &&
|
||||
<div className='grow text-center italic'>
|
||||
{Languages.t('components.user_picker.modal_results_trucated',
|
||||
[maxUserResultsShown, result.length],
|
||||
'First {{$1}} results of {{$2}} shown...')}
|
||||
</div>}
|
||||
<div className='-mb-px' />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -29,6 +29,7 @@ export const AccessModal = () => {
|
||||
return (
|
||||
<Modal
|
||||
open={state.open}
|
||||
className='!overflow-visible'
|
||||
onClose={() => setState({ ...state, open: false })}
|
||||
>
|
||||
{!!state.id && <AccessModalContent id={state.id} />}
|
||||
|
||||
+175
-59
@@ -1,90 +1,206 @@
|
||||
import { Subtitle } from '@atoms/text';
|
||||
import UserBlock from '@molecules/grouped-rows/user';
|
||||
import { useState, useRef } from 'react';
|
||||
|
||||
import type { DriveFileAccessLevel } from '@features/drive/types';
|
||||
|
||||
import Languages from 'features/global/services/languages-service';
|
||||
import { useDriveItem } from '@features/drive/hooks/use-drive-item';
|
||||
import { DriveFileAccessLevel } from '@features/drive/types';
|
||||
import { useCurrentUser } from '@features/users/hooks/use-current-user';
|
||||
import { useUser } from '@features/users/hooks/use-user';
|
||||
import { UserType } from '@features/users/types/user';
|
||||
import { useState } from 'react';
|
||||
import SelectUsers from '../../components/select-users';
|
||||
import { AccessLevelDropdown } from '../../components/access-level-dropdown';
|
||||
import Languages from 'features/global/services/languages-service';
|
||||
import { useSearchUsers } from '@features/users/hooks/use-search-user-list';
|
||||
import { changeUserAccess, getUserAccessLevel, getAllUserAccesses } from '@features/files/utils/access-info-helpers';
|
||||
|
||||
import { Subtitle } from '@atoms/text';
|
||||
import { Input } from '@atoms/input/input-text';
|
||||
import { Button } from '@atoms/button/button';
|
||||
import { SearchIcon } from '@heroicons/react/solid';
|
||||
import { InputDecorationIcon } from '@atoms/input/input-decoration-icon';
|
||||
import { AccessLevelDropdown } from '../../components/access-level-dropdown';
|
||||
import UserBlock from '@molecules/grouped-rows/user';
|
||||
|
||||
export const InternalAccessManager = ({ id, disabled }: { id: string; disabled: boolean }) => {
|
||||
const { item } = useDriveItem(id);
|
||||
const { item, loading, update } = useDriveItem(id);
|
||||
const [level, setLevel] = useState<DriveFileAccessLevel>('manage');
|
||||
const usersWithAccess = item && getAllUserAccesses(item);
|
||||
const { search, result, query } = useSearchUsers({ scope: 'company' });
|
||||
const [searchString, setSearchString] = useState("");
|
||||
const [isFocus, setFocus] = useState(false);
|
||||
const inputElement = useRef<HTMLInputElement>(null);
|
||||
const maxUserResultsShown = 5;
|
||||
const minUserHeight = 3;
|
||||
const showResults = isFocus && query?.trim();
|
||||
|
||||
const knownUsers = item && new Set(getAllUserAccesses(item).map(({id}) => id));
|
||||
const absentUserResults = item && result && result.filter(({id}) => !knownUsers?.has(id!));
|
||||
const shownResults = absentUserResults && absentUserResults.slice(0, maxUserResultsShown);
|
||||
const resultFooterText = knownUsers && shownResults &&
|
||||
(shownResults.length == 0
|
||||
? Languages.t('components.user_picker.modal_results_count_none', [searchString])
|
||||
: (absentUserResults.length > maxUserResultsShown
|
||||
? Languages.t('components.user_picker.modal_results_truncated', [maxUserResultsShown, absentUserResults.length])
|
||||
: false
|
||||
));
|
||||
|
||||
const [selectedUserKey, setSelectedUserKey] = useState('');
|
||||
const selectedKeyIndex = shownResults ? shownResults.findIndex(({id}) => id === selectedUserKey) : -1;
|
||||
|
||||
const moveSelection = (offset: number) => {
|
||||
if (!shownResults) return;
|
||||
if (selectedKeyIndex === -1) {
|
||||
if (offset < 0)
|
||||
setSelectedUserKey(shownResults[shownResults.length - 1].id || '');
|
||||
else if (offset > 0)
|
||||
setSelectedUserKey(shownResults[0].id || '');
|
||||
} else if (selectedKeyIndex != null) {
|
||||
const nextIndex = (selectedKeyIndex + shownResults.length + offset) % shownResults.length;
|
||||
setSelectedUserKey(shownResults[nextIndex].id || '');
|
||||
}
|
||||
};
|
||||
|
||||
const doEnterKey = () => {
|
||||
const user = shownResults && selectedKeyIndex > -1 && shownResults[selectedKeyIndex];
|
||||
if (user) {
|
||||
item && update(changeUserAccess(item, user.id!, level));
|
||||
search('');
|
||||
}
|
||||
setSelectedUserKey('');
|
||||
};
|
||||
|
||||
const keyEvents : { [key: string]: () => void } = {
|
||||
"ArrowUp": () => moveSelection(-1),
|
||||
"ArrowDown": () => moveSelection(1),
|
||||
"Enter": () => doEnterKey(),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle className="block mt-2 mb-1">
|
||||
{Languages.t('components.internal-access_specific_rules')}
|
||||
{Languages.t('components.internal-access_manage_subtitle')}
|
||||
</Subtitle>
|
||||
<div className="rounded-md border mt-2 dark:border-zinc-700">
|
||||
<UserAccessSelector id={id} disabled={disabled} />
|
||||
|
||||
{item && getAllUserAccesses(item)?.map(user =>
|
||||
<UserAccessLevel key={user.id} id={id} userId={user?.id} disabled={disabled} />
|
||||
<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="grow">
|
||||
<InputDecorationIcon
|
||||
prefix={SearchIcon}
|
||||
input={({ className }) => (
|
||||
<Input
|
||||
onFocus={() => setFocus(true)}
|
||||
onBlur={() => {
|
||||
setTimeout(() => {
|
||||
if (inputElement.current !== document.activeElement) {
|
||||
setFocus(false);
|
||||
}
|
||||
}, 200);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.code in keyEvents) {
|
||||
keyEvents[e.code]();
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
placeholder={Languages.t('components.select-users_search_users')}
|
||||
className={className + ' w-full rounded-r-none'}
|
||||
theme="plain"
|
||||
onChange={e => {
|
||||
search(e.target.value);
|
||||
setSearchString(e.target.value);
|
||||
}}
|
||||
value={searchString}
|
||||
inputRef={inputElement}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<AccessLevelDropdown
|
||||
className="rounded-l-none !p-0 leading-tight text-end !pr-8 !pl-2 border-none bg-zinc-100 dark:bg-zinc-800"
|
||||
disabled={loading || disabled}
|
||||
hiddenLevels={['remove']}
|
||||
level={level}
|
||||
onChange={level => setLevel(level)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{showResults &&
|
||||
<>
|
||||
<div className="flex flex-row border-t !border-x-0 dark:border-zinc-700 h-1" />
|
||||
<div className="relative w-full h-0"> {/* Necessary so the relative results don't take height */}
|
||||
<div className={(showResults ? "rounded-md " : "rounded-md ") + "absolute z-10 w-full shadow-md border bg-white dark:bg-zinc-900 mt-2 dark:border-zinc-700"}>
|
||||
{ shownResults && shownResults.map((user, index) =>
|
||||
<UserAccessLevel
|
||||
key={user.id}
|
||||
id={id}
|
||||
userId={user.id!}
|
||||
disabled={disabled}
|
||||
className={'!border-x-0' + (index === selectedKeyIndex ? ' ring' : (index > 0 && index !== ((selectedKeyIndex ?? 0) + 1) ? ' border-t' : ''))}
|
||||
isSearchResultAdd={level}
|
||||
onAddSearchResult={(userId) => {
|
||||
item && update(changeUserAccess(item, userId, level));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!loading && resultFooterText && <>
|
||||
<div className={(result.length == 0 ? 'rounded-md' : 'rounded-b-md') + ' grow text-center italic dark:text-zinc-400 py-2 dark:border-zinc-700 border-t bg-zinc-100 dark:bg-zinc-800'}>
|
||||
{resultFooterText}
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row !border-x-0 dark:border-zinc-700 h-2" />
|
||||
</>}
|
||||
{usersWithAccess?.map((user, index) =>
|
||||
<UserAccessLevel
|
||||
key={user.id}
|
||||
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')}
|
||||
/>
|
||||
)}
|
||||
<div className="-mb-px" />
|
||||
{(usersWithAccess?.length ?? 0) < minUserHeight &&
|
||||
[...Array(minUserHeight - (usersWithAccess?.length ?? 0))].map((_, i) =>
|
||||
<div key={i} className="p-4 flex flex-row">
|
||||
<div className='w-9 h-9 border border-transparent' /> {/* make same size as userblock */}
|
||||
</div>
|
||||
)}
|
||||
{(!showResults) &&
|
||||
<div className="mt-2 flex flex-row items-center justify-center border-none h-1"> </div>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const UserAccessSelector = ({ id, disabled }: { id: string; disabled: boolean }) => {
|
||||
const { item, loading, update } = useDriveItem(id);
|
||||
const [level, setLevel] = useState<DriveFileAccessLevel>('manage');
|
||||
|
||||
return (
|
||||
<div className="p-4 flex flex-row items-center justify-center">
|
||||
<div className="grow">
|
||||
<SelectUsers
|
||||
className="rounded-r-none"
|
||||
level={level}
|
||||
onChange={(users: UserType[]) => {
|
||||
const id = users[0]?.id; //TODO: all others ignored
|
||||
item && id && update(changeUserAccess(item, id, level));
|
||||
}}
|
||||
initialUsers={[]}
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<AccessLevelDropdown
|
||||
className="rounded-l-none"
|
||||
disabled={loading || disabled}
|
||||
hiddenLevels={['remove']}
|
||||
level={level}
|
||||
onChange={level => setLevel(level)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UserAccessLevel = ({
|
||||
id,
|
||||
userId,
|
||||
disabled,
|
||||
}: {
|
||||
const UserAccessLevel = (props: {
|
||||
id: string;
|
||||
userId: string;
|
||||
disabled: boolean;
|
||||
className?: string;
|
||||
isSearchResultAdd?: DriveFileAccessLevel;
|
||||
onAddSearchResult?: (userId: string) => void,
|
||||
}) => {
|
||||
const { item, loading, update } = useDriveItem(id);
|
||||
const user = useUser(userId);
|
||||
const { item, loading, update } = useDriveItem(props.id);
|
||||
const user = useUser(props.userId);
|
||||
const { user: currentUser } = useCurrentUser();
|
||||
return (
|
||||
<UserBlock
|
||||
className="p-4 border-t dark:border-zinc-700"
|
||||
className={"p-4 border-x dark:border-zinc-700" + ' ' + (props.className ?? '')}
|
||||
user={user}
|
||||
isSelf={!!currentUser?.id && user?.id === currentUser?.id}
|
||||
suffix={
|
||||
<AccessLevelDropdown
|
||||
disabled={loading || disabled || user?.id === currentUser?.id}
|
||||
level={(item && getUserAccessLevel(item, userId)) || "none"}
|
||||
onChange={level => item && update(changeUserAccess(item, userId, level === 'remove' ? false : level))}
|
||||
/>
|
||||
props.isSearchResultAdd
|
||||
? <Button
|
||||
disabled={loading || props.disabled || user?.id === currentUser?.id}
|
||||
onClick={() => user && props.onAddSearchResult!(props.userId)}
|
||||
size="sm"
|
||||
>
|
||||
{Languages.t('components.user_picker.modal.result_add.' + props.isSearchResultAdd)}
|
||||
</Button>
|
||||
|
||||
: <AccessLevelDropdown
|
||||
disabled={loading || props.disabled || user?.id === currentUser?.id}
|
||||
level={(item && getUserAccessLevel(item, props.userId)) || "none"}
|
||||
onChange={level => item && update(changeUserAccess(item, props.userId, level === 'remove' ? false : level))}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user