💄 front: user rights search and add dialog (#50)

This commit is contained in:
Eric Doughty-Papassideris
2024-04-29 10:19:31 +02:00
committed by ericlinagora
parent a524df0168
commit 62dcb71c8a
20 changed files with 206 additions and 216 deletions
+1 -1
View File
@@ -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",
+5 -3
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+4 -2
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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": "メンバー",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+4 -2
View File
@@ -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",
+1 -1
View File
@@ -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 ඔබගේ දායකත්වය මත පදනම්ව ආරාධනා ගණන සීමා කළ හැකි බව මතක තබා ගන්න.",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+4 -2
View File
@@ -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",
+1 -1
View File
@@ -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} />}
@@ -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">&nbsp;</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))}
/>
}
/>
);